文章

K8s 容器启动失败 cgroup v1-v2 不匹配排查实战

K8s 容器启动失败:cgroup v1/v2 不匹配(cpu.cfs_quota_us 写入 EINVAL)

概述

容器创建时,运行时(runc)按 cgroup v1 接口去写 CPU 配额 cpu.cfs_quota_us,但节点底层 cgroup 实际是 v2(或 v2 接口)——v2 下文件名是 cpu.max,v1 的 cpu.cfs_quota_us 在 v2 层级里不存在/不可写,内核返回 EINVAL,容器一路 init 失败。这是 K8s 1.25→1.31 强制 cgroup v2 迁移期最经典的坑,根因几乎都是 containerd/kubelet 的 cgroup 驱动没统一到 v2 + systemd


场景:容器反复 FailedCreateContainer

现象(kubectl describe pod 原文)

Warning  Failed   39m (x4 over 41m)    kubelet
Error: failed to create containerd task: failed to create shim task:
OCI runtime create failed: runc create failed: unable to start container process:
error during container init: error setting cgroup config for procHooks process:
failed to write "52000": write
/sys/fs/cgroup/cpu,cpuacct/kubepods.slice/kubepods-burstable.slice/
kubepods-burstable-pod473ff63b_482f_4b3b_a0db_4d1afa0f4211.slice/
cri-containerd-go-center-mis-gateway.scope/cpu.cfs_quota_us:
invalid argument: unknown

关键信息解码

线索含义
cpu.cfs_quota_us: 52000写入值 52000 µs = 0.52 核,对应容器 limits.cpu: 520m(值本身合法,排除值错)
路径含 cpu,cpuacct/...cgroup v1 的控制器挂载风格(v2 是统一层级,无逗号拼接名)
kubepods-burstable.slicePod QoS = Burstable
go-center-mis-gateway出问题的容器 / Pod 名
x4 over 41m重试 4 次全失败,kubelet 持续 BackOff,容器起不来

报错链路:kubelet → containerd task → shim task → OCI(runc) → container init → 设 cgroup → 写 v1 文件失败。失败在容器初始化最后一步:挂 cgroup 限制。


排查 SOP

# 1. 节点 cgroup 版本:cgroup2fs = v2,tmpfs = v1
stat -fc %T /sys/fs/cgroup/

# 2. containerd 是否开了 systemd cgroup(关键)
containerd config dump 2>/dev/null | grep -i systemdcgroup
# 或直接看配置文件
grep -i systemdcgroup /etc/containerd/config.toml

# 3. kubelet 的 cgroup 驱动
ps -o cmd= -p $(pidof kubelet) | tr ' ' '\n' | grep cgroup-driver

# 4. 看目标 cgroup 下实际有什么文件(验证接口版本)
#    v2 应有 cpu.max;v1 才有 cpu.cfs_quota_us
#    对应 pod uid: 473ff63b_482f_4b3b_a0db_4d1afa0f4211
ls /sys/fs/cgroup/kubepods.slice/kubepods-burstable.slice/ 2>/dev/null

判定矩阵

现象结论
stat 返回 cgroup2fs + 仍写 cpu.cfs_quota_us 报错节点 v2,但运行时按 v1 写 → 驱动不匹配
grep SystemdCgroup 返回 falsecontainerd 未启用 systemd cgroup → 根因命中
目标 cgroup 下只有 cpu.maxcpu.cfs_quota_us确认是 v2 层级,v1 文件名无效

根因分析 Checklist

  • containerd SystemdCgroup = false:运行时按 v1 路径规范建 cgroup,写 cpu.cfs_quota_us 到 v2 层级 → EINVAL
  • kubelet --cgroup-driver 与 containerd 不一致(一个是 systemd,一个是 cgroupfs)
  • 节点系统 cgroup 版本与集群期望不符:K8s 1.31+ 强制 v2,但节点可能残留 v1 配置或混合挂载(hybrid)
  • 节点扩容 / 升级时未统一 cgroup 驱动:新节点用了默认 cgroupfs,老节点是 systemd

核心矛盾:K8s 1.25 起 cgroup v2 GA、1.31 起强制 v2。v2 下 CPU 配额写在 cpu.max(格式 "$QUOTA $PERIOD"),不再是 v1 的 cpu.cfs_quota_us + cpu.cfs_period_us 两个文件。漏配 systemd cgroup 的运行时仍走 v1 接口,于是撞上 EINVAL。


解决方案

统一到 cgroup v2 + systemd 驱动

# 1. 修改 /etc/containerd/config.toml
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
  SystemdCgroup = true
# 2. kubelet 驱动需一致(kubeadm 节点通常在 /var/lib/kubelet/config.yaml)
cgroupDriver: systemd

# 3. 重启使配置生效
systemctl restart containerd
systemctl restart kubelet
# 4. 验证:节点 cgroup 应是 cgroup2fs,且运行时走 v2
stat -fc %T /sys/fs/cgroup/
# -> cgroup2fs
crictl info | grep -i cgroupdriver
# -> "cgroupDriver": "systemd"

# 5. 该 Pod 会自动从 BackOff 恢复,无需手动删
kubectl get pod go-center-mis-gateway -n <ns> -w

反向方案(不推荐):内核加 systemd.unified_cgroup_hierarchy=0 回退 v1 —— 已废弃,别走。正确做法是让运行时适配 v2。


预防措施

  • 所有节点统一 cgroup v2 + systemd 驱动,纳入节点初始化 IaC(kubespray/kubeadm 已默认 SystemdCgroup = true
  • 节点扩容 / 升级后,自动校验 stat -fc %T /sys/fs/cgroup/ + containerd SystemdCgroup
  • K8s 大版本升级(尤其跨 1.25 / 1.31)前,先确认集群 cgroup 版本策略
  • FailedCreateContainer / OCI runtime create failed 类事件配置监控告警,避免 41 分钟才发现
  • containerd 与 kubelet 配置纳管为 IaC,禁止手动漂移

关联知识

复盘要点

  1. 集群所有节点的 cgroup 驱动是否统一为 systemd?
  2. 节点扩容 / 版本升级时,是否校验了 cgroup 版本与驱动一致?
  3. 对该类 FailedCreateContainer 是否有监控告警?(本例 41 分钟才发现)
  4. containerd / kubelet 配置是否纳管为 IaC,是否存在手动漂移?
  5. K8s 版本(尤其 1.25→1.31)升级是否评估过 cgroup v2 强制影响?

状态: 已解决 复盘日期: 2026-07-22