Chips

自动更新开了,但只覆盖一部分源——怎么验证它真的按你预期在跑

· 约 4 分钟 · 1344 字

unattended-upgrades 的默认 Allowed-Origins 只含 release 与 security 两个 pocket,universe 组件的补丁不会自动装。记录用 --dry-run --debug 读 apt 的 pin 行为来验证覆盖范围的方法,以及两个容易误读的输出。

一个常见误解

APT::Periodic::Unattended-Upgrade 设成 1 之后,很多人认为"补丁会自动装上了"。实际默认的 Allowed-Origins 是:

Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}";            // 发行版池(版本永不高于已装,等于空转)
    "${distro_id}:${distro_codename}-security";   // 安全池
//  "${distro_id}:${distro_codename}-updates";   // ← 默认被注释掉
};

注意 ${distro_id}:${distro_codename}(不带后缀)指向发行版池(如 jammy),它只包含初始发布版本,永远比已装的旧,实际上什么都不会装。真正起作用的是 -security

-updates(SRU 池)默认是注释状态。这带来一个分布上的后果:

组件 补丁发布位置 是否被自动安装
main / restricted 安全修复进 -security
universe 安全修复进 -updates

发行版安全团队只维护 mainrestricteduniverse 里的包由社区维护,其安全修复走 SRU 通道,也就是 -updates。所以在默认配置下,universe 里的软件不会自动获得安全补丁

这不是 bug,是有意的默认值(避免自动引入行为变更较多的 SRU)。但如果你以为"自动更新已开启 = 所有安全补丁都会装",那是错的。

怎么验证真实的覆盖范围

读配置文件只能告诉你"写了什么"。要确认"实际会发生什么",得看 apt 的依赖解析过程。

unattended-upgrade --dry-run --debug > /tmp/uu.log 2>&1

然后查两处关键输出。

1. 允许的源列表

Allowed origins are: o=Ubuntu,a=jammy, o=Ubuntu,a=jammy-security, ...

2. pin 行为——这才是决定性证据

日志里会对每个索引文件打一个 pin 决策:

Marking not allowed <... dists_jammy-updates_main_binary-amd64_Packages ...> with -32768 pin

-32768 是"禁止"的哨兵值。一个源被标成不允许,唯一可靠的判据是它被 pin 成 -32768。 检查哪些索引文件被打上了这个 pin,就知道哪些源不会参与自动升级:

grep 'Marking not allowed' /tmp/uu.log

如果 jammy-updates 的各个组件都在这个列表里,那么 SRU 通道确实被排除了——与预期一致。反之如果某个你想覆盖的源出现在列表中,说明它没被覆盖。

两个容易误读的输出

误读一:把 dry-run 里的 Get: URL 当成"该源被放行"

dry-run 的输出里有大量这样的行:

Get:12 http://deb.debian.org/... jammy-updates/main amd64 some-package ...

看起来像是要从 -updates 装东西了。实际上,这是 unattended-upgrade 调用 apt 下载阶段时按 apt 默认策略解析出的 URL,进程内应用的 pin 并没有作用到这一步。当同一个版本同时存在于 -updates-security 时,apt 会选择 -updates 路径下载。

判据应该始终是 pin 决策,而不是下载 URL。

误读二:把服务状态当成执行者状态

systemctl status unattended-upgrades

可能显示 active (running),但这很可能是 unattended-upgrade-shutdown --wait-for-signal——开机钩子,只在关机时通知 apt 完成未竟事项,不是升级执行器。

真正周期性触发的执行者是 systemd timer:

systemctl list-timers 'apt-daily*'
apt-daily.timer          → 刷新包索引
apt-daily-upgrade.timer  → 执行 unattended-upgrade

另外,判断它历史上是否真的跑过,看日志目录里有没有实际升级日志,而不是看服务在不在跑:

ls -l /var/log/unattended-upgrades/

如果里面只有 unattended-upgrades-shutdown.log,那说明从来没有执行过实际升级

其他几个值得改的默认值

配置项 默认 影响
APT::Periodic::AutocleanInterval 0 旧包不清理,长期会堆积
Unattended-Upgrade::Automatic-Reboot 未设(false) 内核/glibc 更新装了但不生效,需人工重启
Unattended-Upgrade::Remove-Unused-Kernel-Packages 未设 旧内核留存
Unattended-Upgrade::Mail 未设 出问题不会有通知

这里有个必须理解的连带关系:自动装安全更新 ≠ 补丁生效。内核和 glibc 这类需要重启的更新,装完之后系统进入"已安装但未运行"的状态:

[ -f /var/run/reboot-required ] && cat /var/run/reboot-required
cat /var/run/reboot-required.pkgs     # 哪些包导致的

Automatic-Reboot 默认关闭是合理的(生产环境不该自己重启),但代价是重启这件事从此需要有人记得。如果这台机器有服务在上面,自动重启可能比不重启造成的损失更大——这个取舍要显式做,而不是让它默认。

首次执行会做什么

把范围放宽到包含 -updates 之后,第一次执行可能一次性装上几百个包(含新内核和 initramfs 重新生成,耗时数分钟到十几分钟)。建议先用 --dry-run 看清清单规模,不要在不知情的情况下等它自己发生。

--dry-run 会真实下载需要的包(只为了模拟依赖),几百 MB 是常见的。如果不想为模拟付出带宽,可以先看包清单:

apt list --upgradable | wc -l
apt list --upgradable | grep -c security

小结

← 全部文章