LabHub

博客

systemd v261 — PID 1 迎来分阶段发布、云 IMDS 的收编,以及 dlopen 迁移的收官

한국어English日本語中文

引言 — 三个月一次,这次轮到 fleet

systemd 最近几乎每个季度都会发一个大版本。直接照抄raw NEWS 文件里的发布页脚:v258 是 2025-09-17,v259 是 2025-12-17,v260 是 2026-03-17,而v261 是 2026-06-19。截至本文写作时,Arch Linux 的 core 仓库已经在分发261.1,Ubuntu 的26.10 开发分支(stonking)里也进了 261.1,而 26.04 LTS 还停留在 259.5。也就是说,滚动发行版用户现在就会遇到它,LTS 用户大概要等到明年。

光看版本号,这不过是又一次季度发布,但把 NEWS 从头读到尾,就能看出这次发布有一个清晰的方向。PID 1 已经不满足于做单台机器的 init,开始吸收 fleet 运维的原语了。只让单元在 fleet 一定比例上启用的 ConditionFraction=、用标签把机器分组成环的标签系统、把云实例元数据(IMDS)整合进启动流程的新子系统,全都落在了这一个版本里。与此同时,从 2024 年就开始的依赖隔离工作 — 外部库的 dlopen() 迁移 — 终于收官,从 v258 的 cgroup v1 移除开始的「移除之弧」也仍在继续。

本文不按卖点排序,而是按运维视角的重要程度排序,重点在于分清每一项是什么、能用在哪里,以及 — 同样重要的 — 不是什么。如果你对 systemd 的单元和条件表达式本身还不熟,建议先读systemd 服务管理与单元文件故障排查

ConditionFraction= — 分阶段发布来到了单元文件

v261 中最有意思的新功能。以v261 标签下的 systemd.unit man 手册源码为准,梳理如下。

ConditionFraction= 只在「fleet 的一个稳定的伪随机子集」上激活单元。也就是把同一个单元(或 drop-in)部署到整个 fleet,但只让配置比例的机器上条件为真。判定完全在本地完成 — 把机器 ID 和标签字符串一起哈希,得到一个 32 位整数,如果这个值小于 2^32 的指定百分比就为真(按 NEWS 的说法)。没有中心协调者,没有网络调用,同一台机器永远得到同样的判定。

语法是一个百分比,或者标签加百分比的组合。百分比允许精确到小数点后两位。

# /etc/systemd/system/telemetry-v2.service.d/rollout.conf
# 只在约 10% 的 fleet 上启用这个单元
[Unit]
ConditionFraction=telemetry-v2 10%

在前面加一个感叹号就变成补集 — 照搬 man 手册自己的例子,!myrollout 30% 在剩下大约 70% 的机器上为真。可以直接用它把新旧版本互斥地分开跑。

# 旧版本一侧的单元:只在同一标签的补集上启用
[Unit]
ConditionFraction=!telemetry-v2 10%

这里标签的作用很关键。标签是混入哈希推导的任意字符串(不能有空格),标签不同,选出来的就是相互独立的子集。反过来说,所有不带标签的 ConditionFraction= 条件选出的都是同一批机器 — 不带标签跑两个 10% 的发布,两次实验会精确重叠在同一批机器上。man 手册的建议是:不相关的发布务必用不同的标签,只有想把多个单元绑定到同一批机器时才共享标签。如果读不到机器 ID,条件就判定失败(为假)。

部署前也可以在特定机器上先确认判定结果。systemd-analyze condition 命令能当场求值一个条件字符串。

systemd-analyze condition 'ConditionFraction=telemetry-v2 10%'

这不是金丝雀发布系统

该诚实一下了。「PID 1 用上了金丝雀发布」这句话只对了一半。ConditionFraction= 提供的只有总体选择,而让金丝雀发布之所以成为金丝雀发布的其余部分 — 观测、判定、中止 — 它一概不提供。

那这东西到底是给谁用的?正是 man 手册明说的那个用途 — 面向把同一制品部署到整个 fleet 的镜像式系统的分阶段暴露。像 OSTree 或 UKI 镜像那样、没法(也不想)给每台机器发不同文件的环境里,「发给所有人,但只在一部分上打开」实际上是唯一的发布手段。不依赖中心协调而是确定性运作,这个特性在这类环境里不是缺点,而是刚需。反过来,如果 Kubernetes 或配置管理工具已经能做到按机器控制部署,这个功能几乎打不开什么新的门。

如果需要的是有意为之的环划分,而不是随机比例,就该用新加入的标签系统。从 v261 起,机器可以在 /etc/machine-infoTAGS= 字段里带一份标签列表,按hostnamectl man 手册源码的说法,用 hostnamectl tags 查询、替换(标签是 1 到 255 个 ASCII 字母数字、连字符和点)。在单元这一侧,ConditionMachineTag= 用 shell glob 模式来检查标签。

# 只在带有 ring0 标签的机器(内部 dogfood 环)上启用
[Unit]
ConditionMachineTag=ring0

AssertMachineTag= 也一并加入,而且据 NEWS 所说,systemd-firstboot 可以在配置阶段通过命令行参数或 credential 埋入标签。综合起来就是这样 — 明确的环用标签划,环内的随机比例用 fraction 划,观测和判定交给你自己的监控。这种分工是最贴近设计初衷的理解。

IMDS 子系统 — systemd 走进了 cloud-init 的地盘

v261 新增了处理云实例元数据服务(IMDS)的一整套子系统,组成部分相当多。

运维者需要留意的地方是网络锁定。内核命令行 systemd.imds.network= 接受 off、locked、unlocked 三个值,在 locked 模式下,会给 IMDS 端点铺一条 prohibit 路由,阻断非特权进程的直接访问,所有访问都必须经过 systemd-imdsd。考虑到 IMDS credential 被窃取一直是云端入侵的常见路径,这个方向本身是站得住的。问题在于 NEWS 自己写下的那句话 — 这个锁定从安全角度值得推荐,但它「typically conflicts」,也就是通常会和假定能直接访问 IMDS 的 cloud-init 这类传统客户端冲突。

至于默认值是什么,文档给出的是两个不一致的答案。meson_options.txt 里的 imds-network 选项没有显式默认值,choices 的顺序是 unlocked、locked,按上游源码的规则(meson 的 combo 选项默认取第一项),默认应该是 unlocked;但 man 手册里对内核开关的说明写的是默认为 locked。你系统上实际的默认值取决于发行版在构建时选了什么,所以如果是要和 cloud-init 一起用的镜像,最稳妥的做法是去查发行版软件包的构建选项。

有一点需要说清楚。这不是 cloud-init 的替代品 — 至少现在还不是。systemd-imds 做的事情,止步于把元数据字段拉进来变成 credential;cloud-config 解析、装包、建用户这类配置逻辑都不在它的范围内(NEWS 和 man 手册都没有这么宣称)。不过,hostname、SSH 密钥、user-data 开始进入 credential 体系这件事,读作「简单的配置需求以后可能只靠 systemd 原生部件就能处理」这个未来的第一步棋,是很自然的。cloud-init 那边会怎么和这个锁定模式共存,还有待观察。

dlopen 迁移完成 — 以及 rsyslog 是怎么被搞崩的

2024 年 3 月的 xz-utils 后门(CVE-2024-3094),走的通道正是各发行版把 libsystemd 链接进 sshd 这个习惯 — liblzma 作为 libsystemd 的传递依赖,被一并带进了 sshd 进程。三个月后、2024 年 6 月发布的 v256 开始,systemd 把包括那个 liblzma 在内的五个库(liblz4、libzstd、liblzma、libkmod、libgcrypt)从普通链接改成了基于 dlopen() 的方式,此后每个版本都在扩大这个名单。

v261 是这项工作的终点。这一次 libgnutls、libmicrohttpd、libcurl、libcrypto、libssl、libfdisk、libcryptsetup 也都完成了转换,NEWS 甚至专门用一个方框宣布 — 对外部库的直接链接现在已经全部替换成了 dlopen(),唯一的例外是 libc("with the sole exception of libc")。具体存在哪些 dlopen 依赖,通过嵌入每个二进制文件里的ELF dlopen metadata 注记来声明,因此打包工具能机械化地识别出可选依赖。

这个方向的好处很明显 — 进程里携带的代码变少了,最小化镜像能剔除的库变多了,像 xz 事件那样的传递依赖攻击面也变窄了。但这次转换也造出了反方向的陷阱:libsystemd 过去替别人顺带拉进来的那些库,消失了

v261 的 NEWS 里记录的真实案例,正是这个。libsystemd 链接 libm(数学库)的保证一旦消失,就暴露出了 libfastjson 的问题 — 它一直在用 libm 的符号,却没有真正链接 libm,结果就是 rsyslog 在启动时崩溃。这原本是一个一直被 libsystemd 顺带拉进 libm 掩盖住的 bug。在根本修复(libfastjson 直接链接 libm)落地之前,NEWS 给出了一个临时绕过用的链接器参数。

-Wl,--push-state,--no-as-needed,-lm,--pop-state

这个教训不止于 rsyslog。如果你在构建或打包链接 libsystemd 的软件,现在是该检查一下自己是不是一直在依赖某个「碰巧被链接进来」的传递依赖了。用到的符号就该直接链接,这条原则现在开始被字面意义上地强制执行。

升级前检查清单 — 那些悄悄咬人的东西

从 NEWS 的不兼容变更列表里,只挑出了真正会碰到运维的那些。

补充一点背景:这份清单是 v258 开始的移除潮的延续。v258(2025-09)移除了 cgroup v1 和 System V 的状态控制(runlevel、telinit、init 3),v260(2026-03)接着移除了 System V 服务脚本支持(systemd-sysv-generator、rc-local.service)。如果你还有以 SysV 脚本方式分发的内部守护进程,它在 v260 系列的发行版上已经启动不起来了。

其他值得关注的点

篇幅有限,这里简短带过,但按你的环境不同,这些项目可能比上面那些更重要。

那么到底谁该在什么时候关心

按对象归纳一下就是这样。

现在马上 — 在机密计算里验证基于 RTMR/PCR 的 attestation 的人(测量值变了)、打包链接 libsystemd 的软件的人(得清理传递依赖)、自己构建 rsyslog 的人。如果你在滚动发行版上,v261 已经到了。

该反映进设计里 — 运维镜像式 fleet 的团队。ConditionFraction= 和机器标签,把过去一直靠外部工具模拟的分阶段暴露,下沉成了单元文件的语法。但请按前面说的那个前提去设计 — 观测、判定、回滚依然是你自己的事。IMDS 子系统对一直想减少 cloud-init 依赖的极简镜像阵营来说,正开始成为一个实实在在的选项。

暂时还不急 — 大多数留在 LTS 发行版上的服务器运维者。Ubuntu 26.04 停在 v259,大多数企业发行版比它更靠后。不过 v258 以来的移除潮(cgroup v1、SysV 脚本、udev DB v0)会在你下次大版本升级时一并到来,如果有遗留依赖,值得提前列个清单。

结语

把 v261 压缩成一句话,就是这样 — systemd 不再只是启动单台机器的软件,它已经把自己当成了 fleet 的最小单位。ConditionFraction= 把发布对象的选择、标签把环的划分、IMDS 子系统把云上下文的导入,分别变成了 PID 1 的语法。这三者都不能替代你现有的编排系统,但每一个都把「过去必须靠外部工具才能做到的事」这份清单,往下削了一点点。

与此同时,这次发布也说明 systemd 正把同一条原则用在自己身上。dlopen 迁移的完成,是对 xz 事件暴露出的传递依赖问题一个耗时两年的回答,而 rsyslog 的案例,则是这个回答向整个生态系统开出的账单。升级本身应该会和任何一次季度发布一样平顺,但 attestation 的预期值和传递依赖 — 唯独这两点,是那种会精确地咬到没读发布说明的团队的变更。希望这篇文章,能替你把那份阅读补上。

参考资料

评论

还没有评论。

登录后即可发表评论