系统学习 故障排查学习路径:从方法论到系统、网络、数据库与 K8s

incident-kubelet-hang

一次网络存储挂起导致 kubelet 与容器运行时同时卡死的复盘(匿名化合成案例)。

现象

某业务集群的一个节点变为 NotReady。节点上的 kubelet 进程仍然存在,systemctl status kubelet 甚至显示 active,但日志反复出现 PLEG is not healthy。crictl 命令长时间无输出,依赖该节点的 Pod 状态不再更新。其他节点正常,集群整体没有大面积故障。重启 kubelet 和 containerd 都没有恢复,重启命令本身有时也会卡住。

时间线

影响面

故障范围被限制在一个节点,但该节点上的所有 Pod 都无法正常上报和停止。有多副本的在线服务在其他节点继续服务,容量相应下降;单副本有状态服务在节点隔离期间不可用。正在写入网络存储的数据是否完整,需要业务对账确认,不能因为节点恢复就假定没有损失。另有部分 Pod 因反亲和和资源约束短暂 Pending,节点替换后恢复。

误导线索

最直接的误导来自 kubelet 自己的日志:PLEG is not healthy 和容器运行时超时看起来都像 kubelet 或 containerd 的进程故障。服务状态又是 active,于是很容易陷入“重启服务—观察失败—再重启”的循环。但真正的证据是进程状态和内核等待点:kubelet 与 containerd 并没有退出,而是和 shim 一起卡在不可中断的存储等待上;这种情况下普通重启既无法清理进程,也无法让 CRI 恢复响应。

另一个误判是资源压力。最初只检查了根分区和内存,看到空间充足便排除了基础设施。后来对业务挂载点执行 stat 立即卡住,结合内核日志中的网络存储超时,才确认是特定挂载链路失效,而不是整机 CPU、内存或根盘问题。只有找到多个进程共同阻塞在同一个挂载或内核调用上,才能把 PLEG 报错从“根因”降级为“症状”。

根因

一个工作负载使用的网络存储端点失联,而该挂载采用硬挂载语义,存储客户端会持续重试,不会快速向上返回错误。同一时间,节点上使用该卷的 Pod 正在重建,容器清理时需要卸载挂载路径,shim 进入不可中断等待,containerd 的相关任务处理被拖住;kubelet 随后无法在超时内完成 CRI 调用和 PLEG 同步,节点心跳最终停止。重启 kubelet 或 containerd 不能解除内核中的挂载等待,因此看起来“服务能启动,但节点始终不恢复”。

反过来看,kubelet 不是最先损坏的组件。它是整条依赖链末端的观察者和受害者:存储不可达导致运行时调用挂起,运行时挂起导致 PLEG 失败,PLEG 失败导致节点 NotReady。

修复

先保留现场,避免不断重启制造更多线索丢失。以下命令中凡是可能访问故障挂载的操作都应加 timeout,防止诊断命令本身被卡死:

kubectl get nodes -o wide
kubectl describe node <node> | grep -A20 -i 'Conditions:'
kubectl get pods -A -o wide --field-selector spec.nodeName=<node>
systemctl status kubelet --no-pager
journalctl -u kubelet --since '-20 min' --no-pager | tail -100
journalctl -u containerd --since '-20 min' --no-pager | tail -100
ps -eo pid,ppid,stat,wchan:32,comm,args | grep -E '[k]ubelet|[c]ontainerd|[s]him'
timeout 5 crictl info; echo "crictl_exit=$?"
timeout 5 crictl ps -a | head
dmesg -T | tail -100
mount | grep -E 'nfs|<storage-endpoint>'
timeout 5 stat -f <mountpoint>

如果具备 root 权限,可对 D 状态进程查看 /proc/<pid>/stack,确认阻塞在哪个内核路径;该接口并非所有内核配置都可用,不能作为唯一依据。处理顺序应为:先 kubectl cordon <node> 阻止新调度,再通过带外管理或网络隔离 fence 节点,确认工作负载无法继续写入后,才驱逐或删除 Pod。不要在未隔离时对有状态 Pod 执行强制删除,否则旧容器可能仍在写同一份数据。

节点恢复通常需要带外重置或重建。普通 reboot 可能因为挂载等待而卡在关机阶段,不能一直等待。新节点加入后检查 CNI、kubelet、运行时和存储挂载,再逐步恢复工作负载。若存储侧已经恢复,仍需验证挂载可读、应用一致性后再允许写入。

预防

相关命令

参考链接

读完自测

合上页面,先回答,再展开对照。

  1. 为什么 kubelet 服务显示 active,节点仍可能已经不可用?
  2. 哪些证据说明问题不在 kubelet 本身,而在底层挂载?
  3. 节点隔离和强制删除 Pod 的正确顺序是什么?
答案
  1. active 只表示服务进程存在,不代表主循环和 CRI 调用能及时完成。PLEG 超时、CRI 无响应或心跳停止时,节点仍会 NotReady;若进程处于 D 状态,普通重启也无法恢复。
  2. 多个进程同时处于 D 状态,wchan 或内核栈指向网络存储调用,业务挂载点 stat 卡住,同时内核日志出现存储超时,这些都说明 PLEG 只是症状,底层挂载才是阻塞源。
  3. 先 cordon 阻止新调度,再在带外隔离或 fence 节点,确认没有容器继续写入,之后才驱逐或删除 Pod 并重建节点。未隔离就强制删除有状态 Pod,可能造成并发写入和数据损坏。
微信小程序码:扫码打开命令速查小程序

小程序版

微信扫码,随时查命令