K8s节点异常诊断与修复实战:从NotReady到自动化运维
2026/9/7 11:28:41 网站建设 项目流程

1. 项目概述:当K8s节点“生病”了怎么办?

在K8s集群的日常运维中,节点异常是每个管理员迟早都会面对的“必修课”。想象一下,你正悠闲地喝着咖啡,监控大盘上突然亮起一片刺眼的红色告警,某个工作节点(Node)失联了,或者它的状态变成了NotReady。紧接着,你可能会发现一批Pod被标记为TerminatingUnknown,服务开始出现间歇性中断。这不仅仅是某个虚拟机重启那么简单,它背后牵扯到Pod的驱逐策略、工作负载的重新调度、存储卷的挂载状态以及整个集群的稳定性。处理节点异常,远不止是登录机器看看日志,它是一套需要清晰思路、标准流程和丰富经验的综合应对策略。今天,我就结合自己踩过的坑和总结的经验,来系统性地拆解一下K8s节点异常处理的方方面面,从快速诊断到根因排查,再到预防优化,给你一套可以直接“抄作业”的实战手册。

2. 节点异常的核心表现与快速诊断

当节点出现问题时,它会在K8s API中表现出特定的状态,我们的第一步就是快速、准确地识别这些信号。

2.1 识别关键异常状态

首先,打开你的终端,使用kubectl get nodes命令。一个健康的节点状态应该是Ready。常见的异常状态包括:

  • NotReady: 这是最常见的异常状态。意味着Kubelet(节点上的代理)无法向控制平面(Control Plane)报告其状态,或者节点上的核心组件运行不正常。这通常是硬件故障、系统负载过高、网络分区或Kubelet进程崩溃导致的。
  • Unknown: API Server在一段时间内(由--node-monitor-grace-period参数控制,默认40秒)完全无法从该节点收到任何状态更新。这比NotReady更严重,通常指向节点与控制平面之间的网络完全中断,或者节点本身已经宕机。
  • 特定条件的异常: 使用kubectl describe node <node-name>查看详细情况。你需要关注Conditions部分,例如:
    • MemoryPressure/DiskPressure/PIDPressure: 内存、磁盘或进程ID资源压力。这会导致K8s调度器避免向该节点调度新Pod,并可能开始驱逐现有Pod。
    • NetworkUnavailable: 节点网络配置有问题。
    • KubeletReady: 如果这个条件是False,那就是Kubelet自身出了问题。

注意NotReadyUnknown状态都会触发K8s的“节点生命周期控制器”行动,但行为略有不同。对于Unknown节点,在经过一个更长的容忍期(--pod-eviction-timeout,默认5分钟)后,控制平面会认为该节点已不可恢复,并开始强制驱逐其上的Pod。而对于NotReady,驱逐行为可能更早或依据其他条件触发。

2.2 五分钟快速诊断清单

遇到节点异常,不要慌,按以下清单快速过一遍,能解决大部分表面问题:

  1. 检查节点资源:登录到问题节点(如果还能登录),运行tophtop,查看CPU、内存使用率。运行df -h检查根目录和关键挂载点(如/var/lib/kubelet)的磁盘使用率。95%的磁盘使用率就可能触发DiskPressure
  2. 检查Kubelet服务systemctl status kubelet。看看服务是否在运行,有没有崩溃重启的记录(journalctl -u kubelet --since “1 hour ago”)。一个常见的坑是证书过期导致Kubelet无法启动。
  3. 检查容器运行时:如果是Docker,systemctl status docker;如果是Containerd,systemctl status containerd。运行时挂了,Kubelet自然无法工作。
  4. 检查网络连通性:从节点上ping一下API Server的Service IP(通常是kubernetes.default.svc.cluster.local对应的IP)和控制平面节点的IP。同时,检查cni0flannel.1calico*等CNI接口是否存在且状态正常。
  5. 查看节点事件kubectl describe node <node-name>输出的Events部分包含了非常宝贵的线索,比如“NodeControllerEviction”开始驱逐Pod,或者“KubeletHasSufficientPID”等。

3. 深入排查:常见根因分析与实操

快速诊断可能能解决一些简单问题,但更多时候我们需要深入挖掘。下面针对几种典型场景,展开详细的排查步骤。

3.1 场景一:资源压力导致的节点异常

这是生产环境中最频繁的一类问题。K8s本身有资源压力处理机制,但我们需要理解其逻辑并主动干预。

内存压力(MemoryPressure): 当节点可用内存低于一个阈值(由--eviction-hard--eviction-soft参数定义,例如memory.available<100Mi)时,节点会报告MemoryPressure。Kubelet会按照Pod的优先级和服务质量(QoS),从低到高开始驱逐Pod。BestEffort Pod最先被牺牲。

排查步骤:

  1. kubectl top nodekubectl top pod -n <namespace> --use-protocol-buffers查看资源使用情况。注意,top命令依赖Metrics Server,确保它已部署且正常。
  2. 登录节点,使用ps aux --sort=-%mem找出宿主机上占用内存最多的进程。有时不是容器的问题,而是某个宿主机进程内存泄漏。
  3. 检查Pod的内存限制(Limit)和请求(Request)。如果Pod实际使用量持续超过其Limit,它会被OOM Killer杀掉。查看kubectl describe pod中是否有OOMKilled的事件。
  4. 实操心得:不要只依赖Pod的Limit。务必为关键Pod设置合理的内存Request,这能帮助调度器做出更优决策。同时,考虑启用HPA(水平Pod自动扩缩容)基于内存使用率来自动调整副本数。

磁盘压力(DiskPressure): 通常由镜像、日志或容器可写层占满磁盘引起。Kubelet会清理未使用的镜像和退出的容器,但如果清理速度跟不上增长,节点就会异常。

排查步骤:

  1. df -h重点查看/var/lib/docker(Docker运行时) 或/var/lib/containerd(Containerd运行时) 以及/var/log目录。
  2. 使用docker system df(Docker) 或crictl images/crictl ps -a(Containerd) 查看镜像和容器占用的空间。
  3. 清理策略:可以手动删除无用镜像和退出状态的容器。但治本之策是配置Kubelet的垃圾回收参数,例如--image-gc-high-threshold(默认85%)和--image-gc-low-threshold(默认80%),并配置日志轮转(如使用logrotate)。

踩坑记录:有一次线上告警,节点DiskPressure。排查发现是某个应用在/var/log下疯狂写日志,且没配置轮转。临时解决后,我们立即为所有Pod配置了日志输出到标准输出,并由DaemonSet(如Fluentd)收集到中心日志系统,彻底杜绝了此类问题。

3.2 场景二:Kubelet与组件故障

节点状态上报依赖于Kubelet。如果Kubelet挂了,节点就是“瞎子和聋子”。

证书问题: Kubelet需要使用客户端证书与API Server进行双向TLS认证。证书通常一年有效。过期后,Kubelet将无法连接API Server,节点状态变为NotReady

排查与解决:

  1. 在节点上检查Kubelet证书:openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -text | grep -A 2 Validity。查看Not After日期。
  2. 如果证书即将过期或已过期,在控制平面节点上,通常证书轮换是自动的。检查Kubelet配置--rotate-certificates是否为true。对于已过期的,可以尝试重启Kubelet(systemctl restart kubelet),它会自动向API Server申请新的证书(如果配置了自动批准)。
  3. 如果自动轮换失败,可能需要手动批准CSR(证书签名请求):kubectl get csr找到对应的请求,然后kubectl certificate approve <csr-name>

配置错误或文件丢失:/var/lib/kubelet/config.yaml文件被误删或kubelet.conf损坏,会导致Kubelet启动失败。

排查与解决:

  1. 从集群中其他正常节点或备份中恢复config.yaml
  2. 检查/etc/kubernetes/kubelet.conf,它包含了连接API Server的认证信息。如果损坏,可以从控制平面重新生成并下发。对于kubeadm搭建的集群,可以在控制平面执行kubeadm init phase kubeconfig kubelet然后scp到对应节点。

内核参数或系统依赖问题: 例如,net.ipv4.ip_forward未设置为1,或者bridge-nf-call-iptables未设置,会导致网络插件(如Calico、Flannel)工作不正常,进而影响Kubelet。

排查步骤:

  1. sysctl net.ipv4.ip_forward确认值为1。
  2. sysctl net.bridge.bridge-nf-call-iptables确认值为1。
  3. 确保conntrackiptables(或nftables)等工具可用。

3.3 场景三:网络分区与CNI故障

网络问题是最棘手的之一,现象往往是节点状态时好时坏,或者Pod之间网络不通。

控制平面与节点网络中断: 节点无法访问API Server的6443端口。这可能是安全组规则、防火墙(iptables/firewalld)、网络设备或云服务商网络ACL的问题。

排查步骤:

  1. 从节点telnet <api-server-ip> 6443。如果不通,逐层排查:
  2. 检查节点本地防火墙:sudo iptables -L -nsudo firewall-cmd --list-all
  3. 检查云服务商的安全组/网络ACL,确保允许节点IP访问控制平面IP的6443端口,以及控制平面节点之间相关端口(如etcd的2379/2380)。
  4. 如果是混合云或复杂网络,检查路由表是否正确。

Pod网络(CNI)故障: 节点内部Pod无法跨节点通信,或者无法访问Service。这通常是CNI插件(Calico, Cilium, Flannel等)的问题。

排查步骤:

  1. kubectl get pods -n kube-system查看CNI插件的Pod是否全部Running。
  2. 登录节点,检查CNI的二进制文件(通常在/opt/cni/bin)和配置文件(/etc/cni/net.d)是否存在且正确。
  3. 检查CNI插件创建的网桥(如cni0)和虚拟网卡。使用ip link showip addr show
  4. 一个非常实用的命令是kubectl run net-test --image=nicolaka/netshoot -it --rm -- /bin/bash,启动一个网络诊断工具Pod,在里面可以测试到其他Pod、Service和外部地址的网络连通性。

4. 高级处理策略与自动化运维

当手动排查清楚根因并修复后,我们更需要一套自动化的预防和恢复机制。

4.1 利用Node Affinity/Taint与Toleration预防

这不是事后的处理,而是事前的预防。通过污点(Taint)和容忍度(Toleration),你可以主动管理Pod的调度。

  • 给不稳定节点打上污点:例如,某个节点硬件较老,你可以给它打上node.kubernetes.io/unstable=true:NoSchedule。这样,没有对应容忍度的Pod就不会被调度上去。
  • 关键Pod使用节点亲和性:对于数据库等有状态应用,使用nodeAffinity将其绑定到性能稳定、存储可靠的特定节点上,避免被调度到问题节点。
  • DaemonSet的容忍度:像日志收集、网络插件这类DaemonSet,必须容忍所有的污点(包括node.kubernetes.io/unreachablenode.kubernetes.io/not-ready),以确保它们在所有节点(包括异常节点)上都能运行,这对于排查问题至关重要。

4.2 配置合理的Pod中断预算(PDB)

Pod Disruption Budget (PDB) 用于保护应用在自愿中断(如节点维护)时,至少有多少个副本可用。虽然它主要针对自愿中断,但在节点异常导致Pod被驱逐时,理解PDB有助于你评估影响范围。

例如,一个Deployment有3个副本,你设置PDB为minAvailable: 2。那么当节点异常,K8s尝试驱逐该Deployment的Pod时,会确保任何时候至少有两个Pod在运行。如果第三个Pod所在的节点挂了,PDB不会阻止这个非自愿中断,但它能让你明确应用的高可用性设计是否达标。

4.3 实现节点自动修复

在云环境中,你可以结合云提供商的健康检查与节点自动伸缩组(Auto Scaling Group)实现节点自愈。

基本逻辑

  1. 创建一个监控检查,定期探测节点的健康状态(不仅仅是K8s的Ready,还包括系统负载、关键进程等)。
  2. 当检测到节点持续异常且无法自动恢复时,标记该节点。
  3. 通过调用云提供商API,从ASG中移出该异常节点并终止实例。ASG会自动启动一个新的实例来替代。
  4. 新的实例通过启动脚本自动加入K8s集群(通常需要预装Kubelet、容器运行时,并自动执行kubeadm join或类似操作)。

注意事项

  • 有状态应用:这种“直接替换节点”的策略对有状态应用是灾难性的。确保有状态应用使用StatefulSet,并配置了持久化存储(Persistent Volume),且存储与节点生命周期解耦。
  • 优雅驱逐:在终止节点前,最好能通过kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data命令优雅地驱逐节点上的Pod。这给了Pod一个体面的关闭时间,并触发控制器(如Deployment)在其他节点上创建新的副本。
  • 防止雪崩:设置速率限制,避免短时间内大量节点同时被替换,对控制平面和存储后端造成冲击。

5. 构建可观测性:监控与告警体系

“没有度量,就没有管理”。一套完善的监控告警体系能让你在用户感知之前发现问题。

5.1 核心监控指标

你需要监控以下四个层面的指标:

  1. 节点层面
    • CPU/内存/磁盘/网络的使用率和饱和度。
    • Kubelet和容器运行时的进程状态。
    • node_status_condition(如ReadyMemoryPressure的状态)。
  2. Pod/容器层面
    • 容器的CPU、内存使用率(相对于其Limit)。
    • 容器的重启次数(kube_pod_container_status_restarts_total),频繁重启是重要信号。
    • Pod的阶段(Phase)和状态(Status)。
  3. K8s组件层面
    • API Server、Scheduler、Controller Manager的请求延迟和错误率。
    • etcd的写入延迟、wal同步延迟和leader健康状况。
  4. 应用业务层面
    • 服务的请求延迟、错误率和吞吐量。这能帮你判断节点异常是否真的影响了业务。

5.2 告警规则配置示例

使用Prometheus等监控系统时,可以配置如下告警规则:

# 节点不可用告警 - alert: NodeNotReady expr: kube_node_status_condition{condition="Ready", status="true"} == 0 for: 5m # 持续5分钟才告警,避免网络抖动误报 labels: severity: critical annotations: summary: "节点 {{ $labels.node }} 已超过5分钟未就绪" description: "节点 {{ $labels.node }} 状态为 NotReady,可能影响其上运行的Pod。" # 节点资源压力告警 - alert: NodeMemoryPressure expr: kube_node_status_condition{condition="MemoryPressure", status="true"} == 1 for: 2m labels: severity: warning annotations: summary: "节点 {{ $labels.node }} 内存压力" # Kubelet心跳丢失告警(比NotReady更敏感) - alert: KubeletDown expr: absent(up{job="kubelet"} == 1) for: 1m labels: severity: critical annotations: summary: "Kubelet {{ $labels.instance }} 心跳丢失"

5.3 日志收集与集中分析

除了指标,日志同样关键。确保通过DaemonSet(如Fluentd或Filebeat)将每个节点上/var/log/containers/下的容器日志,以及Kubelet、容器运行时、内核的系统日志,全部收集到Elasticsearch或Loki这样的中心化日志平台。当节点异常时,你可以第一时间在日志平台检索该节点相关的所有日志,无需登录服务器,极大提升排查效率。

6. 典型故障场景实战复盘

最后,分享两个我亲身经历的、比较复杂的故障排查案例,希望能给你带来更直观的感受。

6.1 案例一:内核内存碎片导致节点间歇性NotReady

现象:集群中几个节点每隔几小时就会随机变成NotReady,持续几分钟后自动恢复。kubectl describe node显示大量Failed to update node status的错误。

排查过程

  1. 初步检查CPU、内存、磁盘均无压力,网络也正常。
  2. 查看Kubelet日志 (journalctl -u kubelet),发现大量node not foundconnection refused错误,指向API Server。
  3. 但API Server监控指标正常,其他节点连接良好。
  4. 登录问题节点,在故障发生时尝试curl -k https://<api-server>:6443,发现非常缓慢甚至超时。同时,执行任何kubectl命令(通过节点上的kubeconfig)也很慢。
  5. 使用ss -tnp发现存在大量TIME-WAIT状态的连接到API Server。怀疑是连接数或端口耗尽。
  6. 检查sysctl net.ipv4.ip_local_port_rangenet.ipv4.tcp_tw_reuse/tcp_tw_recycle(注意:tcp_tw_recycle在NAT环境下有问题,不推荐启用)。
  7. 最终,通过dmesg -T | grep -i “memory”发现内核日志中有“page allocation failure”错误。这表明是内核内存碎片化严重,导致无法为新的网络连接分配内存。
  8. 根本原因是节点上某个遗留的Java应用(非容器化)存在内存泄漏,且长时间运行,导致内核内存碎片。Kubelet需要频繁与API Server通信,创建新连接时申请内存失败。

解决方案

  • 短期:重启有问题的宿主机进程,并重启Kubelet (systemctl restart kubelet)。重启会释放所有内核内存。
  • 长期:将非容器化的应用迁移到K8s集群内管理,并为其设置合理的内存限制。同时,调整内核参数vm.min_free_kbytes为一个更高的值(需谨慎测试),为内核保留更多空闲内存,减少碎片化概率。

6.2 案例二:CoreDNS副本数不足引发的连锁反应

现象:部分节点上的Pod解析内部Service域名超时,但解析外部域名(如百度)正常。同时,这些节点上的Pod日志里出现大量“i/o timeout”错误,且这些Pod恰好需要频繁调用其他Service。

排查过程

  1. 首先怀疑节点DNS配置。检查/etc/resolv.conf,指向的是正确的CoreDNS Service IP(通常是10.96.0.10)。
  2. 在问题Pod内执行nslookup kubernetes.default.svc.cluster.local,发现超时。
  3. 检查CoreDNS Pod:kubectl get pods -n kube-system -l k8s-app=kube-dns。发现只有1个副本在运行,且它被调度到了节点A上。
  4. 查看节点A的状态,是Ready的。但节点B、C上的Pod为什么解析失败?
  5. 检查CoreDNS Service:kubectl get svc kube-dns -n kube-system -o yaml。确认其clusterIP正确,且Endpoints指向了唯一的那个CoreDNS Pod。
  6. 在节点B上,直接curl 10.96.0.10:53测试端口,不通。问题来了:Service是集群范围的,理论上任何节点都应该能访问到clusterIP
  7. 检查Calico网络策略,没有限制。检查kube-proxy,日志正常。
  8. 最终,在节点B上执行iptables-save | grep 10.96.0.10,发现相关的iptables规则缺失。重启节点B的kube-proxy Pod后,规则恢复,DNS解析正常。
  9. 根本原因是:kube-proxy在某些节点上异常,未能正确同步Service的iptables/ipvs规则。而CoreDNS只有一个副本,当它所在的节点A与节点B之间的kube-proxy出现问题时,节点B上的Pod就无法通过Service IP访问到CoreDNS。

解决方案与心得

  1. 增加CoreDNS副本数:至少部署2个副本,并配置podAntiAffinity让它们分散在不同节点上。这样即使一个节点或一个Pod出问题,DNS服务依然可用。命令示例:kubectl scale deployment coredns -n kube-system --replicas=2
  2. 监控kube-proxy:将kube-proxy的容器日志纳入收集,并监控其健康状态。考虑将kube-proxy的DaemonSet更新策略从RollingUpdate改为OnDelete,并在维护窗口手动重启,以减少对网络规则的瞬时影响。
  3. 使用NodeLocal DNSCache:在生产环境大规模集群中,强烈建议部署NodeLocal DNSCache。它在每个节点上作为一个DaemonSet运行,缓存DNS查询结果,可以大幅减少对CoreDNS的依赖和跨节点查询,即使CoreDNS短暂异常或网络有轻微波动,本地缓存也能保障基础解析不中断。这起故障最终促使我们全面上线了NodeLocal DNSCache。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询