自动更新开了,但只覆盖一部分源——怎么验证它真的按你预期在跑
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 |
❌ |
发行版安全团队只维护 main 和 restricted。universe 里的包由社区维护,其安全修复走 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
小结
Allowed-Origins的默认值只覆盖发行版池与安全池,-updates被注释掉,universe组件的补丁因此不会自动安装- 验证方法是看
--dry-run --debug里的 pin 决策(Marking not allowed ... with -32768 pin),不是看下载 URL unattended-upgrades.service可能是关机钩子;执行者、以及"是否真的跑过",分别看 timer 和/var/log/unattended-upgrades/- 装完不等于生效:内核/glibc 需要重启,
Automatic-Reboot默认关,重启时机得有人负责