节点故障自愈与集群重调度是Kubernetes运维中两个紧密咬合的齿轮,衔接得好,业务中断时间能控制在分钟级;衔接不上,故障节点上的Pod就会陷入反复重启的泥潭。本文从自愈触发重调度的完整链路出发,讲清楚两者如何配合、常见卡点在哪,以及手动介入时的正确姿势。
节点故障自愈与集群重调度如何衔接?先看两个环节的分工
集群出故障时,系统里其实有两套动作在并行:自愈负责判断“这个节点还能不能救”,重调度负责“救不了的话,上面住的Pod去哪安家”,两者分工不同,但必须按顺序衔接,否则就会出现“节点已经活了,Pod却被踢走了”或者“节点还死着,Pod却原地等待”的尴尬局面。
自愈解决的是“节点本身”的问题
自愈动作通常由kubelet、Node Controller或外部监控系统发起,健康检查失败、心跳超时、磁盘压力、内存压力,这些都属于节点层的异常,自愈的逻辑很直接:能重启就重启,能重新注册就重新注册,实在不行就标记为不可调度。
重调度解决的是“Pod被分配在哪”的问题
重调度的核心是剔除故障节点上的Pod,并把它们调度到健康节点上,这个过程由kube-controller-manager中的节点生命周期控制器驱动,配合调度器完成新位置的选择,注意,重调度不是把Pod原封不动搬过去,而是重新创建Pod,所以有状态应用需要额外考虑数据恢复。
衔接的关键:节点状态感知的时效性
行业共识认为,两者衔接的痛点在于从节点故障发生到控制器决定驱逐Pod之间的延迟,这个延迟取决于故障类型:如果是kubelet进程崩溃,心跳丢失会按默认的node-monitor-grace-period(通常40秒)触发;如果是网络分区,可能还要加上node-monitor-period的抖动时间。
节点故障自愈后,重调度何时触发?关键时间线解析
把时间线拆开看,衔接点其实有三道闸门。
第一道闸门:节点控制器标记NotReady
节点心跳丢失后,控制器会先给节点打上NotReady状态,此时不会立刻驱逐Pod,因为节点可能只是临时抖动,系统会再等一个宽限期,这就是pod-eviction-timeout

,默认是5分钟。这5分钟就是给自愈留的缓冲时间。
第二道闸门:Taint与Toleration生效
超过pod-eviction-timeout后,控制器会给故障节点打上node.kubernetes.io/not-ready:NoExecute污点,没有对应容忍度的Pod会被驱逐,如果节点在污点生效前恢复了,自愈成功,Pod继续留在原地,这是最理想的情况。
第三道闸门:驱逐Pod并重新调度
一旦驱逐启动,Pod会进入Terminating状态,如果节点已经完全不可达,kubelet无法完成优雅终止,Pod会卡在Terminating,这时候需要靠force-delete或节点对象删除来强制清理,随后调度器才会把Pod放到健康节点上。
这里有个容易忽略的点:自愈系统如果只是把节点重启了,但没清理旧Pod的终态,重调度会一直卡住,所以好的自愈方案应该在节点恢复后主动删除仍处于Terminating的Pod,让调度器接手。
实战衔接:从故障节点到Pod重新运行的全过程
用一套具体场景来走一遍:某个凌晨,你的K8s集群里一个node2节点突然网络不通,上面跑了三个无状态服务Pod。
第1步:观察故障表现
登录master节点,执行kubectl get nodes,看到node2状态为NotReady,此时Pod还是Running,但已经无法访问。
第2步:等待自愈窗口
默认情况下,系统会先等待40秒心跳检测,再等待5分钟驱逐宽限,如果你想缩短这个时间,可以在kube-controller-manager启动参数里调整:
--node-monitor-grace-period=20s --node-monitor-period=2s --pod-eviction-timeout=1m30s
注意,参数过短会增加误杀风险,比如节点短暂CPU飚高导致心跳延迟,就会造成不必要的Pod迁移。
第3步:检查污点与Pod状态
5分钟后,再次查看:
kubectl describe node node2
可以看到Taints里多了node.kubernetes.io/not-ready:NoExecute,同时执行kubectl get pods -o wide,会发现node2上的Pod变成了Terminating或Pending。
第4步:手动介入(如果卡住了)
如果Pod卡在Terminating超过几分钟,而节点已经恢复,可以执行:
kubectl delete pod <pod-name> --force --grace-period=0
但更推荐做法是先确认节点健康状态,再删除Pod,因为强制删除可能让有状态应用丢失数据,对于无状态应用,这一步通常几分钟内完成。
第5步:验证重调度结果
Pod被重新调度后,检查新节点资源是否充足:
kubectl get pods -o wide | grep <deployment-name> kubectl top nodes
如果新节点资源不足,Pod会一直Pending,这就是另一个层面的衔接问题:自愈把Pod踢出来了,但集群没有足够空间接收,这种情况下,需要配合Cluster Autoscaler扩容节点,才能完成完整闭环。
节点故障自愈超时怎么办?手动介入的正确姿势
自愈和重调度的衔接不是总那么顺畅,以下几种情况需要人工介入。
节点假死,但Pod还在Running
现象是节点显示NotReady,但Pod状态一直是Running,且无法连通,这时不要急着删Pod,先尝试重启kubelet或看节点监控,如果确认节点无法恢复,再手动cordon节点并驱逐。
kubectl cordon node2 kubectl drain node2 --ignore-daemonsets --delete-emptydir-data
节点恢复了,但新Pod调度不到旧节点
如果节点自愈成功,但之前被驱逐的Pod已经在新节点上运行,旧节点上的污点还在,需要手动清除:
kubectl taint node node2 node.kubernetes.io/not-ready:NoExecute-
自愈脚本误判,频繁重启节点
有些团队会用脚本检测K8s节点异常并执行重启,但脚本如果只检查ping通不通,可能把网络抖动当成节点故障,导致节点反复重启,业内专家指出,自愈脚本必须结合kubelet健康状态、容器运行时状态、系统负载三个维度综合判断,否则重调度会被频繁触发,反而加剧集群不稳定。
节点故障自愈方案对比与选型建议
不同故障场景下,自愈和重调度的衔接方式差异很大,以下对比常见方案。
| 方案 | 自愈触发方式 | 重调度衔接特点 | 适用场景 |
|---|---|---|---|
| 原生K8s控制器 | 心跳丢失+污点驱逐 |
等待时间长,但逻辑简单可靠 |
大多数无状态应用 |
| 自建健康检查脚本 | 自定义探测+API操作 | 响应快,但需自己处理删除Pod和清理污点 | 对恢复时间有较高要求的内部系统 |
| 节点自动修复工具(如MIG/ANA) | 云厂商实例级检测替换 | 替换后新节点自动加入,Pod自动重新调度 | 公有云环境,且Pod使用云盘等外部存储 |
| Karpenter(结合AWS) | 节点池动态调整 | 驱逐旧节点+替换新节点一体化 | 动态扩缩容频繁的集群 |
选型时注意一个原则:自愈动作越激进,重调度的误伤范围越大,如果你把pod-eviction-timeout调得很短,节点稍微抖动就会引发大规模Pod迁移,对数据库等有状态服务可能是灾难,反过来,如果自愈只负责重启kubelet,不清理Pod对象,重调度又会被卡住,所以两者必须一起设置,而不是单独调优。
节点故障自愈与集群重调度衔接的常见问题
节点自愈成功后,原节点的Pod还会回来吗?
不会自动回来,重调度是创建新Pod,而不是迁移原Pod,原节点上的Pod对象已经被驱逐删除,新Pod已经调度到其他节点,如果原节点想重新承载业务,需要等新Pod被手动或自动扩容方式迁移回来,或者等Pod因其他原因重新调度。
如何避免自愈和重调度同时操作同一个Pod?
通过PodDisruptionBudget(PDB)可以控制驱逐速率,给关键服务设置minAvailable或maxUnavailable,重调度器会遵守这个约束,即使节点故障,也不会一次性删除大量Pod,但注意,PDB只对自愿驱逐生效,节点永久故障时的驱逐属于非自愿,PDB可能被绕过,所以有状态服务还需要配合拓扑分布约束。
如何验证自愈和重调度的衔接是否正常?
推荐做一次主动故障演练,选择一个低峰期节点,手动停止kubelet或模拟网络隔离,观察从节点NotReady到Pod重新Running的总耗时,记录三个关键指标:节点状态切换时间、污点生效时间、Pod调度完成时间,如果总耗时超过预期,优先检查pod-eviction-timeout和调度器队列积压。

