kubectl drain 卡了 2 小时?找你的 PodDisruptionBudget
场景:节点下线维护,
kubectl drain执行后 10 分钟一个 Pod 都没驱逐——PDB 设了minAvailable: 100%,allowedDisruptions=0,drain 卡死了 路径:坐标(drain 卡住) → 分层(drain→PDB→eviction 链路) → 路径(诊断命令+一眼判断) → 定位(minAvailable 陷阱) → 标点(修复+逃生口+check-list) 版本:K8s v1.28
上篇讲了 VPA 推荐的坑——Pod 重启过导致历史数据失真。这篇聊 PodDisruptionBudget(PDB——Pod 扰动预算,控制允许同时中断的 Pod 数量)——之前 DaemonSet 那篇提过它用来保护服务,但很多人不知道:PDB 设太保守,节点维护会完全卡死。
节点要下线维护,kubectl drain node-web-1 一执行,等了 10 分钟一个 Pod 都没驱逐——不是 Pod 卡住,不是网络问题,是 PodDisruptionBudget 设成了 100%,allowedDisruptions=0,一个 Pod 都不准走。
K8s 不是没有告诉你问题在哪——Events 已经说了,只是你没看。
【坐标】→ drain 卡住现场
drain 输出停在了 Evicting
kubectl drain 执行后,输出停在 Evicting pod 阶段不动,没有新进展:

drain 命令(PodDisruptionBudget 感知的节点维护工具,负责将节点上的 Pod 安全地驱逐到其他节点)默认行为是:对每个 Pod 发送 Eviction 请求,等 kube-apiserver 验证 PDB 通过后才真正终止 Pod。如果 PDB 拒绝,drain 就卡在 Evicting 状态——它不会跳过,会一直重试。
Events 已经说了原因
kubectl describe pod 看 Events 段,能看到 Eviction 被拒绝的记录——注意上图中 Events 部分的 FailedEviction:
关键 Event:
Warning FailedEviction pod resource limits: PDB "web-pdb" prevented eviction of pod
全局 Events:不是单个 Pod 的问题
只查一个 Pod 的 Events,你可能以为只是它运气不好。来全局看看:

5 个匹配同一 PDB 的 Pod 全部被拒,没有一个例外。注意截图中 kubectl get pdb -A 的输出——web-pdb 的 Allowed Disruptions: 0,而 monitoring 命名空间的 prom-pdb 正常(值为 1)。说明不是集群整体异常,就是 web-pdb 这一条 PDB 配死了。
PDB(PodDisruptionBudget——Pod 扰动预算,控制允许同时中断的 Pod 数量)的检查是 Eviction 请求流程的一环:kube-apiserver 收到 Eviction 请求后,会查对应 PDB 的 status.allowedDisruptions(当前允许中断数),如果 ≤ 0,直接拒绝。
小结:不是 drain 坏了,是 PDB 不让走
drain 没坏,kubelet 没挂——是 PDB 的 allowedDisruptions 被算成了 0。往下走,看 PDB 是怎么算出这个 0 的。
【分层】→ drain → PDB → Eviction 链路
drain 执行 Eviction 的完整流程
drain 不是直接 kill Pod,它走了一条有安全校验的链路:

这个链路里,kube-apiserver 是 PDB 校验的执行者,不是 PDB controller。PDB controller(运行在 kube-controller-manager 中,负责持续计算 PDB 状态)会定期根据当前匹配的 Pod 数量和 minAvailable/maxUnavailable 计算 allowedDisruptions,结果写入 PDB 的 status 字段。apiserver 在校验 Eviction 时直接读这个 status,不会重新计算。
这里有件反直觉的事:PDB controller 不是实时同步的——默认每 5-10 秒才刷一次 status。这意味着 drain 卡住时,你改完 PDB 配置不会马上生效,要等 controller 下次 sync 才能算出新的 allowedDisruptions。同样,如果你同时 drain 两个节点,第一个成功了但第二个在第 3 秒发起,而 controller 在第 8 秒才重算——第二个 drain 拿到的是过期的 0,依然被拒。
allowedDisruptions 的计算公式
对于 minAvailable(最少可用 Pod 数):
allowedDisruptions = max(0, currentHealthyPods - minAvailable)
对于 replicas=3, minAvailable=3(即 100%):
currentHealthyPods=3 → allowedDisruptions = max(0, 3-3) = 0
这就是卡住的根因:所有 Pod 都在 Running,currentHealthyPods = minAvailable,没有余量给 drain 用。
小结:allowedDisruptions=0 时 drain 必然卡住
PDB 的公式决定了——minAvailable 等于当前健康 Pod 数时,allowedDisruptions=0,任何 Eviction 请求都会被拒绝。不是 bug,是设计。PDB 的职责是保护服务可用性,它不知道你正在做节点维护。
【路径】→ 核心诊断命令
一眼判断标准
遇到 drain 卡住,跑这三个命令,按输出判断:
| 症状 | 命令 | 关键字段 |
|---|---|---|
| drain 卡在 Evicting | kubectl describe pod <卡住的pod> |
Events 段搜 FailedEviction |
| PDB 拒绝 | kubectl describe pdb <name> |
Allowed Disruptions: 0 |
| Pod 分布 | kubectl get pods -l <selector> |
Pod 数量和健康状态 |
只要 Allowed Disruptions: 0,就是 PDB 的锅,不用排查其他原因。
kubectl get pdb 看全局状态

重点关注 Allowed Disruptions、Current Healthy、Desired Healthy 三个字段。
判断逻辑:Allowed Disruptions: 0 + Current Healthy: 3 + Desired Healthy: 3 → minAvailable 等于 total replicas,没有给 drain 留空间。
kubectl get pods -l 看 Pod 分布

3 个 Pod 各跑在不同节点,全部健康。PDB 要求 minAvailable=3,drain 任何一个节点都会试图驱逐 1 个 Pod,但驱逐后只剩 2 个——低于 minAvailable,所以拒绝。
【定位】→ PDB 常见的配置陷阱
大多数人会怎么做:minAvailable 设越大越安全
spec:
minAvailable: 3 或 minAvailable: 100%
"PDB 是保护服务的,设大点更安全"——这个直觉在滚动更新时是对的,在节点维护时是反的。
minAvailable=100%(等价于 replicas=3 时 minAvailable=3)意味着任何时候都不能有 Pod 少于 3 个。drain 需要驱逐 1 个 → 只剩 2 个 → 低于 3 → 拒绝。PDB 不知道你在做维护,它只知道"当前 3 个,要求 3 个,少一个都不行"。
正确的配置:maxUnavailable 替代 minAvailable
节点维护场景,直觉需求是"每次最多允许 N 个 Pod 不可用"——这正是 maxUnavailable 的语义:

❌ 错误配置:
spec:
minAvailable: 3 → allowedDisruptions: 0,drain 卡死
✅ 正确配置(等价语义,留出 drain 余量):
spec:
maxUnavailable: 1 → 允许 1 个 Pod 不可用,drain 可以驱逐 1 个
✅ 或者用 minAvailable 留余量:
spec:
minAvailable: 2 → allowedDisruptions: max(0, 3-2) = 1
maxUnavailable 在节点维护场景比 minAvailable 更直觉——它直接说"最多允许多少个 Pod 不在线",而不是"最少要留多少个"。
多副本跨 Node 的特殊情况
如果 Pod 分布在 3 个节点上,PDB 设 maxUnavailable: 1,drain 单个节点正常。但批量 drain 两个节点时,第一个 drain 成功后 allowedDisruptions=0,第二个会卡住。
PDB 不感知节点拓扑——它只看匹配的 Pod 总数,不看这些 Pod 分布在哪些节点上。你 drain 的节点数超过 maxUnavailable 时,必定有 drain 卡住。
PDB 不是为了锁死运维操作而存在的——它是在说"可以停,但一次少停点"。
【标点】→ 修复 + 逃生口 + Check-list
修复:改 PDB 配置
将 minAvailable 改为 maxUnavailable,给 drain 留出余量:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-pdb
spec:
maxUnavailable: 1 ← 允许 1 个 Pod 不可用
selector:
matchLabels:
app: web
修复前后对比:一行之差
改动只差一行——从 minAvailable: 3 换成 maxUnavailable: 1。看高亮行:左边第 6 行是 minAvailable: 3,右边对应位置是 maxUnavailable: 1:

两个配置在 replicas=3 的场景下语义等价:minAvailable=3 要求至少 3 个 Pod 在线,maxUnavailable=1 要求最多 1 个不可用。但 maxUnavailable 给 drain 留出了操作空间——它允许有 1 个 Pod 不在线,drain 可以驱逐 1 个。
验证:修复后 drain 恢复
改完 PDB 后,重新 drain 同一个节点:

紧急逃生口:--disable-eviction
如果节点必须立刻下线,PDB 来不及改,可以用 --disable-eviction 绕过 PDB 检查:
# ⚠️ 跳过 PDB 检查直接删除 Pod,可能影响服务可用性
kubectl drain node-web-1 --disable-eviction --delete-emptydir-data --force
--disable-eviction:绕过 Eviction API,直接发 DELETE Pod 请求,PDB 不起作用--delete-emptydir-data:如果 Pod 使用 emptyDir 卷,需要加这个 flag 允许 drain 删除包含 emptyDir 的 Pod(默认跳过,怕数据丢失)--force:即使 Pod 不由 ReplicaSet/Deployment 管理,也强制删除
风险:这相当于绕过了 PDB 的保护——如果 maxUnavailable=1 但有 3 个 Pod 分布在当前节点上,直接 DELETE 可能导致 3 个 Pod 同时被删除。只在紧急场景(节点硬件故障、内核 panic)使用。
Check-list
| 步骤 | 命令 | 判断标准 |
|---|---|---|
| 1. drain 卡了先看 Pod | kubectl describe pod <pod> |
Events 段有 FailedEviction → PDB 问题 |
| 2. 查 PDB 状态 | kubectl describe pdb <name> |
Allowed Disruptions: 0 → 确诊 |
| 3. 算公式 | currentHealthy - minAvailable |
≤ 0 → PDB 太保守,没留余量 |
| 4. 修复配置 | 改 minAvailable → maxUnavailable: 1 |
drain 可驱逐 1 个 Pod |
| 5. 紧急绕过 | --disable-eviction |
仅节点紧急下线时使用 |
附:完整命令清单
# 诊断
kubectl describe pod <pod> | grep -A 5 FailedEviction
kubectl describe pdb <name>
kubectl get pdb -A
kubectl get pods -l app=<label> -o wide
# 修复
kubectl edit pdb <name> # minAvailable → maxUnavailable
# 紧急绕过
kubectl drain <node> --disable-eviction --delete-emptydir-data --force
# 验证 drain 是否恢复
kubectl drain <node> --ignore-daemonsets
下篇我们聊 K8s Service 访问不通——从 iptables 到 IPVS 逐层排查。你会发现 Service 不通 90% 的情况用三步就能定位。
📺 公众号「Ai拆代码的曹操」 | 🌟 知识星球「Ai拆代码的曹操」