节点翻车时,不要只盯着镜像和网络

生产集群升级后,最容易被误判的现象有三类:

  • 节点突然 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.tomlcontainerd 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 常见为 cpumemoryblkiopids 等多个 controller 分开挂载。

再看内核命令行是否显式指定:

cat /proc/cmdline

用途:确认是否通过启动参数强制启用或禁用 unified cgroup hierarchy。

关注字段:

  • systemd.unified_cgroup_hierarchy=1
  • systemd.unified_cgroup_hierarchy=0
  • cgroup_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=cgroupfs
  • KUBELET_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 相关错误。

关注关键词:

  • cgroup
  • systemd
  • runc
  • runtime
  • failed to create task
  • failed to update resources

检查 kubelet 日志:

journalctl -u kubelet -n 200 --no-pager

用途:从 Kubernetes 节点视角看 runtime 和资源管理异常。

关注关键词:

  • container runtime
  • PLEG
  • cgroup
  • eviction
  • runtime status
  • failed 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.yamlkubelet-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 行为异常。

生产排查时按这个顺序走:

  1. stat -fc %T /sys/fs/cgroupfindmnt -R /sys/fs/cgroup 判断 cgroup v1/v2。
  2. grep cgroupDriversystemctl cat kubeletps -ef | grep '[k]ubelet' 确认 kubelet 实际配置。
  3. containerd config dump 确认 SystemdCgroup 实际生效值。
  4. crictl info、kubelet 日志、containerd 日志确认 runtime 链路。
  5. 修改前备份,修改时灰度,失败时按备份回滚。

下一篇 B 篇会把这个问题收敛成更短的判断清单:SystemdCgroup 到底该设 true 还是 false?Kubernetes 节点判断方法,重点放在生产节点该怎么做决策。

参考资料

  • Kubernetes 文档:Container Runtimes / cgroup drivers
  • Kubernetes 文档:kubelet Configuration
  • containerd 文档与默认配置:containerd config defaultcontainerd config dump
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐