资讯动态

Kubernetes v1.35+弃用cgroup v1?节点启动失败排查与迁移实战

发布时间:2026/9/17 9:05:37 来源:尧图企业网站定制
先说明一个现实如果你还在用 cgroup v1 的节点跑 Kubernetes当集群升级到 v1.35 时节点会直接起不来而且报错信息非常容易让人误判成“kubelet 崩溃”或者“容器运行时挂了”。这个坑我最近帮朋友排查时踩了个遍折腾了快一天才定位到根因。这篇文章我不打算给你复述官方文档而是把问题背后的机制、排查思路、以及最稳妥的修复路径讲清楚。无论你是集群管理员、SRE还是刚接手自建集群的运维这篇内容都能帮你节省大量排查时间少走弯路。1. 问题背景cgroup 版本演进与 v1.35 的“硬门槛”1.1 Kubernetes 为什么要管 cgroup 版本cgroup 是 Linux 内核用来限制、隔离进程资源CPU、内存、IO、PID 等的核心机制。Kubernetes 的 Pod 资源请求requests和限制limits最终都是靠 kubelet 调用容器运行时再由运行时通过 cgroup 落实到内核里的。可以说没有 cgroupKubernetes 的“资源管理”就是纸面承诺。cgroup 有两个大的版本v1 和 v2。v1 年代久远每个资源类型cpu、memory、blkio、pids 等都挂在独立的层级目录里管理分散、路径冗长v2 则是把统一的层级结构建立起来所有资源控制都在同一个 cgroup 树下面通过不同的接口文件cpu.max、memory.max 等分别控制设计更整洁也更符合现代容器的资源管理需求。Kubernetes 官方从很早开始就在推动 cgroup v2 的支持但真正把“v2 作为默认”写进逻辑是在一系列版本迭代中逐步完成的。到了 v1.35这个变化已经变成硬性要求默认情况下kubelet 假定节点已经运行 cgroup v2如果你还在用 cgroup v1节点启动流程直接失败。1.2 为什么 v1.35 不再容忍 cgroup v1有朋友可能会问“既然 v1 还能用为什么不继续兼容”这事得从 Kubernetes 的维护策略说起。Kubernetes 对底层依赖的清理通常采取“先警告后移除”的节奏。官方很早就宣布 cgroup v1 支持进入弃用deprecated状态然后在后续大版本里开始逐步收紧早期版本cgroup v1 和 v2 都能正常工作kubelet 自动探测。中期版本启动时会打印警告提醒你尽快迁移到 v2。v1.31 前后官方在 kubelet 中开始明确警告未来将移除 v1 支持。v1.32 左右kubelet 代码中已经移除了部分 cgroup v1 路径继续用 v1 即使能跑也会丢失部分资源统计或管理能力。v1.35行为收紧默认配置下节点在 cgroup v1 环境里直接拒绝启动。这个演进的核心逻辑是维护两套 cgroup 代码路径的成本太高。尤其是 kubelet 的资源管理模块如 kubepods 的 cgroup 管理、QoS 分类很多底层调用已经深度依赖 v2 的目录结构和接口语义再为 v1 做兼容会让核心控制器代码越来越大也越来越难以维护。而且主流发行版Ubuntu 22.04、Debian 11、RHEL 9、openEuler 22.03早已默认使用 cgroup v2继续保留 v1 作为一等公民投入产出比已经非常低。1.3 影响范围谁会被这个变化击中下面这几类环境最容易踩中这个雷建议先对号入座使用 CentOS 7、RHEL 7、Ubuntu 18.04 等老系统跑 Kubernetes 的集群这些系统默认或常见配置就是 cgroup v1。云厂商提供的托管节点镜像如果没有同步升级内核参数大概率还是 v1。自建集群中之前手工在 GRUB 里加过 systemd.unified_cgroup_hierarchy0 来强制使用 v1 的节点。容器运行时配置中显式指定了 cgroup 驱动为 cgroupfs且节点本身跑的是 v1 的老环境。如果你的集群符合上面任意一条并且在 v1.35 版本升级中出现了节点无法加入集群、kubelet 反复重启、或者启动日志里出现 “cgroup v1 is not supported” 之类的内容那么这篇文章就是写给你的。2. 核心细节解析从启动报错到根因定位2.1 启动失败时你会看到的典型报错先给你看几个实际环境中会出现的典型症状方便你快速确认是不是这个问题kubelet 服务状态是 active (running)但节点始终处于 NotReady。kubelet 日志反复打印类似 “Failed to run kubelet: failed to validate kubelet flags” 或 “unsupported cgroup driver: cgroupfs”。containerd 日志中出现 “failed to load cgroup” 相关错误。更隐晦的kubelet 能启动但 Pod 创建后一直处于 ContainerCreating事件中提示 “failed to create task: cgroup v1 is not supported”。这些报错单独看很容易被误判成“CRI 配置错误”“kubelet 参数写错”“目录权限不对”。但实际上根子都在 cgroup 版本上。2.2 根因kubelet 与容器运行时的 cgroup 驱动必须匹配且版本必须为 v2这里要展开讲一下 Kubernetes 和 cgroup 之间的两条关键链路第一条是kubelet 与容器运行时之间的 cgroup 驱动协商。kubelet 启动时会读取配置确定自己使用哪种 cgroup 驱动systemd 或 cgroupfs然后通过 CRI 调用容器运行时如 containerd、CRI-O要求运行时也使用一致的驱动。如果两者不一致kubelet 会和运行时互相推卸责任最终表现为节点无法正常工作。第二条是kubelet 对节点 cgroup 版本的探测。在 v1.35 中kubelet 启动时会检查 /sys/fs/cgroup 是否挂载为 v2如果检测到不是 v2就直接拒绝启动。这一步是一个硬性检查不再像旧版本那样“给个警告然后继续跑”。之所以必须一致是因为 cgroup v1 和 v2 的文件系统布局完全不同。kubelet 在 v2 环境下会向 /sys/fs/cgroup/kubepods.slice 写入资源控制参数如果在 v1 环境下则需要操作 /sys/fs/cgroup/cpu/kubepods 之类的路径。当 kubelet、运行时、内核三者之间对“当前该用哪套布局”的理解不一致时资源管理就彻底错乱最轻的表现是 QoS 失效最重的表现就是节点直接不可用。2.3 如何确认节点当前的 cgroup 版本在动手修复之前先确认你的节点到底跑的是 v1 还是 v2。这里有一套快速的检查方法直接在节点上执行即可# 方法一查看 cgroup 挂载点 stat -fc %T /sys/fs/cgroup/ # 输出 tmpfs 表示 cgroup v2 # 输出 cgroup 表示 cgroup v1 # 方法二检查是否存在 cgroup.controllers 文件 ls /sys/fs/cgroup/cgroup.controllers 2/dev/null echo cgroup v2 || echo cgroup v1 # 方法三通过系统参数确认 grep -i cgroup /proc/cmdline # 如果有 systemd.unified_cgroup_hierarchy0说明被强制指定为 v1实际操作中我建议三种方法都跑一遍因为它们判断的维度不太一样。方法一确认挂载文件系统类型方法二确认是否存在 v2 独有的统一控制文件方法三确认是不是有人在内核启动参数里做过强制指定。如果三者结论不一致比如方法一是 v2 但方法三显示强制指定了 v1多半是挂载状态比较混乱需要重启节点让参数生效。另外容器运行时也可能有自己的配置。以 containerd 为例可以执行containerd config dump | grep -i cgroup # 查看 SystemdCgroup 是否为 true以及是否有 cgroup v1 相关的兼容配置如果 containerd 没找到 SystemdCgroup 配置或者显式配置为 false它就会在 v1 环境下使用 cgroupfs 驱动。在 v2 环境下则默认走 v2 的 cgroup 管理路径。这里必须和 kubelet 的配置保持一致。2.4 排查三连内核、运行时、kubelet 逐层排除问题当你发现节点起不来不要急着改配置按下面的顺序做一次快速体检先看内核uname -r 确认内核版本。cgroup v2 从内核 4.5 开始支持5.2 之后才比较稳定。如果你还在用 3.10 之类的老内核CentOS 7 默认内核那不用看别的直接升级系统或者换发行版因为老内核根本没法正常工作在 v2 模式。再看运行时systemctl status containerd 确认运行时是否健康。如果 containerd 正常再看 kubelet如果 containerd 也不正常优先排查运行时日志。最后看 kubeletjournalctl -u kubelet -f 实时跟踪启动日志重点看前 20 行左右的错误输出。这套排查顺序的核心逻辑是从底层到上层逐层排除。内核出问题上层再折腾也没用运行时不健康kubelet 自然无法注册节点。定位问题就像剥洋葱一层一层来效率最高。3. 实操过程与核心环节实现把节点迁移到 cgroup v23.1 迁移前的准备备份与影响评估切换到 cgroup v2 不是改个配置文件那么简单它意味着节点上的所有进程都要切换到新的资源管理机制下。因此迁移前必须做好两件事第一确认节点上的应用兼容性。绝大多数容器化应用不受影响因为容器内看到的 cgroup 视图由运行时处理应用本身很少直接操作 cgroup。但如果你在宿主机上跑了一些需要直接读写 /sys/fs/cgroup 的监控组件或调优工具比如老版本的 cadvisor、netdata、某些 Java 版本对 cgroup 内存感知就需要检查它们是否有 v2 兼容版本。第二确认容器运行时支持 v2。这里分两类containerd1.5 版本官方支持 v2建议直接用 1.7很稳定。CRI-O1.20 版本支持 v2建议用 1.29。Docker如果还有节点直接跑 docker20.10 才开始良好支持 v2建议直接用最新稳定版。对于 Kubernetes 节点来说kubelet 的版本和运行时版本还有配套关系。如果 kubelet 已经升级到 v1.35但运行时还是老版本同样可能出现 cgroup 驱动或探测逻辑的兼容问题。我的建议是在 cgroup 版本切换这件事上先把运行时升级到至少一年内的稳定版再做 v2 切换能避免很多莫名其妙的兼容性问题。另外一个容易忽略的点是节点上的GPU、SR-IOV、DPDK 等特殊硬件设备插件。这些组件有时会依赖 cgroup v1 的特定路径来设置设备限制或大页内存分配。如果你在集群里用到了这类设备迁移前务必联系设备插件厂商或查看官方文档确认 cgroup v2 兼容性。否则节点虽然起来了但设备资源分配可能悄悄失效。3.2 修改内核启动参数systemd.unified_cgroup_hierarchy要让节点从 v1 切到 v2最核心的操作是修改 GRUB 内核启动参数。这里以最常见的 GRUB2 引导为例手把手演示整个过程。先编辑 /etc/default/grubvim /etc/default/grub找到 GRUB_CMDLINE_LINUX 这一行检查里面是否有以下参数systemd.unified_cgroup_hierarchy0强制使用 v1必须移除。systemd.unified_cgroup_hierarchy1可选显式启用 v2可以保留。cgroup_no_v1all可选禁用所有 v1 控制器只保留 v2强烈建议加上。systemd.legacy_systemd_cgroup_controlleryes老系统才有的参数必须移除。典型修改前后的对比# 修改前v1 强制模式 GRUB_CMDLINE_LINUXrhgb quiet systemd.unified_cgroup_hierarchy0 # 修改后切换为 v2 模式 GRUB_CMDLINE_LINUXrhgb quiet systemd.unified_cgroup_hierarchy1 cgroup_no_v1allcgroup_no_v1all 这个参数非常重要它的作用是禁用所有 v1 控制器。如果不加这个参数系统在切换后可能仍会挂载一部分 v1 控制器比如保留老的 blkio 或 pids 控制器造成“混合模式”cgroup v2 和 v1 共存。虽然混合模式在大多数情况下也能工作但会带来不确定的行为特别是在资源统计和 OOM 判定上。既然我们下决心要切换到 v2就一步到位。修改完成后更新 GRUB 配置# BIOS 系统 grub2-mkconfig -o /boot/grub2/grub.cfg # UEFI 系统根据实际挂载点调整 grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg # Debian/Ubuntu 系 update-grub如果节点上有多个内核版本建议在重启前确认当前使用的内核是否支持 v2。下面的命令可以查看已安装内核rpm -qa | grep kernel # 或 dpkg -l | grep linux-image我建议选择一个支持 cgroup v2 的双内核至少内核版本 5.8不要用旧内核碰运气。3.3 修改容器运行时配置内核参数改好后重启前还有一个必做动作调整容器运行时的 cgroup 驱动配置确保它和 kubelet 一致并且使用 systemd 驱动。以 containerd 为例修改 /etc/containerd/config.toml[plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc] base_runtime_spec cni_conf_dir cni_max_conf_num 0 cni_network_plugin_dir container_annotations [] pod_annotations [] privileged_without_host_devices false runtime_engine runtime_path runtime_root runtime_type io.containerd.runc.v2 sandbox_mode podsandbox [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options] BinaryName runc CriuImagePath CriuPath CriuWorkPath IoGid 0 IoUid 0 NoNewKeyring false NoPivotRoot false Root ShimCgroup SystemdCgroup true关键点就在 SystemdCgroup true。在 v2 环境下这个选项必须为 true否则 containerd 会尝试自己挂载 cgroup 并管理 cgroupfs与 systemd 的管理职责冲突最终造成资源管理混乱。修改完成后systemctl restart containerdCRI-O 的配置在 /etc/crio/crio.conf 中[crio.runtime] conmon_cgroup pod cgroup_manager systemdcgroup_manager systemd 是 CRI-O 在 v2 环境下的正确姿势。3.4 修改 kubelet 配置kubelet 端同样需要确认配置。在 v1.35 中kubelet 默认使用 systemd cgroup 驱动如果你的 kubelet 配置了 cgroupDriver: cgroupfs需要改成 systemd# /var/lib/kubelet/config.yaml apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration cgroupDriver: systemd同时确认 kubelet 的 systemd unit 中没有强制指定 --cgroup-drivercgroupfs 的参数。很多发行版的 kubelet 是通过 EnvironmentFile 加载参数的检查一下 /etc/systemd/system/kubelet.service.d/ 目录下的 drop-in 文件grep -r cgroup /etc/systemd/system/kubelet.service.d/如果找到了 --cgroup-drivercgroupfs直接删掉这一行让 kubelet 用默认值v1.35 默认就是 systemd 驱动。3.5 节点重启与验证确认所有配置修改完毕后重启节点systemctl reboot重启完成后按下面的顺序验证确认 cgroup 版本stat -fc %T /sys/fs/cgroup/ # 必须输出 tmpfs确认没有残留的 v1 控制器ls /sys/fs/cgroup # 应该看到 cgroup.controllers、cgroup.procs、init.scope 等 v2 特征文件 # 不应该看到 cpu、memory、blkio 等 v1 目录确认 containerd 正常systemctl status containerd ctr version确认 kubelet 能正常注册节点systemctl status kubelet kubectl get node 节点名 # 状态应该是 Ready在节点上跑一个测试 Pod确认资源限制生效kubectl run test-cgroup --imagebusybox --restartNever --limitscpu200m,memory256Mi -- sh -c sleep 3600 kubectl exec -it test-cgroup -- cat /sys/fs/cgroup/cpu.max # 输出应该是类似 20000 100000 的值表示 200m CPU 限制 kubectl exec -it test-cgroup -- cat /sys/fs/cgroup/memory.max # 输出应该是 268435456对应 256Mi最后一步非常重要。很多节点迁移后表面上 Ready但资源限制实际没生效。通过进入 Pod 查看 v2 的控制文件内容才能确认整条链路是通的。检查项期望值说明/sys/fs/cgroup 文件系统类型tmpfs说明挂载的是 cgroup v2 的简化虚拟文件系统/sys/fs/cgroup/cgroup.controllers存在v2 的控制器列表文件kubelet cgroupDriversystemdsystemd 与 containerd 一致containerd SystemdCgrouptrueCRI 与 kubelet 协商无误kubectl get nodeReady节点成功加入集群Pod 内 /sys/fs/cgroup/cpu.max如 20000 100000CPU 限制真正生效4. 常见问题与排查技巧实录我在迁移中踩过的坑4.1 坑一升级 kubelet 后containerd 还在用 v1 配置这是我最常遇到的情况。很多集群的 kubelet 已经由安装脚本升级到了 v1.35但 containerd 的配置还是几年前的初始配置SystemdCgroup 为 false。结果节点启动后kubelet 报错与 containerd 的 cgroup 驱动不一致或者 containerd 静默地尝试使用 v1 路径导致节点无法注册。排查方法很简单journalctl -u containerd -n 100 | grep -i cgroup如果看到类似 “cgroup v1 detected” 的日志说明 containerd 检测到了 v1但 kubelet 又要求 v2两者必然打架。解决办法就是把 SystemdCgroup 改为 true并重启 containerd。4.2 坑二GRUB 更新命令用错重启后“戛然而止”有些朋友在生成 GRUB 配置时没搞清楚自己系统是 BIOS 还是 UEFI把配置文件写错了位置。最常见的错误是在 UEFI 系统上执行 grub2-mkconfig -o /boot/grub2/grub.cfg结果系统重启后直接进不了操作系统卡在 GRUB 界面。为了避免这种情况我每次都先确认系统引导模式[ -d /sys/firmware/efi ] echo UEFI || echo BIOS然后再选择正确的写盘路径。如果是云服务器尤其是云厂商的镜像可能还需要使用 grubby 工具来修改内核参数而不是手工编辑 /etc/default/grub# 查看当前内核参数 grubby --infoALL | grep args # 修改默认内核的启动参数 grubby --update-kernelDEFAULT --argssystemd.unified_cgroup_hierarchy1 cgroup_no_v1all在迁移前建议先把关键配置备份到 /backup 目录宁可多花两分钟备份也不要在生产环境里赌一次。4.3 坑三忽略节点上的自定义 systemd 服务如果你在节点上跑了一些自定义的 systemd 服务比如自研的 agent、日志采集器它们可能显式依赖 cgroup v1 的路径。迁移到 v2 后这些服务可能起不来或者行为异常。典型的例子是旧版 node-exporter、telegraf它们在采集 cgroup 统计信息时如果硬编码了 v1 路径v2 环境下会直接采集不到数据。解决办法就是升级到最新版本或者调整配置让它们自动探测 cgroup 版本。这一项要提前加进迁移 checklist否则节点切完第二天才被监控告警叫醒那体验就太酸爽了。4.4 常见问题速查表异常现象可能原因快速排查/解决kubelet 启动报错 “failed to validate kubelet flags”kubelet 探测到 cgroup v1检查 /sys/fs/cgroup 是否为 v2检查 GRUB 参数containerd 报 “cgroup v1 is not supported”containerd 不支持 v1或节点仍为 v1升级 containerd 到 1.7切换内核参数为 v2节点 Ready 但 Pod 卡在 ContainerCreating运行时和 kubelet 的 cgroup 驱动不一致对比 kubelet 和 containerd 配置统一为 systemdPod 内看到的内存限制和 limits 不匹配cgroup v1/v2 路径混用导致读取错误进入 Pod 查看 /sys/fs/cgroup/memory.max对比限制值迁移后监控组件采集不到容器指标监控组件硬编码 v1 路径升级采集器或开启兼容模式重启后系统起不来GRUB 参数错误或写错引导文件通过云控制台/救援模式进入系统修正配置4.5 独家避坑技巧如何把迁移风险降到最低最后分享两个我个人的习惯平常可能不会写在公开文档里第一迁移前先用临时节点验证。如果你的集群有多个节点一定先挑一个非关键节点做迁移确认完整流程没问题再推广到其他节点。不要图省事一次性批量切万一哪个节点的硬件或驱动有特殊依赖批量操作会放大问题。第二准备一个回滚预案。切换到 v2 后如果出现极端问题比如新的内核和 runc 配合不好导致容器无法启动你需要在 10 分钟内恢复节点。我的做法是保留一个 GRUB 菜单项里面有一份旧的 v1 内核参数配置。万一 v2 出问题重启时选旧项节点就回到 v1 模式先把业务恢复再慢慢排查。这个兜底操作在真实运维中救了我好几次。5. 迁移后的长期运维观察点5.1 资源统计口径变化对监控的影响从 v1 切到 v2 后监控数据的采集口径会发生变化。最典型的是内存使用量v1 里容器内存使用量通常看 memory.usage_in_bytes包含 page cache。v2 里对应的是 memory.current其语义与 v1 略有差异且更接近真实内存压力。如果你在监控面板上发现切换后 Pod 内存使用率突然“涨”了十几个百分点先不要慌很可能是采集口径变了而不是业务真实的内存暴涨。建议在迁移前就记录好切换前的监控基线切换后做一次对比确认业务负载稳定的情况下内存曲线的基线平移是正常的。CPU 使用率的统计也有差异。v1 的 cpuacct.usage 和 v2 的 cpu.stat 在统计单位、是否包含 hyper-threading 的核数计算上都有细微差别。如果占用率监控出现 1%-2% 的偏移不必大惊小怪但要记录下来方便后续告警阈值的微调。5.2 OOM 行为的差异cgroup v2 对 OOM 的处理也与 v1 不同。v2 在 OOM 时是“按 cgroup 范围”选择的杀掉某个 cgroup 内最占内存的进程而不是像 v1 那样有时直接杀掉整个 cgroup 中的任意进程。如果你是跑 Java 应用的集群需要留意这个差异Java 进程可能在 OOM 时被“精准清除”而不是整个 Pod 被杀死。从稳定性角度看这其实是更好的行为但应用如果有状态仍然可能产生脏数据。建议在迁移后做一次故障演练或压力测试明确你集群里 OOM 时的真实表现。5.3 不要迷信“切换一次永远安逸”cgroup 版本只是 Kubernetes 节点底层环境的一部分。v2 切换完成后我建议顺手检查一下节点的 systemd 版本。cgroup v2 是 systemd 230 才逐步支持的太老的 systemd 版本即使在 v2 内核上也会出现部分功能异常。如果节点 systemd 版本太老下一步就该考虑系统升级了。另外Kubernetes 未来对 cgroup v2 的依赖只会加深不太可能回头兼容 v1。早切早安心拖着不切只会让后续的集群升级越来越难。如果你所在团队对迁移风险有顾虑可以分批推进先灰度一个节点池跑一两周观察稳定性再逐步扩大到全量节点。这个过程我经历过好几轮整体风险是可控的只要按照上面的操作步骤一步步来不会出大问题。就我个人体会而言这次 v1.35 的强制切换虽然是“靴子落地”但对于老集群来说也算是一次变相的技术债清洗。借着这次机会把系统版本、内核参数、运行时配置、监控口径全部梳理一遍后面维护起来反而更省心了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价