Kubernetes 基础体系 · 第 58/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。

PodDisruptionBudget 与可用性:自愿中断、Eviction、滚动和误区

PodDisruptionBudget(PDB)是 Kubernetes 中用于约束自愿中断的策略对象。它回答的问题不是“这个应用是否高可用”,而是:

当 Kubernetes 或运维工具主动尝试删除某个 Pod 时,当前是否至少保留足够多的健康副本?

PDB 主要影响节点维护、集群扩缩容、Cluster Autoscaler 等通过 Eviction API 发起的 Pod 驱逐;它不会阻止所有类型的 Pod 消失,也不会替代 Deployment、Service、探针、跨可用区部署或应用自身的容错设计。


一、先区分三个概念:可用性、中断和删除

1. Pod 可用不等于 Pod 存在

对 PDB 而言,一个 Pod 是否“健康”主要由 Pod 的 Ready 状态决定。一个处于 Running 但尚未通过 readiness probe 的 Pod,通常不能作为 PDB 的健康副本计数。

因此下面几种状态不同:

Pod 状态 是否通常计入 PDB 的 currentHealthy
Running 且 Ready
Running 但未 Ready
正在启动、尚未 Ready
已进入删除流程 通常不作为可安全中断的健康副本
容器进程仍在运行但 Ready 已变为 False

PDB 保护的是 Kubernetes 观察到的健康副本数量,而不是业务真正能处理的请求数。例如,应用虽然通过 readiness probe,但数据库连接池已经耗尽,PDB 并不知道这一点。

2. 中断分为自愿中断和非自愿中断

**自愿中断(voluntary disruption)**是某个组件或操作主动尝试让 Pod 终止,例如:

  • kubectl drain 为节点维护驱逐 Pod;
  • Cluster Autoscaler 为缩容节点驱逐 Pod;
  • 集群管理员或自动化系统通过 Eviction API 删除 Pod;
  • 某些集群管理组件主动迁移工作负载。

**非自愿中断(involuntary disruption)**是 Pod 因故障而消失,例如:

  • 节点宕机;
  • 节点失联或网络分区;
  • 内核崩溃;
  • 容器运行时异常;
  • 节点资源压力导致 kubelet 驱逐;
  • 底层虚拟机或云主机故障。

PDB 主要约束前者。它不能保证节点突然断电时仍有足够副本,也不能阻止 kubelet 因本地磁盘或内存压力采取回收动作。

可以把 PDB 理解成“主动维护时的中断预算”,而不是“副本永远不会减少”的保证。


二、PDB 的核心模型:可用副本、期望副本和预算

一个 PDB 通过 selector 找到一组 Pod,并声明其中至少要保留多少健康 Pod,或者最多允许多少 Pod 同时因自愿中断而不可用。

两种写法二选一:

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-pdb
  namespace: demo
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: web

或者:

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-pdb
  namespace: demo
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: web

minAvailablemaxUnavailable 不能同时设置。

设:

  • EE:PDB 选择到的期望 Pod 数量;
  • HH:当前被认为健康的 Pod 数量;
  • DD:期望保留的健康 Pod 数量;
  • BB:当前允许的自愿中断数量。

对于 minAvailable

D=minAvailableD = \text{minAvailable}

对于 maxUnavailable

D=EmaxUnavailableD = E - \text{maxUnavailable}

预算近似为:

B=max(0,HD)B = \max(0, H-D)

直觉是:如果当前健康副本比最低要求多 1 个,就允许主动中断 1 个;如果刚好达到最低要求,就不允许再主动中断。

PDB 状态中常见的字段对应如下:

status:
  currentHealthy: 3
  desiredHealthy: 2
  expectedPods: 3
  disruptionsAllowed: 1

这里表示:

  • 选中的 Pod 总数为 3;
  • 当前健康 Pod 为 3;
  • 至少需要保留 2 个健康 Pod;
  • 当前还允许 1 个 Pod 进行自愿中断。

disruptionsAllowed 是控制 Eviction 是否可以继续的关键状态,但它由控制器异步计算,不应被当作一个实时事务锁来理解。Eviction 处理过程中还会使用状态更新和并发控制,避免多个驱逐请求简单地同时消费同一个预算。


三、完整算例:minAvailable 如何限制驱逐

假设 Deployment 有 3 个副本:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: demo
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: web
        image: nginx:1.27
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 2
          periodSeconds: 5
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-pdb
  namespace: demo
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: web

初始状态:

变量 数值
expectedPods 3
currentHealthy 3
desiredHealthy 2
disruptionsAllowed 1

第一次驱逐

第一个 Eviction 请求到达时,PDB 允许它继续。假设 Pod web-abc 开始终止:

  • 该 Pod 不再作为可用副本;
  • currentHealthy 可能变为 2;
  • desiredHealthy 仍为 2;
  • 后续预算变为 0。

此时第二个驱逐请求会被拒绝,常见结果是 HTTP 429 Too Many Requests,客户端通常会看到类似“would violate the pod's disruption budget”的错误。

新 Pod 恢复后

如果 Deployment 创建的新 Pod 通过 readiness probe:

  • expectedPods 回到 3;
  • currentHealthy 回到 3;
  • disruptionsAllowed 回到 1。

因此,PDB 不会“一次性批准所有要维护的 Pod”,而是随着健康副本恢复,逐步释放新的中断预算。

反例:已有一个 Pod 不健康

如果 3 个 Pod 中有一个尚未 Ready:

变量 数值
expectedPods 3
currentHealthy 2
desiredHealthy 2
disruptionsAllowed 0

即使实际上仍有两个 Pod 可以响应请求,PDB 也不会再批准自愿驱逐,因为它已经达到了最低健康数。这个行为是有意的:节点维护不应继续放大一个已经存在的可用性风险。


四、百分比配置的取整会改变结果

minAvailablemaxUnavailable 可以使用整数或百分比字符串:

spec:
  minAvailable: 67%

对于百分比,当前 Kubernetes 文档定义的取整行为可能使小规模工作负载比直觉更严格或更宽松。以 3 个副本为例:

67% × 3 = 2.01

minAvailable 向上取整为 3,因此它实际上要求 3 个 Pod 都健康,等价于暂时不允许任何一个 Pod 被自愿中断。

同样:

spec:
  maxUnavailable: 50%

对于 3 个副本:

50% × 3 = 1.5

按当前 API 语义向上取整为 2,这意味着最多允许 2 个 Pod 不可用。对小规模工作负载而言,这可能比作者预期的“允许一半”更宽松。

因此,对于 1~3 个副本的关键服务,整数配置通常更容易审计:

spec:
  minAvailable: 2

比下面这种配置更明确:

spec:
  minAvailable: 67%

百分比适合副本数变化范围较大的工作负载,但应先把不同副本数下的实际整数结果算出来。


五、Eviction 是什么:不是普通的 DELETE

Kubernetes 的 Eviction 是一个专门的 API 子资源,用于请求优雅驱逐 Pod。典型对象如下:

apiVersion: policy/v1
kind: Eviction
metadata:
  name: web-abc
  namespace: demo

可以使用 kubectl 创建它:

kubectl create -f eviction.yaml

也可以直接使用:

kubectl delete pod web-abc -n demo

但二者语义不同:

  • Eviction 会经过 PDB 检查;
  • 普通删除请求并不是同一个 PDB 保护路径;
  • Eviction 成功通常表示“驱逐请求被接受”,不一定表示 Pod 已经立刻消失;
  • Pod 仍会遵守终止流程和 terminationGracePeriodSeconds

一个完整的 Eviction 路径可以概括为:

sequenceDiagram
    participant O as 操作员或控制器
    participant A as API Server
    participant P as PDB/准入逻辑
    participant K as Kubelet
    participant C as 容器进程

    O->>A: 创建 Pod Eviction
    A->>P: 检查匹配的 PDB 与当前预算
    alt disruptionsAllowed > 0
        P-->>A: 允许并记录中断意图
        A-->>O: 接受请求
        A->>K: Pod 进入删除流程
        K->>C: 发送终止信号
        C-->>K: 退出或等待宽限期
        K-->>A: Pod 状态更新
    else 预算不足
        P-->>A: 拒绝,通常返回 429
        A-->>O: 驱逐失败
    end

实际实现涉及 API Server、PDB 控制器、Pod 状态更新和并发协调,不能把图理解为一个完全同步的单线程事务。多个请求可能同时到达,控制面会通过资源版本、状态更新和驱逐记录尽量避免超额批准;客户端仍应正确处理 429、冲突、超时和 Pod 已经被删除等情况。


六、kubectl drain 如何使用 PDB

节点维护通常先标记节点不可调度,再驱逐已有 Pod:

kubectl cordon worker-1
kubectl drain worker-1 \
  --ignore-daemonsets \
  --delete-emptydir-data

1. cordon 做什么

kubectl cordon worker-1

会把节点设置为不可调度。新的普通 Pod 不会再被调度到该节点,但它不会自动迁移已经运行的 Pod,也不会自动执行驱逐。

因此:

cordon ≠ drain

cordon 是阻止新增调度,drain 才是尝试清空节点上的可驱逐工作负载。

2. drain 做什么

kubectl drain 会:

  1. 标记或确认节点不可调度;
  2. 找出节点上的 Pod;
  3. 跳过或处理 DaemonSet、镜像 Pod、裸 Pod 等特殊情况;
  4. 对可驱逐 Pod 使用 Eviction API;
  5. 等待 Pod 终止;
  6. 如果 PDB 不允许,等待或报错,而不是强行忽略预算。

如果命令因为 PDB 失败,常见现象包括:

error when evicting pods/...:
Cannot evict pod as it would violate the pod's disruption budget.

这不表示节点不可维护,而是表示当前维护速度超过了应用声明的中断预算。

3. 常见参数的含义和风险

kubectl drain worker-1 \
  --ignore-daemonsets \
  --delete-emptydir-data
  • --ignore-daemonsets:忽略由 DaemonSet 管理的 Pod。DaemonSet 通常每个节点都需要一个副本,不能按普通工作负载迁移。
  • --delete-emptydir-data:允许删除使用 emptyDir 的 Pod。emptyDir 中的数据随 Pod 删除而丢失。
  • --force:允许处理没有控制器管理的裸 Pod,可能导致这些 Pod 不会自动恢复。
  • --disable-eviction:使用普通删除而不是 Eviction,从而绕过 PDB。该选项具有明显的可用性风险,不应作为“让 drain 通过”的默认手段。

kubectl drain --dry-run=client 只能检查命令参数和本地客户端逻辑,不能模拟真实控制面中的 PDB 竞争、Pod Ready 状态和节点上的全部对象。生产维护前仍要观察实际 PDB 状态和工作负载状态。


七、PDB 与 Deployment 滚动更新不是同一个控制器

这是最容易混淆的部分之一。

Deployment 的滚动更新由 Deployment 控制器根据以下参数协调:

spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  • maxSurge:更新期间允许超过期望副本数的额外 Pod 数量;
  • maxUnavailable:滚动更新期间允许不可用的 Pod 数量。

例如,期望副本数为 3,配置:

maxSurge: 1
maxUnavailable: 0

典型过程是:

旧版本 3,新版本 0
旧版本 3,新版本 1,暂时总数 4
新版本 Pod Ready
旧版本 2,新版本 1
新版本 Pod Ready
旧版本 1,新版本 2
新版本 Pod Ready
旧版本 0,新版本 3

Deployment 的 maxUnavailable 控制的是滚动更新算法,PDB 的 minAvailable 控制的是通过 Eviction API 发起的自愿中断。它们名称相似,但作用域不同。

PDB 不会自动阻止普通滚动更新

Deployment 控制器在滚动更新时会调整 ReplicaSet 的副本数,并删除旧版本 Pod。这个流程不是把每个 Pod 都包装成 Eviction 请求,因此 PDB 不是滚动更新的主保护机制。

例如:

spec:
  replicas: 3
  strategy:
    rollingUpdate:
      maxSurge: 0
      maxUnavailable: 1

这允许 Deployment 在更新过程中最多有 1 个副本不可用。即使 PDB 写成:

spec:
  minAvailable: 3

也不能把 PDB 当成 Deployment 更新的替代控制器。要控制滚动期间的可用副本,应配置 Deployment 的滚动策略,并通过 readiness probe 确保“新 Pod 真正可接流量”后再继续。

反过来,maxUnavailable: 0 也不意味着更新永远不会失败。它可能因以下原因卡住:

  • 新版本 Pod 无法通过 readiness probe;
  • 节点资源不足,无法满足 maxSurge
  • 镜像拉取失败;
  • 配置或 Secret 错误;
  • 新版本启动后立即崩溃。

滚动更新策略约束“更新控制器如何推进”,并不替应用修复启动错误。


八、PDB 与 Deployment 副本数之间的可行性

PDB 依赖工作负载实际创建出来的 Pod。如果 Deployment 的副本数为 3,但由于调度失败实际只有 2 个 Pod,PDB 不会凭空创建第 3 个 Pod。

假设:

spec:
  replicas: 3

PDB:

spec:
  minAvailable: 3

当 3 个 Pod 都 Ready 时,自愿驱逐预算为 0。这意味着任何一个节点维护操作都不能安全地驱逐这些 Pod,除非其他副本先增加并变为健康。

如果配置:

spec:
  replicas: 3
  minAvailable: 2

则在 3 个 Pod 健康时可驱逐 1 个;但在其中一个 Pod 已经故障时,预算为 0。

单副本工作负载的典型误区

spec:
  replicas: 1
---
spec:
  minAvailable: 1

这不是“保证单副本服务高可用”,而是声明:

这个唯一副本不能被自愿中断。

节点 drain 时,PDB 可能一直阻止驱逐;但节点突然宕机时,PDB 仍然无法保护该 Pod。对于单副本服务,PDB 只能防止主动维护造成停机,不能解决单副本本身没有冗余的问题。


九、PDB 选择器必须准确,重叠选择器会制造意外约束

PDB 通过 spec.selector 选择 Pod:

selector:
  matchLabels:
    app: web

它只能选择同一命名空间内的 Pod。实际使用时,选择器应尽量只对应一个工作负载的 Pod 集合。

1. 标签选择器写错会导致 PDB 失效

例如 Deployment 使用:

template:
  metadata:
    labels:
      app: web
      component: frontend

但 PDB 写成:

selector:
  matchLabels:
    app: api

那么 PDB 不会匹配目标 Pod。表面上 PDB 对象存在,实际上它没有保护 Web 工作负载。

应检查:

kubectl get pods -n demo -l app=web --show-labels
kubectl describe pdb web-pdb -n demo

重点观察:

kubectl get pdb web-pdb -n demo -o yaml

其中的:

  • status.expectedPods
  • status.currentHealthy
  • status.desiredHealthy
  • status.disruptionsAllowed
  • status.conditions

能帮助判断 PDB 是否匹配到了预期对象。

2. 不要让多个 PDB 重叠保护同一组 Pod

假设同一批 Pod 同时匹配两个 PDB:

pdb-a: minAvailable: 2
pdb-b: minAvailable: 3

一个 Eviction 必须同时满足所有相关 PDB 的约束。只要其中一个 PDB 不允许,驱逐就可能失败。

这会产生一种常见故障路径:

  1. 运维看到某个 PDB 还有预算;
  2. 另一个未注意到的 PDB 没有预算;
  3. kubectl drain 仍然失败;
  4. 操作者误以为 PDB 检查“不稳定”。

PDB 的选择器应避免交叉覆盖,尤其不要让一个“全局标签”选择器和一个“应用标签”选择器同时匹配同一工作负载。

3. 空选择器的版本差异

使用 policy/v1 时,空的 matchLabels 选择器具有选择命名空间内全部 Pod 的语义;旧的 policy/v1beta1 曾存在不同的空选择器行为。由于 policy/v1beta1 已被移除,不应依赖旧版本的解释。

因此生产配置不要使用含义不明显的空选择器:

selector: {}

应明确写出业务标签,并在升级 Kubernetes 或迁移 API 版本时重新检查选择范围。


十、Pod 终止宽限期不会被 PDB 取消

PDB 决定“现在是否允许开始驱逐”,不决定 Pod 被批准后多久退出。

例如:

spec:
  terminationGracePeriodSeconds: 60

当 Eviction 被接受后,Pod 仍可能经历:

  1. Pod 被设置删除时间;
  2. EndpointSlice 或 Service 流量转发逐步更新;
  3. kubelet 向容器发送 SIGTERM
  4. 应用执行连接排空;
  5. 等待宽限期;
  6. 若仍未退出,才可能收到 SIGKILL

如果应用忽略 SIGTERM,或者连接排空时间超过宽限期,即使 PDB 正确生效,也可能出现请求中断。

此外,PDB 只关心 Pod 数量和健康状态,不检查:

  • 剩余 Pod 是否分布在不同节点;
  • 剩余 Pod 是否位于不同可用区;
  • Service 是否有正确的 Endpoint;
  • 应用是否具备多副本并发能力;
  • 数据库是否允许故障转移;
  • 负载是否已经超过剩余副本容量。

因此,PDB 与优雅终止、readiness probe、拓扑分布约束是互补关系,而不是替代关系。


十一、DaemonSet、StatefulSet 和其他控制器的边界

1. DaemonSet

DaemonSet 的目标通常是“每个节点运行一个 Pod”,例如日志采集、网络插件或存储插件。kubectl drain 默认不会删除 DaemonSet Pod,因为即使删除,它也可能立即在同一节点上被重新创建。

通常需要:

kubectl drain worker-1 --ignore-daemonsets

但这只是告诉 drain 忽略 DaemonSet Pod,不表示这些 Pod 已经被迁移。节点维护流程还必须确认:

  • DaemonSet 是否在目标节点之外健康运行;
  • 网络、存储、日志等节点级组件是否允许该节点暂时缺失;
  • 节点恢复后 DaemonSet Pod 是否重新就绪。

2. StatefulSet

StatefulSet 具有稳定身份、持久卷和有序更新特征。PDB 可以约束通过 Eviction API 发起的 Pod 驱逐,但不会自动理解数据库复制协议或主从角色。

例如,一个有 3 个数据库 Pod 的 StatefulSet 即使满足:

minAvailable: 2

也不代表随便驱逐一个 Pod 都安全。被驱逐的可能是当前主节点,业务能否继续取决于数据库本身是否完成故障转移。

3. Job、裸 Pod 和镜像 Pod

  • Job 创建的 Pod 是否可重新运行,取决于 Job 的重试和完成语义;
  • 裸 Pod 没有控制器负责重建,drain --force 可能导致它永久消失;
  • 静态 Pod 或镜像 Pod 由 kubelet 管理,不能按普通 Deployment Pod 处理。

PDB 不是所有 Pod 生命周期的统一抽象。执行 drain 前,必须先识别节点上的 Pod 来源:

kubectl get pods -A \
  --field-selector spec.nodeName=worker-1 \
  -o custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,OWNER:.metadata.ownerReferences[0].kind'

十二、unhealthyPodEvictionPolicy:不健康 Pod 是否允许驱逐

当前 policy/v1 PDB 支持 unhealthyPodEvictionPolicy,用于决定不健康 Pod 的 Eviction 行为。常见取值包括:

  • IfHealthyBudget:默认行为,只有在当前应用健康副本满足预算条件时,才允许驱逐不健康 Pod;
  • AlwaysAllow:允许驱逐不健康 Pod。

示例:

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-pdb
  namespace: demo
spec:
  minAvailable: 2
  unhealthyPodEvictionPolicy: AlwaysAllow
  selector:
    matchLabels:
      app: web

这个选项解决的是一种实际矛盾:

  1. 一个 Pod 已经不 Ready;
  2. 由于 PDB 的健康统计,驱逐预算为 0;
  3. 节点 drain 无法移除这个本来就不能提供服务的 Pod;
  4. 它又可能阻碍节点维护。

AlwaysAllow 允许在这类场景下驱逐不健康 Pod,但它不是无条件安全开关。若 readiness probe 配置错误,Pod 可能被错误判定为不健康;如果应用本身正在短暂恢复,驱逐也可能进一步放大故障。

该字段存在版本可用性差异,使用前应检查目标集群的 Kubernetes 版本和 policy/v1 API 文档。不能因为控制面支持该字段,就假设所有云厂商托管节点组件的行为完全相同。


十三、滚动更新失败与 PDB 阻塞的诊断顺序

当更新或节点维护卡住时,先判断是哪一种控制路径。

场景一:Deployment 滚动更新卡住

查看 Deployment 和 ReplicaSet:

kubectl rollout status deployment/web -n demo
kubectl describe deployment web -n demo
kubectl get rs -n demo
kubectl get pods -n demo -l app=web -o wide

重点检查:

  • 新 ReplicaSet 是否创建;
  • 新 Pod 是否 Ready
  • 是否有镜像拉取错误;
  • 是否有资源不足或调度失败;
  • Deployment 的 maxSurgemaxUnavailable
  • 是否触发了 progressDeadlineSeconds

PDB 通常不是 Deployment 滚动更新卡住的第一诊断对象,因为 Deployment 更新不以 Eviction 作为主要推进机制。

场景二:kubectl drain 被拒绝

查看 PDB:

kubectl get pdb -A
kubectl describe pdb web-pdb -n demo

再查看健康状态:

kubectl get pods -n demo -l app=web \
  -o custom-columns='NAME:.metadata.name,READY:.status.containerStatuses[*].ready,PHASE:.status.phase,NODE:.spec.nodeName'

典型推导过程:

PDB minAvailable = 2
匹配 Pod = 3
Ready Pod = 2
disruptionsAllowed = 0

此时继续重试 drain 不会解决问题。应先判断:

  • 是否有 Pod 因启动失败而不 Ready;
  • 是否有 Pod 正在被删除;
  • Deployment 是否正在扩容或滚动更新;
  • 资源配额、节点容量或拓扑约束是否阻止新 Pod 创建;
  • PDB 选择器是否匹配了错误的 Pod;
  • 是否存在多个重叠 PDB。

场景三:预算看起来足够,但 Eviction 仍失败

可能原因包括:

  • 另一个 PDB 同时匹配该 Pod;
  • 预算状态尚未被控制器更新;
  • Pod 已经进入删除流程;
  • API Server 返回资源版本冲突;
  • Pod 所属控制器和选择器状态正在变化;
  • 客户端遇到临时的 429 或超时。

因此不能只看一个 PDB 的 disruptionsAllowed,还要确认目标 Pod 匹配到的全部 PDB 和工作负载状态。


十四、回滚和暂停不会绕过 PDB 的语义边界

Deployment 支持暂停、继续和回滚:

kubectl rollout pause deployment/web -n demo
kubectl rollout resume deployment/web -n demo
kubectl rollout undo deployment/web -n demo
kubectl rollout history deployment/web -n demo

这些操作改变的是 Deployment 的 ReplicaSet 协调过程和版本状态,不会把 PDB 变成全局删除拦截器。

例如,回滚过程中:

  1. Deployment 指向旧版本 ReplicaSet;
  2. 控制器调整新旧 ReplicaSet 的副本数;
  3. 旧版本 Pod 创建并等待 Ready;
  4. 新版本 Pod 逐步缩减;
  5. Deployment 根据滚动更新参数判断是否继续。

这里的安全边界由 Deployment 更新策略、Pod 就绪状态和副本数共同决定。PDB 主要在节点 drain 或其他 Eviction 路径中发挥作用。

暂停更新也不会冻结所有 Pod 状态变化。已有 Pod 仍可能崩溃、被节点故障影响或被其他操作删除;暂停只是暂停 Deployment 控制器推进更新。


十五、几个常见误区

误区一:PDB 能保证高可用

错误。PDB 只能约束特定的自愿中断路径。它不能防止:

  • 节点断电;
  • 整个可用区故障;
  • 应用自身崩溃;
  • Service 配置错误;
  • 所有副本错误地调度到同一节点;
  • 数据库或外部依赖不可用。

高可用还需要多个副本、合理的拓扑分布、正确的探针、容量余量和应用级故障转移。

误区二:PDB 会保护普通 kubectl delete pod

不能这样假设。直接删除 Pod 与通过 Eviction API 驱逐 Pod 是不同请求路径。测试 PDB 时应使用 Eviction 或 kubectl drain,而不是用普通删除命令得出结论。

误区三:PDB 的 maxUnavailable 等于 Deployment 的 maxUnavailable

名称相同但控制器和语义不同:

  • PDB:限制自愿中断;
  • Deployment:限制滚动更新过程中的不可用副本。

两者必须分别设计。

误区四:minAvailable: 100% 表示更安全

它表示任何自愿中断都不被允许。在节点维护、集群缩容和故障修复场景中,这可能使工作负载无法迁移,形成运维阻塞。

误区五:PDB 能让单副本应用高可用

单副本加 PDB 只能阻止主动维护删除唯一副本,不能抵抗节点故障,也不能让应用在重启期间继续服务。

误区六:PDB 数字越严格越好

PDB 过于严格时,节点无法 drain,安全补丁和硬件维护可能被长期阻塞。PDB 的约束应与副本数、业务容量、故障域和维护方式共同计算,而不是单独追求更大的 minAvailable


十六、如何验证 PDB 确实按预期工作

下面给出一个最小验证流程。假设命名空间为 demo,Deployment 为 web,PDB 为 web-pdb

1. 检查匹配范围

kubectl get pods -n demo -l app=web -o wide
kubectl describe pdb web-pdb -n demo

确认:

  • PDB 选择器与 Pod 标签一致;
  • expectedPods 等于预期副本数;
  • 健康 Pod 数量符合实际 Ready 状态;
  • disruptionsAllowed 与公式推导一致。

2. 检查控制器状态

kubectl get deployment web -n demo
kubectl get rs -n demo -l app=web
kubectl rollout status deployment/web -n demo

如果 expectedPods 不符合预期,不要先调 PDB,先确认 Deployment 是否真正创建了足够副本。

3. 在非生产环境执行驱逐测试

kubectl get pod -n demo -l app=web -o name
kubectl create -f eviction.yaml

如果 PDB 允许:

  • Eviction 请求被接受;
  • 目标 Pod 进入 Terminating;
  • Deployment 创建替代 Pod;
  • 新 Pod 通过 readiness probe 后,预算逐渐恢复。

如果 PDB 不允许:

  • 请求通常返回 429
  • Pod 不应因为这个 Eviction 请求立即被删除;
  • PDB 的 disruptionsAllowed 通常为 0。

4. 验证恢复,而不是只验证删除

kubectl wait \
  --for=condition=Available \
  deployment/web \
  -n demo \
  --timeout=120s

kubectl get pdb web-pdb -n demo
kubectl get endpointslice -n demo -l kubernetes.io/service-name=web

要同时确认:

  • Deployment 达到期望可用副本;
  • PDB 状态恢复;
  • Service 的 EndpointSlice 中存在预期后端;
  • 应用实际请求成功。

仅看到 Pod 从 Terminating 消失,不能证明业务已经恢复。


十七、生产设计中的取舍

对于副本数为 NN 的无状态服务,常见的最小约束是:

minAvailable=N1\text{minAvailable} = N-1

这允许一次维护一个 Pod,同时要求其余副本健康。但这个推导有前提:

  1. 单个剩余 Pod 能承受增加的流量;
  2. 副本分布不集中在同一个故障域;
  3. 新 Pod 能在可接受时间内启动;
  4. readiness probe 能准确反映可接流量状态;
  5. 应用支持优雅终止;
  6. 维护过程中不会同时发生其他独立故障。

如果副本为 3,设置 minAvailable: 2 并不能抵抗两个节点同时故障;如果三个副本都在同一节点上,也不能抵抗该节点故障。

对于跨节点或跨可用区的高可用,还需要配合:

  • topologySpreadConstraints
  • podAntiAffinity
  • 节点池和可用区容量;
  • 合理的 Deployment maxSurge
  • 资源请求与集群余量;
  • Service 和 EndpointSlice 验证;
  • 应用级重试、超时与连接排空。

PDB 的价值在于把“维护时最多可以主动损失多少健康副本”明确表达给 Kubernetes,而不是替应用完成整个高可用设计。


结语

PDB 的核心不是“禁止删除 Pod”,而是约束通过 Eviction API 发起的自愿中断。理解它至少需要同时区分四条路径:

PDB:约束自愿驱逐
Eviction:带 PDB 检查的优雅驱逐请求
Deployment:控制 ReplicaSet 和滚动更新
节点故障或压力:可能绕过 PDB 的非自愿中断

使用 PDB 时,应先根据副本数和健康状态计算 desiredHealthydisruptionsAllowed,再检查选择器、Eviction 路径、Deployment 更新策略和节点维护命令。只有把这些机制分开,才能解释为什么一次 drain 会被阻塞、为什么滚动更新仍可能继续、以及为什么 PDB 存在时应用仍可能因节点故障而不可用。


系列导航与关联阅读

官方资料

本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。