Kubernetes 基础体系 · 第 62/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
Kubernetes 节点维护:Cordon、Drain、Eviction、DaemonSet 和恢复验证
节点维护不是简单地执行一条 kubectl drain 命令。一个节点从“继续接收工作负载”变成“安全退出”,通常会同时涉及:
- Cordon:阻止调度器把新的普通 Pod 放到节点上;
- Drain:迁移或删除节点上的可迁移 Pod,并等待它们结束;
- Eviction:通过 Kubernetes API 请求驱逐 Pod,并受 PodDisruptionBudget 约束;
- DaemonSet:代表节点级代理、网络、日志、存储等组件,其 Pod 不应按普通工作负载迁移;
- 恢复验证:节点重新加入调度前,必须确认 kubelet、Node Condition、Lease、污点、网络、存储和业务副本都已恢复。
这些动作分别由调度器、控制器管理器、API Server、kubelet、DaemonSet 控制器和应用控制器参与完成。理解它们的边界,比记住几个命令更重要。
一、先区分节点状态、调度状态和工作负载状态
1. Node 的 Ready 不是“允许调度”的同义词
Node 至少存在三类容易混淆的状态信息:
-
Node Condition
Ready=True:kubelet 能够向控制面报告节点基本健康;Ready=False:节点明确不健康;Ready=Unknown:控制面在一段时间内没有收到有效状态;MemoryPressure、DiskPressure、PIDPressure等:资源或系统压力状态。
-
Node Lease
- kubelet 会周期性更新位于
kube-node-lease命名空间中的 Lease; - Lease 主要用于快速判断 kubelet 是否仍然活跃;
- Lease 正常不等于业务一定正常,Lease 失联也不等于 Pod 已经立即消失。
- kubelet 会周期性更新位于
-
spec.unschedulabletrue表示节点被 cordon,不接受普通调度;- 它不是 Node Condition;
- 它不等于
Ready=False,一个节点可以同时是Ready=True且unschedulable=true。
可以先查看这些信息:
kubectl get node worker-1
kubectl describe node worker-1
kubectl get lease -n kube-node-lease worker-1 -o yaml
典型的健康节点可能显示:
NAME STATUS ROLES AGE VERSION
worker-1 Ready <none> 20d v1.xx.y
STATUS 通常会把 Ready、SchedulingDisabled 等信息组合展示。例如被 cordon 的节点可能显示:
worker-1 Ready,SchedulingDisabled <none> 20d v1.xx.y
这里的 SchedulingDisabled 只说明新 Pod 不会被普通调度到该节点,并不说明节点已经停止运行现有 Pod。
1.1 手工 cordon 与故障自动隔离不是同一条路径
管理员执行:
kubectl cordon worker-1
本质上会把 Node 的 spec.unschedulable 设置为 true。调度器之后不会把新的、普通的 Pod 绑定到该节点。
节点异常时,控制面还可能通过污点传播故障。例如常见的:
node.kubernetes.io/not-ready:NoSchedule
node.kubernetes.io/not-ready:NoExecute
node.kubernetes.io/unreachable:NoSchedule
node.kubernetes.io/unreachable:NoExecute
它们与 cordon 有不同作用:
NoSchedule:阻止不容忍该污点的新 Pod 调度;NoExecute:还可能驱逐已经运行在节点上的、不容忍该污点的 Pod;unschedulable=true:是调度层面的人工或工具设置,主要阻止新调度;- Node Controller 的故障处理:受心跳、Lease、Node 状态和相关超时影响,并非执行 cordon 的同义操作。
因此,Ready=True、Ready=False、SchedulingDisabled 和 NoExecute 污点必须分别观察,不能仅凭 kubectl get node 的一列输出判断完整状态。
二、Cordon:只改变“以后能不能调度”,不迁移现有 Pod
2.1 Cordon 的语义
执行:
kubectl cordon worker-1
可以理解为:
当 worker-1.spec.unschedulable=true 时,调度器不会为普通 Pod 选择它,即使:
- 节点还有充足 CPU 和内存;
- Pod 的 nodeSelector、亲和性和拓扑约束都匹配;
- 节点仍然是
Ready=True。
Cordon 不会自动执行以下动作:
- 不会删除 Pod;
- 不会把 Pod 复制到其他节点;
- 不会等待 Pod 变为
Terminated; - 不会保证 StatefulSet、Deployment 或 Job 已经在其他节点运行;
- 不会停止 DaemonSet Pod;
- 不会修复节点上的 kubelet、容器运行时、CNI 或 CSI。
检查结果:
kubectl get node worker-1 -o jsonpath='{.spec.unschedulable}{"\n"}'
预期为:
true
也可以查看节点上的 Pod:
kubectl get pod -A -o wide --field-selector spec.nodeName=worker-1
Cordon 后,这些现有 Pod 通常仍然处于 Running,所以只执行 cordon 不能称为“节点已排空”。
2.2 为什么先 cordon 再 drain
drain 的目标是在节点维护期间排出可迁移工作负载。先 cordon 的因果关系是:
- 先禁止新的普通 Pod 进入节点;
- 再处理已有 Pod;
- 迁移过程中,其他控制器创建的新副本不会把节点重新填满;
- 维护期间节点不会继续承接新的业务调度。
当前 kubectl drain 在正常使用时也会尝试将节点标记为不可调度,但显式执行 cordon 有两个好处:
- 维护脚本的状态转换更清晰;
- 如果后续 drain 因 PDB、孤立 Pod 或超时失败,节点仍不会继续接收新工作负载。
不过,DaemonSet 控制器是一个重要例外:DaemonSet 的目标是“每个匹配节点运行一个 Pod”,它可以继续在被 cordon 的节点上维持自己的 Pod。cordon 不会阻止 DaemonSet 的节点级代理运行。
2.3 Uncordon:恢复调度资格,而不是恢复业务
恢复时可以执行:
kubectl uncordon worker-1
它将 spec.unschedulable 清除,使节点重新成为普通调度候选节点。
但 uncordon 不会:
- 重新创建被删除的业务 Pod;
- 清除所有故障污点;
- 修复节点网络或磁盘;
- 保证节点上的旧 Pod 仍然存在;
- 自动验证应用副本是否满足可用性。
因此,uncordon 应该是验证流程的最后一步,而不是修复流程的第一步。
三、Drain:排空节点上的可迁移工作负载
3.1 Drain 实际要处理哪些 Pod
执行:
kubectl drain worker-1 \
--ignore-daemonsets \
--timeout=30m
通常会经历以下逻辑:
- 将节点标记为不可调度;
- 列出绑定到该节点的 Pod;
- 跳过或拒绝处理不适合 drain 的 Pod;
- 对符合条件的 Pod 发起驱逐或删除;
- 等待这些 Pod 从节点上消失;
- 由 Deployment、ReplicaSet、StatefulSet、Job 等控制器在其他可用节点创建替代 Pod;
- 所有目标 Pod 退出后,命令成功返回。
Drain 不是一个 Kubernetes API 对象,而是 kubectl 客户端执行的一套运维流程。它可能调用 Eviction API,也可能在某些情况下使用普通删除请求;具体行为受资源类型、API 能力和命令参数影响。
3.2 一个可控的排空流程
先检查节点上的 Pod:
kubectl get pod -A \
--field-selector spec.nodeName=worker-1 \
-o custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,OWNER:.metadata.ownerReferences[0].kind,PHASE:.status.phase'
然后执行:
kubectl cordon worker-1
kubectl drain worker-1 \
--ignore-daemonsets \
--timeout=30m
参数含义:
--ignore-daemonsets:忽略 DaemonSet 管理的 Pod;--timeout=30m:整个 drain 操作最多等待 30 分钟;- 默认情况下,存在未被控制器管理的 Pod、镜像 Pod,或其他不可安全处理的 Pod 时,命令可能拒绝继续。
排空期间,可以在另一个终端观察:
kubectl get pod -A -o wide --watch
同时观察 PDB:
kubectl get pdb -A
kubectl describe pdb -n application api-pdb
观察节点上的剩余 Pod:
watch 'kubectl get pod -A -o wide --field-selector spec.nodeName=worker-1'
在没有特殊约束的 Deployment 场景中,预期现象是:
- 原节点上的业务 Pod 进入
Terminating; - 新节点上出现由控制器创建的新 Pod;
- 新 Pod 就绪后,原 Pod 完成终止;
- DaemonSet Pod 仍然存在;
- drain 最终退出码为 0。
3.3 Drain 为什么可能卡住或失败
情况一:Pod 没有控制器
例如手工创建:
kubectl run debug-shell --image=busybox:1.36 -- sleep 3600
这个 Pod 没有 Deployment、Job 等控制器。删除它之后没有控制器负责重建,因此 kubectl drain 默认会拒绝处理,以避免管理员意外删除临时或重要工作负载。
只有在确认它确实可以丢弃时,才使用:
kubectl drain worker-1 \
--ignore-daemonsets \
--force \
--timeout=30m
--force 的风险不是“强制迁移”,而是允许 drain 处理没有控制器管理的 Pod。它不提供数据安全保证。
情况二:使用了 emptyDir
如果 Pod 使用了本地临时目录:
volumes:
- name: cache
emptyDir: {}
迁移或删除 Pod 时,该 emptyDir 数据会丢失。若明确接受此损失,才使用:
kubectl drain worker-1 \
--ignore-daemonsets \
--delete-emptydir-data \
--timeout=30m
这个参数只改变 kubectl 对风险的确认,不会把 emptyDir 转换成持久存储,也不会备份数据。
情况三:PodDisruptionBudget 阻止驱逐
如果 PDB 计算出当前不允许再发生自愿中断,Eviction API 会返回类似 HTTP 429 Too Many Requests,kubectl drain 通常会持续重试或最终超时。
这不是命令失效,而是 Kubernetes 正在执行 PDB 的保护规则。直接改用普通删除可能绕过该保护,生产环境不能把它作为无条件的“解决方案”。
情况四:DaemonSet Pod
默认情况下,节点上有 DaemonSet Pod 时,drain 可能提示必须使用 --ignore-daemonsets。使用该参数后,kubectl 不会删除这些 Pod,因为 DaemonSet 控制器会继续认为节点需要它们。
这不是说明节点已经完全没有 Pod,而是说明“业务工作负载已排空,节点级 DaemonSet 被保留”。
情况五:静态 Pod和镜像 Pod
由 kubelet 静态清单管理的 Pod,其真实来源是节点文件系统中的 manifest,而不是普通 API 对象控制器。通过 API 删除它通常不能实现持久删除,kubelet 可能重新创建。
由 kubelet 镜像 Pod 机制产生的 Pod 也不是普通控制器管理。对这类 Pod 使用 --force 或手工删除前,应先确认其来源。否则可能得到一个“命令返回了,但节点上的组件仍在运行”的误判。
四、Eviction:受可用性策略约束的 Pod 驱逐请求
4.1 Eviction 的定义
Eviction 是 Kubernetes 提供的一个子资源,用于请求删除某个 Pod,同时让 API Server 根据 PodDisruptionBudget 等策略判断这次删除是否允许。
它与普通删除的关键区别是:
而普通删除通常表示:
Eviction 主要用于自愿中断,例如:
- 节点维护;
- 集群缩容;
- 手工安全迁移;
- 其他管理员或控制器主动减少 Pod。
它不是节点宕机时的强制故障恢复机制。节点突然断电、内核崩溃或网络隔离时,控制面无法先向 kubelet 发起一个正常的优雅驱逐流程。
4.2 使用 policy/v1 Eviction API
可以直接构造 Eviction 对象:
apiVersion: policy/v1
kind: Eviction
metadata:
name: api-7d8f6c9d7b-abcde
namespace: application
通过 API 提交:
kubectl create -f eviction.yaml
也可以使用 kubectl proxy 后发送 HTTP 请求,但命令行直接调用时必须正确编码命名空间和 Pod 名称。生产脚本通常优先使用客户端库或 kubectl 自带的 drain 逻辑,避免自行实现重试、超时和状态判断。
如果 PDB 不允许驱逐,API Server 可能返回:
Error from server (TooManyRequests): Cannot evict pod as it would violate the pod's disruption budget.
这时应检查:
kubectl get pdb -n application api-pdb -o yaml
kubectl get pod -n application -l app=api --show-labels
kubectl get pod -n application -l app=api \
-o custom-columns='NAME:.metadata.name,READY:.status.containerStatuses[*].ready,PHASE:.status.phase,NODE:.spec.nodeName'
4.3 Eviction 的数据流
典型路径可以表示为:
sequenceDiagram
participant Admin as kubectl/operator
participant API as API Server
participant PDB as PDB admission
participant ETCD as etcd
participant Kubelet as 节点 kubelet
participant Ctrl as Deployment/ReplicaSet controller
participant Sched as Scheduler
Admin->>API: POST /api/v1/namespaces/ns/pods/pod/eviction
API->>PDB: 检查可用 Pod 与 disruptionAllowed
alt PDB 不允许
PDB-->>API: 拒绝,HTTP 429
API-->>Admin: Eviction failed
else PDB 允许
PDB-->>API: 允许
API->>ETCD: 设置 Pod deletionTimestamp
API-->>Admin: Eviction accepted
API-->>Kubelet: Pod 进入删除流程
Kubelet->>Kubelet: 执行 preStop、发送终止信号、等待 grace period
Kubelet->>API: 更新 Pod 状态/最终删除
Ctrl->>API: 发现副本不足,创建新 Pod
Sched->>API: 为新 Pod 选择其他节点
end
这里有三个容易忽略的并发关系:
- Eviction 被接受,不等于 Pod 已经立即从节点消失;
- 控制器可以在旧 Pod 仍处于
Terminating时创建替代 Pod; - 新 Pod 是否能在其他节点运行,还取决于资源、亲和性、拓扑约束、污点容忍和存储绑定。
因此,驱逐成功后仍要等待:
kubectl wait \
--for=delete pod/api-7d8f6c9d7b-abcde \
-n application \
--timeout=5m
kubectl wait --for=delete 适用于指定对象仍存在的情况;如果对象已经不存在,命令行为应结合脚本逻辑处理,不能简单把“对象不存在”当成所有清理步骤都成功。
4.4 优雅终止不等于立即停止
Pod 被驱逐后,kubelet 通常会进行优雅终止:
- Pod 获得
deletionTimestamp; - kubelet 执行容器的终止流程;
- 如果配置了
preStop,按生命周期规则执行; - 向主进程发送终止信号;
- 等待
terminationGracePeriodSeconds; - 仍未退出时才可能强制结束;
- Pod 对象最终从 API 中消失。
例如:
spec:
terminationGracePeriodSeconds: 60
这只表示应用最多获得一个优雅退出窗口,不保证应用一定在 60 秒内完成事务提交。应用必须正确处理终止信号,连接池、消息消费、选主、临时文件和状态持久化都要与这个窗口相匹配。
如果在 drain 中使用:
kubectl drain worker-1 --grace-period=10
它会影响此次删除所使用的宽限期,可能覆盖 Pod 原有的终止时间设置。缩短该时间可能导致请求中断、事务回滚或消息重复处理,不能作为解决 drain 慢的默认手段。
五、PodDisruptionBudget 如何影响 Drain
5.1 PDB 保护的是“自愿中断”
PDB 通过标签选择器匹配一组 Pod,并声明在自愿中断期间至少保留多少可用副本,或者最多允许多少副本不可用。
例如:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-pdb
namespace: application
spec:
maxUnavailable: 1
selector:
matchLabels:
app: api
如果该选择器匹配 3 个 Pod,且 3 个 Pod 都 Ready,则最多允许 1 个 Pod 因 Eviction 进入中断。
一个简化模型是:
其中:
- :当前可用 Pod 数量;
- :PDB 要求保留的可用数量;
- :当前允许的自愿中断数量。
当使用 minAvailable 时:
当使用 maxUnavailable 时:
但实际计算还涉及选择器匹配、控制器期望副本数、不可用 Pod 统计和百分比取整,不能只看 Deployment 的副本数。
5.2 完整算例:为什么 drain 会被阻塞
假设 Deployment 有 3 个副本:
api-1 Ready worker-1
api-2 Ready worker-2
api-3 NotReady worker-3
PDB:
spec:
minAvailable: 2
selector:
matchLabels:
app: api
当前可用数量:
要求保留:
因此:
如果此时维护 worker-1,驱逐 api-1 会使可用 Pod 从 2 降到 1,违反 minAvailable: 2,所以 Eviction API 拒绝请求。
正确的处理顺序应是:
- 先恢复
api-3,使 ; - 此时 ;
- 驱逐
api-1; - 等待新的 Pod 在其他节点 Ready;
- 再进行下一个可能影响该 PDB 的操作。
如果把 PDB 临时改成 minAvailable: 1,确实可能允许排空,但这同时降低了业务可用性保证。它应当是经过容量和故障预算评估的变更,而不是为了让命令尽快返回而盲目修改。
5.3 PDB 的边界
PDB 不是所有故障的保护机制:
- 节点断电时,系统无法先进行正常 Eviction;
- kubelet 无法通信时,Pod 可能长时间处于未知或终止状态;
- 强制删除可以绕过正常的自愿中断保护;
- PDB 不会自动增加集群容量;
- PDB 不会解决拓扑约束导致的新 Pod无法调度的问题。
此外,PDB 选择器必须与工作负载标签正确匹配。选择器不匹配时,PDB 看似存在,但实际没有保护目标。应检查:
kubectl get pdb -n application api-pdb -o yaml
kubectl get pod -n application --show-labels
kubectl get deployment -n application api -o yaml
如果使用百分比,必须注意取整。例如某些 PDB 百分比在副本数较小时,取整方向可能使实际允许中断数比直觉更严格或更宽松。生产维护前应查看 PDB 的状态字段,而不是只从 YAML 文本推算:
kubectl get pdb -n application api-pdb \
-o custom-columns='NAME:.metadata.name,EXPECTED:.status.expectedPods,HEALTHY:.status.currentHealthy,DESIRED:.status.desiredHealthy,ALLOWED:.status.disruptionsAllowed'
字段具体展示依赖 Kubernetes 版本和客户端列定义,但核心应关注 currentHealthy、desiredHealthy 和 disruptionsAllowed。
六、DaemonSet:节点级工作负载为什么不随普通 Pod Drain
6.1 DaemonSet 的控制目标
Deployment 的目标通常是某个数量的副本:
DaemonSet 的目标则更接近:
典型用途包括:
- CNI 网络代理;
- 节点日志采集;
- 节点监控;
- CSI 节点插件;
- 安全或运行时代理;
- 存储、设备和硬件发现组件。
当节点被 cordon 时,它仍然是一个匹配 DaemonSet 选择器的节点,因此 DaemonSet 控制器通常不会因为 unschedulable=true 就删除其 Pod。
6.2 为什么 drain 使用 --ignore-daemonsets
不使用参数时:
kubectl drain worker-1
如果节点上有 DaemonSet Pod,kubectl 通常会拒绝继续,并提示使用 --ignore-daemonsets。
使用:
kubectl drain worker-1 --ignore-daemonsets
语义是:
- 不把 DaemonSet Pod 视为需要排出的普通业务 Pod;
- 不删除这些 DaemonSet Pod;
- 允许 drain 继续处理其他 Pod。
它不是“忽略所有系统 Pod”,也不是“强制删除 DaemonSet”。例如静态 Pod、镜像 Pod和未管理 Pod仍可能触发其他检查。
6.3 Drain 后节点仍有 Pod是否正常
假设节点上只剩下:
kube-system cni-agent-xxxxx DaemonSet
kube-system node-exporter-yyyyy DaemonSet
kube-system csi-node-zzzzz DaemonSet
这通常表示普通工作负载已经排空,而节点级代理仍在运行。此时不能用:
kubectl get pod -A --field-selector spec.nodeName=worker-1
输出非空作为“drain 失败”的唯一判断依据。
更准确的判断是区分 owner:
kubectl get pod -A \
--field-selector spec.nodeName=worker-1 \
-o json
然后检查 metadata.ownerReferences 是否指向 DaemonSet,以及是否存在:
- Deployment/ReplicaSet 管理的业务 Pod;
- StatefulSet 管理的有状态 Pod;
- Job 管理的任务 Pod;
- 没有 owner 的孤立 Pod;
- 静态 Pod或镜像 Pod。
6.4 DaemonSet 的污点容忍与维护风险
许多 DaemonSet 会配置对节点污点的容忍,以便在故障或初始化场景中继续运行。例如网络插件、存储插件经常需要在节点状态特殊时仍存在。
这意味着:
- cordon 不一定阻止 DaemonSet;
NoSchedule污点不一定阻止有对应 toleration 的 DaemonSet;NoExecute污点是否驱逐某个 DaemonSet Pod,取决于该 Pod 的 toleration;- 删除 DaemonSet Pod 后,控制器可能立即重建它。
因此,升级内核、容器运行时或 CNI 时,不应只依赖 --ignore-daemonsets。应明确知道每个 DaemonSet 的终止和重建行为,否则可能在节点维护期间失去日志、网络、存储或监控能力。
七、从维护前检查到节点恢复的完整流程
下面是一套适合单节点维护的基础流程。节点名、命名空间和工作负载名称都应替换为实际值。
7.1 维护前:确认节点和集群容量
kubectl get node worker-1 -o wide
kubectl describe node worker-1
kubectl get events --all-namespaces \
--field-selector involvedObject.kind=Node,involvedObject.name=worker-1 \
--sort-by=.lastTimestamp
检查:
Ready是否为True;- 是否已有
MemoryPressure、DiskPressure或PIDPressure; - 是否存在
NoSchedule、NoExecute污点; - kubelet Lease 是否持续更新;
- 其他节点是否有足够的可分配 CPU、内存和临时存储;
- Pod 的 nodeSelector、亲和性、反亲和性和拓扑分布是否允许迁移;
- 相关 PVC 是否可以在目标节点挂载;
- 业务 PDB 是否允许至少一次驱逐。
查看节点资源:
kubectl get nodes
kubectl top node 2>/dev/null || true
kubectl describe node worker-2
kubectl top 依赖 Metrics Server,命令失败不代表节点一定不健康。资源调度还使用 Pod requests 和 Node allocatable,不完全等于实时使用量。
7.2 维护前:识别所有 Pod 来源
kubectl get pod -A \
--field-selector spec.nodeName=worker-1 \
-o custom-columns='NS:.metadata.namespace,NAME:.metadata.name,OWNER_KIND:.metadata.ownerReferences[0].kind,OWNER_NAME:.metadata.ownerReferences[0].name,PHASE:.status.phase'
重点分类:
| Pod 类型 | 默认处理思路 |
|---|---|
| Deployment/ReplicaSet | 通常可通过 Eviction 迁移 |
| StatefulSet | 需确认副本、存储和顺序语义 |
| Job/CronJob | 需确认中断是否会影响任务结果 |
| DaemonSet | 通常使用 --ignore-daemonsets,不作为普通 Pod 排空 |
| 无 owner Pod | 默认拒绝,确认可丢弃后才考虑 --force |
使用 emptyDir |
删除或迁移会丢失本地临时数据 |
| 静态 Pod/镜像 Pod | 先定位 kubelet manifest 或生成来源 |
| 关键系统 Pod | 确认维护期间控制面、网络、DNS、存储仍可用 |
有状态 Pod 即使被安全 Eviction,也不意味着数据已经迁移。PVC 的绑定、访问模式、卷分区、CSI attach/detach 过程都可能延长恢复时间。
7.3 执行排空
kubectl cordon worker-1
kubectl drain worker-1 \
--ignore-daemonsets \
--timeout=30m
如果存在明确确认可以删除的 emptyDir 数据:
kubectl drain worker-1 \
--ignore-daemonsets \
--delete-emptydir-data \
--timeout=30m
如果存在确认可以丢弃的无 owner Pod:
kubectl drain worker-1 \
--ignore-daemonsets \
--force \
--timeout=30m
不建议把所有危险参数一次性组合:
# 不应作为默认生产命令
kubectl drain worker-1 \
--ignore-daemonsets \
--force \
--delete-emptydir-data
这会同时放宽 DaemonSet、孤立 Pod 和本地数据三个不同层面的安全检查,失败原因变得难以区分,也容易把本不应删除的数据带走。
7.4 排空期间如何判断是“慢”还是“错误”
Pod 处于 Terminating
查看:
kubectl get pod -A -o wide --field-selector spec.nodeName=worker-1
kubectl describe pod -n application api-xxxxx
重点看:
deletionTimestamp;preStop是否阻塞;- 容器是否忽略终止信号;
- Volume 卸载是否卡住;
- finalizer 是否未完成;
- kubelet 是否仍然能更新状态;
- Pod 是否被
terminationGracePeriodSeconds限制。
新 Pod 一直 Pending
查看:
kubectl get pod -n application -l app=api
kubectl describe pod -n application api-new-xxxxx
常见事件包括:
0/3 nodes are available: insufficient cpu
0/3 nodes are available: pod has unbound immediate PersistentVolumeClaims
node(s) didn't match Pod's node affinity/selector
node(s) had untolerated taint
这说明 drain 已经发起迁移,但集群没有满足调度约束的目标节点。增加 --timeout 只会延长等待,不会创造资源或改变亲和性规则。
PDB 一直不允许
查看:
kubectl get pdb -A
kubectl describe pdb -n application api-pdb
kubectl get pod -n application -l app=api \
-o custom-columns='NAME:.metadata.name,READY:.status.containerStatuses[*].ready,NODE:.spec.nodeName'
应先恢复不健康副本、增加可用容量或调整维护批次,而不是立即绕过 PDB。
八、Drain、普通 Delete 和节点故障驱逐的区别
| 操作 | 发起者 | 是否通常检查 PDB | 典型场景 | 主要风险 |
|---|---|---|---|---|
kubectl drain |
管理员客户端 | 通常使用 Eviction | 节点计划维护 | 受容量、PDB、Pod 类型约束 |
| Eviction API | 客户端或控制器 | 是 | 请求一次受保护驱逐 | 可能返回 429 |
kubectl delete pod |
管理员客户端 | 通常不以 PDB 为准 | 明确删除对象 | 可能绕过可用性保护 |
| 节点故障处理 | Node Controller 等 | 不等同于正常 Eviction | 节点失联、NotReady | 可能无法优雅终止,状态延迟 |
“Pod 被删除”与“Pod 被驱逐”在结果上都可能导致 Pod 结束,但准入语义不同。生产脚本不应通过批量 kubectl delete pod -l ... 模拟安全 drain,因为这会绕过节点维护所需的可用性约束。
某些旧教程会使用 policy/v1beta1 Eviction。当前稳定 API 应使用:
apiVersion: policy/v1
kind: Eviction
具体 Kubernetes 发行版、托管平台或旧客户端可能存在版本偏差;提交前可检查:
kubectl api-resources | grep -i eviction
kubectl explain eviction
云厂商的节点替换、自动扩缩容和托管升级还可能在后台执行额外的 drain、实例删除或容量调度。不要假设云平台一定沿用与你手工命令完全相同的超时和强制删除策略,应查阅对应平台的节点升级行为。
九、恢复节点:先恢复系统,再恢复调度
节点维护结束后,不能只执行:
kubectl uncordon worker-1
更可靠的顺序是先在节点主机上确认:
- kubelet 正常运行;
- 容器运行时正常;
- CNI 接口、路由和 DNS 正常;
- 节点磁盘、inode、PID 和内存没有压力;
- CSI 节点插件正常;
- 内核、挂载点和设备状态符合预期;
- 节点时间同步正常;
- 节点没有遗留的维护污点或错误配置。
随后检查控制面观察到的状态:
kubectl get node worker-1
kubectl describe node worker-1
kubectl get lease -n kube-node-lease worker-1 -o yaml
至少确认:
Ready=True
MemoryPressure=False
DiskPressure=False
PIDPressure=False
这些 Condition 的具体集合会随版本和节点实现变化,但 Ready 与资源压力是恢复验证的核心。
9.1 检查污点和调度标志
kubectl get node worker-1 \
-o jsonpath='unschedulable={.spec.unschedulable}{"\n"}taints={.spec.taints}{"\n"}'
确认:
unschedulable不再为true;- 不存在本次维护遗留的
NoSchedule或NoExecute污点; - 如果污点由云平台、Node Controller 或自动化系统添加,先确认其来源,再决定是否清除。
只有明确知道污点用途时,才执行:
kubectl taint node worker-1 key=value:NoSchedule-
盲目删除污点可能重新允许工作负载进入本来仍不安全的节点。
9.2 恢复 DaemonSet 和基础设施组件
kubectl get pod -A -o wide --field-selector spec.nodeName=worker-1
kubectl get daemonset -A
kubectl rollout status daemonset/cni-agent -n kube-system --timeout=10m
将 cni-agent 替换为实际名称。应确认:
- CNI DaemonSet Pod 为
Ready; - 节点网络插件已在本机建立;
- kube-proxy 或等价组件正常;
- CSI node plugin 正常;
- 日志、监控和安全代理恢复;
- DNS、Service 网络和跨节点通信可用。
如果节点在升级过程中曾经删除或重建 DaemonSet Pod,kubectl get daemonset 的 DESIRED、CURRENT、READY 和 AVAILABLE 应与集群状态一致。
9.3 取消 cordon
当节点和基础设施检查通过后:
kubectl uncordon worker-1
验证:
kubectl get node worker-1
预期不再出现:
SchedulingDisabled
但此时业务 Pod 不一定立即回到该节点。调度器只会为新创建且满足调度约束的 Pod 选择节点;已经在其他节点运行的 Pod 不会因为 uncordon 自动迁回。节点恢复后的“重新均衡”不是 uncordon 的默认行为。
十、恢复后的端到端验证
10.1 验证节点能够承接新 Pod
可以创建一个临时测试 Pod:
kubectl run node-recovery-check \
--image=busybox:1.36 \
--restart=Never \
--command -- sh -c 'echo node-ok; sleep 20'
查看其调度位置:
kubectl get pod node-recovery-check -o wide
如果需要确保测试 Pod 能落到目标节点,可使用临时 nodeSelector,但这会测试“节点标签和调度约束”,不一定等价于普通业务调度:
kubectl get node worker-1 --show-labels
kubectl delete pod node-recovery-check --ignore-not-found
测试完成后清理:
kubectl delete pod node-recovery-check --ignore-not-found
不要把“测试 Pod 被调度成功”当作完整健康证明。它可能只验证了 kubelet 能启动一个简单容器,未验证 CNI、Service、PVC、探针、业务依赖和节点资源压力。
10.2 验证工作负载控制器和业务可用性
kubectl get deployment,statefulset,daemonset -A
kubectl get pod -A
kubectl get events -A --sort-by=.lastTimestamp
对关键 Deployment:
kubectl rollout status deployment/api \
-n application \
--timeout=10m
对关键 Service:
kubectl get endpointslice -n application \
-l kubernetes.io/service-name=api
检查:
- Deployment 的
availableReplicas是否满足预期; - StatefulSet 的序号副本是否都 Ready;
- PDB 的
disruptionsAllowed是否恢复到合理值; - Service 的 EndpointSlice 是否包含预期后端;
- readiness probe 是否通过;
- 应用日志是否有连接重建、数据恢复或重复消费问题;
- PVC 是否完成重新挂载;
- 监控和日志是否能看到恢复节点的数据。
10.3 验证“没有残留 Pod”时要排除正常例外
可以先列出所有 Pod:
kubectl get pod -A \
--field-selector spec.nodeName=worker-1 \
-o wide
然后逐个确认其来源。一个被成功 drain 的节点仍可能保留:
- DaemonSet Pod;
- 静态 Pod;
- 镜像 Pod;
- 某些节点级管理组件。
所以正确的问题不是“节点上是否还有任何 Pod”,而是:
节点上是否还存在本次维护目标中的业务 Pod,或者存在未被明确批准保留的 Pod?
十一、常见误区与反例
误区一:Cordon 会迁移 Pod
反例:
kubectl cordon worker-1
kubectl get pod -A -o wide --field-selector spec.nodeName=worker-1
如果原有 Pod 仍为 Running,这是正确行为。Cordon 只影响未来调度,不负责迁移当前工作负载。
误区二:Drain 成功意味着节点完全没有 Pod
反例:节点只剩 DaemonSet、静态 Pod 或镜像 Pod。drain 成功表示符合 drain 目标的 Pod 已被处理,不表示 kubelet 上没有任何容器。
误区三:Eviction 成功意味着新副本已经 Ready
Eviction API 接受请求后,旧 Pod 可能仍在 Terminating,新 Pod 可能处于 Pending、ContainerCreating 或探针失败状态。必须分别等待:
kubectl get pod -n application -l app=api -w
并检查新副本的调度、启动和 Ready 状态。
误区四:PDB 能防止所有节点故障造成的中断
PDB 只约束受支持的自愿驱逐路径。节点断电、硬件故障、网络分区和 kubelet 崩溃不一定有机会进行正常 Eviction。高可用还需要:
- 多节点部署;
- 合理的拓扑分布;
- 足够的备用容量;
- 应用级重试和故障转移;
- 正确的存储和数据复制策略。
误区五:为了排空成功而直接强删
例如:
kubectl delete pod -n application api-xxxxx --force --grace-period=0
这可能只会快速从 API Server 中移除对象,并不等于节点上的进程已经结束。网络分区或 kubelet 失联时,旧进程可能仍在运行,造成:
- 双实例同时访问外部系统;
- Stateful 工作负载重复挂载或脑裂;
- 消息重复消费;
- 控制器过早创建替代副本;
- API 状态与实际进程不一致。
强制删除只适用于已经评估过节点确实不可恢复、旧 Pod 不会继续运行,或业务明确允许该风险的场景。
误区六:恢复时只执行 uncordon
如果 Node 仍为 NotReady、存在 DiskPressure,或者 CNI/CSI 没有恢复,uncordon 只会让调度器重新尝试使用一个仍不健康的节点。恢复验证必须覆盖节点状态、污点、DaemonSet、调度、存储和业务。
十二、生产取舍:维护速度与可用性之间的约束
节点排空的安全性可以粗略看成多个条件的交集:
其中任意一项为假,drain 都可能阻塞或产生业务风险。
例如:
- 节点资源充足,但 PDB 不允许中断:Eviction 被拒绝;
- PDB 允许中断,但其他节点没有 CPU:新 Pod Pending;
- 资源和 PDB 都满足,但 Pod 使用本地
emptyDir:数据丢失; - 新 Pod 已启动,但 Service readiness 未通过:业务可用副本仍不足;
- 节点已经恢复 Ready,但遗留
NoSchedule污点:uncordon 后仍无法调度; - 业务副本可迁移,但 PVC 受单节点访问模式约束:Pod 可能卡在卷挂载阶段。
因此,生产维护通常按小批次执行:先 cordon 一个节点,观察 drain、PDB、调度和业务指标,确认恢复后再处理下一个节点。批次大小不应只由节点数量决定,还要由业务可用副本、拓扑约束、存储能力和故障预算共同决定。
十三、把节点维护看成一个可验证的状态机
一个完整的状态转换可以表示为:
stateDiagram-v2
[*] --> ReadySchedulable
ReadySchedulable --> Cordoned: cordon
Cordoned --> Draining: drain / eviction
Draining --> Drained: 业务 Pod 已处理\nDaemonSet 等例外已确认
Draining --> Blocked: PDB/容量/孤立 Pod/超时
Blocked --> Draining: 修复约束或调整维护批次
Drained --> Maintained: 主机与节点组件维护
Maintained --> Recovering: kubelet/CNI/CSI 恢复
Recovering --> ReadyUnschedulable: Ready=True 且压力正常
ReadyUnschedulable --> ReadySchedulable: uncordon
ReadyUnschedulable --> Recovering: Ready/Lease/污点检查失败
每个状态都应有可观察条件:
ReadySchedulable:Ready=True,无不应存在的维护污点,unschedulable未设置;Cordoned:unschedulable=true,但现有 Pod 仍可运行;Draining:Pod 出现删除时间戳,控制器在其他节点创建替代 Pod;Drained:业务 Pod 已离开,DaemonSet 等保留对象有明确解释;Maintained:主机操作完成,但不代表 Kubernetes 已恢复;ReadyUnschedulable:节点健康,但仍禁止调度,等待最终验证;ReadySchedulable:节点健康且恢复调度资格。
这个状态机的价值在于:任何命令失败,都能定位是在“阻止新调度”“驱逐准入”“新 Pod 调度”“Pod 终止”“节点恢复”还是“业务验证”阶段,而不是笼统地认为“drain 有问题”。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:Kubernetes Node 生命周期:Condition、Lease、Taint、NotReady 和恢复
- 下一篇:Kubernetes 集群升级:Version Skew、API 弃用、节点灰度和回滚
- 延伸:PodDisruptionBudget 与可用性:自愿中断、Eviction、滚动和误区
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论