1. 架构定位与组件分工1.1 为什么高可用集群里最终选了 containerd聊到 Kubernetes 高可用HA集群的容器运行时很多人第一反应还是 Docker。其实从 k8s 1.24 版本开始dockershim 被正式移除之后containerd 就成了绝大多数生产集群的默认选择。我之前在维护某跨平台系统的时候还见过不少老集群里硬生生跑着 Docker 模式的操作后来升级到 1.26 的时候踩了一堆坑才彻底明白当年 Kubernetes 团队做这个决定背后的逻辑。containerd 和 Docker 的关系通俗一点说就是 Docker 把 containerd 包了一层提供了更上层的镜像构建、命令行交互和一整套开发者体验。但在 Kubernetes 的运行环境里真正干活的其实是 containerd。去掉 Docker 这层壳直接让 kubelet 通过 CRIContainer Runtime Interface和 containerd 通信链路更短组件更少占用的系统资源也小得多。生产环境里这意味着一件事故障点少了排查问题的路径短了性能浪费也明显降低了。在 HA 架构下这一点尤其重要。高可用集群往往有多个控制平面节点和多个工作节点如果每个节点上还额外跑着一个 Docker daemon 进程无疑增加了内存占用和崩溃概率。用 containerd 作为底座每个节点上只需要管理一个轻量的容器运行时服务配合 kubelet 工作逻辑上就清晰很多。还有一个很实在的因素containerd 的崩溃恢复速度比 Docker daemon 快不少这在节点故障切换的场景里非常关键。1.2 kubelet 和容器运行时之间的 CRI 桥梁kubelet 是 Kubernetes 里负责管理节点上容器生命周期的核心组件但它自己并不直接创建容器。它通过 CRI 接口向容器运行时发指令让 containerd 去实际执行创建、启动、停止、删除这些操作。这个过程有点像你去餐厅点菜kubelet 是服务员而后厨的厨师就是 containerd。服务员不需要知道菜是怎么炒出来的只需要把订单递进去再把菜端出来。在 containerd 这个方案里kubelet 默认通过一个 Unix Socket 和 containerd 通信。这个 Socket 的路径一般是/run/containerd/containerd.sockkubelet 启动的时候通过--container-runtime-endpoint这个参数指定。很多人配置的时候会忽略一个细节不同版本的 Kubernetes 对这个参数的默认值不一样。有些版本默认走的是 Docker 的 Socket如果不显式指定kubelet 就一直连不上 containerd然后疯狂报错。CRI 的设计初衷就是让 kubelet 和具体的运行时实现解耦。你换上 containerdkubelet 的代码不需要改动将来如果出现更好的运行时理论上也可以无缝切换。但在 HA 集群里这个抽象层也带来一个问题你必须在所有节点上保证 kubelet 和 containerd 的版本兼容性。我见过一个事故某团队某个节点上 containerd 版本很老kubelet 版本很新结果那个节点一直注册不上集群后来发现是 CRI 版本协商失败导致的。1.3 HA 架构下的特殊考量高可用 Kubernetes 集群和单节点测试环境最大的区别在于每一个组件都不能是单点。控制平面的 kubelet 要配合静态 Pod 把 apiserver、etcd、controller-manager、scheduler 这些组件拉起来工作节点的 kubelet 要负责调度上来的业务 Pod 的生命周期。而 containerd 则是所有这些 Pod 的栖身之所。在 HA 架构下kubelet 配置里有一个非常关键的参数--register-with-taints。控制平面节点默认会打上污点让普通业务 Pod 不会被调度到这些节点上。你如果配置 kubelet 启动参数的时候不小心把这个选项搞错了可能会出现业务 Pod 被调度到控制平面节点上、和 apiserver 抢占资源的情况。这个问题在单节点环境里不明显但在三控制节点的 HA 集群里就是一个比较严重的隐患。另外HA 集群里各节点的/etc/hosts往往会配置多个 apiserver 的地址kubelet 需要通过--kubeconfig指向一个包含集群访问信息的文件。如果集群里有多个 apiserver 做了负载均衡kubelet 只需要指向负载均衡器的虚拟 IP 就行。但如果用了 keepalived 之类的方案虚拟 IP 切换的时候kubelet 的长连接可能会短暂中断这就需要 kubelet 能自动重连。好在 kubelet 默认就有这个能力只是重连期间的日志会刷得比较吓人习惯了就好。2. containerd 核心配置解析2.1 config.toml 里的关键参数装好 containerd 之后通常系统会生成一个默认的配置文件路径在/etc/containerd/config.toml。生产环境中我们一般不会直接使用默认配置而是执行containerd config default /etc/containerd/config.toml先生成一份完整的配置模板再基于它做修改。我第一次做这块的时候对着那个动辄几百行的 TOML 文件有点懵。后来发现真正在生产环境中需要手工调整的其实就那么几个核心参数。第一个是version当前 containerd 主流版本用的是 2老版本可能还在用 1这个字段决定了后续配置项的解析方式。第二个是root和state分别对应 containerd 的数据目录和运行目录默认分别是/var/lib/containerd和/run/containerd。一般不建议去改动它们除非磁盘分区有特殊规划。大部分初学的人会在plugins.io.containerd.grpc.v1.cri这一段里翻车。这个段落是 containerd 内置的 CRI 插件的配置区kubelet 通过 CRI 创建的所有 Pod 都走这个插件。如今大部分发行版提供的默认 containerd 已经内置了 CRI Plugin所以重启 containerd 服务之后那个containerd.sock就会自动出现。如果你的 containerd 版本较老或者发行版特殊还需要手动确认这个插件是否有启用否则后面 kubelet 会一直连不上 Socket。2.2 systemd cgroup driver 的一致性在 Kubernetes 的环境里cgroup 驱动的一致性是最容易出错、又最难排查的问题之一。kubelet 有一堆 cgroup 相关的参数containerd 也有一堆 cgroup 相关的参数它们俩必须配置成同一个驱动否则节点虽然能注册成功但 Pod 运行一段时间之后就会出现各种资源统计错乱、稳定性问题。目前主流的做法是用systemdcgroup driver。原因很简单systemd 是现代 Linux 发行版的默认初始化系统它本身就接管了 cgroup 的一部分管理逻辑。如果容器运行时绕过 systemd 自己直接操作 cgroup就会和 systemd 的预期状态不一致在资源紧张的时候容易出现进程被错误杀掉的情况。containerd 侧需要在 config.toml 里找到SystemdCgroup这个布尔值把它设为true。很多老版本默认是false如果你用的是 Docker 时代的习惯很可能会忽略这一步。kubelet 侧的配置也简单就是启动参数里加一行--cgroup-driversystemd。两边设置完之后还可以用一个简单的方法验证一致性在节点上执行kubectl describe node看看输出的System Info里的Container Runtime Version部分是否正常同时观察节点上的容器是否能正确显示 CPU 和内存使用量。还有一个细节如果你使用的是云服务器某些供应商会给内核打一些特殊的 cgroup 补丁这时候你还要注意 cgroupfs 版本的问题。CentOS 7 用的是 cgroup v1比较新的 Ubuntu 22.04 已经默认启用 cgroup v2。containerd 和 kubelet 都能同时支持 v1 和 v2但你最好确认一下操作系统实际挂载的 cgroup 版本再把配置保持一致。这个问题在集群混合节点的时候尤其明显A 节点和 B 节点系统版本不一致就会导致同样的配置在一个节点上正常、另一个节点上报警。2.3 sandbox_image 与 pause 容器Kubernetes 里的每个 Pod 其实都有一个看不见的地基容器——pause 容器也叫 infra 容器。它负责hold住 Pod 的网络命名空间和一部分资源其他业务容器通过共享这个 pause 容器的命名空间来实现网络互通。这个 pause 镜像是由 kubelet 或者 containerd 的 CRI 插件负责拉取的配置项就在 containerd 的 config.toml 里字段名是sandbox_image。默认的 sandbox_image 指向的是 Kubernetes 官方的镜像仓库地址比如registry.k8s.io/pause:3.9。但是在某些网络环境下直接访问这个地址可能会超时导致 Pod 一直处于ContainerCreating状态。这里就体现出配置镜像加速的必要性了。你可以把 sandbox_image 换成国内可访问的镜像地址或者自建的私有镜像仓库里的 pause 镜像。关键是镜像的 tag 版本要和 Kubernetes 主版本匹配不匹配的话虽然大概率也能跑但有些新特性的行为会不一致。换 sandbox_image 的时候记住一个原则不要只改 containerd 的配置而忽略了 kubelet 侧可能有运行的旧 Pod。改完配置之后systemctl restart containerd只能让新创建的 Pod 使用新的 pause 镜像已经存在的 Pod 还是会继续用旧的。要想全部切换需要先 drain 节点把旧 Pod 全部驱逐掉再在节点上重启 containerd最后 uncordon 恢复调度。这个过程在高可用集群里是可以无缝进行的因为你还有其他节点在承接业务流量。2.4 registry mirror 配置生产环境里大概率需要访问自有镜像仓库或者对官方仓库做镜像加速。containerd 的 config.toml 里提供了[plugins.io.containerd.grpc.v1.cri.registry.mirrors]这个配置段来管理镜像仓库访问。这里有个常见的坑很多照着旧文档来的人会以为配置方式是docker.io [https://mirror.example.com]但 containerd 的 TOML 解析对 key 名的格式要求很严格URL 里的冒号和斜杠需要特别注意转义的处理方式。我建议的写法是在 config.toml 里这样定义一个 mirror[plugins.io.containerd.grpc.v1.cri.registry.mirrors.docker.io] endpoint [https://mirror.example.com, https://registry-1.docker.io]这样做的含义是当 kubelet 指令 containerd 去拉取docker.io/library/nginx:1.25时containerd 会优先访问第一个 endpoint 也就是你的镜像加速地址如果失败再回退到后面的官方地址。endpoint 列表的优先级是从上到下的所以把最快的镜像源放在最前面是有道理的。还有一个容易让人困惑的点如果你使用的是私有仓库且没有配置 TLS 证书需要在 config.toml 里额外增加[plugins.io.containerd.grpc.v1.cri.registry.configs.myregistry.example.com.tls]这段配置把insecure_skip_verify设为true。生产环境我不建议直接这么干但如果内网测试环境临时用这种做法能省掉繁琐的证书分发流程。3. kubelet 核心配置详解3.1 systemd unit 文件里的启动参数Kubernetes 集群里的 kubelet 一般都用 systemd 管理unit 文件路径通常是/etc/systemd/system/kubelet.service.d/10-kubeadm.conf如果你用的是 kubeadm 部署的话。这个文件里包含了 kubelet 的全部启动参数。很多人在配置的时候会忽略一个细节kubelet 加载配置的优先级顺序命令行参数会覆盖配置文件里的内容所以如果在 unit 文件里写了参数又在kubelet config.yaml里配置了同样的字段最终生效的实际上是命令行里的那一个。我在实际项目中习惯把 kubelet 的参数尽量集中在 systemd drop-in 文件里管理。这样每个节点上的行为高度一致排查问题的时候只需要把 unit 文件拉出来看一遍就能理解这个节点的 kubelet 是什么配置。要改参数的话也简单修改完执行systemctl daemon-reload systemctl restart kubelet即可。kubelet 最核心的几个启动参数总结起来包括--kubeconfig指定连接 apiserver 用的凭据文件--config指定 kubelet 自身的配置文件路径--container-runtime-endpoint指定容器运行时 Socket--pod-infra-container-image指定 pause 镜像。后面两个参数在 containerd 模式下尤其重要如果你集群里用了私有镜像仓库需要在--pod-infra-container-image里显式指定和 containerd sandbox_image 一致的镜像。3.2 节点注册、认证与 apiserver 通信kubelet 启动之后要做的第一件事就是向 apiserver 注册节点信息同时建立后续通信用的心跳连接。这个注册过程依赖--kubeconfig指向的文件里的 client 证书和 token 信息。用 kubeadm 部署时这个文件通常是/etc/kubernetes/kubelet.conf里面有一次性的 bootstrap token用于完成初始认证。有个细节值得说一说kubelet 在完成 bootstrap 认证之后会自动生成一份属于自己的证书路径在/var/lib/kubelet/pki/kubelet-client-current.pem后续的通信都用这份证书。如果你因为某种原因删除了这个文件kubelet 会重新走一遍 bootstrap 流程。在 HA 集群里如果控制平面节点的证书过期kubelet 会反复尝试重新联系 apiserver日志里能看到大量的认证失败信息。这种情况的恢复方法是重新分发集群的 CA 证书并在各节点上更新 kubelet.conf一定不要图省事直接把kubelet.conf删掉否则可能触发更复杂的证书签发流程。然后还有一个容易被忽略的配置项--node-ip。在有多个网卡的节点上kubelet 默认会选择一个内网 IP 作为节点通信地址。如果选错了网卡Pod 与节点之间的网络通信就会出现问题。比如节点上有 docker0 网桥又有物理网卡kubelet 很可能会误选 docker0 的 IP。解决办法是在 unit 文件里显式指定--node-ip或者在 kubelet 的 config.yaml 里设置nodeIP字段。这一点在 HA 集群中需要特别关注因为节点如果注册了两个 IP集群里的 Service 转发就会出现路由混乱的情况。3.3 静态 Pod 与控制平面自举在 Kubernetes HA 架构里控制平面组件apiserver、etcd、controller-manager、scheduler不是直接以 systemd 服务方式运行的而是以静态 Pod 的方式由 kubelet 拉起。所谓静态 Pod就是直接放在节点上某个目录下的 Pod 定义文件kubelet 会定期扫描这个目录根据文件内容自行创建和更新对应 Pod。这个目录一般是/etc/kubernetes/manifests。这种设计的高明之处在于控制平面组件的生命周期完全由 kubelet 管理kubelet 挂了组件就挂kubelet 活着组件就能自动恢复。对于 HA 集群来说这就避免了手动启动 apiserver 等服务的繁琐操作。但如果你的静态 Pod 配置有问题表现出的症状会非常诡异——比如 apiserver 反复重启、etcd 集群成员频繁变化。我在实际操作中发现静态 Pod 目录下的 YAML 文件命名以及内容格式特别敏感。文件后缀必须是.yaml或者.json写成.yml可能会让 kubelet 无法识别。文件内的kind字段必须是Pod而且metadata.name必须填好如果留空kubelet 会把文件名当作 Pod 名。另外静态 Pod 的镜像拉取策略默认是IfNotPresent如果你改了镜像标签但节点上还缓存着旧版本那 kubelet 不会主动拉取新镜像这是一个隐蔽的坑。我遇到过某团队升级控制平面版本时改了 manifests 目录下的 YAML但 kubelet 硬是用了旧镜像跑了几个小时最后手动删掉节点上的旧镜像才恢复正常。4. 启动顺序与联合健康检查4.1 为什么先 containerd 后 kubelet每个节点在开机之后systemd 会按照依赖关系拉起各种服务。kubelet 的 unit 文件里通常有一行Aftercontainerd.service这个声明是为了保证在 containerd 起来之后 kubelet 才会启动。否则 kubelet 一开始就会连接 containerd 的 Socket 失败虽然它会一直重试但前面的报错日志会误导排查方向。这里我建议额外注意 systemd 的依赖关系不仅仅是After还可能需要Requires或者Wants。有些团队为了让 kubelet 在 containerd 崩溃时也能重启会配置PartOfcontainerd.service之类的指令。但生产环境我不太推荐这样强绑定因为 containerd 出故障时如果强制重启 kubelet可能会造成所有 Pod 的大规模重启对业务的影响范围反而更大了。比较稳妥的做法是containerd 和 kubelet 各自独立管理containerd 崩溃的时候有 systemd 自动拉起kubelet 则能通过 CRI 重新建立通信。kubelet 在失去与容器运行时的连接后会在日志里打印类似Failed to get sandbox image或者GRPC error的提示但不会自杀。等 containerd 恢复之后kubelet 会自动重新建立连接并继续工作。这个自动恢复的过程在 HA 集群里尤其重要之前我遇到过 containerd 因为磁盘问题短暂僵死之后自行恢复节点上的业务 Pod 虽然经历了几分钟的Unknown状态但最终都完好地活了下来。4.2 containerd 常用检查命令containerd 没有docker ps这种直观的命令它提供的是ctr和crictl两个命令行工具。ctr是 containerd 的原生命令功能相对底层一般用于管理镜像和运行时的底层操作。而crictl是 Kubernetes 社区提供的 CRI 兼容命令行工具可以理解为专门为 kubelet 场景定制的docker ps日常排查用它就够了。判断 containerd 是否正常别光看服务状态而是要实际调一下 Socket 看看响应。我常用的几组命令是这样的用ctr version检查 containerd 版本信息用ctr plugins list查看 CRI 插件和各个功能模块的加载状态用crictl ps -a查看节点上所有容器的状态用crictl images查看已拉取的镜像列表。这里面最容易踩坑的是crictl本身就依赖一个配置文件才能知道该连哪个 Socket。如果你在节点上执行crictl ps报错说failed to connect先检查/etc/crictl.yaml里的runtime-endpoint字段填的是不是unix:///run/containerd/containerd.sock。我遇到不少同事在 Kubernetes 环境里直接敲crictl却连不上以为 containerd 挂了实际上只是没有配置连接端点、默认还在找 Docker 的 Socket 路径。4.3 判断 kubelet 与 containerd 协作是否健康最直观的检查方式当然是kubectl get nodes看所有节点是不是Ready。如果节点不是Ready第一步就需要去节点上看journalctl -u kubelet -f和journalctl -u containerd -f的日志输出。这里有一个经验kubelet 的日志信息量很大光靠肉眼扫可能找不到重点可以使用grep -i error先过滤出明显错误级别的内容然后再逐条分析。还有一个实用技巧是查看/var/log/pods目录下各 Pod 的日志Kubernetes 默认会把这个目录以卷的方式挂载到每个节点上。当你发现某个 Pod 长时间处于ContainerCreating先检查这个 Pod 对应的容器是否在 containerd 里存在。如果存在但一直起不来可以执行crictl logs看容器日志如果压根不存在那就是 kubelet 给 containerd 下达的创建指令没有执行成功问题大概率出在镜像拉取或者配置错误上。健康状态下kubelet 的日志应该是相对安静的偶尔有SyncLoop或者Pulling image之类的正常记录定期会向 apiserver 上报节点状态心跳。如果你在日志里看到反复出现的PLEG is not healthy这是一个信号说明节点的 Pod 生命周期事件循环出现卡顿。这个问题的根源经常不是因为 kubelet 本身而是 containerd 在反复重新加载配置、或者镜像拉取超时导致事件队列堆积。遇到这种情况先观察 CPU 和磁盘 IO 是否异常再考虑重启 containerd 还是重启 kubelet。5. 常见问题与排查技巧实录5.1 kubelet 一直报 CRI 连接失败现象描述节点上 kubelet 服务状态显示不断重启日志里反复出现failed to connect to containerd或者failed to determine if the container runtime is already running。排查思路先分三步走第一步看 containerd 是否真的活着执行systemctl status containerd第二步确认 Socket 文件是否存在执行ls -l /run/containerd/containerd.sock第三步检查 kubelet 启动参数里的--container-runtime-endpoint是否指向了这个 Socket。如果 containerd 活着但 Socket 文件不存在多半是 containerd 配置里的 CRI 插件没启用。这个问题在 containerd 1.7 版本之后比较少见老版本里就需要检查是不是存在disabled_plugins数组包含cri字样。处理方法是在 config.toml 里去掉对 CRI 插件的禁用重启 containerd 服务。还有一个小概率情况Socket 文件存在但权限不对。kubelet 通常以 root 运行而 containerd 的 Socket 需要具备相应的读写权限正常 root 用户可以直接访问但如果配置过User限定 kubelet 运行用户就需要注意 Socket 是否对那个用户开放了访问权限。我见过一个节点上 containerd 和 kubelet 都以 root 运行但 containerd 的 Socket 目录权限被某些安全策略设置成0750导致问题的例子排查了半天最后发现是权限多了一位数。5.2 cgroup driver 不匹配导致的资源异常如果你的节点注册成功但运行一些容器后kubectl top node显示的 CPU 内存用量异常高或者全为 0那很有可能是 cgroup driver 不一致。kubelet 和 containerd 一个用systemd一个用cgroupfs就会出现这种错乱的现象。原因在于两套驱动会把进程归属到不同的 cgroup 层级里统计数据自然对不上。验证方法在节点上执行ps -ef | grep kubelet看启动参数里的--cgroup-driver用cat /etc/containerd/config.toml | grep SystemdCgroup看 containerd 的配置再结合节点操作系统的 cgroup 版本来综合判断。通常如果操作系统是使用 systemd 的现代发行版推荐两边都是systemd如果你的发行版用的是 cgroupfs 年轻时的那一套配置那还是得根据实际情况来而不是无脑照着网上的教程抄。这个问题的修复不难难的是发现过程。它不像连接失败那样在日志里直接报错更多是以一个各种功能看起来都正常但就是资源数据不对的形式存在。我通常会在部署完一个节点之后立刻用kubectl top和crictl stats做对比看两组数据是否接近。如果不一致马上检查驱动配置不要等项目上运行业务之后再来查那时候数据全线飘红很难定位是哪一层出的问题。5.3 sandbox 镜像拉取失败导致 Pod 卡在 ContainerCreating这是 containerd 模式下最经典的问题了。现象就是kubectl get pods显示状态ContainerCreating很久不动节点上crictl ps -a只能看到零星的业务容器、或者是没有任何容器存在进一步用crictl images查看发现 pause 镜像缺失或者 tag 不对。排查方法journalctl -u containerd日志里多半会有一条镜像拉取超时的记录。如果你配置了镜像加速先去确认加速地址是否可用如果没配置尝试直接拉一次ctr images pull registry.k8s.io/pause:3.9看能否成功以此判断网络链路是否正常。另外要注意一个细节kubelet 在创建 Pod 的时候用的是--pod-infra-container-image参数指定的镜像而 containerd 创建沙箱时用的却是 config.toml 里的sandbox_image。两者的值如果不一致可能会出现 kubelet 认为已经拉好了containerd 却找不到 的怪现象。所以在 containerd 方案里建议把这三个地方联动起来看kubelet 的启动参数、containerd 的 config.toml、以及节点上实际缓存的镜像列表。5.4 节点反复 Ready-NotReady 抖动另一个让运维人员头疼的问题是节点状态不稳定一会儿 Ready 一会儿 NotReady。这种抖动大多不是单一故障而是多种因素叠加的结果。常见的原因是 kubelet 心跳上报超时而心跳超时的背后可能是镜像拉取频繁失败导致 kubelet 事件处理阻塞或者 containerd 频繁 OOM 重启。排查这类抖动问题的思路是收集时间线从journalctl -u kubelet和 containerd 日志里找出从 Ready 到 NotReady 的时间点再看那段时间内节点上发生了什么。我之前处理过一个案例某节点上部署了大量带有 hostPath 卷的 Pod磁盘 inode 被日志文件塞满containerd 无法写数据导致所有容器操作都超时最终 kubelet 判定节点失联。如果你的节点使用了本地 SSD 且开启了 fstrim 之类的定时任务也要小心 trim 操作和容器运行时的文件层操作争抢 IO。这种问题在日志上表现不明显但节点会隔一段时间就无规律抖动。解决办法是在系统层面错开 trim 时间和业务高峰或者调整 containerd 的 IO 优先级。5.5 常见问题速查表现象可能原因快速排查命令参考解法kubelet 反复重启日志报连接失败kubelet 未指定 containerd Socketls -l /run/containerd/containerd.sock修改 kubelet 启动参数并重启Pod 长时间 ContainerCreatingsandbox 镜像拉取失败crictl images查看 pause 镜像配置镜像加速或手动 pull节点 Ready 但 kubectl top 数据异常cgroup 驱动不一致ps -ef | grep kubelet统一 kubelet 和 containerd 的 cgroup driver节点反复 Ready-NotReady 抖动磁盘 IO 阻塞或 inode 耗尽df -i、journalctl -u kubelet -n 200清理磁盘空间、调整日志策略crictl 命令无法连接 Socket缺少/etc/crictl.yaml配置查看/etc/crictl.yaml添加 runtime-endpoint 字段修改 kubelet 配置不生效systemd 未重载systemctl daemon-reload重载 systemd 配置再重启服务6. 实操过程中的个人体会6.1 踩过几次坑之后的总结从我维护过的那几套 Kubernetes 高可用集群来看containerd 和 kubelet 这套组合只要配置对了真的可以长期稳定运行不去管它。但前提是你要理解每一条配置背后的为什么。比如为什么要统一 cgroup driver因为 systemd 接管了 cgroup 资源控制你绕过它就会打架为什么要指定 node-ip因为 Linux 多网卡环境下默认选择策略不一定是你要的那个地址。还有一个体会比较深排查问题时要先确认底层再去上层。节点的底层是系统再往上是 containerd再往上是 kubelet之后才是 Kubernetes 集群对象。很多人在 Pod 起不来的时候直接去翻 apiserver 日志绕了一大圈才发现是 containerd 的 Socket 没监听。从底层往上逐层排查定位问题的效率会高很多。具体来说就是先看 containerd 服务是否健康、再看 kubelet 能否连上运行时、最后才去看集群层面的调度和网络策略。6.2 建议养成的几个好习惯第一每次修改 containerd 或 kubelet 配置之前先把原配置做备份最好是加个时间戳拷贝到临时目录。改坏了随时能回滚不用重新配置一遍。我见过有人改完丢了个参数又得靠回忆恢复现场效率很低。第二在节点上始终保存一个可以独立运行的 crictl 配置这样即使 kubelet 配置出错你也能用 crictl 检查底层容器状态。第三养成查看完整日志的习惯不要只用systemctl status看那一页的摘录journalctl -u kubelet -f -n 500这样的命令才是排查问题的真实工具。真的哪一天出了故障可用的日志越详细恢复的时间就越短。至于后续的扩展方向如果你已经把这套 containerd 加 kubelet 的基础配置吃透了下一步可以继续研究 kubelet 的配置热更新能力也就是通过 config.yaml 管理 kubelet 参数而不是靠命令行一堆参数堆砌这在多节点统一管理时能省不少事。也可以探索 containerd 的快照插件机制、镜像加速的使用技巧以及节点层面的安全加固方案。基础打牢了后面这些扩展点学起来都会很快。