Kubernetes节点NotReady故障排查:四层健康检查机制深度解析
1. 项目概述当 k8s 集群节点状态卡在 NotReady你在面对的不是报错而是一整套系统健康信号的集体失语k8s 集群节点状态显示 NotReady——这句话在运维值班群里刷出来时我手边的咖啡还没凉透。它不像 Pod 一直处于 Pending 或 CrashLoopBackOff 那样有明确指向性也不像 API Server 503 那样直接宣告服务不可用它更像体检报告里“多项指标异常但未达临床诊断标准”的模糊提示。你刷新kubectl get nodes看到那个刺眼的NotReady心里立刻绷紧三根弦是 kubelet 挂了网络插件没起来还是底层容器运行时彻底失联更麻烦的是它可能只影响单个节点也可能正悄悄蔓延——上一秒还在调度新任务的集群下一秒就因节点批量失联而触发自动扩缩容风暴。这个现象背后实际覆盖了 Kubernetes 最核心的四层健康保障机制节点自检Node Condition、组件心跳ComponentStatus、容器运行时连通性、网络平面可达性。它不单是“某个服务没启动”而是整个节点与控制平面之间信任链的局部断裂。我经历过三次典型场景一次是某次内核升级后 containerd 的 cgroup v2 兼容性问题导致 kubelet 无法上报状态一次是 Calico 的 felix 进程因内存泄漏被 OOMKilled但节点仍维持着 kubelet 连接造成“假在线真瘫痪”还有一次最隐蔽——节点时间漂移超过 1 秒etcd 证书校验失败kubelet 重连时反复认证失败日志里全是 TLS handshake timeout却没有任何明确错误码指向时间同步。所以当你看到 NotReady别急着systemctl restart kubelet。先问自己三个问题这个节点是孤立异常还是集群性现象NotReady 是瞬间出现还是持续存在kubectl describe node node-name输出里Conditions 字段哪一项为 False这三个问题的答案直接决定你该翻哪本手册、敲哪条命令、甚至要不要立刻联系硬件团队。这不是故障排查流程而是对 Kubernetes 控制面与数据面协作逻辑的一次现场压力测试。2. 核心机制拆解NotReady 不是终点而是四层健康检查失败的综合显影2.1 Node ConditionsKubernetes 官方定义的节点健康“体检表”Kubernetes 并不靠单一指标判断节点状态而是通过一组标准化的Node Conditions节点条件来综合评估。执行kubectl describe node node-name后在Conditions区域你会看到类似这样的结构Conditions: Type Status LastHeartbeatTime LastTransitionTime Reason Message ---- ------ ----------------- ------------------ ------ ------- NetworkUnavailable False 2024-06-15T08:22:11Z 2024-06-15T08:22:11Z RouteCreated RouteController created a route MemoryPressure False 2024-06-15T08:22:11Z 2024-06-15T08:22:11Z KubeletHasSufficientMemory kubelet has sufficient memory available DiskPressure False 2024-06-15T08:22:11Z 2024-06-15T08:22:11Z KubeletHasNoDiskPressure kubelet has no disk pressure PIDPressure False 2024-06-15T08:22:11Z 2024-06-15T08:22:11Z KubeletHasSufficientPID kubelet has sufficient PID available Ready False 2024-06-15T08:22:11Z 2024-06-15T08:22:11Z KubeletNotReady runtime network not ready: NetworkReadyfalse reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized这里的关键在于只有Ready条件为True时节点才被视为 Ready。其他条件MemoryPressure、DiskPressure 等为True反而表示有问题。而ReadyFalse的Reason和Message字段就是第一手破案线索。比如上面例子中reason: NetworkPluginNotReady直接锁定了 CNI 插件问题message里甚至精确到cni config uninitialized——这说明 kubelet 已启动但尚未加载任何 CNI 配置文件。提示kubectl get nodes -o wide只显示最终状态而describe node才是真正的“诊断报告”。很多新手跳过这一步直接去查 kubelet 日志结果在海量日志里大海捞针。记住先看 Conditions再定方向。2.2 Kubelet 心跳机制节点与 API Server 的“生命体征监护仪”节点状态本质上是 kubelet 主动向 API Server 上报的“心跳”。kubelet 每隔几秒默认 10 秒会调用/healthz接口并更新Node.Status.Conditions中的Ready状态。这个过程涉及三个关键环节kubelet 自身健康它必须正常运行且能成功连接本地容器运行时如 containerd 或 dockerd。如果systemctl status kubelet显示inactive (dead)或journalctl -u kubelet -n 100里有failed to connect to container runtime那心跳根本发不出去。API Server 连通性kubelet 需要能通过--kubeconfig指定的证书和地址访问 API Server。常见陷阱包括节点防火墙屏蔽了6443端口、/etc/kubernetes/kubelet.conf中的 server 地址写成了127.0.0.1:6443应为 Master 节点真实 IP、或证书过期openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -text -noout | grep Not After。状态上报逻辑kubelet 并非简单地“活着就报 Ready”。它会执行一系列内部检查容器运行时是否响应crictl ps -q能否列出容器CNI 插件配置是否存在且可读ls -l /etc/cni/net.d/节点资源CPU、内存能否被正确探测cat /proc/meminfo | head -3如果任一检查失败Ready状态就会被设为False并附上对应Reason。我曾遇到一个案例节点磁盘/var/lib/kubelet所在分区被占满 100%kubelet 无法写入 pod 状态文件但它仍能连接 API Server 并上报心跳——只是每次上报时都因写入失败而将Ready设为FalseReason却是KubeletNotReadyMessage里藏着failed to write status for pod xxx: no space left on device。这种细节只有深挖describe node和 kubelet 日志才能发现。2.3 容器运行时CRI与网络插件CNI数据面的“双引擎”协同NotReady 很多时候是 CRI 和 CNI 协同失效的结果。它们的关系就像汽车的发动机和变速箱CRIContainer Runtime Interfacekubelet 与底层容器引擎containerd、docker的通信协议。kubelet 通过 CRI 调用ListContainers、CreateContainer等接口。如果 CRI socket 不可达如/run/containerd/containerd.sock权限错误或路径不对kubelet 就无法管理容器自然无法确认节点是否“准备好运行工作负载”。CNIContainer Network Interface负责为 Pod 分配 IP、配置网络策略。kubelet 在启动 Pod 前必须调用 CNI 插件如 Calico、Cilium、Flannel的二进制文件。如果 CNI 配置缺失/etc/cni/net.d/下无.conf文件、插件二进制不可执行chmod x /opt/cni/bin/calico、或插件自身依赖的服务如 etcd不可达kubelet 就会卡在NetworkPluginNotReady状态。一个经典误区是看到NetworkPluginNotReady就以为一定是网络插件坏了。实则不然。有一次某节点的containerd配置里plugins.io.containerd.grpc.v1.cri.registry.mirrors被误删导致 kubelet 无法拉取 CNI 插件镜像如quay.io/calico/cni:v3.26.1crictl pull命令超时最终表现为NetworkPluginNotReady。问题根源在镜像仓库配置而非网络本身。2.4 底层基础设施被忽视的“地基”隐患所有上层机制都建立在稳定的基础之上。以下底层问题常被忽略却直接导致 NotReady系统时间严重偏差Kubernetes 组件间大量使用 TLS 证书证书有效期校验依赖系统时间。若节点时间比 Master 快或慢超过 1 分钟某些环境要求更严kubelet 与 API Server 的 TLS 握手会失败journalctl -u kubelet里会出现x509: certificate has expired or is not yet valid。用timedatectl status检查System clock synchronized: yes必须为 true。内核模块缺失或版本不兼容CNI 插件尤其是 Calico、Cilium依赖特定内核模块如ip_vs、xt_conntrack、nf_nat。modprobe ip_vs lsmod | grep ip_vs可验证。某次升级内核后新内核未编译ip_vs_rr模块导致 Calico 的kube-controllers无法创建 service VIP进而使节点NetworkUnavailableTrue最终ReadyFalse。资源耗尽的“静默崩溃”节点内存被其他进程如日志收集器、监控 agent吃光kubelet 因 OOM 被系统杀死。dmesg -T | grep -i killed process是黄金命令。更隐蔽的是 CPU 被占满kubelet 无法及时响应 API Server 的请求心跳超时状态降为 NotReady。3. 实操排查路径从宏观到微观的四级漏斗式诊断法3.1 第一级漏斗全局扫描——确认是孤例还是集群性事件不要一上来就登录出问题的节点。先站在集群视角俯瞰# 查看所有节点状态及 AGE上线时间 kubectl get nodes -o wide # 检查集群组件状态注意此命令已弃用但仍有参考价值 kubectl get componentstatuses # 更可靠的方式检查 kubelet、etcd、scheduler、controller-manager 的健康端点 curl -k https://master-ip:6443/healthz curl -k https://master-ip:6443/healthz/etcd # 需在 Master 节点执行或配置好 kubeconfig # 查看最近 5 分钟内所有节点的状态变更事件 kubectl get events --sort-by.lastTimestamp | grep -i node.*notready\|node.*ready | tail -20关键判断点如果仅一个节点 NotReady优先排查该节点自身网络、资源、配置。如果多个节点同时 NotReady立即检查 Master 节点API Server 是否存活etcd 集群是否健康网络插件的控制平面如 Calico 的calico-kube-controllersPod 是否 Running此时问题大概率在控制平面或共享基础设施如负载均衡器、DNS。实操心得我习惯在集群健康检查脚本里加入kubectl get nodes | awk NR1 {if ($2 ! Ready) print $1, $2}一旦输出非空就触发告警。这比等用户投诉快得多。3.2 第二级漏斗节点聚焦——describe node与kubectl logs的黄金组合锁定目标节点后执行# 获取详细诊断信息重点看 Conditions 和 Addresses kubectl describe node node-name # 查看该节点上所有 Pod 的状态特别是系统组件 kubectl get pods -A --field-selector spec.nodeNamenode-name -o wide # 获取 kubelet 日志实时跟踪最后 100 行 journalctl -u kubelet -n 100 -f # 如果 kubelet 日志太多用 grep 精准过滤 journalctl -u kubelet --since 2024-06-15 08:00:00 | grep -E (error|fail|warn|notready|network|cni|cri)解读describe node的实战技巧Addresses字段确认InternalIP是否正确。曾有案例因 DHCP 分配了错误 IP导致 kubelet 上报的 IP 与 API Server 记录不符状态反复震荡。Capacity和Allocatable对比两者。如果Allocatable远小于Capacity如 CPU Allocatable 为 0说明 kubelet 无法探测到可用资源可能是 cgroup 配置错误或内核参数问题。Conditions的LastTransitionTime记录状态变为 NotReady 的精确时间然后去查那个时间点前后的 kubelet 日志事半功倍。日志分析避坑指南journalctl -u kubelet默认按时间倒序最新日志在最前。但-ffollow模式下新日志在最后。务必看清上下文。不要只看error关键字。warn级别日志常是前兆如WARNING: No swap limit support可能预示后续 cgroup 初始化失败。如果日志里反复出现connection refused或timeout立即检查目标服务API Server、etcd、containerd的端口监听状态ss -tuln | grep :6443。3.3 第三级漏斗深入节点——容器运行时与网络插件的现场取证登录到 NotReady 节点进行深度检查# 1. 检查 kubelet 服务状态 systemctl status kubelet # 2. 检查容器运行时以 containerd 为例 sudo systemctl status containerd sudo crictl version # 验证 CRI 接口连通性 sudo crictl ps -q # 列出运行中容器 ID空输出即异常 # 3. 检查 CNI 配置与插件 ls -l /etc/cni/net.d/ # 应有 .conf 或 .conflist 文件 cat /etc/cni/net.d/10-calico.conflist # 查看配置内容确认 network 名称、etcd endpoints ls -l /opt/cni/bin/ # CNI 插件二进制文件权限应为可执行 sudo /opt/cni/bin/calico --version # 测试插件二进制是否可运行 # 4. 检查网络连通性关键 ping -c 3 master-ip # 节点到 Master 的基础连通性 curl -k https://master-ip:6443/healthz # API Server 健康检查 telnet master-ip 6443 # 测试端口是否开放若 telnet 未安装用 nc -zv # 5. 检查系统资源与时间 free -h # 内存使用 df -h /var/lib/kubelet # kubelet 数据目录磁盘 timedatectl status # 时间同步状态一个真实案例复盘某节点 NotReadydescribe node显示NetworkPluginNotReady。登录后发现/etc/cni/net.d/下只有一个calico.conflist但cat查看发现etcd_endpoints配置写成了https://127.0.0.1:2379而 etcd 实际运行在另一台机器上。修复配置后systemctl restart kubelet状态秒变 Ready。这个错误之所以难发现是因为calico.conflist文件语法正确crictl也能正常工作唯独 CNI 插件初始化时连接 etcd 失败而 kubelet 日志里只有一句模糊的failed to initialize network plugin。3.4 第四级漏斗终极验证——模拟 kubelet 的关键操作链当以上步骤仍无法定位就手动走一遍 kubelet 的核心工作流# 步骤1模拟 kubelet 连接容器运行时 sudo crictl info # 应返回 containerd 信息包含 status: ready # 步骤2模拟 kubelet 加载 CNI 配置 sudo CNI_PATH/opt/cni/bin NETCONFPATH/etc/cni/net.d /opt/cni/bin/calico \ --config-file /etc/cni/net.d/10-calico.conflist \ --log-level debug \ version # 步骤3模拟 kubelet 创建一个最小化 Pod需提前准备 pod.json # pod.json 内容精简版 # { # metadata: {name: test-pod, namespace: default}, # spec: {containers: [{name: busybox, image: busybox:1.35}]} # } sudo crictl runp pod.json # 步骤4检查 Pod 网络命名空间 sudo crictl inspect pod-id | grep -A 5 network # 应看到 network: {type: calico, ip: 10.233.64.2}为什么这步至关重要它把 kubelet 的黑盒操作拆解成可验证的原子步骤。如果crictl info失败问题在 CRI如果calico version报错问题在 CNI 配置或依赖如果crictl runp卡住或失败问题在网络插件执行逻辑。我曾用此法揪出一个隐藏 bugcalico插件二进制文件被意外 chmod 为700root 用户可执行但 kubelet 进程以 kubelet 用户身份运行无权执行导致NetworkPluginNotReady。sudo calico version成功但crictl runp失败——因为后者是 kubelet 的实际行为。4. 常见问题速查表与独家避坑经验4.1 高频问题速查表问题现象根本原因快速验证命令解决方案ReadyFalse,ReasonKubeletNotReady,Messageruntime network not ready: NetworkReadyfalse reason:NetworkPluginNotReadyCNI 配置文件缺失、路径错误、权限不足或 CNI 插件二进制不可执行ls -l /etc/cni/net.d/;ls -l /opt/cni/bin/;sudo /opt/cni/bin/calico version检查/etc/cni/net.d/下是否有有效配置确保/opt/cni/bin/下插件二进制chmod x确认配置中plugin字段指向的二进制存在ReadyFalse,ReasonKubeletNotReady,Messagefailed to run Kubelet: unable to load bootstrap kubeconfigkubelet 启动时找不到或无法读取--bootstrap-kubeconfig指定的文件sudo systemctl cat kubelet | grep bootstrap-kubeconfig;ls -l /etc/kubernetes/bootstrap-kubelet.conf检查 kubelet 服务文件中--bootstrap-kubeconfig参数路径确认文件存在且 kubelet 用户有读取权限若已获取正式证书删除该参数并重启ReadyFalse,ReasonKubeletNotReady,Messagefailed to run Kubelet: failed to create kubeconfig: open /var/lib/kubelet/pki/kubelet-client-current.pem: no such file or directorykubelet 证书轮换失败kubelet-client-current.pem符号链接损坏ls -l /var/lib/kubelet/pki/;ls -l /var/lib/kubelet/pki/kubelet-client-current.pem删除损坏的符号链接rm /var/lib/kubelet/pki/kubelet-client-current.pem重启 kubelet 触发重新生成ReadyFalse,ReasonKubeletNotReady,Messagefailed to run Kubelet: running with swap on is not supported系统启用了 swap 分区而 kubelet 要求禁用swapon --show;cat /proc/swapssudo swapoff -a永久禁用注释/etc/fstab中 swap 行sudo sysctl vm.swappiness0ReadyFalse,ReasonKubeletNotReady,Messagefailed to run Kubelet: failed to get node info: failed to get external ID from cloud provider使用云厂商插件如 AWS EBS CSI但--cloud-provideraws参数未正确配置或元数据服务不可达curl -s http://169.254.169.254/latest/meta-data/instance-idAWS确认--cloud-provider参数与云环境匹配检查节点能否访问云厂商元数据服务若非云环境移除--cloud-provider参数4.2 我踩过的坑与独家经验坑一systemctl restart kubelet的“假治愈”陷阱有一次节点 NotReadyjournalctl显示connection refused到127.0.0.1:6443。我本能地systemctl restart kubelet状态立刻变 Ready。但 30 秒后又变回 NotReady。后来发现kubelet 服务文件里EnvironmentKUBELET_EXTRA_ARGS--serverhttps://127.0.0.1:6443是硬编码的而该节点并非 Master127.0.0.1:6443根本不存在。重启只是让 kubelet 临时重试了其他地址但配置错误未改必然复发。教训重启前先systemctl cat kubelet看清所有参数尤其--server和--kubeconfig。坑二CNI 配置中的 “https://” 与 “http://” 之谜Calico 的etcd_endpoints配置官方文档有时写https://有时写http://。我曾在一个自建 etcd 集群中将etcd_endpoints设为https://etcd1:2379,https://etcd2:2379但 etcd 未配置 TLS导致 Calico 插件连接失败ReadyFalse。journalctl里只有Failed to connect to etcd。解决方法先用etcdctl --endpointshttp://etcd1:2379 endpoint health测试裸连通性再决定 CNI 配置用http还是https。坑三“一切正常”的最大敌人——时间同步某次跨机房集群升级后边缘节点批量 NotReady。describe node显示KubeletNotReadyjournalctl里全是x509: certificate has expired or is not yet valid。timedatectl status显示System clock synchronized: no。原来机房 NTP 服务器配置错误节点时间比 Master 慢了 2 分钟。经验在集群部署规范里必须强制要求所有节点timedatectl set-ntp on并定期巡检timedatectl status \| grep System clock synchronized。坑四crictl版本与 containerd 的“代沟”crictl是调试 CRI 的利器但它有版本要求。某次升级 containerd 到 1.7.x 后旧版crictl1.24.x无法连接crictl ps报错failed to connect to container runtime。systemctl status containerd却显示正常。解决方案crictl版本必须与 containerd 兼容。查看 containerd 文档的Compatibility Matrix下载对应版本的crictl。坑五/var/lib/kubelet目录的“隐形杀手”/var/lib/kubelet是 kubelet 的家目录存储 Pod 状态、证书、卷挂载点。如果该目录所在分区被日志或其他应用占满kubelet 无法写入状态必为 NotReady。df -h可能显示95%但du -sh /var/lib/kubelet/* \| sort -hr \| head -5常能发现某个 Pod 的volume子目录异常巨大如日志未轮转。终极清理命令find /var/lib/kubelet/pods -name volume-subpath* -type d -mtime 7 -exec rm -rf {} \;谨慎使用先备份。5. 预防性加固与自动化巡检实践5.1 节点初始化阶段的“防 NotReady” Checklist在节点加入集群前执行以下加固能规避 80% 的 NotReady 问题系统基础swapoff -a 注释/etc/fstab中 swap 行sysctl vm.swappiness0echo vm.swappiness0 /etc/sysctl.conftimedatectl set-ntp onsystemctl restart systemd-timesyncd内核与模块modprobe br_netfilterecho br_netfilter /etc/modulesmodprobe ip_vsmodprobe ip_vs_rrmodprobe nf_conntracksysctl net.bridge.bridge-nf-call-iptables1sysctl net.ipv4.ip_forward1容器运行时确认containerd配置 (/etc/containerd/config.toml) 中plugins.io.containerd.grpc.v1.cri.registry.mirrors指向可用镜像仓库systemctl enable containerd systemctl start containerdCNI 准备提前下载 CNI 插件二进制到/opt/cni/bin/并chmod x准备好 CNI 配置模板/etc/cni/net.d/10-my-cni.conflist其中etcd_endpoints、caFile、certFile、keyFile等字段留空待加入集群时由自动化工具注入5.2 自动化巡检脚本让 NotReady 无所遁形我维护了一个轻量级巡检脚本node-health-check.sh每天凌晨 2 点在所有节点上运行并将结果推送到 Slack#!/bin/bash # node-health-check.sh NODE_NAME$(hostname) STATUSOK ISSUES # 检查 kubelet if ! systemctl is-active --quiet kubelet; then STATUSCRITICAL ISSUES${ISSUES} kubelet inactive; fi # 检查 containerd if ! systemctl is-active --quiet containerd; then STATUSCRITICAL ISSUES${ISSUES} containerd inactive; fi # 检查 CNI 配置 if [ ! $(ls -A /etc/cni/net.d/ 2/dev/null) ]; then STATUSWARNING ISSUES${ISSUES} CNI config missing; fi # 检查磁盘 if [ $(df -h /var/lib/kubelet \| tail -1 \| awk {print $5} \| sed s/%//) -gt 85 ]; then STATUSWARNING ISSUES${ISSUES} /var/lib/kubelet disk 85%; fi # 检查时间同步 if ! timedatectl status | grep -q System clock synchronized: yes; then STATUSCRITICAL ISSUES${ISSUES} time not synced; fi # 输出结果 echo [$NODE_NAME] $STATUS: ${ISSUES:-None}效果脚本运行后Slack 机器人会推送类似[node-03] WARNING: /var/lib/kubelet disk 85%;的消息。运维人员无需登录节点就能在问题演变成 NotReady 前介入。5.3 故障注入演练主动制造 NotReady 来验证你的应急能力最好的防御是进攻。我每季度组织一次“NotReady 故障注入演练”场景1模拟 CNI 失效mv /etc/cni/net.d/10-calico.conflist /tmp/观察kubectl get nodes状态变化验证describe node的Reason是否准确。场景2模拟 kubelet 证书过期openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -set_serial 1 -signkey /var/lib/kubelet/pki/kubelet-client-key.pem -out /tmp/bad.pem替换证书验证告警是否触发。场景3模拟时间漂移sudo date -s 2024-01-01 00:00:00等待 2 分钟观察状态。演练目标确保团队能在 5 分钟内定位问题10 分钟内恢复服务。每一次演练都是对应急预案的一次压力测试和优化。我在实际运维中发现90% 的 NotReady 问题其根源都在节点初始化阶段的疏忽或配置漂移。与其在故障发生后疲于奔命不如把功夫下在平时——把 Checklist 刻进骨子里把巡检脚本跑成肌肉记忆把故障演练当成必修课。当 NotReady 从“惊吓”变成“意料之中”你就真正掌握了 Kubernetes 的脉搏。