cgroup v1-v2 与 SystemdCgroup:Kubernetes + containerd 为什么会因为它翻车?
节点翻车时,不要只盯着镜像和网络
生产集群升级后,最容易被误判的现象有三类:
- 节点突然
NotReady,kubelet 日志里反复出现 runtime、PLEG 或资源统计相关报错。 - Pod 能调度到节点,但启动慢、反复重启,或者资源限制表现和预期不一致。
kubectl top、监控系统、eviction 判断出现异常,排查镜像、DNS、CNI 都没有明显结果。
这类问题不一定是 containerd 坏了,也不一定是 Kubernetes 版本不兼容。很多时候,真正要查的是这条链路:
kubelet -> CRI -> containerd -> runc -> systemd/cgroup
只要 kubelet 认为节点应该用 systemd 管理 cgroup,而 containerd/runc 仍按另一套方式创建容器资源层级,节点就会进入一种很尴尬的状态:服务都能启动,但资源视图不一致,Pod 行为开始变得不稳定。
本文不把 cgroup 讲成纯概念,而是按生产排障顺序讲清楚:先看 cgroup 版本,再看 kubelet,再看 containerd,最后决定是否灰度修正。
先划边界:cgroup、cgroup driver、SystemdCgroup 不是一回事
这三个词经常被混在一起:
| 名称 | 所在层级 | 你应该检查哪里 | 典型问题 |
|---|---|---|---|
| cgroup v1/v2 | Linux 内核资源控制机制 | /sys/fs/cgroup |
节点底层资源层级与旧配置不匹配 |
kubelet cgroupDriver |
Kubernetes 节点代理配置 | /var/lib/kubelet/config.yaml 或 kubelet 启动参数 |
kubelet 统计和管理 Pod 资源的方式不一致 |
containerd SystemdCgroup |
containerd/runc 运行时配置 | /etc/containerd/config.toml 或 containerd config dump |
容器实际创建的 cgroup 层级与 kubelet 预期不一致 |
简单说:
- cgroup v1/v2 决定 Linux 底层资源控制树长什么样。
- kubelet
cgroupDriver决定 kubelet 用什么方式管理 Pod 资源。 - containerd
SystemdCgroup决定 runc 创建容器时是否交给 systemd 管理 cgroup。
这三者不是永远必须同名,但在 Kubernetes 生产节点上,kubelet 与 containerd 的资源管理方式必须对齐。现在主流 systemd 节点通常要求 kubelet 使用 systemd,containerd runtime 配置里 SystemdCgroup = true。

第一步:检查节点到底跑在 cgroup v1 还是 v2
先不要改配置。第一步只回答一个问题:这台节点的 /sys/fs/cgroup 是什么文件系统。
stat -fc %T /sys/fs/cgroup
用途:判断当前节点 cgroup 层级类型。
判断标准:
- 输出
cgroup2fs:节点使用 cgroup v2 unified hierarchy。 - 输出
tmpfs:多半是 cgroup v1 方式,需要继续看/proc/filesystems和/proc/cgroups。
继续确认内核挂载:
findmnt -R /sys/fs/cgroup
用途:查看 cgroup 挂载层级,是单一 unified tree,还是多个 controller 分散挂载。
判断标准:
- cgroup v2 常见为
/sys/fs/cgroup一棵统一树。 - cgroup v1 常见为
cpu、memory、blkio、pids等多个 controller 分开挂载。
再看内核命令行是否显式指定:
cat /proc/cmdline
用途:确认是否通过启动参数强制启用或禁用 unified cgroup hierarchy。
关注字段:
systemd.unified_cgroup_hierarchy=1systemd.unified_cgroup_hierarchy=0cgroup_no_v1=all
判断标准:如果启动参数和操作系统默认策略不一致,要把它写入升级记录。不要只看发行版版本号就假设一定是 v1 或 v2。
第二步:检查 kubelet 的 cgroupDriver
kubelet 的配置入口通常在 /var/lib/kubelet/config.yaml。先看静态配置:
grep -n 'cgroupDriver' /var/lib/kubelet/config.yaml
用途:查看 kubelet 当前声明的 cgroup driver。
判断标准:
cgroupDriver: systemd:主流 systemd 节点推荐配置。cgroupDriver: cgroupfs:老集群或特殊节点可能存在,必须和 containerd runtime 侧一起核对。- 没有输出:继续看 kubelet 启动参数,不能直接判断为空。
继续看 systemd 启动参数:
systemctl cat kubelet
用途:查看 kubelet systemd unit 和 drop-in 文件,确认是否通过参数覆盖配置文件。
关注字段:
--config=/var/lib/kubelet/config.yaml--cgroup-driver=systemd--cgroup-driver=cgroupfsKUBELET_EXTRA_ARGS
判断标准:启动参数优先级可能高于你看到的配置文件。如果 drop-in 里写了 --cgroup-driver,要以实际启动参数为准。
最后用进程命令行兜底:
ps -ef | grep '[k]ubelet'
用途:确认 kubelet 实际运行时参数。
判断标准:如果配置文件、systemd drop-in、进程参数三者不一致,先不要改 containerd,先把 kubelet 配置来源理清楚。
第三步:检查 containerd 的 SystemdCgroup
containerd 的配置文件一般在 /etc/containerd/config.toml,但生产上更建议先看运行时 dump 出来的最终配置。
containerd config dump | grep -n -A 12 -B 4 'SystemdCgroup'
用途:查看 containerd 当前 runtime options 里 SystemdCgroup 的实际值。
判断标准:
SystemdCgroup = true:runc 交给 systemd 管理 cgroup。SystemdCgroup = false:runc 使用非 systemd 管理方式。- 没有输出:可能配置路径不同,继续检查 runtime 插件路径。
containerd 2.x 的 CRI 配置路径和旧文章里常见的 1.x 路径可能不完全一样,建议同时定位 runtime 配置块:
containerd config dump | grep -n -E 'io.containerd.cri|runtime_type|BinaryName|SystemdCgroup'
用途:确认 CRI 插件、runtime handler、runc options 是否在同一个配置块里。
判断标准:不要只复制网上某一段 [plugins."io.containerd.grpc.v1.cri"] 配置。不同版本的 containerd 配置路径可能不同,必须以本机 containerd config dump 为准。
如果要看文件本身:
grep -n -A 20 -B 6 'SystemdCgroup' /etc/containerd/config.toml
用途:确认持久化配置文件里是否写了 SystemdCgroup。
判断标准:如果 config dump 和文件不一致,说明可能用了默认配置、导入配置或服务未重启。此时不要只改文件,要核对 containerd 服务实际加载状态。
第四步:确认 CRI 链路和节点状态
cgroup 配置检查不能脱离 CRI 连通性。先确认 crictl 指向 containerd:
cat /etc/crictl.yaml
用途:确认 CRI endpoint 与 image endpoint。
判断标准:
- 推荐看到
runtime-endpoint: unix:///run/containerd/containerd.sock。 - 如果还指向 Docker、CRI-O 或不存在的 socket,先修正 crictl 配置,否则后面的判断可能查错对象。
检查 CRI 是否能返回 runtime 信息:
crictl info
用途:验证 kubelet 使用的 CRI runtime 是否可连通。
判断标准:
- 能输出 runtime、config、status:CRI 基本连通。
connection refused:查 containerd 服务和 socket。context deadline exceeded:查服务卡顿、CRI 插件和日志。permission denied:查执行用户权限或用 root/sudo 验证。
检查 containerd 服务:
systemctl status containerd --no-pager
用途:确认 containerd 当前状态和最近错误。
判断标准:状态必须是 active (running)。如果反复重启,先看日志,不要直接改 cgroup。
查看 containerd 日志:
journalctl -u containerd -n 200 --no-pager
用途:查 runtime、CRI plugin、runc、cgroup 相关错误。
关注关键词:
cgroupsystemdruncruntimefailed to create taskfailed to update resources
检查 kubelet 日志:
journalctl -u kubelet -n 200 --no-pager
用途:从 Kubernetes 节点视角看 runtime 和资源管理异常。
关注关键词:
container runtimePLEGcgroupevictionruntime statusfailed to get node info
第五步:用表格判断是否真的不一致
不要看到 SystemdCgroup = false 就直接改。先按场景判断:
| 节点场景 | kubelet cgroupDriver |
containerd SystemdCgroup |
是否推荐 | 风险说明 |
|---|---|---|---|---|
| systemd 管理的现代 Linux 节点 | systemd |
true |
推荐 | kubelet、containerd、runc 资源视图一致 |
| systemd 节点但 containerd 为 false | systemd |
false |
不推荐 | Pod 资源层级和 kubelet 预期不一致,升级后容易暴露 |
| 老集群历史配置 | cgroupfs |
false |
仅限存量确认 | 不要无验证直接切换,先做单节点灰度 |
| 混合节点池 | 不同节点不一致 | 不同节点不一致 | 高风险 | 调度、监控、排障口径混乱,建议分池治理 |
| 新增节点基线 | systemd |
true |
推荐 | 写入节点初始化脚本和基线检查 |
再把现象和下一步对应起来:
| 现象 | 优先检查 | 命令 | 判断标准 | 下一步 |
|---|---|---|---|---|
| 节点升级后 NotReady | kubelet 日志和 CRI 状态 | journalctl -u kubelet -n 200 --no-pager |
有 runtime/cgroup/PLEG 关键词 | 继续查 containerd 和 cgroup 配置 |
| Pod 启动失败但镜像可拉取 | containerd/runc 日志 | journalctl -u containerd -n 200 --no-pager |
有 create task/update resources 错误 | 核对 SystemdCgroup |
| 资源统计不准 | cgroup 版本和 kubelet driver | stat -fc %T /sys/fs/cgroup |
v1/v2 与预期不一致 | 核对节点 OS 和 kubelet 配置 |
| eviction 异常 | kubelet 配置和系统资源 | grep -n 'cgroupDriver' /var/lib/kubelet/config.yaml |
driver 与 runtime 不一致 | 做灰度修正 |
| 同一集群节点表现不同 | 节点池配置差异 | kubectl get nodes -o wide |
OS/内核/runtime 版本混杂 | 分批治理,不要全量改 |
第六步:生产修正前先备份
涉及 /etc/containerd、/var/lib/kubelet/config.yaml、systemd drop-in 的修改,必须先备份。不要在生产节点上凭记忆直接改。
mkdir -p /root/k8s-node-backup/$(date +%F-%H%M%S)
用途:创建本次节点变更备份目录。
判断标准:目录创建成功,并且路径带时间戳,便于回滚时定位。
backup_dir=/root/k8s-node-backup/$(date +%F-%H%M%S)
mkdir -p "$backup_dir"
cp -a /etc/containerd "$backup_dir"/
cp -a /var/lib/kubelet/config.yaml "$backup_dir"/kubelet-config.yaml
systemctl cat kubelet > "$backup_dir"/kubelet-systemd-unit.txt
用途:备份 containerd 配置、kubelet 配置和 systemd unit 实际内容。
判断标准:备份目录里至少包含 containerd/、kubelet-config.yaml、kubelet-systemd-unit.txt。
ls -lh "$backup_dir"
用途:确认备份文件真实存在,不是只执行了变量赋值。
判断标准:能看到备份文件大小不为 0。正式变更前把备份路径记录到变更单。
第七步:灰度修正 kubelet 和 containerd
修正原则是:一次只选一个节点池或一台节点灰度,先 drain,再改配置,再重启服务,再验证,再放量。
先隔离节点:
kubectl cordon <node-name>
用途:阻止新 Pod 调度到目标节点。
判断标准:kubectl get nodes 中目标节点显示 SchedulingDisabled。
驱逐业务 Pod 前先确认 PDB 和关键业务:
kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data
用途:把普通工作负载迁出目标节点,降低重启 kubelet/containerd 的影响。
判断标准:非 DaemonSet Pod 已迁出;如果被 PDB 阻塞,不要强行加 --force,先找业务负责人确认。
修改 kubelet 配置时,确认字段在 /var/lib/kubelet/config.yaml:
grep -n 'cgroupDriver' /var/lib/kubelet/config.yaml || true
用途:定位 kubelet 配置字段。
判断标准:如果已有字段,改成目标值;如果没有字段,按 kubelet 配置结构补充,并确认 kubelet 版本支持。
containerd 侧要修改 SystemdCgroup,不要盲目追加重复块。先定位:
containerd config dump | grep -n -A 12 -B 4 'SystemdCgroup'
用途:确认当前 runtime options 所在位置。
判断标准:找到当前 runtime handler 后,再改 /etc/containerd/config.toml 对应配置。改完后用 containerd config dump 复查实际生效值。
重启服务:
systemctl daemon-reload
systemctl restart containerd
systemctl restart kubelet
用途:让 containerd 和 kubelet 重新加载配置。
判断标准:服务重启后必须进入 active (running),并且日志中没有连续 runtime/cgroup 报错。
验证节点:
systemctl is-active containerd kubelet
用途:快速确认两个关键服务是否运行。
判断标准:两个服务都输出 active。
crictl info >/tmp/crictl-info.json && head -n 20 /tmp/crictl-info.json
用途:确认 CRI 仍可用。
判断标准:能正常返回 runtime 信息;如果失败,立即回滚配置并查看 containerd 日志。
kubectl get node <node-name> -o wide
用途:确认节点状态。
判断标准:节点回到 Ready,版本、内核、runtime 信息符合预期。
恢复调度:
kubectl uncordon <node-name>
用途:灰度节点验证通过后恢复调度。
判断标准:节点不再显示 SchedulingDisabled。
第八步:回滚路径要提前写好
如果灰度节点修正后出现 kubelet 起不来、containerd 重启失败、Pod 无法创建,优先按备份回滚,不要在故障节点上继续叠加修改。
cp -a "$backup_dir"/containerd /etc/
cp -a "$backup_dir"/kubelet-config.yaml /var/lib/kubelet/config.yaml
systemctl daemon-reload
systemctl restart containerd
systemctl restart kubelet
用途:恢复变更前的 containerd 和 kubelet 配置。
判断标准:服务恢复 active,节点重新 Ready。如果仍然失败,再查系统层、内核参数和 runtime 包版本。
回滚后保留现场:
journalctl -u kubelet -n 300 --no-pager > /tmp/kubelet-after-rollback.log
journalctl -u containerd -n 300 --no-pager > /tmp/containerd-after-rollback.log
用途:保存回滚后的日志,方便复盘而不是只凭记忆判断。
判断标准:日志文件存在,并包含回滚前后关键时间段。
一页式 cgroup 检查 checklist

| 步骤 | 检查项 | 命令 | 正常标准 | 异常处理 |
|---|---|---|---|---|
| 1 | cgroup 版本 | stat -fc %T /sys/fs/cgroup |
明确 v1 或 v2,不靠猜 | 结合 findmnt 和 /proc/cmdline 复核 |
| 2 | kubelet driver | grep -n 'cgroupDriver' /var/lib/kubelet/config.yaml |
systemd 节点通常为 systemd |
查 systemd drop-in 是否覆盖 |
| 3 | kubelet 启动参数 | systemctl cat kubelet |
配置来源清楚 | 消除配置文件与参数冲突 |
| 4 | containerd runtime | containerd config dump \| grep -n -A 12 -B 4 'SystemdCgroup' |
与 kubelet 资源管理方式对齐 | 不要复制错误版本配置路径 |
| 5 | CRI 连通性 | crictl info |
能返回 runtime 信息 | 查 socket、服务、CRI 插件 |
| 6 | 服务状态 | systemctl status containerd --no-pager |
active (running) |
查日志,不要直接改配置 |
| 7 | kubelet 日志 | journalctl -u kubelet -n 200 --no-pager |
无连续 runtime/cgroup 错误 | 结合 containerd 日志定位 |
| 8 | 备份 | cp -a /etc/containerd ... |
配置可回滚 | 没有备份不做生产变更 |
| 9 | 灰度 | kubectl cordon/drain <node-name> |
单节点验证通过再放量 | 被 PDB 阻塞时先找业务确认 |
| 10 | 验证 | kubectl get node <node-name> -o wide |
节点 Ready,Pod 正常创建 | 失败则回滚并保留日志 |
常见误区
误区一:只看 Kubernetes 版本,不看节点 OS 和 systemd
同一个 Kubernetes 版本,在不同 OS、内核、systemd 版本、containerd 配置下,cgroup 行为可能不一样。升级前必须把节点池拆开看。
误区二:只改 containerd,不看 kubelet
SystemdCgroup = true 不是孤立配置。它要和 kubelet 的 cgroupDriver 对齐。如果 kubelet 仍按另一套方式管理,问题不会真正消失。
误区三:全量节点一起改
cgroup 相关配置属于节点底层资源控制链路。生产修正必须单节点或单节点池灰度,配合 cordon/drain、备份、回滚和日志留存。
误区四:看到 cgroup v1 就立刻切 v2
cgroup v2 是趋势,但生产迁移不是一条命令。要看内核、发行版、systemd、容器运行时、监控组件和历史工作负载。本文的重点不是推动你立刻切 v2,而是避免 kubelet 与 containerd 资源管理方式不一致。
总结
cgroup 相关故障难查,是因为它不像镜像拉取失败那样直接给出清晰 Event。它更多表现为节点 NotReady、Pod 启动异常、资源统计异常、eviction 行为异常。
生产排查时按这个顺序走:
- 用
stat -fc %T /sys/fs/cgroup和findmnt -R /sys/fs/cgroup判断 cgroup v1/v2。 - 用
grep cgroupDriver、systemctl cat kubelet、ps -ef | grep '[k]ubelet'确认 kubelet 实际配置。 - 用
containerd config dump确认SystemdCgroup实际生效值。 - 用
crictl info、kubelet 日志、containerd 日志确认 runtime 链路。 - 修改前备份,修改时灰度,失败时按备份回滚。
下一篇 B 篇会把这个问题收敛成更短的判断清单:SystemdCgroup 到底该设 true 还是 false?Kubernetes 节点判断方法,重点放在生产节点该怎么做决策。
参考资料
- Kubernetes 文档:Container Runtimes / cgroup drivers
- Kubernetes 文档:kubelet Configuration
- containerd 文档与默认配置:
containerd config default、containerd config dump
更多推荐




所有评论(0)