为什么需要 PodDisruptionBudget?理解自愿中断与非自愿中断
在 Kubernetes 生产环境中,Pod 的”死亡”其实分两种。一种是非自愿中断(Involuntary Disruptions),比如节点硬件故障、内核 panic、虚拟机被云平台回收——这些是不可预测的,Kubernetes 只能通过 ReplicaSet 和控制器来事后恢复。另一种是自愿中断(Voluntary Disruptions),包括运维人员主动 drain 节点做升级、修改 Deployment 触发滚动更新、删除 ReplicaSet 减少副本数,甚至 HPA 缩容——这些是可控的,但如果没有保护机制,就会造成服务不可用。

PodDisruptionBudget(简称 PDB)就是 Kubernetes 为第二种场景设计的”安全护栏”。它允许你声明:在任何自愿中断操作中,我的应用最少需要保持多少个 Pod 正常运行。系统会尊重这个承诺,在违反 PDB 之前拒绝执行破坏性操作。
从架构角度看,PDB 是 Kubernetes 调度器(kube-scheduler)和节点管理模块(kubelet 的 drain 逻辑)之间的协调契约。当运维人员执行
1 | kubectl drain |
时,kubelet 会先检查目标节点上所有 Pod 所属的 PDB 规则,如果驱逐会导致低于 PDB 允许的最小值,drain 操作就会阻塞,直到手动干预或超时。这种”先检查后执行”的机制,确保了有状态应用和无状态应用在运维操作中都能保持约定的可用性下限。
理解 PDB 的关键在于区分 minAvailable 和 maxUnavailable 两个参数。前者指定最小可用副本数(可以是绝对数字或百分比),后者指定最大不可用副本数。两者选其一,语义等价但表达角度不同:minAvailable 是从”必须保留多少”的角度,maxUnavailable 是从”最多允许挂多少”的角度。
PDB 的核心参数与配置示例
PDB 是一个标准的 Kubernetes API 对象,属于
1 | policy/v1 |
API 组。它的 YAML 结构非常简洁,核心字段只有 selector、minAvailable 和 maxUnavailable 三者。selector 通过标签选择器匹配受保护的 Pod,minAvailable 和 maxUnavailable 互斥,只能设置其中一个。
使用 minAvailable 确保最低副本数
1
2
3
4
5
6
7
8
9
10
11 apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-server-pdb
namespace: production
spec:
minAvailable: 3
selector:
matchLabels:
app: api-server
tier: backend
这个 PDB 的意思是:在任何自愿中断操作中,标签为
1 | app=api-server, tier=backend |
的 Pod 至少要有 3 个处于 Running 状态。如果当前只有 3 个副本,drain 节点操作会被阻塞,因为驱逐任何一个 Pod 都会使可用副本数降到 2,低于阈值。
使用百分比表达
1
2
3
4
5
6
7
8
9
10 apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-frontend-pdb
namespace: production
spec:
minAvailable: 80%
selector:
matchLabels:
app: web-frontend
百分比是一个非常实用的配置方式,特别适合副本数动态变化的场景。如果当前有 10 个副本,80% 意味着至少 8 个必须可用,最多允许 2 个不可用。如果 HPA 将副本数扩展到 20,则自动调整为至少 16 个可用。百分比计算时向下取整——如果 80% 的 3 个副本等于 2.4,取整后为 2 个必须可用。
使用 maxUnavailable 控制最大不可用数
1
2
3
4
5
6
7
8
9
10 apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: worker-pdb
namespace: production
spec:
maxUnavailable: 1
selector:
matchLabels:
app: background-worker
这个配置的语义是:任何时候最多允许 1 个 worker Pod 处于不可用状态。如果你的应用有 50 个副本,这个配置会比较宽松(允许 2% 不可用);如果只有 2 个副本,则非常严格(只允许 50% 不可用,即至少保留 1 个)。maxUnavailable 同样支持百分比,
1 | maxUnavailable: 25% |
表示最多允许四分之一副本不可用。
不设置 PDB 的后果
很多团队在初期阶段不会配置 PDB,这在开发环境问题不大,但在生产环境可能酿成大祸。想象一个场景:你有一个 API 网关服务,运行了 3 个副本。某天你需要在所有节点上打内核安全补丁,于是执行
1 | kubectl drain node-1 |
,接着
1 | drain node-2 |
,再
1 | drain node-3 |
。由于没有 PDB 保护,三个节点的 Pod 被依次驱逐,在第三批 Pod 重新调度完成之前,你的 API 网关完全不可用——这就是”自愿中断导致的服务降级事故”。
PDB 的工作原理:驱逐 API 与 Drain 流程
要深入理解 PDB,必须了解 Kubernetes 的驱逐机制。当节点需要维护时,kubectl drain 命令会触发一个逐出(Eviction)请求,而不是直接杀死 Pod。这个请求会经过两个关键检查点:
逐出子资源(Eviction Subresource)
Kubernetes 提供了一组
1 | PodDisruptionBudget |
相关的准入检查,通过
1 | POST /api/v1/namespaces/{ns}/pods/{name}/eviction |
端点触发。当 kubelet 发起逐出请求时,API Server 会执行以下步骤:
- 检查 Pod 是否被 PDB 规则匹配
- 如果匹配,计算驱逐后受影响的 PDB 是否仍然满足 minAvailable/maxUnavailable
- 如果满足,允许驱逐,Pod 进入 Terminating 状态
- 如果不满足,拒绝驱逐,返回 429 Too Many Requests 错误
这个拒绝机制非常重要——它意味着 drain 操作不会失败(不会退出),而是会阻塞等待,直到超时或条件满足。kubectl drain 默认会一直等待,直到所有 Pod 被成功驱逐,或者超过
1 | --pod-eviction-timeout |
(默认 5 分钟)。
drain 命令的完整流程
1
2
3
4
5
6
7
8 # 标记节点为不可调度,然后驱逐所有 Pod
kubectl drain node-5 --ignore-daemonsets --delete-emptydir-data
# 强制驱逐,忽略 PDB 检查(不推荐)
kubectl drain node-5 --force --ignore-daemonsets
# 设置较短的驱逐超时时间
kubectl drain node-5 --pod-eviction-timeout=30s --ignore-daemonsets
当执行 drain 时,kubelet 会按照以下顺序处理节点上的 Pod:
| 步骤 | 操作 | 说明 |
|---|---|---|
| 1 | 标记节点为不可调度 | 设置 node.spec.unschedulable = true,新 Pod 不会调度到该节点 |
| 2 | 驱逐 DaemonSet Pod | 需要 –ignore-daemonsets 跳过,否则会报错 |
| 3 | 驱逐 Mirror Pod(静态 Pod) | 无法驱逐,需要 –delete-emptydir-data 参数 |
| 4 | 驱逐普通 Pod | 按 PDB 规则逐出,阻塞直到所有 Pod 被驱逐或超时 |
| 5 | 完成 | 节点处于 Ready,SchedulingDisabled 状态 |
PDB 的统计口径与 Pod 生命周期
PDB 在计算可用副本数时,只考虑处于 Ready 状态的 Pod。具体来说,Pod 的 status.conditions 中必须有一个
1 | type: Ready, status: "True" |
。这意味着:
- Pending 状态的 Pod 不计入可用
- CrashLoopBackOff 状态的 Pod 不计入可用
- 正在 Terminating 的 Pod 不计入可用
- 探针失败(Readiness Probe 返回非 200)的 Pod 不计入可用
这也就意味着,如果你的应用 Readiness Probe 配置过于严格,导致 Pod 经常被标记为不可用,PDB 的保护效果会大打折扣——因为系统会认为可用副本数很少,从而拒绝驱逐操作。
PDB 的实际应用场景

场景一:节点滚动升级
团队需要对集群中所有 10 个工作节点进行内核升级,采用逐个 drain 的策略。假设每个节点上运行着 3 个 API 网关 Pod 和 2 个数据库缓存 Pod:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 # 为 API 网关设置 PDB:至少保留 2 个可用
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-gateway-pdb
namespace: production
spec:
minAvailable: 2
selector:
matchLabels:
app: api-gateway
# 为缓存服务设置 PDB:最多允许 1 个不可用
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: cache-pdb
namespace: production
spec:
maxUnavailable: 1
selector:
matchLabels:
app: redis-cache
在 drain 过程中,如果某个节点上运行着 2 个 API 网关 Pod,而当前集群中 API 网关的可用副本总共只有 2 个,驱逐操作会被阻塞。此时要么等待其他节点上的 Pod 完全启动并进入 Ready 状态,要么手动干预。这个阻塞机制给运维团队提供了安全缓冲,防止因操作过快导致服务瞬间降级。
场景二:Deployment 滚动更新
当执行
1 | kubectl set image deployment/api-server api-server=v2 |
触发滚动更新时,Deployment 控制器会创建新 Pod,等待新 Pod Ready 后删除旧 Pod。在这个过程中,PDB 同样生效——如果新 Pod 启动失败,旧 Pod 又因为 PDB 限制不能被删除,滚动更新会暂停。这与 Deployment 的
1 | maxSurge |
和
1 | maxUnavailable |
参数共同作用,形成双重保护。
需要注意的是,RollingUpdate 的 maxUnavailable 和 PDB 的 maxUnavailable 是两套独立的机制。前者控制 Deployment 的更新节奏,后者控制整个集群层面任何自愿中断操作的安全下限。两者同时配置时,取更严格的那一方生效。
场景三:HPA 缩容保护
HPA 在缩容时也会触发 Pod 删除,这同样属于”自愿中断”的范畴。PDB 可以防止 HPA 过度缩容导致服务不可用:
1
2
3
4
5
6
7
8
9
10
11 # 即使 HPA 认为只需要 1 个副本,PDB 也确保至少 2 个可用
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: worker-pdb
namespace: production
spec:
minAvailable: 2
selector:
matchLabels:
app: message-worker
这种情况下,HPA 会尝试缩容,但驱逐请求会被 PDB 拒绝。HPA 控制器会重试,最终在满足 PDB 条件的情况下继续缩容。这个机制防止了因流量突降导致 HPA 缩容过快而引发的服务抖动。
PDB 的缺陷与注意事项
PDB 不保护非自愿中断
这是 PDB 最大的局限性。如果节点宕机、云实例被意外终止、磁盘损坏,PDB 不会阻止 Pod 被删除。这些场景下,Pod 直接被节点控制器强制删除,绕过了驱逐 API。PDB 的保护范围仅限于通过 Eviction API 发起的逐出请求。因此,PDB 必须与节点多可用区部署、PodAntiAffinity 等机制配合使用,才能实现真正的高可用。
单副本应用无法被 PDB 保护
如果应用只有一个副本,设置
1 | minAvailable: 1 |
的 PDB 会阻止任何驱逐操作,这看起来是”保护”了 Pod,但实践中会导致节点 drain 永远阻塞。更合理的做法是:
- 关键应用至少运行 2 个副本
- 使用 PodAntiAffinity 确保副本分布在不同节点
- 设置合理的 PDB 值(minAvailable 小于总副本数)
PDB 与 Cluster Autoscaler 的冲突
Cluster Autoscaler 在缩容节点时同样会触发驱逐。如果缩容目标节点上的 Pod 有严格的 PDB 限制,CA 将无法驱逐这些 Pod,导致节点无法缩容。这在成本优化场景下可能造成问题:
1
2
3
4
5
6
7
8
9
10
11 # 解决方案:为可以被 disruption 的 Pod 设置宽松的 PDB
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: batch-worker-pdb
namespace: production
spec:
maxUnavailable: 30% # 允许 30% 副本被驱逐
selector:
matchLabels:
app: batch-worker
对于可以容忍短暂中断的批处理任务,建议使用
1 | maxUnavailable |
而非
1 | minAvailable |
,给 Cluster Autoscaler 留出足够的缩容空间。
PDB 的悲观锁定特性
当一个 PDB 匹配的 Pod 正在被驱逐时,其他驱逐请求也会被阻塞,即使当前可用副本数仍然满足 PDB 条件。这是 Kubernetes 的悲观策略——避免并发驱逐导致”不可用”的边界条件。这意味着如果你有 10 个副本,PDB 设置 minAvailable: 8,同时有 3 个节点的 drain 操作并行执行,可能只有 2 个驱逐被允许,第 3 个会被阻塞,直到前两个驱逐完成且新 Pod 进入 Ready 状态。
PDB 的最佳实践
合理设置 PDB 值
不同场景下的 PDB 推荐值:
| 应用类型 | 副本数 | 推荐 PDB | 说明 |
|---|---|---|---|
| API 网关 | 5+ | minAvailable: 80% | 需要高可用,留 20% 给运维操作 |
| Web 前端 | 3+ | maxUnavailable: 1 | 逐节点驱逐,一次只影响一个副本 |
| 消息队列消费者 | 10+ | maxUnavailable: 25% | 允许一定比例中断,配合重试机制 |
| 数据库缓存 | 3 | minAvailable: 2 | 至少保留两副本,防止缓存雪崩 |
| 批处理任务 | 20+ | maxUnavailable: 50% | 宽松限制,允许 CA 缩容 |
| 单副本应用 | 1 | 不设置 PDB | PDB 会导致 drain 永远阻塞 |
结合 PodAntiAffinity 使用
PDB 和 PodAntiAffinity 是天然的搭档。PDB 确保数量上的下限,PodAntiAffinity 确保分布上的分散:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
namespace: production
spec:
replicas: 5
template:
metadata:
labels:
app: api-server
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: api-server
topologyKey: kubernetes.io/hostname
containers:
- name: api-server
image: api-server:v2
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
这个组合确保:5 个副本尽量分布在 5 个不同节点上(PodAntiAffinity),且任何时候至少 4 个可用(PDB minAvailable: 80%)。任何一个节点出现问题,最多影响 20% 的流量。
监控 PDB 状态
Kubernetes 提供了 PDB 的状态字段,用于监控当前保护状态:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 # 查看 PDB 详情
kubectl get pdb api-server-pdb -n production -o yaml
# 输出示例
status:
conditions:
- lastTransitionTime: "2026-07-07T10:00:00Z"
message: ""
observedGeneration: 1
reason: SufficientPods
status: "True"
type: DisruptionAllowed
currentHealthy: 5
desiredHealthy: 4
disruptionsAllowed: 1
expectedPods: 5
observedGeneration: 1
关键字段:
-
1currentHealthy
:当前健康的 Pod 数(Ready 状态)
-
1desiredHealthy
:PDB 要求的最小健康数
-
1disruptionsAllowed
:当前允许被驱逐的 Pod 数
-
1expectedPods
:匹配的 Pod 总数
当
1 | disruptionsAllowed |
为 0 时,任何驱逐操作都会被阻塞。建议在监控系统中对
1 | disruptionsAllowed == 0 |
的状态设置告警,它可能意味着:
- Pod 副本数不足(部分 Pod 异常)
- PDB 配置过于严格
- 即将有 drain 操作被阻塞
使用 kubectl 快速验证 PDB
1
2
3
4
5
6
7
8
9
10
11 # 列出所有 PDB
kubectl get pdb --all-namespaces
# 检查特定 PDB 的驱逐是否允许
kubectl get pdb api-server-pdb -n production -o jsonpath='{.status.disruptionsAllowed}'
# 模拟驱逐(dry-run 模式)
kubectl drain node-5 --dry-run=server --ignore-daemonsets
# 查看哪些 PDB 会影响某个节点
kubectl describe node node-5 | grep -A5 "Non-terminated Pods"
总结

PodDisruptionBudget 是 Kubernetes 生产集群中不可或缺的配置项,它解决了自愿中断操作中的服务可用性问题。通过合理设置 PDB,运维团队可以在执行节点升级、滚动更新、缩容等操作时,确保关键服务始终维持约定的可用性下限。
PDB 虽然本身不复杂,但需要结合副本数、PodAntiAffinity、Readiness Probe 等机制综合设计。一个典型的建议是:所有生产环境的关键工作负载都应该配置 PDB,副本数不低于 3 个,PDB 设置为 minAvailable: 80% 或 maxUnavailable: 1。同时,将 PDB 的状态纳入监控告警体系,确保在 disruption 被阻塞时能及时发现和处理。
最后提醒一点:PDB 不是万能的,它只保护自愿中断,不保护节点宕机等非自愿中断。真正的高可用需要多可用区部署、Pod 反亲和、HPA 弹性伸缩和 PDB 这几层机制共同作用。只有把这些工具组合起来,才能构建出能从容应对各种故障场景的健壮 Kubernetes 集群。
汤不热吧