痛点
集群升级节点、内核补丁滚动更新、节点缩容时,kubectl drain 一把梭直接驱逐 Pod,结果核心服务瞬间副本数归零,告警群炸了——这是很多运维团队踩过的坑。
根本原因:Kubernetes 默认不关心你的业务可用性,drain 操作会无差别驱逐节点上所有 Pod。如果多个副本恰好调度在同一节点,或者驱逐速度快于新 Pod 就绪速度,服务就会出现中断窗口。
PodDisruptionBudget(PDB) 正是解决这个问题的原生机制——它告诉集群:"在任何自愿中断(voluntary disruption)期间,我的服务至少要保持 N 个副本可用"。
方案
PDB 的核心逻辑很简单:
| 字段 | 含义 | 适用场景 |
|---|---|---|
minAvailable |
任何时刻最少可用 Pod 数 | 明确知道最低副本数 |
maxUnavailable |
最多允许不可用 Pod 数 | 弹性伸缩场景,副本数波动大 |
两者互斥,只能设其一。PDB 只约束自愿中断(drain、滚动更新、集群自动缩容),不影响 OOM Kill、节点宕机等非自愿中断。
实操步骤
第 1 步:为核心服务创建 PDB
以一个 3 副本的 API 网关 Deployment 为例:
# pdb-api-gateway.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-gateway-pdb
namespace: production
spec:
minAvailable: 2 # 至少保留 2 个 Pod 可用
selector:
matchLabels:
app: api-gateway
kubectl apply -f pdb-api-gateway.yaml
kubectl get pdb -n production
输出示例:
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
api-gateway-pdb 2 N/A 1 5s
ALLOWED DISRUPTIONS = 1 表示当前允许驱逐 1 个 Pod(3 副本 - minAvailable 2 = 1)。
第 2 步:用百分比应对弹性伸缩
当副本数随 HPA 动态变化时,硬编码数字不合适,用百分比更灵活:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: worker-pdb
namespace: production
spec:
maxUnavailable: "25%" # 最多 25% Pod 同时不可用
selector:
matchLabels:
app: async-worker
当 HPA 将副本扩到 8 个时,maxUnavailable=25% 意味着最多 2 个 Pod 可同时被驱逐,保障至少 6 个在处理请求。
第 3 步:验证 drain 行为
在非生产环境模拟节点维护:
# 查看目标节点上的 Pod 分布
kubectl get pods -n production -o wide | grep node-02
# 执行 drain(会受 PDB 约束)
kubectl drain node-02 --ignore-daemonsets --delete-emptydir-data
# 如果驱逐被 PDB 阻止,会看到类似输出:
# error when evicting pods/"api-gateway-xxx" -n "production" (will retry):
# Cannot evict pod as it would violate the pod's disruption budget.
drain 会持续重试,直到其他节点上的新 Pod 就绪、当前节点 Pod 可安全驱逐为止。
第 4 步:配合 Pod Topology Spread 避免单点
PDB 只控制驱逐速率,不解决"所有副本堆在一个节点"的问题。配合 topologySpreadConstraints 打散分布:
spec:
template:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: api-gateway
这样确保副本均匀分布到不同节点,drain 单个节点时影响面最小。
避坑
1. minAvailable 设置过高导致 drain 卡死
如果 3 副本服务设 minAvailable: 3,那 ALLOWED DISRUPTIONS = 0,任何 drain 都会被永久阻塞。节点维护直接卡住,半夜收到集群升级超时告警。
建议: minAvailable 至少比总副本数少 1,或使用 maxUnavailable: 1。
2. PDB 对单副本服务的困境
单副本 + minAvailable: 1 = drain 永远无法执行。要么接受短暂中断不设 PDB,要么将服务扩为多副本。对于有状态单副本(如 Redis 单机),考虑设置 maxUnavailable: 1 并配合 terminationGracePeriodSeconds 做优雅退出。
3. 忽略 unhealthyPodEvictionPolicy(Kubernetes 1.27+)
默认情况下,不健康的 Pod(非 Ready)也计入 PDB 预算。如果一个 Pod 一直 CrashLoopBackOff 占着名额,drain 会被卡住。1.27+ 版本可设置:
spec:
unhealthyPodEvictionPolicy: AlwaysAllow # 不健康 Pod 不占 PDB 预算
总结
| 场景 | 推荐配置 |
|---|---|
| 无状态服务 3+ 副本 | maxUnavailable: 1 或 minAvailable: N-1 |
| HPA 弹性伸缩 | maxUnavailable: "25%" |
| 有状态单副本 | 不设 PDB,或扩为多副本 |
| 配合节点维护窗口 | PDB + TopologySpread + PriorityClass |
PDB 是 Kubernetes 生产环境的基础配置,不是可选项。每个核心服务都应该有对应的 PDB,配合 TopologySpread 和合理的 terminationGracePeriodSeconds,才能真正实现集群维护时的零停机。建议将 PDB 纳入 Helm Chart / Kustomize 模板,作为服务上线 checklist 的必选项。