每天自动更新是个笑话:AL2023、dnf-automatic 与 metadata_expire

缘起

最近一直在啃 WIZ 扫出来的漏洞,修了有一两个月。今天打开一看:某两台 Amazon Linux 2023 的 EC2,操作系统软件包漏洞突然多了大几十个、上百个。

第一反应不是「又有新 CVE 发布了」,而是:你们不是开了每天自动更新吗?

上去一查,更气。dnf-automatic-install.timer 天天在跑,journal 里全是 Success;再对比同环境另一台机器,那边包版本已经新一截了,这两台还停在旧的。不是 timer 挂了,不是没装 dnf-automatic,也不是没写 releasever=latest——配置「看起来全都对」,预期作用却等于零。

气从何来?因为这不是偶发运维疏忽,而是 默认行为在逻辑上就不该和「每天更新」摆在一起。你以为自己买了份日更保险,其实保单条款里写着:索引最多可以旧两天。

背景:我们以为自己做对了的事

独立 Linux EC2(不是 ECS ASG 那套 Patch Manager)上,套路很常见:

  1. echo latest > /etc/dnf/vars/releasever,追 AL2023 的 rolling 快照
  2. 装 dnf-automatic,upgrade_type = security,apply_updates = yes
  3. systemctl enable --now dnf-automatic-install.timer(大约每天 06:00 UTC 起跑,再加随机延迟)

对照机一切正常:某天 automatic 日志里实打实 Upgrade 了二十多个包。出问题的两台:timer 同样 Success,事务却经常是空的。

有人会猜:ARM 包发布慢?其中一台确实是 aarch64。查下来 不是主因——同日 aarch64 仓库里已经能看到新版本 security 包;真正卡住的是 本机还在用一份旧的主仓元数据。

排查:绿灯下面是什么

几条关键证据(同一天、只读核对):

  • 三台 dnf-automatic-install.timer 都是 enabled / active
  • 出问题的那台:amazonlinux.solv 的 mtime 停了十来天;本地缓存的 mirrorlist 仍指向旧的 repo GUID
  • 同机 curl 官方 S3 mirror.list,已经返回 新的 GUID
  • dnf check-update -v 明确写着:repo: using cache for: amazonlinux,元数据日期还是旧快照那天
  • 对照机:某次 automatic 运行时 solv / mirrorlist 一起更新,然后才有 Transaction

结论很粗暴:

自动更新在跑;它装的是「当前缓存索引里认为存在的 security 包」。
缓存钉在旧 GUID 上时,新包对它来说等于不存在。
timer 天天绿,洞照样堆。

原因:24 小时的安装,撞上 48 小时的索引

Amazon Linux 2023 的 releasever=latest 不是「永远对着一个活的包目录扫」。大致是:

  1. 去拉 .../core/mirrors/latest/$basearch/mirror.list
  2. 文件里给一个带 GUID 的快照 URL
  3. dnf 只从这个 GUID 目录取 repodata / 包

AWS 发新 composition 时,会改 mirror.list 指向新 GUID。旧 GUID 上的包集合基本就冻住了。

再看 dnf 默认:[amazonlinux] 主仓往往 不写 metadata_expire(source/debug 反倒写了 6h),于是主仓吃到默认 48 小时。含义是:

  • 距上次成功拉元数据 不到 48h:不重拉 mirror.list,只用本地索引
  • 满 48h 之后,下一次 dnf 操作才会去刷新

于是笑话成型:

你配置的 实际默认
每天跑一次安装 主仓索引最长可旧 48 小时才肯换

数学上就能推出:在「索引未过期、GUID 未切换」的日子里,至少有一部分「每日更新」必然空转。对 AL2023 + latest 这种靠换 GUID 才出新包的模型,空转不是边角料,是主菜。

对照机「及时」也不是因为它绕过了 48h,而是 某次过期重拉碰巧撞上了新 GUID;出问题的机器上次 check 还停在旧 GUID,随后又被新的 48h 窗口兜住。策略一样,运气不同——这种安全机制不配叫机制。

再补一刀:系统自带的 dnf-makecache.timer 默认常常是 disabled;即便启用,服务跑的是 dnf makecache --timer——没过期照样不拉。指望靠它「每小时强制刷新」而不动 metadata_expire,是自我安慰。

修复:让过期时间和更新周期对齐

原则就一句:

多久装一次,索引最多就允许多旧。
不该用默认 48h 去伺候 24h 的 timer。

改法很简单,在 /etc/dnf/dnf.conf 的 [main] 里加(推荐写这里,少被 amazon-linux-repo-* 覆盖 repo 文件时冲掉):

1
2
3
[main]
# ...原有配置...
metadata_expire=1h

注意:写 metadata_expire=1 是 1 秒,不是 1 小时。要 1 小时请写 1h 或 3600。

想「每次 dnf 都强制重拉」可以用 metadata_expire=0,语义最干净,但交互式 dnf 也会变慢、多吃带宽;我最后还是选了 1h,对日更足够跟手。

现网若已经落后一截,配置改完仍建议手工逼一次:

1
2
3
dnf clean expire-cache
dnf makecache
dnf upgrade --security -y --releasever=latest

(与 automatic 的 security 策略一致;若要整系统追 latest,再用全量 dnf upgrade。)

user_data / 镜像构建里把同一行写进去,新机才不会再踩默认 48h。

多说几句

WIZ 一夜暴涨几十上百个 OS 包洞,不一定是扫描器抽风,也可能是:你以为在修的那套「每天自动更新」,从来没按你想象的频率看见过新索引。

配置检查清单如果只停在「timer enabled、automatic.conf 写了 apply_updates」,会漏掉真正要命的那一项:metadata_expire。绿灯 journal 也不能当结案——要看有没有 Transaction,要看 solv / mirrorlist 的 GUID 是否还在追 latest。

这篇文章没有高深架构,就是把一肚子火写成烂笔头:默认值可以省流量,但不能 silently 把「每日安全更新」变成「隔天碰巧才换目录的空转」。下次再在 AL2023 上开 dnf-automatic + releasever=latest,先把 metadata_expire 写进 dnf.conf,别再跟默认 48h 讲感情。