每天自动更新是个笑话: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)上,套路很常见:
echo latest > /etc/dnf/vars/releasever,追 AL2023 的 rolling 快照- 装
dnf-automatic,upgrade_type = security,apply_updates = yes 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 不是「永远对着一个活的包目录扫」。大致是:
- 去拉
.../core/mirrors/latest/$basearch/mirror.list - 文件里给一个带 GUID 的快照 URL
- 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 | [main] |
注意:写 metadata_expire=1 是 1 秒,不是 1 小时。要 1 小时请写 1h 或 3600。
想「每次 dnf 都强制重拉」可以用 metadata_expire=0,语义最干净,但交互式 dnf 也会变慢、多吃带宽;我最后还是选了 1h,对日更足够跟手。
现网若已经落后一截,配置改完仍建议手工逼一次:
1 | dnf clean expire-cache |
(与 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 讲感情。