Kubernetes 节点 NotReady 排障:从 kubelet 到 containerd 的完整检查路径

NotReady 不是原因,而是结果

生产集群里最容易误判的告警之一,就是节点突然变成 NotReady。很多人第一反应是重启 kubelet,甚至直接重启节点。这样有时会恢复,但也很容易把真正原因盖掉:磁盘 inode 满了、containerd socket 权限异常、CNI 配置丢了、证书过期、节点时间漂移、或者 kubelet 和 containerd 之间的 CRI 调用失败。

NotReady 更准确的理解是:控制面认为这台 Node 的健康链路不可信。它不是单个组件的错误码,而是多个信号聚合后的结果。

本文的排障原则很简单:

  1. 先从 Kubernetes API 看到的事实开始,不要直接登录节点乱翻日志。

  2. 先判断故障层级,再决定处理动作。

  3. 每一步都要有命令、关注字段和下一步判断,不写“看情况”。

  4. 涉及重启 kubelet、containerd、改 CNI 或清理磁盘前,先确认影响范围和回滚方式。

一、先划清 NotReady 的链路边界

一台 Node 要保持 Ready,至少依赖下面几层都能正常工作:

层级 组件或对象 主要职责 异常时常见表现
Kubernetes API 层 Node 对象、Events、Conditions 记录控制面看到的节点状态 Ready=FalseUnknown、事件持续刷新
kubelet 层 kubelet.service 上报节点状态、管理 Pod 生命周期 heartbeat 中断、PLEG 异常、CRI 调用失败
CRI 层 /run/containerd/containerd.sockcrictl kubelet 与容器运行时通信 crictl info 失败、runtime not ready
containerd 层 containerd.service 拉镜像、创建容器、管理 sandbox containerd 日志报错、shim 异常
CNI 网络层 /etc/cni/net.d//opt/cni/bin/ 给 Pod 配网络 NetworkUnavailable、sandbox 网络创建失败
系统资源层 磁盘、inode、内存、PID、时间、证书 提供节点基础运行条件 Pressure、证书报错、系统服务异常

这张边界表很关键。NotReady 排障不是“把所有日志都看一遍”,而是沿着这条链路逐层收敛。

二、第一层:看 Node Conditions,先判断告警入口

先不要登录节点,先从控制面拿到 Node 状态。

kubectl get nodes -o wide

用途:确认是哪台节点 NotReady,以及它的角色、版本、内网 IP、容器运行时版本。 判断标准:如果只有单台节点异常,优先按节点本地问题查;如果一批节点同时异常,要先怀疑控制面、网络、证书、镜像仓库或统一配置变更。

kubectl describe node <node-name>

用途:查看 ConditionsEventsTaints、资源容量和最近状态变化。 判断标准:重点看 ReadyMemoryPressureDiskPressurePIDPressureNetworkUnavailableStatusReasonMessageLastTransitionTime

Node Conditions 可以按下面这张表判断:

Condition 正常状态 异常现象 优先下一步
Ready True FalseUnknown 看 kubelet 日志和节点 heartbeat
MemoryPressure False True 查内存、驱逐事件、异常 Pod
DiskPressure False True 查磁盘、inode、imagefs、containerfs
PIDPressure False True 查进程数、fork 失败、异常进程
NetworkUnavailable False True 查 CNI 配置、CNI Pod、节点路由

如果 Ready=Unknown,通常说明控制面已经收不到 kubelet 心跳;如果 Ready=False,通常说明 kubelet 还能上报,但它认为节点运行条件不满足。

继续看事件:

kubectl get events -A --field-selector involvedObject.kind=Node,involvedObject.name=<node-name> --sort-by=.lastTimestamp

用途:按时间顺序查看这台节点相关事件。 判断标准:如果事件里出现 NodeNotReadyNodeHasDiskPressureContainerGCFailedImageGCFailedKubeletNotReady,不要跳过,后面要按对应层级验证。

三、第二层:查 kubelet 服务是否还能工作

登录异常节点后,先看 kubelet 服务状态。

systemctl status kubelet --no-pager

用途:确认 kubelet 进程是否在运行。 判断标准:active (running) 只是说明进程还在,不代表节点健康;如果是 failed、频繁重启、activating,先看本机日志。

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

用途:查看最近 200 行 kubelet 日志,快速判断是否有明显错误。 判断标准:重点搜索 PLEG is not healthycontainer runtime is downCRIcgroupcertificateNetworkPluginNotReadyeviction manager

journalctl -u kubelet --since "30 min ago" --no-pager

用途:把观察窗口收窄到故障发生前后,避免被旧日志干扰。 判断标准:如果最近 30 分钟内持续出现同类错误,按错误关键词进入对应层级;如果没有新日志,要查 kubelet 是否已无法启动或 journald 是否异常。

常见 kubelet 日志可以这样归类:

日志关键词 可能原因 下一条命令 处理方向
container runtime is down kubelet 连不上 containerd 或 CRI 不健康 crictl info 查 socket、endpoint、containerd 状态
PLEG is not healthy runtime 响应慢、Pod 状态同步卡住 crictl ps -a 查 containerd、异常容器、节点压力
NetworkPluginNotReady CNI 配置或插件异常 ls -l /etc/cni/net.d /opt/cni/bin 查 CNI 配置和 CNI DaemonSet
eviction manager 磁盘、内存、inode 压力 df -h && df -i 先判断是否触发驱逐
x509certificate 证书过期、时间漂移、CA 不一致 date -R 查证书有效期和节点时间
cgroup kubelet/containerd cgroup driver 或 cgroup v2 异常 cat /var/lib/kubelet/config.yaml 核对 cgroup driver

不要看到 kubelet 日志里有 container runtime is down 就立刻重启 kubelet。kubelet 是发现问题的一层,containerd 或 CRI socket 才可能是实际断点。

四、第三层:查 CRI endpoint 和 containerd socket

Kubernetes 1.24 之后,很多生产集群已经没有 Docker shim,节点排障要习惯用 crictl 看 CRI。

先看 crictl 配置:

cat /etc/crictl.yaml

用途:确认 crictl 连接的 runtime endpoint。 判断标准:生产 containerd 节点通常应指向 unix:///run/containerd/containerd.sock;如果文件不存在,crictl 可能会尝试多个默认 endpoint,排障输出会变慢且容易误导。

建议配置示例:

runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 10
debug: false

再看 socket 是否存在:

ls -l /run/containerd/containerd.sock

用途:确认 containerd CRI socket 文件是否存在、权限是否合理。 判断标准:如果文件不存在,先查 containerd 是否启动;如果权限异常,确认执行用户是否有权限,kubelet 通常以 root 运行,但手工 crictl 可能受用户权限影响。

执行 CRI 连通性检查:

crictl info

用途:确认 CRI 能否返回 runtime 基本信息。 判断标准:能返回 runtimeTyperuntimeVersionconfig,说明 CRI 基本可用;如果报 connection refuseddeadline exceededcontext deadline exceeded,继续查 containerd。

crictl pods

用途:确认 Pod sandbox 列表能否读取。 判断标准:如果 crictl info 成功但 crictl pods 卡住,可能是 runtime 响应慢、sandbox 数据异常或节点资源压力。

crictl ps -a

用途:查看容器列表,包括已退出容器。 判断标准:大量 ExitedUnknown、频繁重启的容器,可能会放大 kubelet PLEG 或 GC 压力。

五、第四层:查 containerd 服务和日志

如果 CRI 不通,接下来查 containerd。

systemctl status containerd --no-pager

用途:确认 containerd 是否运行。 判断标准:active (running) 只能说明主进程还在;如果频繁重启或启动失败,马上看日志。

journalctl -u containerd -n 200 --no-pager

用途:查看 containerd 最近错误。 判断标准:重点看 failed to load cnifailed to create shim tasksnapshotterno space left on devicepermission deniedfailed to pull and unpack image

ctr -n k8s.io containers list

用途:从 containerd 自身视角查看 k8s 命名空间下容器。 判断标准:如果 ctr 可用而 crictl 不通,问题更可能在 CRI 插件或 endpoint;如果 ctr 也卡住,containerd 本身或系统资源更可疑。

生产提醒:不要在没有确认影响面的情况下直接 systemctl restart containerd。重启 containerd 可能影响该节点上已有 Pod 的状态感知和后续管理。建议先做三件事:

  1. 保存日志:journalctl -u containerd --since "2 hours ago" --no-pager > /tmp/containerd-notready.log

  2. 确认节点上业务 Pod:kubectl get pod -A -o wide --field-selector spec.nodeName=<node-name>

  3. 能驱逐则先 kubectl drain,不能驱逐则和业务负责人确认窗口。

六、第五层:查 CNI 配置和网络插件

如果 Node Conditions 或 kubelet 日志出现 NetworkUnavailableNetworkPluginNotReadycni config uninitialized,进入 CNI 层。

ls -l /etc/cni/net.d/

用途:确认节点上是否有 CNI 配置文件。 判断标准:目录为空、配置文件后缀不对、最近被误删,都会导致 kubelet 认为网络插件未就绪。

ls -l /opt/cni/bin/

用途:确认 CNI 插件二进制是否存在。 判断标准:配置里引用的插件,例如 calicoflannelbridgeloopback,必须能在该目录找到对应二进制。

kubectl get pod -A -o wide | grep -E 'calico|flannel|cilium|weave|kube-router'

用途:查看常见 CNI 组件 Pod 是否正常。 判断标准:如果同一节点上的 CNI DaemonSet Pod 不健康,先看该 Pod 日志和 Events;如果全局 CNI 都异常,要看网络插件控制面。

ip route

用途:查看节点路由是否异常。 判断标准:默认路由丢失、Pod CIDR 路由缺失、CNI 网卡不存在,都可能导致节点网络不可用。

七、第六层:查磁盘、inode、内存、PID、时间和证书

很多 NotReady 最后不是 kubelet 或 containerd 的 bug,而是系统基础条件坏了。

df -h

用途:检查磁盘空间。 判断标准:根分区、/var/lib/containerd/var/lib/kubelet 所在分区如果超过 85%-90%,要关注 kubelet eviction 和 image GC。

df -i

用途:检查 inode。 判断标准:磁盘容量没满但 inode 100%,一样会导致容器、日志、临时文件创建失败。

free -h

用途:检查内存压力。 判断标准:可用内存极低且 kubelet 日志出现 eviction,要继续查高内存 Pod 和系统进程。

ps -eLf | wc -l

用途:粗略检查线程或进程数量。 判断标准:数量接近系统限制,且 Node Condition 出现 PIDPressure=True,需要查异常进程。

date -R

用途:确认节点时间。 判断标准:如果时间明显漂移,证书校验和控制面通信都可能异常;先修 NTP/chrony,再判断 kubelet 是否恢复。

openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -dates

用途:检查 kubelet client 证书有效期。 判断标准:如果证书过期或不在有效期内,要按集群证书轮转方案处理,不要手工随意替换。

资源压力判断表:

现象 检查命令 异常判断 处理方向
DiskPressure=True df -h kubelet/containerd 分区接近满 清理无用镜像、日志,必要时扩容
DiskPressure=True 但容量未满 df -i inode 接近 100% 清理小文件、异常日志目录
MemoryPressure=True free -h 可用内存极低 查异常 Pod,必要时驱逐或扩容
PIDPressure=True ps -eLf \| wc -l 线程/进程数异常 查 runaway 进程,调大限制前先定位来源
证书相关报错 openssl x509 ... -dates 过期或未生效 按集群证书轮转流程恢复
大量 x509/timeout date -R 时间漂移 恢复 NTP/chrony,再重试 kubelet

八、把排障路径串起来:一张最小流程图

可以把现场排障压缩成下面这个顺序:

  1. kubectl get nodes -o wide:确认异常范围,是单节点还是批量节点。

  2. kubectl describe node <node-name>:看 Conditions、Events、Pressure 和时间点。

  3. journalctl -u kubelet -n 200 --no-pager:从 kubelet 日志找到入口关键词。

  4. cat /etc/crictl.yaml:确认 CRI endpoint 是否指向 containerd socket。

  5. ls -l /run/containerd/containerd.sock:确认 socket 是否存在。

  6. crictl info:验证 kubelet 到 runtime 这条链路是否通。

  7. systemctl status containerd --no-pager:确认 containerd 服务状态。

  8. journalctl -u containerd -n 200 --no-pager:查 runtime 侧具体错误。

  9. ls -l /etc/cni/net.d/ /opt/cni/bin/:确认 CNI 配置和插件。

  10. df -h && df -i && free -h:确认资源压力。

  11. date -R 和证书检查:排除时间和证书问题。

  12. 处理前先确认是否需要 cordondrain、备份日志和保留回滚路径。

九、生产处理建议:先隔离,再修复,再验证

如果节点已经 NotReady,先判断是否还承载业务 Pod。

kubectl get pod -A -o wide --field-selector spec.nodeName=<node-name>

用途:确认这台节点上还有哪些 Pod。 判断标准:如果还有关键业务 Pod,不要直接重启 containerd 或节点;先确认业务是否已经被副本接管。

必要时先禁止新 Pod 调度:

kubectl cordon <node-name>

用途:防止新的 Pod 调度到异常节点。 判断标准:kubectl get node <node-name> 显示 SchedulingDisabled,说明已生效。

如果节点还能被 kubelet 正常管理,且业务允许迁移,再考虑驱逐:

kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data

用途:把普通 Pod 从节点迁走,为维修留窗口。 判断标准:如果被 PDB 阻塞,不要强行加 --force;先确认业务可用性。

修复后验证:

kubectl uncordon <node-name>

用途:恢复节点调度。 判断标准:只在 Ready=True、关键 DaemonSet 正常、crictl info 正常、CNI Pod 正常后执行。

最终确认:

kubectl get node <node-name> -o wide
kubectl describe node <node-name> | sed -n '/Conditions:/,/Addresses:/p'

用途:确认节点状态和 Conditions 已恢复。 判断标准:Ready=True,Pressure 类 Condition 为 False,Events 不再持续刷同类错误。

十、一页式 Checklist

检查项 命令 通过标准
异常范围 kubectl get nodes -o wide 只影响预期节点,或已确认批量故障原因
Conditions kubectl describe node <node-name> Ready/Pressure/NetworkUnavailable 有明确判断
Node Events kubectl get events -A --field-selector involvedObject.kind=Node,involvedObject.name=<node-name> --sort-by=.lastTimestamp 事件指向明确层级
kubelet 状态 systemctl status kubelet --no-pager 服务运行或失败原因明确
kubelet 日志 journalctl -u kubelet -n 200 --no-pager 关键词已归类
CRI 配置 cat /etc/crictl.yaml endpoint 指向正确 socket
CRI 连通性 crictl info 能返回 runtime 信息
containerd 状态 systemctl status containerd --no-pager 服务运行且无持续重启
containerd 日志 journalctl -u containerd -n 200 --no-pager 无持续 no spacepermissionshim 错误
CNI 配置 ls -l /etc/cni/net.d/ /opt/cni/bin/ 配置和插件二进制存在
磁盘与 inode df -h && df -i 关键分区未触发压力
时间与证书 date -Ropenssl x509 ... -dates 时间正确,证书有效

总结

节点 NotReady 的关键不是“先重启哪个服务”,而是先回答三个问题:

  1. 控制面看到的异常入口是什么?看 Node Conditions 和 Events。

  2. kubelet 还能不能正常工作?看 kubelet 服务、日志和关键词。

  3. kubelet 往下依赖的 runtime、CNI、系统资源哪一层断了?用 CRI、containerd、CNI 和系统命令逐层验证。

生产排障里,最值钱的是稳定的路径。路径稳定,处理动作才不会变成碰运气。

参考资料

  • Kubernetes 官方文档:Nodes、Node status、Node pressure eviction

  • Kubernetes 官方文档:Debugging Kubernetes nodes

  • Kubernetes 官方文档:Container Runtime Interface

  • containerd 官方文档与 GitHub 项目说明

  • CRI tools 项目文档:crictl 配置与常用命令

Logo

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

更多推荐