综合 用发行版自带工具做服务器自动化:systemd timer、加固 SSH、机群循环与自动打补丁

2026-10-16 21:32:19

cron 脚本坟场不是必然:用 cron、systemd timer、加固 SSH、logrotate 与 unattended-upgrades 做服务器自动化

三年以上的机群都有 cron 脚本坟场:2019 年起每晚跑,2021 年离职工程师写的,清理一个 2023 年已经搬走的目录。没人敢删。没有结构地搭自动化,最后就变成这种坟场,而它可以避免。下面全部用主流发行版已经装好的工具:systemd timers、cron、OpenSSH、bash、logrotate 和 unattended-upgrades。第一台主机一个下午能搞定,第二台就能推到全机群。

工具 1:cron 与 systemd timer,调度器

定期检查是最容易拿到的收益:磁盘使用、服务健康、证书过期。经典 cron 对单个检查仍然可用:

# crontab -e
# cron mails any output to the crontab owner: print filesystems above 85%
*/30 * * * * df -P | awk 'NR>1 && $5+0 > 85'

对于你在意的任何事,优先 systemd timers。输出进 journal,Persistent=true 补上主机关机期间错过的运行,RandomizedDelaySec 防止 200 台主机同一秒砸包镜像(systemd.timer)。

检查脚本本身,写成出错就大声失败:

#!/usr/bin/env bash
# /usr/local/sbin/disk-check.sh: exit non-zero if any filesystem is at or above 85%
set -euo pipefail
over=$(df -P -x tmpfs -x devtmpfs | awk 'NR>1 && $5+0 >= 85 {print $6" "$5}')
if [ -n "$over" ]; then
 echo "Filesystems over threshold: $over"
 exit 1
fi

一个 service 和 timer 对来跑它:

# /etc/systemd/system/disk-check.service
[Unit]
Description=Alert on filesystems above 85%
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/disk-check.sh

# /etc/systemd/system/disk-check.timer
[Unit]
Description=Run disk check every 30 minutes
[Timer]
OnCalendar=*:0/30
RandomizedDelaySec=120
Persistent=true
[Install]
WantedBy=timers.target

启用并验证:

sudo systemctl daemon-reload
sudo systemctl enable --now disk-check.timer
systemctl list-timers disk-check.timer
journalctl -u disk-check.service --since today

因为脚本以非零退出,越界会让 disk-check.service 进入 failed 状态,主机上 systemctl --failed 能看到,OnFailure= unit 也能把它送到有人看的地方。最后那条 journalctl 是对 cron 的安静优势:每次运行的输出、带时间戳、能在机器上查询。

工具 2:加固 SSH,传输层

机群自动化意味着一把能登很多机器的密钥,这把密钥就是最大负债。OpenSSH 给三层限制。全用上。

第 1 层:仅密钥认证,且仅在受信主机上

在每台受管主机上,drop-in 如 /etc/ssh/sshd_config.d/10-automation.conf:

PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
PermitRootLogin prohibit-password

对多数 sshd 选项,读到的第一个值生效,当前 Debian、Ubuntu、RHEL 的 stock sshd_config 在顶部包含 sshd_config.d/*.conf,所以 drop-in 覆盖主文件。重载前验证,并确认有效值:

sudo sshd -t && sudo sshd -T | grep -Ei 'passwordauth|kbdinteractive|permitrootlogin'
sudo systemctl reload ssh # Debian and Ubuntu; the unit is sshd on RHEL-family

测试时保持一个活动会话打开。把自己锁在机群外是一种可以跳过的成人礼。为自动化用户维护精选 known_hosts,而不是 StrictHostKeyChecking no:受信主机的意义就是意外密钥变更会终止运行。

第 2 层:用 command= 和 from= 限制密钥

自动化密钥不该是通用登录。在目标主机的 ~/.ssh/authorized_keys,给密钥加限制前缀(sshd authorized_keys 格式):

command="/usr/local/sbin/healthcheck.sh",from="10.0.4.10",no-pty,no-agent-forwarding,no-port-forwarding,no-X11-forwarding ssh-ed25519 AAAA... automation@bastion

现在这把密钥只能做一件事(运行 wrapper 脚本),从一个源 IP,没有交互终端和转发。如果泄露,爆炸半径是一个只读脚本。需要多动作的自动化,把 command= 指向 dispatcher,读取 $SSH_ORIGINAL_COMMAND 并 allowlist 它能跑什么:

#!/usr/bin/env bash
# /usr/local/sbin/healthcheck.sh: allowlist dispatcher for the read-only key
case "$SSH_ORIGINAL_COMMAND" in
 "disk") df -P -x tmpfs -x devtmpfs ;;
 "failed") systemctl --failed --no-pager ;;
 "errors") journalctl -p err --since "1 hour ago" --no-pager ;;
 *) echo "denied: $SSH_ORIGINAL_COMMAND" >&2; exit 1 ;;
esac

精确字符串匹配,绝不把 $SSH_ORIGINAL_COMMAND 传给 shell。每个权限层用一把受限密钥,不要一把 god key。

第 3 层:客户端 Match 块与 ProxyJump

在控制机,结构化 ~/.ssh/config,让自动化流量显式并经 bastion 路由(ssh_config):

Host bastion
 HostName bastion.example.com
 User automation
 IdentityFile ~/.ssh/automation_ed25519
 IdentitiesOnly yes

Match host "prod-*" user automation
 ProxyJump bastion
 IdentityFile ~/.ssh/automation_ed25519
 IdentitiesOnly yes
 BatchMode yes
 ConnectTimeout 10
 StrictHostKeyChecking yes

ProxyJump 通过 bastion 隧道,不把密钥留在它上面。BatchMode yes 让失败快速失败,而不是挂在密码提示;IdentitiesOnly 阻止 ssh 把 agent 里每把密钥提供给每台主机,既泄露密钥指纹又触发 MaxAuthTries。

工具 3:用 ssh 和 bash 做机群循环

有了加固传输,全机群检查就是遍历 hosts 文件:

#!/usr/bin/env bash
# fleet-check.sh: read-only inspection across the fleet
set -euo pipefail
mkdir -p logs
while read -r host; do
 echo "== ${host} =="
 ssh -n -o BatchMode=yes -o ConnectTimeout=10 "${host}" \
 'df -P /var; systemctl is-active nginx postgresql; uptime' \
 2>&1 | tee -a "logs/${host}.$(date +%F).log" || true
done < hosts-prod.txt

两个会咬人的细节:ssh -n 阻止 ssh 从 stdin 吞掉 hosts-prod.txt 剩余内容(没有它循环会在第一台主机后悄悄停止),|| true 防止在 set -e 下单台不可达杀死整个运行。

串行循环超过 20 台就爬。用 xargs 并行:

xargs -a hosts-prod.txt -P 10 -I {} \
 ssh -n -o BatchMode=yes -o ConnectTimeout=10 {} 'echo "$(hostname) $(df -P / | tail -1)"' \
 2>&1 | tee "fleet-disk.$(date +%F).log"

两条规则保安全。第一,读脚本与写脚本分离:不同文件、不同受限密钥,所以报告循环里的 typo 永远不会重启服务。第二,一切 tee 到带日期日志。那些文件是唯一审计轨迹,当作 append-only 并轮转。任何会变更的操作(重启、清理、升级)都有 dry-run 模式,只打印,人看过输出后才真正执行。

工具 4:用 logrotate 从源头止住磁盘告警

# /etc/logrotate.d/myapp
/var/log/myapp/*.log {
 daily
 rotate 14
 compress
 delaycompress
 missingok
 notifempty
 copytruncate
}

不用等一天验证:

sudo logrotate --debug /etc/logrotate.d/myapp
sudo logrotate --force /etc/logrotate.d/myapp

如果应用支持,让它收到信号时重开日志文件(postrotate 脚本发 kill -USR1 或 systemctl kill -s HUP),而不是 copytruncate,后者可能丢掉 truncate 窗口内写入的行。现代发行版 logrotate 已由 logrotate.timer 运行,无需再排。顺便给 journal 设上限:/etc/systemd/journald.conf 里 SystemMaxUse=1G,然后 sudo systemctl restart systemd-journald。logrotate 的配置细节见 logrotate(8)。

工具 5:unattended-upgrades,自动打补丁

在 Debian 和 Ubuntu(Debian wiki):

sudo apt-get install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades

然后在 /etc/apt/apt.conf.d/50unattended-upgrades 限定为安全更新:

Unattended-Upgrade::Allowed-Origins {
 "${distro_id}:${distro_codename}-security";
};
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Mail "ops@example.com";

干跑:sudo unattended-upgrade --dry-run --debug。

RHEL 系用 dnf-automatic 得到同样行为:安装它,在 /etc/dnf/automatic.conf 的 [commands] 下设置 upgrade_type = security 和 apply_updates = yes,然后 sudo systemctl enable --now dnf-automatic.timer。无论哪种,重启保持手动,让机群循环报告 /var/run/reboot-required(RHEL 上 needs-restarting -r)。打补丁自动化安全,重启时机通常不安全。

脚本扩展到哪里为止

跑一年、跨 50+ 台主机,同样的失败模式会出现:脚本能执行,不能观察、决策或解释。

不能观察。timer 会触发,不管它检查的东西是否仍然重要。没人注意到磁盘检查从某次主机名变更后一直静默失败,也没人注意到一个“临时”清理任务正在删除另一个团队依赖的目录。cron 把邮件发给 root,没人读 root 的邮件。坟场就是这么形成的。

不能决策。脚本编码一个分支:超阈值,跑清理。它无法权衡上下文,比如磁盘满是因为正在部署,主机正在事故中,或者今晚需要和昨晚不同的动作。每个判断仍要叫人。

不能解释。当机群循环改了 50 台主机,审计轨迹是一堆带日期的 tee 日志:跑了什么,但没有为什么、为谁、结果如何。事故后重建要花一晚上,作者离职后就是考古。

复制全文 生成海报 运维 Shell Linux SSH systemd

推荐文章

程序员茄子在线接单