文章总结: 本文复盘了K8s集群中执行systemd命令导致绑核容器CPU冲突的故障。排查发现runcv1.1.5向systemd传递CPU绑核信息时存在解析顺序错误,导致systemd对账时将错误的配置写入cgroup,使多容器共用CPU引发争抢。文章详细展示了利用Perfetto和BPF工具定位根因的过程,最终通过升级runc至v1.1.6解决问题,为云原生环境下的类似故障排查提供了宝贵经验。 综合评分: 98 文章分类: 实战经验,应急响应,漏洞分析
干货 | 执行个 systemd 命令,竟然让容器CPU“打架”?一文看懂绑核错乱的根因
原创
YY YY
携程技术
2026年3月12日 16:30 上海
作者简介
YY,携程高级云原生研发经理,关注云原生技术相关;
licabbage,云原生研发工程师,关注单机性能优化方向;
团队热招岗位:资深云原生开发工程师(容器)
导读:近期,我们遇到了一个棘手的线上故障:在 K8s 集群宿主机执行 systemd 相关发布操作时,会触发宿主机上的业务服务出现超时问题。
本文将完整复盘这一故障的排查全过程,从业务超时的异常现象切入,一步步抽丝剥茧,最终定位到runc 1.1.5 版本向 systemd 传递错误绑核信息这一根因。排查过程中,我们还会分享如何巧用 Perfetto、BPF 等工具捕捉关键线索,还原问题本质。无论你是 K8s 运维人员、容器技术爱好者,还是经常与线上故障 “过招” 的研发同学,都能从这份实战案例中收获排查思路与工具使用技巧,为日后解决同类问题提供可借鉴的参考。
关键词:k8s, BPF, containerd, runc, systemd
- 一、故障的起因
- 二、排查过程
- 2.1 为什么 CPU Load 会升高?
- 2.2 为什么 CPU 使用率掉底了?
- 2.3 为什么绑核容器使用了同样的 CPU?
- 2.4 Systemd 的错误参数是哪里来的?
- 三、总结与思考
一、故障的起因
在某次对 systemd 管理的组件进行变更后,观测到服务出现大量超时。这些服务都是绑核的时延敏感型服务,所在机器的 cpu load 都显著升高,这可能是造成业务超时的原因。
二、排查过程
2.1 为什么 CPU Load 会升高?
在故障发生的机器上,我们注意到 CPU load 明显升高。
为了复现问题,我们选取了一台宿主机,排空上面的业务实例,并部署了 90 个 2 核的绑核容器用于模拟业务实例。对这 90 个容器发送请求,使得 cpu 利用率稳定在 50%,然后对这台宿主机进行一次相同的变更。cpu load 并没有像故障时那样剧烈上升,但是我们却发现了 cpu util 的异常现象:在本次变更后,cpu util 瞬间下降。下图为我们在问题复现时的 cpu util 变化情况。
我们又重新看了下机器监控,发现在故障发生时,很多其他的指标也符合 cpu util 下降的表现,例如:
- cpu idle time 上升
- 进程切换的次数下降
- 软中断的次数下降
我们意识到,cpu load 升高可能并不是业务超时的原因,而是结果。我们有一直往被测试的 Pod 打流量,按理说它们应该一直很繁忙猜对,为什么 CPU 使用率会下降呢?当时的 CPU 核上在做什么?
2.2 为什么 CPU 使用率掉底了?
那么为何发布会导致 cpu 使用率掉底了?我们抓取了 cfs 的调度事件,想看看当时 CPU 上到底在跑什么,使用perfetto 分析得到的数据如下:
可以看到,在发布后,某些核上的任务被切成 idle 了。这些容器都是绑核的容器,是一直有流量进来的,容器一直存活,为何这些核上的任务被切成 idle 了?
检查了下容器的 cpuset.cpus,发现,在发布过后,一些不同的容器竟然绑定到了相同的核!
我们又对比了发布前容器拿到的 cpu 列表和 kubelet 分配的记录,是一致的。
也就是说,在发布前,容器分配到的核与 kubelet 分配的记录保持一致,符合预期。而在发布后,分配被打乱,有些容器使用了同样的 CPU 列表。这就导致在这些 CPU 上排队的线程数多了,cpu load 上升, 线程迟迟得不到调度,业务的延迟也上升。被空出来的 cpu 就处于 idle 状态,所以整体的 cpu 使用率下降了。
2.3 为什么绑核容器使用了同样的 CPU?
那么是谁改了容器的 cpuset.cpus,导致不同的绑核容器跑到了同样的 cpu 核上 ?我们写了一个 bpf 程序,通过埋点 linux 内核函数 cpuset_write_resmask ,监控是谁在修改 cpuset.cpus 文件。
发现在容器创建时,runc 和 systemd 都会去设置 cpuset.cpus 文件,其中 runc 直接写入的 cpu 列表是 kubelet 分配的 cpu 列表,符合预期,而 systemd 写入的是错的。
在容器创建时,执行顺序上,systemd 会先写入,runc 再后写入,因此最终的结果是对的。发布通过 systemd 管理的组件时,会执行 systemd daemon-reload , 这会触发 systemd 对账,将它自己持有的配置数据同步到 cgroup , 从上图我们可以看到它持有的配置数据是错的,同步到 cgroup 后就导致不同的绑核容器使用同样的 cpu 核了,这就是业务超时的原因。
2.4 Systemd 的错误参数是哪里来的?
systemd 设置 cpuset.cpus 的链路为“容器创建请求 -> kubelet -> containerd -> containerd-shim-runc-v2 -> runc -> systemd ->容器 cgroup 的 cpuset.cpus”
结合 bpf 程序的结果。我们可以发现,runc 拿到的是一个正确的参数,但是 systemd 却得到了一个错误的参数,问题可能出现在 runc 传给 systemd 的参数上。
该机器使用的 runc 版本是 v1.1.5,去查看了 runc 的 release log ,在 v1.1.6 中发现了相关的 bug-fix。
于是我们将 runc 从 v1.1.5 升级到了 v1.1.6,再次进行实验,发现错误绑核的问题不再发生。至此,我们将问题定位到了 runc v1.1.5 的 bug 上。
定位问题后,现在来详解一些为何 runc 拿到的是正确的绑核信息,传给 systemd 后就变成了错误的绑核信息。这里先贴一下该 bug-fix 的 PR https://github.com/opencontainers/runc/pull/3808/files
关键的代码为:
// fit cpuset parsing order in systemd for l, r := 0, len(ret)-1; l < r; l, r = l+1, r-1 { ret[l], ret[r] = ret[r], ret[l] }
这段代码的逻辑是做一个反序操作,将 cpu 序号的二进制掩码转成 systemd 要求的顺序,systemd 在这个 PR 里面做了对应的接口。
举例来说,假设一个容器想要绑定的核是”0-3,12″,即 0 至 3 号核,以及 12 号核。
systemd 期望得到的信息是这样的:
// 从右往左数,从上往下数00001111 // 这 8 位就表示 0,1,2,3 号 cpu 00010000 // 这个 1 是第 12 个二进制位,表示 12 号 cpu 也要用
在 runc 的这个 bug-fix 之前,并没有进行反序操作,因此 runc 给 systemd 的信息是这样的
00010000 // 这 8 位被 systemd 解读成 4 号核00001111 // 这 8 位被 systemd 解读成 8 至 11 号核
这就导致 systemd 拿到的 cpu 信息为 4 号核,以及 8 至 11 号核。
那为何会有不同容器绑定到一个核上? 举例来说,容器 A 想要绑定的是 8号核与 10 号核,容器 B 想要绑定的是 16号核与 18 号核。 Systemd 期望的参数是:
容器 A:
0000000000000101 // 8 号以及 10 号核
容器 B:
000000000000000000000101 // 16 号以及 18 号核
实际 runc 1.1.5 版本给 systemd 的信息分别是:
容器 A:
0000010100000000
容器 B:
000001010000000000000000
两者都被 systemd 解析为需要 0号核与 2 号核。
三、总结与思考
最后来总结下,本次故障的原因为:
- 容器创建时,systemd 拿到了错误的绑核信息,runc 拿到了正确的绑核信息,由于 systemd 先写入配置,runc 后覆盖写入,此时容器的绑核信息最终保持正确。
- 执行 systemd 相关发布操作(如 systemctl daemon-reload)时,会触发 systemd 再次写入错误的绑核信息,覆盖原正确配置。多个容器因此绑定到相同 CPU 核,引发算力竞争,业务无法获得足够资源导致超时,进而触发请求重试,最终表现为 CPU load 升高、业务服务异常。
本次故障排查跨越了 K8s、containerd、runc、systemd 多个组件,涉及内核调度、容器绑核等底层逻辑。整个过程既验证了工具的价值,也沉淀了可复用的故障排查与运维思路。
线上故障既是挑战,也是优化的契机。本次排查不仅解决了具体问题,也启发了我们要打破“现象即原因”的思维定式,聚焦指标关联性分析, 同时要重视组件版本管理与 bug 追踪,从源头规避风险。
希望本文能给遇到类似问题的小伙伴们一些启发。最后,感谢烧鱼、kimi song两位同学在故障排查过程中给予的大力支持和帮助。
【推荐阅读】
- 携程IT桌面全链路工具研发运营实践
- 携程光网络抵御光缆中断实践
- 携程分布式图数据库Nebula Graph运维治理实践
- JuiceFS 在携程海量冷数据场景下的实践
“携程技术”公众号
分享,交流,成长
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:携程技术 YY YY《干货 | 执行个 systemd 命令,竟然让容器CPU“打架”?一文看懂绑核错乱的根因》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论