饮墨

子安饮墨馀三斗,留与卿儿作赋来

Kubernetes PDB 生产实战:零停机集群维护的关键配置

痛点

集群升级节点、内核补丁滚动更新、节点缩容时,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: 1minAvailable: N-1
HPA 弹性伸缩 maxUnavailable: "25%"
有状态单副本 不设 PDB,或扩为多副本
配合节点维护窗口 PDB + TopologySpread + PriorityClass

PDB 是 Kubernetes 生产环境的基础配置,不是可选项。每个核心服务都应该有对应的 PDB,配合 TopologySpread 和合理的 terminationGracePeriodSeconds,才能真正实现集群维护时的零停机。建议将 PDB 纳入 Helm Chart / Kustomize 模板,作为服务上线 checklist 的必选项。

您还没有登录,请登录后发表评论。