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

- Name
- Youngju Kim
- @fjvbn20031
- 引言 — 三个月一次,这次轮到 fleet
- ConditionFraction= — 分阶段发布来到了单元文件
- 这不是金丝雀发布系统
- IMDS 子系统 — systemd 走进了 cloud-init 的地盘
- dlopen 迁移完成 — 以及 rsyslog 是怎么被搞崩的
- 升级前检查清单 — 那些悄悄咬人的东西
- 其他值得关注的点
- 那么到底谁该在什么时候关心
- 结语
- 参考资料
引言 — 三个月一次,这次轮到 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= 提供的只有总体选择,而让金丝雀发布之所以成为金丝雀发布的其余部分 — 观测、判定、中止 — 它一概不提供。
- 没有健康门控。 就算那 10% 上错误率飙升,systemd 也一无所知。看指标决定要不要停发布,依然完全是外部的事。
- 没有自动回滚。 条件是在单元激活那一刻求值的静态谓词。撤回一次错误的发布,意味着改 drop-in 再重新发布,而这归根到底是配置管理工具的活。
- 改比例就是重新发布。 想从 10% 提到 50%,得改 drop-in 文件再重新推送到 fleet。如果你想要的是在中控台转个旋钮的体验,这个功能给不了。
- 百分比只是近似值。 man 手册自己也写着「大致(approximately)是配置的比例」。因为是基于哈希的,fleet 越小偏差越大。20 台机器上的 10%,可能是 0 台,也可能是 4 台。
那这东西到底是给谁用的?正是 man 手册明说的那个用途 — 面向把同一制品部署到整个 fleet 的镜像式系统的分阶段暴露。像 OSTree 或 UKI 镜像那样、没法(也不想)给每台机器发不同文件的环境里,「发给所有人,但只在一部分上打开」实际上是唯一的发布手段。不依赖中心协调而是确定性运作,这个特性在这类环境里不是缺点,而是刚需。反过来,如果 Kubernetes 或配置管理工具已经能做到按机器控制部署,这个功能几乎打不开什么新的门。
如果需要的是有意为之的环划分,而不是随机比例,就该用新加入的标签系统。从 v261 起,机器可以在 /etc/machine-info 的 TAGS= 字段里带一份标签列表,按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)的一整套子系统,组成部分相当多。
- hwdb.d/40-imds.hwdb — 一个通过 SMBIOS 信息识别云厂商、并描述该云的 IMDS 端点地址、令牌 URL、请求头格式的硬件数据库。按 NEWS 的说法,它能识别 9 家:Amazon EC2、Microsoft Azure、Google Compute Engine、Hetzner、Oracle Cloud、Scaleway、Tencent Cloud、Alibaba ECS、Vultr。hostname、region、zone、公网 IPv4/IPv6、SSH 公钥、user-data 这类「常见键」在各家云上的映射,也都在这里面。
systemd-imdsd@.service— 按man 手册所说,是一个在/run/systemd/io.systemd.InstanceMetadata上通过 Varlink IPC 接受 IMDS 字段查询的本地守护进程。systemd-imdsd-early-network.service— 生成能连上 IMDS 端点所需的 early-boot 网络配置(.network 文件)。systemd-imds— 客户端工具。按 NEWS 所说,它把 IMDS 提供的字段导入为系统 credential,之后再由各服务消费这些 credential。文档也明确写了,导入之前数据会先被测量(measure)进 TPM。systemd-imds-generator— 一旦检测到受支持的云,就自动把这些服务拉进启动事务。NEWS 说的目标是「真正意义上的通用镜像」 — 有 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 的不兼容变更列表里,只挑出了真正会碰到运维的那些。
- 移除了 udev DB v0 支持。 从低于 v247 的版本直接热升级(不重启替换守护进程)到 v261 及以上,已经不再支持。如果你一直在跑非常老的系统,需要先经过一个中间版本,或者规划一次重启。
- nspawn 的
--user=改名为--uid=。 短选项-u和旧写法还能用,但会给出警告,不带参数的--user现在被重新用作选择 user 服务管理器作用域的开关。用到--user的脚本要检查一下它的含义是不是变了。 - TPM/CC 测量值发生变化。 systemd-stub 过去只把 devicetree、initrd、ucode 插件、UKI profile 测量进 TPM,却漏掉了硬件 CC 寄存器(例如 Intel TDX 的 RTMR),这个 bug 现在被修复了。方向上是正确的修复,但寄存器的预期值会变——如果你在运行基于 RTMR 的 attestation,不更新策略验证就会失败。新加入的
systemd-pcrosseparator.service也会往 PCR 0-7、9、12-14 里加入分隔符测量,因此会影响引用了这些 PCR 的 TPM 策略。 - Varlink io.systemd.Unit 的 enum 表示变了。 原先以字符串形式暴露的字段变成了 enum 类型,wire 格式上的取值写法也随之改变(比如 tty-force 变成 tty_force,kmsg+console 变成 kmsg_console)。如果有工具在解析这个接口,需要修改。
- MinimumUptimeSec= 默认变为 15 秒。 为了防止启动后不久就反复自动关机的启动循环,如果在运行时间不满 15 秒时收到关机请求,就会在 shutdown 的最后一步(reboot() 系统调用之前)插入相应的延迟。按systemd-system.conf man 手册源码所说,这条在容器里不生效,也可以设成 0 来关闭。如果你的测试流水线要快速反复启动-关闭,这个默认值会拉长实际耗时。
- musl 最低版本要求 1.2.6。 与维护 musl 构建的发行版有关。
- v262 的预告。
/run/boot-loader-entries/的支持,以及一直是实验性质的 systemd-sysupdated D-Bus API,都计划在下一个版本中移除(后者会被直连 Varlink 取代)。如果你现在依赖它们,得在一个季度内完成迁移。
补充一点背景:这份清单是 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 系列的发行版上已经启动不起来了。
其他值得关注的点
篇幅有限,这里简短带过,但按你的环境不同,这些项目可能比上面那些更重要。
- 能挺过 kexec 的 FD store。 在有内核 Live Update Orchestration(LUO)/ Kexec Handover(KHO)的系统上,
FileDescriptorStorePreserve=yes的单元,其 FD store 能挺过一次 kexec 重启保留下来。按 NEWS 的说法「截至本文写作时仅支持 memfd」,但换内核的同时保留用户空间状态、实现无停机内核升级这个故事的用户空间那一半,就是从这里起步的。systemctl kexec现在不再依赖 kexec-tools、直接调用 kexec_file_load() 系统调用,以及新增的--kernel-cmdline=选项,也是同一个脉络。 - cgroup 控制扩展。
CPUSetPartition=能让单元直接设置 cpuset 分区类型(root、isolated、member)。如果你一直用脚本处理 CPU 隔离,现在可以挪进单元配置里。PSI 方面,CPUPressureWatch=/IOPressureWatch=系列设置让服务能收到 CPU、IO 压力通知(此前只有内存压力)。 - systemd-oomd 规则集。 现在可以在
/etc/systemd/oomd/rules.d/里定义 OOM 策略规则,再在单元里用OOMRule=挑选使用。 - 没有单元文件也能跑的 PID 1。 管理器现在把 basic.target、multi-user.target 这类核心单元集直接内嵌进了二进制文件,即便容器里一个单元文件都没装,systemd 也能作为 PID 1 启动。
- networkd 的 DHCP 中继改版。 随着新的 sd-dhcp-relay 后端加入,原先放在
[DHCPServer]小节里的 4 项中继相关设置被标记为弃用,迁移到了新的[DHCPRelay]小节。如果你在用这些设置,需要迁移。 - resolved 的静态记录 drop-in。 可以通过
/etc/systemd/resolve/static.d/等目录下的 JSON drop-in 定义用于本地解析的 DNS 记录,可以把它看作 /etc/hosts 的泛化版本。 - 软件 TPM 兜底。 新增了
systemd-tpm2-swtpm.service,在没有物理 TPM 的系统上自动回退运行 IBM 的 swtpm(通过内核命令行选项启用)。NEWS 自己也明确说明,这和硬件 TPM 不是同等的安全态势,所以它面向的是需要「比没有 TPM 更好」而非替代硬件 TPM 的场景。 - coredump 里带上内核 7.1 的原因码。 systemd-coredump 现在会收集内核 7.1 提供的 COREDUMP_CODE 字段,coredumpctl 负责解码。内核 7.1 本身,我在sched_ext 子调度器 — 在内核 7.1 中只到位了一半的按 cgroup 调度一文里讲过。
- 一个小花絮 — v261 的贡献者名单里,混在人名之间的还有「Claude Opus 4.6」。AI 提交的补丁被合并、被记录为贡献者,如今在 systemd 这样的核心基础设施里,也成了发布说明里稀松平常的一行。
那么到底谁该在什么时候关心
按对象归纳一下就是这样。
现在马上 — 在机密计算里验证基于 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 的预期值和传递依赖 — 唯独这两点,是那种会精确地咬到没读发布说明的团队的变更。希望这篇文章,能替你把那份阅读补上。
参考资料
- systemd NEWS(raw,项目官方发布说明)
- systemd v261 发布(2026-06-19)
- systemd.unit man 手册源码 @ v261 — ConditionFraction=, ConditionMachineTag=
- systemd-imdsd man 手册源码 @ v261
- hwdb.d/40-imds.hwdb @ v261 — 可识别的云厂商与 IMDS 键映射
- systemd-system.conf man 手册源码 @ v261 — MinimumUptimeSec=
- meson_options.txt @ v261 — imds-network 构建选项
- ELF dlopen metadata 规范(uapi-group)
- CVE-2024-3094 — xz-utils 后门
- Arch Linux core/systemd 软件包 · Ubuntu systemd 源码包(Launchpad)
- systemd 服务管理与单元文件故障排查(相关文章)
- systemd 定时器完全指南(相关文章)