首页 / 资讯中心 / 文章详情

kubeasz部署K8s:kubelet cgroup driver配置不一致的排查与修复

kubeasz部署K8s:kubelet cgroup driver配置不一致的排查与修复 ★ FEATURED ARTICLE
看到这行报错我估摸着你八成已经把安装脚本重跑了好几遍甚至开始琢磨要不要把节点格式化掉重来。先别急着动手failed to run Kubelet: misconfiguration: kubelet cgroup driver这个错误在 kubeasz 部署 Kubernetes 集群时其实非常典型它既不是你的网卡配置有问题也不是镜像拉不下来更不是某个组件服务挂了而是一个纯粹的“两边配置不一致”导致 kubelet 拒绝启动的问题。我用 kubeasz 部署集群也有几年了从 All-in-One 到多主多节点都搭过中间踩过不少类似的坑。这篇文章专门把这个问题掰开揉碎讲清楚报错背后的机制是什么、kubeasz 里到底哪些配置会影响它、正确的修复路径怎么走以及我实操中总结出来的排查套路。无论你是准备用 kubeasz 装一套新环境还是集群里某台节点突然起不来了这篇都应该能帮你省下不少折腾的时间。1. 拆解这条报错kubelet 为什么宁可“死掉”也不起来1.1 kubelet 启动时的“安检”逻辑很多人看到misconfiguration这个词就有点慌觉得是不是自己哪里配置写错了。其实准确地说这是 kubelet 启动时的一个强校验逻辑。kubelet 在启动阶段大概会做这几件事读取自己的配置文件、初始化容器运行时接口、检查 cgroup driver 是否匹配、然后才去跟 API Server 注册节点。问题就出在第三步。kubelet 会拿自己配置里的 cgroup driver 去和当前节点上容器运行时常见的就是 Docker 或 containerd实际使用的 cgroup driver 做对比。一旦发现两边不一致就不会继续往下走直接抛出misconfiguration然后退出。Kubernetes 在这里选择的是“宁可让节点不加入集群也不能带病运行”的策略。这个强校验不是凭空设计的。因为 kubelet 和容器运行时都需要通过 cgroup 来管理容器的 CPU、内存、PID 等资源限制。如果两套组件分别用不同的 cgroup 管理方式它们对资源层级、子目录、清理逻辑的理解就完全不一样。轻则某个容器无法启动重则系统层级的资源回收、节点驱逐、OOM 打分全都会乱套。你可以理解为两个包工头拿到了同一栋楼的两张不同图纸硬要一起施工最后必然出事。所以 kubelet 宁可启动失败也不愿意在这种状态下工作。1.2 cgroupfs 与 systemd两种驱动到底差在哪要理解这个报错就必须知道 cgroup driver 到底是什么。Linux 内核提供 cgroup 机制来对进程做资源隔离和限制而“driver”就是用户态通过什么接口去操作 cgroup。目前主流的就是两种cgroupfs driver直接读写/sys/fs/cgroup下的文件系统创建目录、写入限制值。这种方式比较底层、直接Docker 的默认值就是它。systemd driver把资源管理交给 systemd 自身的 cgroup 管理机制通过 systemd 的单元scope/slice来建立 cgroup 层级。Kubernetes 官方文档明确建议在使用了 systemd 作为 init 系统的节点上kubelet 和容器运行时都应该把 cgroup driver 配置为systemd。为什么官方推荐 systemd因为现代 Linux 发行版几乎都是 systemd 作为 1 号进程。systemd 自己也会用 cgroup 来管理服务。如果 kubelet 和 Docker 绕过 systemd 直接用 cgroupfs就等于在 systemd 的眼皮底下另起炉灶两套管理逻辑同时操作同一片资源树很容易造成冲突。最典型的问题就是 systemd 在停止某个服务时可能会把 kubelet 创建的 cgroup 目录一并清理掉或者反过来资源统计出现偏差。统一走systemddriver 之后所有进程的资源都由 systemd 统一规划和接管整体上要干净得多。1.3 报错信息本身就把“答案”告诉你了failed to run Kubelet: misconfiguration: kubelet cgroup driver: xxx is different from docker cgroup driver: yyy注意这行报错它其实写得非常清楚xxx是 kubelet 当前使用的驱动yyy是 Docker 当前使用的驱动。两边不一样所以 kubelet 拒绝启动。换句话说问题范围已经被缩小到“节点上的 kubelet 配置”和“节点上的 Docker 配置”这两处了。很多时候你不用去翻日志光看报错末尾就知道该去改谁。不过有一点要提醒报错里的字段不一定都是docker。如果你的集群用的容器运行时是 containerd一样会报类似的信息只是末尾会变成containerd cgroup driver。处理方法大同小异后面我会把两种运行时的情况都讲到。2. 在 kubeasz 里cgroup driver 到底是在哪里被“写死”的2.1 kubeasz 的配置层级kubeasz 这个项目本身是基于 Ansible 的一键部署方案它把 K8s 集群的安装拆成很多 role比如 docker、kubelet、kube-master、kube-node 等等。它最大的特点是“二进制方式部署”和 kubeadm 的生成方式不同很多配置是直接由模板渲染出来的。以我常用的 kubeasz 3.x 目录结构为例你创建集群后主要配置都在/etc/kubeasz/clusters/集群名/config.yml里。这个文件里有节点列表、版本号、网络插件、运行时类型等一堆参数。kubelet 的配置由roles/kube-node/templates/kubelet-config.yaml.j2这类模板渲染渲染时就会读config.yml里定义的变量。如果你用的老版本 kubeasz 2.x配置位置则往往在/etc/ansible/roles/kube-node/defaults/main.yml这类 defaults 文件里。在动手之前建议先确认一下自己用的版本免得按 3.x 的路径去找 2.x 的文件找了半天找不到。2.2 容器运行时那边的 cgroup driver 怎么确认在 kubeasz 部署过程中容器运行时的初始化也会通过 Ansible role 完成。比如 Docker 的配置最终落在/etc/docker/daemon.json这里面就有一个关键字段{ exec-opts: [native.cgroupdriversystemd] }如果这个字段缺失Docker 默认会用cgroupfs。而 kubelet 的配置模板里cgroupDriver字段的值则来自 kubeasz 集群配置里的某个变量常见叫kubelet_cgroup_driver。如果模板里默认值取的是systemd而 Docker 那边还是cgroupfs那启动时必然对不上报错也就顺理成章了。所以排查的一个核心手段就是先看节点上两个地方的实际值# 看 kubelet 当前配置里的 cgroupDriver cat /var/lib/kubelet/config.yaml | grep -i cgroup # 看 Docker 实际使用的驱动 docker info | grep -i cgroup # 如果是 containerd crictl info | grep -i cgroup注意/var/lib/kubelet/config.yaml是 kubelet 最终生效的配置文件它由 kubeasz 渲染生成。如果你手动改过 kubeasz 模板但没重新跑 playbook这个文件里的值很可能还是旧的。2.3 为什么 kubeasz 的环境特别容易中招我复盘了多次踩坑经历发现 kubeasz 部署时这个问题高发主要有三个原因第一kubeasz 支持在多种 Linux 发行版上部署而不同发行版预装的 Docker 或 containerd 默认配置不一致。比如某些系统自带 Docker它可能已经写好了自己的/etc/docker/daemon.json如果里面没有cgroupdriver配置默认就是cgroupfs。第二kubeasz 在初始化节点时可能会因为探测系统类型或运行时类型的不同导致模板渲染出的 kubelet 配置和运行时配置没有对齐。换句话说kubeasz 本身提供了参数去控制但如果你在创建集群时没有显式指定或者指定的值和节点上实际运行时不匹配就会出现“部署过程没报错、kubelet 起不来”的情况。第三也是最常见的部署人员为了省事先手动装了 Docker之后才用 kubeasz 跑集群。手动安装 Docker 时很少有人会特意去写exec-opts于是 Docker 默认cgroupfs而 kubeasz 模板里如果默认渲染systemd两边就打架了。3. 三种修复姿势从“最推荐”到“应急”3.1 姿势一改 kubeasz 集群配置重跑对应 playbook这是我认为最正规的修法适合还没上线或者节点状态还能忍受的集群。思路很简单通过 kubeasz 自身的配置把 kubelet 的 cgroup driver 统一到和运行时一致然后让 Ansible 重新生成配置并重启服务。假设你确认了 Docker 那边是cgroupfs但 kubelet 渲染出来是systemd可以这样做。第一步进入 kubeasz 集群配置目录找到你的集群配置文件cd /etc/kubeasz grep -R kubelet_cgroup_driver clusters/你的集群名/config.yml第二步把它的值改成和 Docker 一致或者干脆统一改成你期望的 driver。如果你用的是 systemd 系统我建议在配置里直接显式设置kubelet_cgroup_driver: systemd同时把 Docker 也改成systemd一步到位。第三步重新执行 kubeasz 的 node 角色 playbookansible-playbook -i clusters/你的集群名/hosts playbooks/02.kube-node.yml这个 playbook 会重新渲染 kubelet 的配置、启动 kubelet并尝试把节点加入集群。执行完毕之后用kubectl get nodes看节点状态。这里有两个细节值得说。一是改配置前一定先备份原文件别问我是怎么知道的改坏了回滚的时候没备份真的想哭。二是如果集群已经在跑业务重跑 playbook 之前最好先评估一下影响面因为 kubelet 的重启会短暂中断该节点上的 Pod。3.2 姿势二修改 Docker daemon 配置来对齐如果你不想动 kubelet 的配置或者你希望保留cgroupfs比如某些极端场景下你确实不需要 systemd driver那也可以反着来修改 Docker 的daemon.json让 Docker 使用和 kubelet 一样的驱动。先备份现有配置cp /etc/docker/daemon.json /etc/docker/daemon.json.bak然后编辑/etc/docker/daemon.json。如果文件不存在就新建一个但要注意如果原来有其他配置项比如镜像加速、log 配置等一定要保留不要覆盖掉。以统一到systemd为例{ exec-opts: [native.cgroupdriversystemd] }保存后重启 Docker 和 kubeletsystemctl restart docker systemctl restart kubelet这里必须提醒一句重启 Docker 是一个重操作。如果节点上已经跑了很多容器Docker 重启会把容器全部拉起但业务中断的那几秒是跑不掉的。所以这个方法更适合单机测试环境或者业务允许中断的场景。线上环境优先用第一种姿势。还有个常见坑你改了daemon.json但 Docker 实际上没有重新加载exec-opts。这是因为 Docker 对daemon.json的某些字段修改必须重启 daemon 才生效不是systemctl reload就行的。如果你用systemctl reload docker很可能看到docker info里还是老样子。老老实实restart。3.3 姿势三单节点手动补丁快速恢复如果你只需要先把一台节点救回来等之后再统一处理可以直接在节点上手动改配置。这个方式最灵活也最直接。kubelet 的最终配置文件通常是/var/lib/kubelet/config.yaml里面有一个顶层字段cgroupDriver: systemd找到它改成和 Docker 一致然后重启 kubeletsystemctl restart kubelet不过这里我要泼一盆冷水手动改/var/lib/kubelet/config.yaml只对当前节点生效而且下次重跑 kubeasz 的 playbook、或者有人重新执行了初始化脚本这个文件可能会被重新渲染覆盖。所以手动补丁只能算“临时措施”治标不治本。另一个手动修改的入口是 kubelet 的 systemd unit 参数。你可以查看cat /etc/systemd/system/kubelet.service.d/*.conf如果有类似--cgroup-driversystemd这样的参数也可以直接改参数后执行systemctl daemon-reload systemctl restart kubelet。但新版本 K8s 更推荐通过config.yaml来管理 kubelet 参数我一般优先改配置文件。顺便讲一个判断技巧如果你不确定 kubelet 到底用哪个配置生效可以执行kubelet --print-config这个命令会打印出 kubelet 实际生效的完整配置包括cgroupDriver字段。这样你就能确认它读的到底是不是/var/lib/kubelet/config.yaml。3.4 修复完成后必须验证的三件事不管用哪种姿势改完之后都不能只看systemctl status kubelet是 active 就完事。我建议按这个顺序验证第一看 kubelet 日志里有没有新的报错journalctl -u kubelet -f如果报错消失kubelet 会继续往下走开始连 API Server 并注册节点。第二核对两边的驱动确实一致docker info | grep -i cgroup grep -i cgroupDriver /var/lib/kubelet/config.yaml这两条命令的输出应该是一致的一个写cgroupfs就都是cgroupfs写systemd就都是systemd。第三到控制平面看节点状态kubectl get nodes如果状态变成Ready说明节点已经成功加入集群。如果还是NotReady那就得继续往下看是不是其他问题比如 CNI 网络没通、容器运行时没起来等。你也可以关注一下kubectl describe node 节点名里的 Conditions那里会告诉你节点当前卡在哪个环节。4. 真实排障场景与常见问题速查4.1 场景A全新集群刚装完第一个 worker 节点就报错有次我搭建测试环境主节点正常但第一个 worker 节点怎么都加不进来报错就是开头那段。当时第一反应是检查网络结果发现主节点和 worker 节点之间通信完全正常。后来仔细看了报错信息末尾写的是docker cgroup driver: cgroupfs。我去 worker 节点上看了一下/var/lib/kubelet/config.yaml里面确实渲染成了systemd而 Docker 是系统自带的默认cgroupfs。这个场景就是典型的“两边不一致”直接撞上强校验。当时集群还没上线我直接用了姿势一修改 kubeasz 集群配置里的kubelet_cgroup_driver为cgroupfs重跑 playbook节点立即加入成功。虽然我个人不太喜欢cgroupfs但在测试环境里没必要为了切换驱动折腾一圈能跑就行。如果是生产环境我还是建议统一到systemd后面再找窗口切换。要注意一个细节这个场景下 kubeasz 的部署日志里往往没有任何异常因为 Ansible 认为配置都执行完了只有 kubelet 自己启动的时候才知道不对。这就是为什么很多人会反复重跑安装脚本因为它根本不会在安装阶段暴露问题。4.2 场景B升级容器运行时后节点突然挂了另一种常见情况是节点原本跑得好好的某次运维操作后 kubelet 起不来了。比如你升级了 Docker 版本或者 containerd 版本重启之后发现节点 NotReady。原因通常是容器运行时升级后默认的 cgroup driver 发生了变化或者升级后的运行时把config.toml/daemon.json里的某个参数重置了。我之前遇到过一个情况某台节点上 containerd 的配置里SystemdCgroup原本是true但升级后配置文件被重置成了默认false于是 containerd 实际使用的是cgroupfs而 kubelet 配置还是systemd两边对不上kubelet 启动时直接报错。这种场景下处理思路就两句话先确认运行时现在的实际 driver再决定改 kubelet 还是改运行时。千万不要凭记忆觉得“之前是 systemd 就不用查了”一定要以当前实际状态为准。如果是 containerd配置文件通常在/etc/containerd/config.toml关键字段是[plugins.io.containerd.grpc.v1.cri] systemd_cgroup true这里有个非常误导人的地方systemd_cgroup false时 containerd 用的是cgroupfs很多人看到字段名带systemd就以为设置成 false 才是“不用 systemd”其实反过来。一定要记住要使用systemddriver必须设置systemd_cgroup true。4.3 常见问题速查表我把几种典型情况整理成了一张表方便你在排查时直接对照报错特征实际原因处理方式kubelet 为 cgroupfsdocker 为 systemd有人改过 Docker 的 exec-opts或发行版 Docker 预设了 systemd将 kubelet 配置改为 systemd或改 daemon.json 回 cgroupfskubelet 为 systemddocker 为 cgroupfsDocker 默认配置kubeasz 模板渲染成了 systemd推荐改 Docker 为 systemd或改 kubeasz 配置为 cgroupfs错误提示包含 containerd cgroup drivercontainerd 配置里 systemd_cgroup 与实际不符修改/etc/containerd/config.toml中 systemd_cgroup 参数重跑 playbook 后配置又被覆盖改的是节点本地文件kubeasz 模板值没同步改 kubeasz 集群配置重跑对应 playbook部分节点 Ready、部分 NotReady节点间 Docker 或 kubelet 的配置存在差异逐台检查 daemon.json、kubelet config.yaml统一配置这张表看起来简单但实际排查时 80% 的情况都能直接套用。核心就是“先看报错末尾的 driver 是什么、再看本地各自的配置是什么”。5. 一些我踩过的坑和额外提醒5.1 别把 cgroupfs 和 systemd 混着用这个问题值得单独拿出来说。虽然 Kubernetes 只强制要求“同一节点上 kubelet 和容器运行时一致”但并没有要求“全集群所有节点一致”。理论上一个节点用cgroupfs、另一个节点用systemd也能组成集群但我不建议这么干。原因很简单不同节点上 kubelet 的行为会因为驱动不同而产生细微差异尤其是在资源统计、驱逐阈值、Pod QoS 等级的判定上。混用状态下的集群排查问题时你会多出一个“节点之间行为不一致”的干扰项。有次我图省事只改了一台 worker 的 kubelet 配置结果这台节点后来出现了 cgroup 目录残留的问题和集群里其他节点的表现完全不同排查了大半天才发现是驱动混用导致的。所以我的建议是要么全集群cgroupfs要么全集群systemd推荐后者。5.2 版本差异3.x 和 2.x 的配置位置不一样kubeasz 不同版本之间的配置路径变动比较大。如果你在网上搜解决方案搜出来的路径未必适用你的版本。以我之前用过的版本为例kubeasz 2.x 时代配置经常直接写在/etc/ansible/roles/kube-node/defaults/main.yml这类文件里改起来相对粗暴。kubeasz 3.x 之后引入了集群概念配置集中在/etc/kubeasz/clusters/xxx/config.yml并且推荐用ezctl命令来管理和创建集群。所以当你准备修改配置时先确认自己的版本。你可以在项目目录下看 README 或者执行ansible-playbook --version # 或者看 kubeasz 项目目录里的版本文件 cat /etc/kubeasz/VERSION避免照着网上的旧教程改了半天的文件结果根本不是渲染模板的数据源那就白费劲了。5.3 主机本身的环境因素不要忽略有时候问题不在 kubeasz 配置里而在主机环境本身。比如某些云厂商或虚拟化平台提供的镜像里Docker 是预装的而且daemon.json里已经写了exec-opts。有些镜像甚至已经把 Docker 改成了systemddriver而 kubeasz 模板还是按默认的cgroupfs渲染了 kubelet这种情况下报错方向是反的。所以我的建议是不要一上来就去翻 kubeasz 集群配置先去节点上跑一遍 3.2 节里的两条检查命令拿到两边的真实 driver 再决定改哪边。先收集事实再动手修改这个顺序能帮你少走很多弯路。另外还有一种少见但确实存在的情况主机上之前安装过其他容器编排工具或者手动挂载过 cgroup 文件系统导致 systemd 和 cgroupfs 的视图不一致。这种环境问题不是改一行配置就能解决的可能需要重启主机或者重新挂载 cgroup 子系统才能恢复干净状态。如果你排查到这一步建议先把主机上的历史容器环境清理干净再重新部署。最后分享一个小经验。我后来部署集群时会在 kubeasz 的集群配置文件里显式写出kubelet_cgroup_driver: systemd同时确认/etc/docker/daemon.json里存在native.cgroupdriversystemd再让 ansible 开始干活。一次锁定之后后面不管怎么扩容、怎么重装节点都不会再被这个报错骚扰。这个检查步骤我建议你加到自己的部署前 checklist 里真的能省很多半夜排障的精力。
阅读完成 · 觉得有帮助?
咨询建站