Ubuntu系统默认每天会检查一次安全更新,但不会自动重启。对于暴露在公网的服务器,一个高危内核漏洞从公布到被广泛利用可能只需几小时。如果没人手动重启,补丁等于白打。解决这个问题的核心手段不是关闭更新,而是让系统在必要时自己完成重启,同时把对业务的影响降到最低。

理解无人值守升级与重启的分离机制

Ubuntu的无人值守升级由unattended-upgrades包控制。安装后它会自动下载并安装安全更新,但默认配置里重启行为是被禁用的。很多人以为装了它就万事大吉,其实系统更新完依然运行着旧内核,直到下次手动重启。要验证这一点,可以执行cat /var/run/reboot-required,如果文件存在,说明系统需要重启才能完成某些更新。

检查当前状态:

sudo apt install unattended-upgrades
sudo systemctl status unattended-upgrades

如果服务处于运行状态,说明自动更新已经在工作,只是重启环节缺失。接下来的工作就是补上这个环节。

配置自动重启的核心文件

关键配置文件有两个:/etc/apt/apt.conf.d/50unattended-upgrades/etc/apt/apt.conf.d/20auto-upgrades。第一个控制升级的具体行为,第二个控制更新检查周期。

编辑50unattended-upgrades,找到并修改以下行:

Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";
Unattended-Upgrade::Automatic-Reboot-WithUsers "true";

第一行开启自动重启。第二行指定重启时间,建议选在凌晨业务低谷期。第三行决定即使有用户登录是否也执行重启,生产环境建议设为true,否则一个挂着的SSH会话就会阻止重启。

还有一个容易被忽略的参数:

Unattended-Upgrade::Remove-Unused-Kernels "true";
Unattended-Upgrade::Remove-New-Unused-Dependencies "true";

这两行让系统在更新后自动清理旧内核和不再需要的依赖包,避免/boot分区被撑满导致后续更新失败。

精确控制重启时间窗口

凌晨三点重启听起来简单,但如果多台服务器同时重启,可能导致服务全部中断。更精细的做法是利用systemd的定时器配合脚本,实现随机延迟或分批重启。

创建一个自定义重启检查脚本/usr/local/bin/reboot-if-required.sh

#!/bin/bash
if [ -f /var/run/reboot-required ]; then
    sleep $(( RANDOM % 1800 ))
    /sbin/shutdown -r now
fi

这个脚本检测到需要重启的标志文件后,会随机等待0到1800秒再执行重启。给脚本添加执行权限:

sudo chmod +x /usr/local/bin/reboot-if-required.sh

然后创建一个systemd服务单元/etc/systemd/system/reboot-if-required.service

[Unit]
Description=Reboot if required after unattended upgrades
After=unattended-upgrades.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/reboot-if-required.sh

再创建一个对应的定时器/etc/systemd/system/reboot-if-required.timer

[Unit]
Description=Timer for reboot-if-required service

[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=3600

[Install]
WantedBy=timers.target

启用定时器:

sudo systemctl daemon-reload
sudo systemctl enable reboot-if-required.timer
sudo systemctl start reboot-if-required.timer

这套方案比单纯依赖unattended-upgrades的内置重启功能更灵活。RandomizedDelaySec让定时器本身就有随机偏移,加上脚本内的随机睡眠,多台服务器几乎不可能同时重启。

处理需要重启的服务而非整机重启

并非所有安全更新都需要重启整台机器。比如OpenSSL库更新后,只需要重启依赖它的服务。Ubuntu的needrestart工具能精确识别哪些服务需要重启,并自动处理。

安装needrestart:

sudo apt install needrestart

编辑配置文件/etc/needrestart/needrestart.conf,设置自动重启服务:

$nrconf{restart} = 'a';

这个值设为'a'表示自动重启所有需要重启的服务。设为'l'则仅列出不执行,设为'i'为交互模式。生产环境建议先用'l'观察一段时间,确认无误后再改为'a'

needrestart会hook到apt的DPkg::Post-Invoke阶段,每次安装或更新软件包后自动运行。它会检查哪些守护进程使用了被替换的库文件,然后根据配置决定是否重启这些服务。对于内核更新,它仍然会提示需要整机重启,但配合前面的重启脚本就能形成完整闭环。

内核热补丁方案绕过重启

对于绝对不能停机的环境,Ubuntu Pro订阅提供了Livepatch服务,可以在不重启的情况下给运行中的内核打补丁。免费用户可以给最多5台机器使用。

获取Ubuntu Pro令牌后,执行:

sudo pro attach YOUR_TOKEN
sudo pro enable livepatch

Livepatch只覆盖高危和关键安全漏洞,不是所有内核补丁都能热更新。当内核版本跨度太大时,最终仍需要重启。它的价值在于大幅降低重启频率,从每月几次降到每年几次。

检查Livepatch状态:

canonical-livepatch status --verbose

输出会显示当前内核的补丁覆盖情况和历史记录。这个方案适合数据库服务器、核心网络设备等对可用性要求极高的场景。

监控与验证机制

配置完自动重启后,必须建立验证手段,否则系统可能悄悄失败而无人知晓。最直接的方式是监控/var/run/reboot-required文件的存在时间。

一个简单的监控脚本思路:如果该文件存在超过48小时,说明自动重启可能未生效,需要人工介入。可以把这个逻辑接入现有的监控系统如Zabbix或Prometheus的textfile collector。

对于使用systemd定时器的方案,检查定时器是否正常触发:

systemctl list-timers reboot-if-required.timer
systemctl status reboot-if-required.service

查看最近一次执行的时间和退出状态。如果服务单元状态为失败,检查日志:

journalctl -u reboot-if-required.service

另外,/var/log/unattended-upgrades/目录下有详细的升级日志,可以确认安全更新是否真的被安装了。如果更新都没下载成功,重启自然也不会发生。

邮件通知与日志审计

unattended-upgrades支持邮件通知。在50unattended-upgrades中配置:

Unattended-Upgrade::Mail "admin@example.com";
Unattended-Upgrade::MailReport "on-change";

MailReport设为"on-change"表示只在有更新时才发邮件,避免每天收到无意义的报告。设为"only-on-error"则只在出错时通知。

对于重启事件,可以在重启脚本中加入日志记录:

#!/bin/bash
if [ -f /var/run/reboot-required ]; then
    echo "$(date): System rebooting due to security updates" >> /var/log/auto-reboot.log
    sleep $(( RANDOM % 1800 ))
    /sbin/shutdown -r now "Automatic reboot for security updates"
fi

这样每次自动重启都会留下时间戳记录,方便事后审计。

针对不同场景的推荐策略

对于个人VPS或小型网站,直接使用unattended-upgrades内置的Automatic-Reboot功能即可,设置凌晨时段重启,简单有效。

对于中型业务集群,采用systemd定时器加随机延迟的方案,确保多台服务器不会同时重启。配合needrestart减少不必要的整机重启。

对于大型生产环境,优先考虑Livepatch减少重启次数,同时用needrestart处理服务级重启。剩余必须重启的情况通过滚动更新的方式分批执行,可以结合负载均衡器的健康检查自动摘除节点。

无论哪种场景,都要确保/boot分区有足够空间。定期检查:

df -h /boot

如果使用率超过80%,手动清理旧内核:

sudo apt autoremove --purge

这条命令可以加入cron月度任务,防止空间耗尽。

安全更新的最终目的是降低风险,但配置不当的自动重启本身就是一种风险。关键在于找到适合自身业务的平衡点,用脚本和监控把不可控变成可控。设置好后定期检查日志,确认整个链路畅通,才能真正做到高枕无忧。