【Kubernetes PDB 主动驱逐保护】3 个配置陷阱与正确避坑指南

你是否遇到过 kubectl drain 卡住半天不动,报错"Cannot evict pod as it would violate the pod's disruption budget"?或者更惨------集群升级失败,就因为某个 PDB 配置得太死,节点排空一直过不去?

我在这上面栽过不止一次。今天就聊聊 PodDisruptionBudget(PDB)这个看似简单、用起来却容易翻车的东西。

PDB 是什么?一句话说清楚

PDB 的全称是 Pod Disruption Budget,中文叫"Pod 中断预算"。它的核心功能就一个:在自愿中断(主动驱逐)操作期间,保证你的 Pod 不会一次性挂掉太多。

用最简单的话说------你要维护节点、升级集群、做滚动更新的时候,PDB 会拦住你,说"兄弟,不能再杀了,再杀 SLA 就崩了"。

关键区分:

  • 自愿中断 :管理员主动排空节点(kubectl drain)、升级集群、删除 Deployment 等(PDB 管)
  • 非自愿中断:节点宕机、硬件故障、网络分区(PDB 管不了)

PDB 是通过 API 驱逐(Eviction API)来实现保护的。驱逐动作发起时,K8s 会校验目标 Pod 所属的 PDB 约束,只有满足条件才允许执行。

PDB 在 K8s v1.4 以 Alpha 形态首次出现,v1.6 转为 Beta,v1.21 正式 GA(稳定版)。

前置条件

  • 用的是 K8s v1.21 及以上(PDB 在 policy/v1 中稳定可用)
  • 有 kubectl 访问集群的权限
  • 集群里至少有一个 Deployment/StatefulSet,且副本数 ≥ 2(单副本配 PDB 是个坑,后面细说)

版本确认:

复制代码
kubectl version --short
# 确保 Server Version 是 v1.21+

原理速览:两个参数,互斥使用

PDB 的核心在 spec 里的两个字段,且只能二选一

|------------------|-----------------------|--------------|
| 字段 | 含义 | 示例 |
| minAvailable | 驱逐后至少还有多少个 Pod 可用 | 3 或者 50% |
| maxUnavailable | 驱逐后最多有多少个 Pod 不可用 | 1 或者 30% |

举例说明:一个 5 副本的 Deployment,minAvailable: 4 等价于 maxUnavailable: 1。但写法只能用其中一个。

3 个常用示例(可直接复制)

示例1:绝对数量模式

复制代码
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: nginx-pdb
  namespace: default
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: nginx

示例2:百分比模式(推荐)

百分比模式的优点是------副本数变了,PDB 自动跟着算,不用改配置。

复制代码
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api-pdb
  namespace: production
spec:
  maxUnavailable: 30%
  selector:
    matchLabels:
      app: api-gateway

示例3:StatefulSet(etcd/ZooKeeper 场景)

有状态应用通常需要保证仲裁数量(quorum)。比如 5 节点的 etcd,至少需要 3 个健康成员才能正常工作:

复制代码
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: etcd-pdb
  namespace: kube-system
spec:
  minAvailable: 3
  selector:
    matchLabels:
      app: etcd

重要提醒 :PDB 只通过 selector 匹配 Pod,不区分 Deployment 还是 StatefulSet。所以只要 selector 写对就行。

一图说清工作原理

(概念性理解就能上手,就不画图了)

当执行 kubectl drain 时,节点排空过程是串行驱逐 Pod 的。每驱逐一个 Pod,K8s API Server 会检查这个 Pod 对应的 PDB:

  1. 统计当前健康 Pod 数量(以 Ready 状态为标准)
  2. 如果驱逐完后还能满足 PDB 约束(minAvailable 达标或 maxUnavailable 未超),允许驱逐
  3. 否则拒绝驱逐,并返回 Cannot evict pod as it would violate the pod's disruption budget

kubectl drain 会持续重试,直到所有 Pod 都排空或超时。

kubectl drain 实操(带 PDB)

假设有一个 3 副本的 nginx,配了 maxUnavailable: 1 的 PDB。

复制代码
# 先封锁节点(不让新 Pod 调度上来)
kubectl cordon node-1

# 排空节点(忽略 DaemonSet 管理的 Pod)
kubectl drain node-1 --ignore-daemonsets

# 预期行为:
# - 第一个 Pod 成功驱逐
# - 第二个 Pod 被 PDB 阻塞(replica 只剩 2 个健康 Pod,再驱逐 1 个会违反 maxUnavailable: 1)
# - drain 会卡住并报错

这也是很多人遇到的问题:drain 到一半卡死,因为 PDB 卡得太死,新 Pod 还没启动完成,旧 Pod 不让走。

怎么解?后面"验证方法"部分会详细说。

⚠️ 坑1:单副本应用配 PDB

这是最大也是最常见的坑。

你用 minAvailable: 1 保护一个单副本的 Deployment,看起来合理对吧?但实际情况是:排空节点时,只有一个 Pod 可用,驱逐它会让可用数变为 0,不满足 PDB 约束,drain 永远卡住。

错误场景

  • 副本数 = 1,PDB = minAvailable: 1maxUnavailable: 0
  • 任何时候主动驱逐都会被 PDB 完全阻止
  • 节点无法正常排空

最佳实践:副本数 < 2 时,不要配置 PDB,或者直接不用。云厂商比如 IBM 的文档也明确写了,PDB 只在副本数 ≥ 2 时才有意义。

⚠️ 坑2:健康检查未就绪导致 PDB 误判

PDB 计数时,只统计健康 的 Pod。健康的标准是 status.conditionstype="Ready"status="True"

如果 Pod 因为启动慢、配置错误、CrashLoopBackOff 等原因一直没 Ready 就绪:

  • PDB 不会把它计入 currentHealthy
  • 集群健康的 Pod 数可能低于 PDB 的 desiredHealthy
  • 所有驱逐请求都会被拒绝,节点排空卡死

⚠️ 坑3:一个 Pod 被多个 PDB 覆盖

K8s 的驱逐子资源不支持一个 Pod 同时属于多个 PDB。如果出现了,API Server 会直接拒绝驱逐请求。

排查命令 :识别 Pod 的标签,再看看哪些 PDB 的 selector 匹配了这些标签。

复制代码
# 查看 Pod 的标签
kubectl get pod <pod-name> -n <namespace> --show-labels

# 查看命名空间下所有 PDB
kubectl get pdb -n <namespace> -o yaml | grep -A5 selector

彩蛋:K8s 1.26+ 的不健康 Pod 驱逐策略(解决节点排空死锁)

这是我觉得近两年最有用的 PDB 增强,但知道的人还不多。

spec.unhealthyPodEvictionPolicy 有两个选项:

|-----------------------|--------------------------------------|-------------------|
| 策略 | 行为 | 适用场景 |
| IfHealthyBudget(默认) | 只有应用的健康 Pod 数满足 PDB 要求时,才允许驱逐不健康 Pod | 数据安全优先,容忍维护困难 |
| AlwaysAllow | 始终允许驱逐不健康的 Pod(无论 PDB 是否满足) | 集群运维优先,尤其适合节点排空场景 |

配置示例(K8s 1.26+,且需要先开启 PDBUnhealthyPodEvictionPolicy 特性门控):

复制代码
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: always-evict-app
  namespace: default
spec:
  minAvailable: 2
  unhealthyPodEvictionPolicy: AlwaysAllow
  selector:
    matchLabels:
      app: myapp

我的建议 :生产环境默认用 AlwaysAllow。除非你的应用有严格的数据安全要求、不能容忍不健康 Pod 被意外驱逐------但这种情况说实话比较少见。AlwaysAllow 能让运维好过很多,节点排空不会因为一个 CrashLoopBackOff 的 Pod 卡半天。

这个功能在 K8s 1.26 中是 Alpha,现在已经稳定了。如果你还在用老版本,该考虑升级了。

验证方法:检查 PDB 状态和驱逐预算

复制代码
# 查看当前 PDB 状态
kubectl get pdb -n <namespace>

# 输出示例:
# NAME         MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
# nginx-pdb    2               N/A               0                     10m
# api-pdb      N/A             1                 1                     5m

# 查看详细信息
kubectl describe pdb <pdb-name> -n <namespace>

ALLOWED DISRUPTIONS 这个字段很关键:

  • 大于 0:表示当前可以驱逐多少个 Pod(不违反 PDB)
  • 等于 0:任何驱逐都会违反 PDB,所有驱逐请求都会被拒绝

故障排查:drain 卡住怎么办?

报错原文

复制代码
error when evicting pod (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget

三步排查法:

步骤1:找到是哪个 PDB 在拦你

复制代码
kubectl describe pdb -A | grep -A10 "Allowed disruptions: 0"

找到 ALLOWED DISRUPTIONS = 0 的那个 PDB。

步骤2:看副本数 vs PDB 约束

可能的情况:

  • 副本数 = minAvailable(驱逐 1 个就不满足)
  • Pod 全都不健康(PG 健康数为 0,但 minAvailable > 0)
  • 副本正在滚动更新(新旧并存,但 Ready 的 Pod 不够)

步骤3:选择解决方案

方案A(推荐) :等新 Pod 启动完成。如果是因为滚动更新导致的,新 Pod 拉镜像慢、启动慢,等一会儿就行。

方案B:临时删除 PDB,排完空再重建。

复制代码
kubectl delete poddisruptionbudget <pdb-name> -n <namespace>
kubectl drain <node>
kubectl apply -f <pdb-file>

这个方法粗暴但有效。云厂商的文档里也推荐过这个应急方案。

方案C:临时扩容,创造驱逐空间(适用于有弹性容量的场景)。

复制代码
# 从 3 副本扩到 4,驱逐时就有缓冲了
kubectl scale deployment <name> --replicas=4

方案D (K8s 1.26+):给 PDB 加上 unhealthyPodEvictionPolicy: AlwaysAllow,然后重试 drain。

不支持 & 限制说明

  1. PDB 不保护非自愿中断:节点直接挂了,PDB 不会帮你恢复 Pod。这时候要靠 ReplicaSet 控制器重新调度。
  2. 滚动更新有自己的逻辑 :Deployment 的 RollingUpdate 策略(maxSurge/maxUnavailable)和 PDB 独立工作,但建议两者协调配置,避免互相冲突------比如 Deployment 的 maxUnavailable: 50%,PDB 的 maxUnavailable: 10%,那滚动更新时会被 PDB 卡住。
  3. 一个 Pod 不能有多个 PDB :这是 API 层面的限制。设计 PDB 时确保不同 PDB 之间的 selector 不会重叠。

总结一下

  1. 副本数 ≥ 2 再考虑配 PDB,单副本配 PDB 等于自掘坟墓。
  2. 优先用百分比maxUnavailable: 25%),副本数变了不用改配置。
  3. K8s 1.26+ 强烈建议配置 unhealthyPodEvictionPolicy: AlwaysAllow,能省很多运维麻烦。
  4. PDB 不能保证 100% 可用,它只管自愿中断。节点直接宕机它管不了。
  5. drain 卡住时先查 ALLOWED DISRUPTIONS,等于 0 就是 PDB 在拦你,想办法给它腾出空间。

你踩过 PDB 的什么坑?有没有更优雅的解法?评论区见。如果觉得有用,欢迎分享给同样在 K8s 运维上挣扎的朋友。

相关推荐
杨云龙UP1 小时前
Docker 部署 MongoDB 6.0 数据库每日自动备份实践:本地 + 异地保留 7 天_20260429
linux·运维·数据库·mongodb·docker·容器·centos
Cat_Rocky1 小时前
K8S-daemonset控制器
云原生·容器·kubernetes
Drache_long1 小时前
K8S(二)
运维·docker·云原生·容器·kubernetes
ai产品老杨3 小时前
GB28181与RTSP全协议兼容之道:基于Docker与微服务架构的AI视频中台架构解析(附源码交付方案)
docker·微服务·架构
空中海12 小时前
Kubernetes 入门基础与核心架构
贪心算法·架构·kubernetes
小猿姐13 小时前
Redis Kubernetes Operator 实测:三个方案的真实差距
redis·容器·kubernetes
米高梅狮子14 小时前
08.CronJob和Service
云原生·容器·架构·kubernetes·自动化
AOwhisky15 小时前
Kubernetes 学习笔记:集群管理、命名空间与 Pod 基础
linux·运维·笔记·学习·云原生·kubernetes
郑寿昌16 小时前
GPU显存HPA:K8s智能扩缩实战
云原生·容器·kubernetes