Kubernetes 基础体系 · 第 61/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。

Kubernetes Node 生命周期:Condition、Lease、Taint、NotReady 和恢复

Kubernetes 中的 Node 不是“服务器在线/离线”的简单映射。一个节点同时包含:

  • Node 对象:API Server 中描述节点身份、容量、可分配资源、条件和调度属性的 API 对象;
  • Condition:节点当前健康状态的结构化判断;
  • Lease:kubelet 面向控制面的轻量心跳;
  • Taint:节点对 Pod 调度或驻留施加的排斥规则;
  • Pod 状态:节点故障后,Pod 是否被标记、驱逐、重建;
  • 节点控制器行为:根据心跳和 Condition 修改 Node、添加 Taint,并触发 Pod 处理。

因此,“Node 变成 NotReady”不是一个单独事件,而是一条状态传播链:

flowchart LR
    K[kubelet] -->|更新 Lease| L[kube-node-lease Lease]
    K -->|更新 NodeStatus| N[Node.status.conditions]
    L --> NC[Node lifecycle controller]
    N --> NC
    NC -->|Ready=False/Unknown| C[节点 Condition]
    NC -->|添加 not-ready/unreachable Taint| T[Taint]
    T --> S[调度器]
    T --> E[Pod NoExecute 处理/驱逐]
    S --> P[新 Pod 调度]
    E --> P

理解这条链路,才能区分以下几个经常被混淆的事实:

  1. Condition 是状态判断,不是调度规则。
  2. Lease 是心跳,不等于完整的 Node 状态。
  3. Taint 是排斥规则,不等于节点已经宕机。
  4. NotReady 可能来自 kubelet、运行时、网络、资源压力,也可能只是控制面收不到节点心跳。
  5. 节点恢复后,状态、Taint、Pod 和调度资格不一定同时恢复。

一、Node 对象的两个重要部分:Spec 与 Status

一个简化的 Node 对象如下:

apiVersion: v1
kind: Node
metadata:
  name: worker-1
spec:
  taints:
    - key: node.kubernetes.io/maintenance
      effect: NoSchedule
  unschedulable: false
status:
  conditions:
    - type: Ready
      status: "True"
      reason: KubeletReady
      message: kubelet is posting ready status
      lastHeartbeatTime: "..."
      lastTransitionTime: "..."
  capacity:
    cpu: "8"
    memory: 32Gi
    pods: "110"
  allocatable:
    cpu: "7800m"
    memory: 30Gi
    pods: "110"

1. spec 描述期望或调度属性

Node 的 spec 主要包含:

  • spec.unschedulable:是否禁止新的普通 Pod 调度到此节点;
  • spec.taints:节点施加的 Taint;
  • 一些节点配置或提供商相关字段。

kubectl cordon 修改的就是:

spec:
  unschedulable: true

它不表示节点故障,只表示调度器不应把新的、普通的 Pod 放到这里。

2. status 描述观察结果

Node 的 status 包含:

  • conditions:节点健康条件;
  • capacity:节点理论容量;
  • allocatable:扣除系统预留、 kubelet 预留等之后,可供 Pod 使用的容量;
  • addresses:InternalIP、Hostname 等地址;
  • nodeInfo:kubelet、容器运行时和操作系统信息。

kubelet 是 Node 状态的主要报告者,但最终看到的状态还会经过 API Server、Node 生命周期控制器和其他控制器处理。因此,kubectl get node 展示的是控制面当前存储和计算出的结果,不一定等于某一时刻主机上的瞬时状态。


二、Condition:节点健康状态的结构化表示

Node Condition 是一个数组,每个元素至少包含:

type: Ready
status: "True"
reason: KubeletReady
message: kubelet is posting ready status
lastHeartbeatTime: "2025-01-01T12:00:00Z"
lastTransitionTime: "2025-01-01T11:50:00Z"

其中:

  • type:条件类型,例如 Ready
  • status:通常是 "True""False""Unknown"
  • reason:机器可读的原因标识;
  • message:面向人的补充说明;
  • lastHeartbeatTime:该条件最近一次被 kubelet 观察或报告的时间;
  • lastTransitionTime:该条件的 status 最近一次发生变化的时间。

不同 Kubernetes 版本和 API 结构可能对时间字段的推荐方式存在变化。诊断时应以当前集群实际返回的 API 字段为准,不应依赖某个字段永远存在。

1. 常见 Condition 类型

Ready

表示节点是否可以运行 Pod:

  • True:节点被认为可用;
  • False:kubelet 明确报告节点不可用;
  • Unknown:控制面在规定时间内无法确认节点状态。

Ready=FalseReady=Unknown 的含义不同:

  • False 更接近“节点明确告诉控制面:我不能正常工作”;
  • Unknown 更接近“控制面没有收到足够的新信息”。

MemoryPressure

表示节点内存压力。

它不一定意味着物理内存已经完全耗尽。kubelet 会基于可用内存、工作集、驱逐阈值等指标判断是否进入压力状态。压力出现时,kubelet 可能驱逐 Pod,节点控制器还可能为节点添加:

node.kubernetes.io/memory-pressure

DiskPressure

表示节点本地磁盘或 inode 压力。例如:

  • 容器镜像占满磁盘;
  • 容器日志持续增长;
  • emptyDir 使用过多;
  • inode 耗尽。

常见相关 Taint 为:

node.kubernetes.io/disk-pressure

PIDPressure

表示节点可用进程 ID 接近上限。一个容器创建大量进程、僵尸进程未回收或系统进程限制过低,都可能导致这种压力。

相关 Taint 通常为:

node.kubernetes.io/pid-pressure

NetworkUnavailable

表示节点网络配置尚不可用。这个 Condition 常与云控制器、CNI 或节点网络初始化有关,并不等价于所有网络请求都失败。

常见相关 Taint 为:

node.kubernetes.io/network-unavailable

2. Condition 与 Ready 的关系

不能简单地写成:

任何一个 Condition 为 False,Ready 就一定为 False。

实际行为取决于 kubelet 的节点状态计算、网络插件、运行时和控制器实现。MemoryPressure=True 表示资源压力,不自动等价于 Ready=False;节点可能仍然能运行已有 Pod,但调度器会因压力相关 Taint 拒绝新 Pod。

一个节点可能呈现:

Ready            True
MemoryPressure   True
DiskPressure     False
PIDPressure      False

这表示节点还能工作,但处于内存压力状态。此时“节点在线”和“节点适合继续接收工作”是两个不同问题。


三、Lease:低成本心跳,不是 Node 状态的替代品

Kubernetes 使用 Lease 对象作为节点心跳。默认情况下,Lease 位于:

kube-node-lease

例如:

kubectl get lease -n kube-node-lease worker-1 -o yaml

可能看到:

apiVersion: coordination.k8s.io/v1
kind: Lease
metadata:
  name: worker-1
  namespace: kube-node-lease
spec:
  holderIdentity: worker-1
  leaseDurationSeconds: 40
  renewTime: "2025-01-01T12:00:10Z"

关键字段含义:

  • holderIdentity:当前持有 Lease 的节点身份;
  • leaseDurationSeconds:Lease 在多长时间没有续租后可被认为过期;
  • renewTime:最近一次续租时间。

1. 为什么需要 Lease

完整更新 Node status 包含较多字段,例如地址、容量、Condition 和运行时信息。若所有节点频繁更新完整 Node 对象,大规模集群中会增加 API Server、etcd 和 Watch 流量。

Lease 只更新一个轻量对象:

kubelet → Lease.renewTime

因此控制面可以先通过 Lease 判断节点最近是否仍然有心跳,而不必每次都解析完整的 Node 状态。

2. Lease 与 NodeStatus 的区别

二者解决的问题不同:

对象 主要作用 能说明什么 不能单独说明什么
Lease 轻量心跳 kubelet 最近仍能访问 API Server 并续租 kubelet、运行时、网络和资源是否完全健康
NodeStatus 完整状态 kubelet 对 Ready、压力、地址和容量的报告 控制面当前是否一定能继续收到新心跳

例如:

  • kubelet 进程仍在运行,并且可以访问 API Server,因此 Lease 正常续租;
  • 但容器运行时卡死,Pod 无法创建,kubelet 可能报告 Ready=False

反过来:

  • kubelet 进程可能仍然健康;
  • 但节点到 API Server 的网络断开,Lease 无法续租;
  • 控制面最终只能把节点状态视为 Unknown

所以 Lease 正常不代表业务一定正常;Lease 过期也不一定代表主机已经断电。

3. Lease 的时间推导

设:

  • tr t_r :Lease 的最近续租时间;
  • tn t_n :控制器当前时间;
  • D D leaseDurationSeconds
  • G G :节点控制器允许的心跳宽限时间。

当:

tntr>min(D,G)t_n - t_r > \min(D, G)

或者实现依据等价的节点监控逻辑判断心跳超时后,节点就可能进入不可确认状态。

这里的公式表达的是“心跳已超过可接受窗口”的直觉。具体触发还受控制器同步周期、时钟偏差、API Server 延迟和版本实现影响,不能把它当作毫秒级 SLA。

Kubernetes 的默认参数在不同版本、发行版和部署方式中可能变化。诊断时应查看实际组件参数,例如:

kubectl -n kube-system get pods -l component=kube-controller-manager
kubectl -n kube-system describe pod <kube-controller-manager-pod>

自托管集群还可以查看控制器启动参数中的:

--node-monitor-grace-period
--node-monitor-period

云厂商托管控制面通常不允许直接查看或修改这些参数。


四、NotReady:明确不可用和无法确认不可用

kubectl get nodes 通常把 Node 的 Ready Condition 映射成简化状态:

kubectl get nodes

示例:

NAME      STATUS     ROLES    AGE   VERSION
worker-1  Ready      <none>   20d   v1.31.2
worker-2  NotReady   <none>   20d   v1.31.2

STATUS 列不是 Node 的完整状态模型。应使用以下命令获得真实原因:

kubectl describe node worker-2

或:

kubectl get node worker-2 \
  -o jsonpath='{range .status.conditions[*]}{.type}{"\t"}{.status}{"\t"}{.reason}{"\t"}{.message}{"\n"}{end}'

1. Ready=False

典型情况是 kubelet 能够向 API Server 报告,但判断自身不能正常运行 Pod,例如:

  • 容器运行时不可用;
  • CNI 初始化失败;
  • kubelet 无法与运行时通信;
  • 节点磁盘、内存或 PID 达到严重阈值;
  • kubelet 配置错误;
  • kubelet 健康检查失败。

此时 Node 可能具有类似条件:

Ready    False    KubeletNotReady    container runtime is down

这是一种“节点仍能说话,但明确报告自己不可用”的状态。

2. Ready=Unknown

典型情况是控制面收不到新状态:

  • 主机断电;
  • 节点到 API Server 的网络中断;
  • kubelet 崩溃;
  • API Server 或中间网络异常;
  • 证书、认证或授权问题阻止 kubelet更新 Lease;
  • 节点严重卡死。

此时控制面并不能证明节点一定已经关机,只能表示“没有足够的新心跳”。

例如:

Ready    Unknown    NodeStatusUnknown    Kubelet stopped posting node status

3. NotReady 不等于 Pod 已经停止

这是最重要的边界之一。

当 Node 变成 NotReadyUnknown 时,控制面看到的是节点状态变化;节点上的进程是否真的停止,要看:

  • kubelet 是否还活着;
  • 容器运行时是否还能运行容器;
  • 网络是否仍然可达;
  • 进程是否仍在使用存储和外部系统。

如果主机已经与控制面隔离,但仍在运行业务进程,就可能形成“脑裂”风险:控制面把 Pod 重新调度到其他节点,而旧节点上的原进程尚未真正停止。

因此,不能因为 API Server 显示 NotReady 就立即认定旧 Pod 已经安全终止。


五、Taint:节点对 Pod 的排斥规则

Taint 位于:

spec:
  taints:
    - key: example.com/maintenance
      value: "true"
      effect: NoSchedule

Toleration 位于 Pod:

spec:
  tolerations:
    - key: example.com/maintenance
      operator: Equal
      value: "true"
      effect: NoSchedule

二者的关系是:

  • Taint 表示节点排斥某类 Pod;
  • Toleration 表示 Pod 可以容忍某类 Taint;
  • Toleration 不是强制调度规则,只是移除某个排斥条件;
  • Pod 仍必须满足资源、亲和性、拓扑、节点选择器等其他条件。

1. 三种 Effect

NoSchedule

新的 Pod 如果不容忍该 Taint,则不能调度到节点。

已有 Pod 通常不会因为添加 NoSchedule 而被驱逐。

PreferNoSchedule

调度器尽量避免把不容忍的 Pod 放到该节点,但这不是绝对禁止。资源和其他约束不足时,仍可能调度过去。

NoExecute

不仅影响新的 Pod,还影响已经运行的 Pod:

  • 不容忍该 Taint 的 Pod 会被驱逐;
  • 带有 tolerationSeconds 的 Pod,在容忍时间结束后被驱逐;
  • 没有时间限制的 Toleration 可以持续容忍。

示例:

tolerations:
  - key: node.kubernetes.io/not-ready
    operator: Exists
    effect: NoExecute
    tolerationSeconds: 300

它表示 Pod 可以在带有该 Taint 的节点上继续存在 300 秒,之后可能被驱逐。

2. 节点故障相关的内置 Taint

Node 生命周期控制器通常使用以下 Taint 表达节点问题:

node.kubernetes.io/not-ready:NoExecute
node.kubernetes.io/unreachable:NoExecute
node.kubernetes.io/memory-pressure:NoSchedule
node.kubernetes.io/disk-pressure:NoSchedule
node.kubernetes.io/pid-pressure:NoSchedule
node.kubernetes.io/network-unavailable:NoSchedule

其中:

  • not-ready 通常对应节点明确处于 Ready=False
  • unreachable 通常对应控制面无法确认节点状态;
  • 压力类 Taint 通常阻止新的 Pod 调度,但不必然立即驱逐已有 Pod;
  • NoExecute 才是直接影响已有 Pod 驻留的 Effect。

具体 Taint 是否由哪个控制器添加、何时删除,受 Kubernetes 版本和控制器配置影响。生产环境不要只根据一次命令输出推断完整时序,应同时检查 Condition、Lease、事件和控制器日志。

3. unschedulable 与 Taint 的区别

kubectl cordon worker-1

worker-1 cordoned

其效果是设置:

spec:
  unschedulable: true

这通常阻止新的普通 Pod 调度,但不会自动删除已有 Pod。

而:

kubectl taint nodes worker-1 example.com/maintenance=true:NoSchedule

是添加一个显式 Taint。只有不具备相应 Toleration 的 Pod 才会被阻止。

二者都可以影响调度,但表达的语义不同:

  • cordon:节点整体暂不接收新的普通调度;
  • Taint:节点排斥某一类不匹配的 Pod;
  • drain:主动迁移或删除节点上的可驱逐工作负载。

六、从心跳丢失到 Pod 处理:完整故障路径

考虑一个节点 worker-2,其 kubelet 在 12:00:00 后无法续租 Lease。

第一步:Lease 停止更新

12:00:00  最后一次 renewTime
12:00:10  预期续租,但没有成功
12:00:20  仍未成功
...

失败原因可能是:

  • kubelet 停止;
  • 节点网络中断;
  • API Server 不可达;
  • TLS 证书过期;
  • ServiceAccount 或节点身份授权失败。

此时不能只看“节点机器是否 ping 得通”,因为 kubelet访问的是 API Server 的 HTTPS 接口,并且还需要正确的证书、身份和 RBAC 权限。

第二步:Node 控制器判断心跳超时

控制器比较当前时间与 Lease 的 renewTime。超过监控宽限后,Node 的 Ready 可能变成 Unknown

示意状态:

Ready=True
    ↓ Lease 超时
Ready=Unknown

如果 kubelet恢复通信后明确报告自身不健康,则可能是:

Ready=True
    ↓ kubelet 报告运行时故障
Ready=False

这两个方向不完全相同。

第三步:添加故障 Taint

控制器可能添加:

node.kubernetes.io/unreachable:NoExecute

或者:

node.kubernetes.io/not-ready:NoExecute

这会开始影响不具备对应 Toleration 的 Pod。

第四步:Pod 是否被驱逐

假设 Pod 没有显式配置 Toleration,而集群准入行为为它添加了默认的故障容忍时间,则 Pod 可能在一段时间内保持运行状态,随后被标记驱逐。

但有几个重要例外:

  • DaemonSet Pod 通常具有特殊的节点故障容忍行为;
  • 静态 Pod 不由 API Server 中的普通 Pod 控制器管理;
  • 宿主机网络、存储和业务副作用可能不会随着 API 对象删除立即消失;
  • Pod 被删除后,Deployment、StatefulSet 等控制器才可能在其他节点创建替代副本;
  • Pod 绑定的本地卷、拓扑约束和副本数会影响是否能成功重建。

第五步:调度器处理新 Pod

调度器在为新 Pod 选择节点时,会检查:

  1. 节点是否 unschedulable
  2. Pod 是否容忍节点 Taint;
  3. 节点是否有足够的 allocatable 资源;
  4. 节点选择器、亲和性、反亲和性是否匹配;
  5. 拓扑分布、卷绑定、端口和其他过滤条件是否满足。

因此,“去掉 Taint”并不意味着 Pod 一定能调度成功。


七、一个可复现的 Taint 与 Toleration 示例

创建一个临时节点 Taint:

kubectl taint node worker-1 demo=true:NoSchedule

预期结果:

node/worker-1 tainted

检查:

kubectl describe node worker-1 | grep -A5 -i taints

预期可以看到:

Taints: demo=true:NoSchedule

此时,一个没有对应 Toleration 的 Pod,即使资源充足,也不能调度到 worker-1。事件中通常会看到类似:

0/3 nodes are available: 1 node(s) had untolerated taint {demo: true}, ...

删除 Taint:

kubectl taint node worker-1 demo=true:NoSchedule-

注意最后的 - 是删除语法,不是给 Taint 设置空值。

再创建带 Toleration 的 Pod:

apiVersion: v1
kind: Pod
metadata:
  name: tolerating-pod
spec:
  tolerations:
    - key: demo
      operator: Equal
      value: "true"
      effect: NoSchedule
  containers:
    - name: pause
      image: registry.k8s.io/pause:3.10

应用并检查:

kubectl apply -f tolerating-pod.yaml
kubectl get pod tolerating-pod -o wide

这个 Pod 只是“允许”进入带有 demo=true:NoSchedule 的节点,并不保证一定会被调度到该节点。若需要指定节点,还必须额外使用 nodeSelector、节点亲和性或其他调度约束。


八、诊断 NotReady 的正确顺序

诊断应先区分“节点明确不可用”与“控制面收不到心跳”,再检查节点本身。

1. 先获取三类信息

kubectl get node worker-2 -o wide
kubectl describe node worker-2
kubectl get lease -n kube-node-lease worker-2 -o yaml

重点观察:

  • ReadyFalse 还是 Unknown
  • reasonmessage
  • lastHeartbeatTimelastTransitionTime
  • Lease 的 renewTime
  • Taints;
  • Conditions 中是否有资源压力;
  • Events 中是否出现运行时、磁盘或网络错误。

2. 检查节点端 kubelet

在节点上执行:

sudo systemctl status kubelet
sudo journalctl -u kubelet --since "30 min ago" --no-pager

常见日志方向:

  • failed to update node status:更新 Node 状态失败;
  • failed to renew lease:Lease 续租失败;
  • container runtime is down:CRI 运行时不可用;
  • PLEG is not healthy:Pod 生命周期事件生成器没有及时处理;
  • network plugin is not ready:CNI 尚未准备好;
  • 证书、认证或授权错误。

PLEG 是 kubelet 用来感知容器状态变化的一部分。它异常时,kubelet 可能无法可靠地同步 Pod,但这不等于容器运行时进程一定已经退出。

3. 检查容器运行时

对于使用 CRI 的节点,可用:

sudo crictl info
sudo crictl ps -a
sudo systemctl status containerd
sudo journalctl -u containerd --since "30 min ago" --no-pager

crictl 需要正确配置 CRI endpoint。不同运行时、发行版和安装方式的 socket 路径可能不同,不应盲目假设 /run/containerd/containerd.sock 一定存在。

要区分:

  • kubelet 到运行时的连接失败;
  • 运行时到镜像仓库失败;
  • CNI 配置失败;
  • 单个容器或 Pod 的应用级失败。

4. 检查资源压力

free -h
df -h
df -ih
ps -eLf | wc -l
dmesg -T | grep -i -E 'oom|killed process|disk|filesystem'

重点检查:

  • 根文件系统是否已满;
  • kubelet 使用的目录是否单独挂载并已满;
  • inode 是否耗尽;
  • 内核是否触发 OOM;
  • PID 上限是否接近;
  • 容器日志是否无限增长。

df -h 正常而 df -ih 满,也足以造成磁盘相关故障。

5. 检查 API Server 可达性与身份

在节点上检查 kubelet 使用的配置和证书,确认:

  • DNS 可以解析控制面地址;
  • TCP/TLS 可以连接 API Server;
  • 节点时钟没有严重偏差;
  • kubelet 客户端证书没有过期;
  • 节点身份仍具备更新 Node 和 Lease 的权限。

如果节点能访问 API Server,但身份无权限,表现可能与网络故障相似:Lease 和 NodeStatus 同样无法更新,但网络本身是通的。


九、恢复流程:修复原因、恢复心跳、验证工作负载

节点恢复不能简化为“执行 kubectl uncordon”。正确顺序是先证明节点工作正常,再决定是否恢复调度。

1. 先修复节点本身

根据故障类型采取对应动作:

  • 重启或修复 kubelet;
  • 修复 containerd 或其他 CRI 运行时;
  • 修复 CNI 配置和网络;
  • 清理或扩容磁盘;
  • 处理内存和 PID 压力;
  • 修复证书、时钟或 API Server 路由;
  • 恢复云主机或物理机网络。

修复后先看 kubelet 日志:

sudo journalctl -u kubelet -f

再确认 Lease 更新:

watch -n 2 'kubectl get lease -n kube-node-lease worker-2 -o jsonpath="{.spec.renewTime}{"\n"}"'

如果 renewTime 持续变化,说明 kubelet 至少能够访问并更新 Lease。

2. 确认 Ready 恢复

kubectl get node worker-2
kubectl describe node worker-2

应确认:

Ready   True

并检查其他条件:

kubectl get node worker-2 \
  -o jsonpath='{range .status.conditions[*]}{.type}{"="}{.status}{" reason="}{.reason}{" message="}{.message}{"\n"}{end}'

仅仅看到 Ready=True 仍不够,还要确认:

  • MemoryPressure=False
  • DiskPressure=False
  • PIDPressure=False
  • 网络插件已就绪;
  • allocatable 资源符合预期;
  • 节点运行时可以创建和删除测试容器。

3. 检查故障 Taint 是否仍存在

kubectl get node worker-2 \
  -o jsonpath='{range .spec.taints[*]}{.key}{"="}{.value}{":"}{.effect}{"\n"}{end}'

由节点生命周期控制器管理的 not-readyunreachable Taint,通常会在节点恢复并满足控制器条件后被移除。但以下情况可能仍然存在:

  • 运维手动添加的 Taint;
  • 资源压力仍然存在;
  • 节点仍然带有维护 Taint;
  • 控制器尚未完成下一轮同步;
  • 云厂商控制器或自定义控制器有额外逻辑。

不要在没有确认故障原因的情况下直接执行:

kubectl taint node worker-2 node.kubernetes.io/unreachable:NoExecute-

手动删除故障 Taint 可能让 Pod 在一个实际上仍不可达的节点上重新调度或继续驻留。

4. 如果此前执行过 cordon,再恢复调度

kubectl uncordon worker-2

预期输出类似:

node/worker-2 uncordoned

然后检查:

kubectl get node worker-2 \
  -o jsonpath='{.spec.unschedulable}{"\n"}'

没有输出或输出 false,表示 unschedulable 已清除。

uncordon 只恢复节点的普通调度资格,不会:

  • 修复 kubelet;
  • 启动容器运行时;
  • 删除残留 Taint;
  • 恢复被驱逐的 Pod;
  • 重新绑定已经失败的卷。

5. 验证实际工作负载

kubectl get pods -A -o wide --field-selector spec.nodeName=worker-2
kubectl get events -A --sort-by=.lastTimestamp

对 Deployment:

kubectl rollout status deployment/<name> -n <namespace>

对 StatefulSet:

kubectl rollout status statefulset/<name> -n <namespace>

还应检查:

  • Pod 是否处于 Running 且 Readiness 为 True
  • 是否有 CrashLoopBackOffImagePullBackOff
  • 是否有 Pod 卡在 Terminating
  • PVC 和挂载是否正常;
  • Service、Ingress 和业务探针是否恢复;
  • 节点上的 CNI、kube-proxy 或等价网络组件是否正常。

十、Node 维护与故障恢复不是同一个流程

主动维护时,推荐使用:

kubectl cordon worker-2
kubectl drain worker-2 --ignore-daemonsets --delete-emptydir-data

其中:

  • cordon:停止新的普通 Pod 调度;
  • drain:尝试优雅地迁移或删除可驱逐 Pod;
  • --ignore-daemonsets:忽略 DaemonSet Pod,因为它们由 DaemonSet 控制器维持;
  • --delete-emptydir-data:允许删除使用 emptyDir 的 Pod 数据,存在数据丢失风险。

生产环境还可能需要:

kubectl drain worker-2 \
  --ignore-daemonsets \
  --delete-emptydir-data \
  --grace-period=60 \
  --timeout=10m

但参数不是越强制越好。--force--disable-eviction 等选项可能绕过保护机制,造成:

  • 单实例业务中断;
  • PodDisruptionBudget 被绕过;
  • 本地数据丢失;
  • 卷卸载和再次挂载冲突。

故障状态下直接 drain 也未必成功,因为 kubelet 可能无法执行 Pod 删除确认。此时必须先判断旧节点是否仍可能运行工作负载,再决定是等待、隔离、修复节点,还是在确认风险后处理残留对象。


十一、常见误解与反例

误解一:Ready=True 就表示业务正常

反例:

Ready=True
MemoryPressure=False

但应用可能因为数据库连接池耗尽、证书过期或上游不可用而全部返回 500。

Node Ready 只描述节点层面的可运行性,不替代 Pod Readiness、应用探针和业务监控。

误解二:Lease 更新就表示容器运行正常

反例:

  • kubelet 线程仍可续租;
  • containerd 的某些操作已经卡死;
  • Pod 创建、删除或状态收集持续超时。

此时 Lease 可能正常,而 Ready 最终变成 False,或者业务已经不可用。

误解三:Node NotReady 后旧 Pod 一定停止

反例:

  • 主机与控制面网络隔离,但进程仍在运行;
  • Pod 使用本地磁盘或外部系统写入;
  • 控制面在其他节点创建替代副本;
  • 原节点恢复后产生两个副本同时访问同一业务资源。

这就是节点脑裂和重复执行风险。对有状态服务、单主服务、外部副作用操作尤其危险。

误解四:删除 Taint 就能恢复节点

反例:

  • kubelet 仍然无法续租;
  • CRI 仍然不可用;
  • CNI 尚未就绪;
  • 节点仍然有磁盘压力;
  • 节点被 unschedulable 标记;
  • 调度器因资源或亲和性约束仍然拒绝 Pod。

Taint 只是调度和驻留的一层过滤条件,不是修复动作。

误解五:Unknown 就等于主机断电

Unknown 只表示控制面无法获得可信的新状态。API Server 故障、网络 ACL、证书、DNS、时钟和 kubelet 权限问题,都可能造成同样结果。


十二、版本、实现和生产风险边界

1. 当前稳定 API 与版本差异

本文使用的核心 API 为:

  • Nodev1
  • Leasecoordination.k8s.io/v1
  • Pod Toleration、Node Taint:稳定 API 字段。

但以下内容可能随版本或发行版变化:

  • kubelet 更新 NodeStatus 的频率;
  • Lease 续租频率;
  • Node 控制器检查周期和宽限期;
  • 默认故障 Toleration;
  • DaemonSet 自动添加的 Toleration;
  • 云控制器对节点地址、生命周期和删除的处理;
  • 发行版对 kubelet、CRI、CNI 和控制器参数的默认值。

应以实际集群版本对应的 Kubernetes 文档和组件启动参数为准。不要把某个托管 Kubernetes 服务的默认行为当作 Kubernetes API 的普遍保证。

2. 云厂商差异

云环境中还可能存在:

  • 云实例被删除后,云控制器删除 Node;
  • 云网络故障与主机故障表现不同;
  • 节点自动修复或自动替换;
  • 托管控制面隐藏 Node 控制器参数;
  • 云盘挂载、拓扑区域和实例生命周期影响 Pod 恢复。

因此,节点恢复不仅要看 Kubernetes,还要结合云平台实例状态、网络、磁盘和负载均衡状态。

3. 恢复操作的最大风险:误判旧节点已停止

Ready=Unknown 且节点不可达时,最危险的操作不是等待,而是在没有确认旧工作负载已经停止的情况下,强制删除 Pod、解绑卷或让替代副本接管外部资源。

对于有状态工作负载,应额外确认:

  • 旧进程是否确实停止;
  • 存储系统是否完成 fencing;
  • 卷是否允许安全地重新挂载;
  • 应用是否具备主从切换或幂等能力;
  • 外部任务是否可能重复执行。

Kubernetes 的 Node Condition、Lease 和 Taint 提供的是控制面状态传播机制,不是通用的分布式 fencing 方案。


十三、最终状态模型

可以把节点从正常到故障再到恢复抽象为:

Healthy
  ├─ Lease 持续续租
  ├─ Ready=True
  ├─ 无故障 Taint
  └─ 可调度(除非主动 cordon)
        │
        ├─ kubelet 明确报告故障
        │       └─ Ready=False
        │           └─ not-ready:NoExecute 等 Taint
        │
        └─ 控制面收不到心跳
                └─ Ready=Unknown
                    └─ unreachable:NoExecute 等 Taint

修复节点与通信
        │
        ├─ Lease 恢复更新
        ├─ kubelet 重新报告 NodeStatus
        ├─ Ready=True
        ├─ 检查并清理仍存在的 Taint
        ├─ 必要时 uncordon
        └─ 验证 Pod、卷、网络和业务健康

这里的关键不是记住几个命令,而是区分状态来源和作用范围:

  • Lease 回答“kubelet 最近是否还能向控制面续租”;
  • Condition 回答“节点或 kubelet报告的健康状态是什么”;
  • NotReadyReady Condition 的异常表现,可能是 FalseUnknown
  • Taint 回答“哪些 Pod 可以调度到或继续留在该节点”;
  • 恢复 必须同时验证心跳、节点健康、Taint、调度资格和工作负载,而不能只看 kubectl get node 的一列状态。

系列导航与关联阅读

官方资料

本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。