Kubernetes 用 PodDisruptionBudget 保住可用副本:节点排空、滚动升级时别把服务打到 0
Kubernetes 用 PodDisruptionBudget 保住可用副本:节点排空、滚动升级时别把服务打到 0
你有没有遇到过这种线上事故:运维半夜给节点打补丁,kubectl drain 一执行,某个服务的三个副本恰好都在这台节点上,瞬间全被驱逐,服务直接 502 了几十秒。或者集群自动缩容,一次性下线两个节点,把一个只有两副本的服务同时干掉。
这类问题的共同点是:Pod 数量在你眼里「够用」,但在「自愿中断」发生时缺乏保护。Kubernetes 的 PodDisruptionBudget(PDB,Pod 中断预算)就是给这类场景兜底的 —— 它告诉集群「无论你想干什么维护操作,这个服务至少得给我留几个活着」。
这篇把 PDB 的作用边界、两种配置方式和几个真实的坑讲清楚。
先分清「自愿中断」和「非自愿中断」
PDB 只管自愿中断(voluntary disruption),这是理解它的前提。
- 自愿中断:人或控制器主动发起的驱逐。
kubectl drain、集群缩容(cluster-autoscaler 下线节点)、节点升级。这些走的是 Eviction API,PDB 能拦。 - 非自愿中断:不受控的意外。节点硬件故障、内核 panic、Pod 被 OOMKilled、网络断开。这些 PDB 管不了 —— 节点都没了,预算再高也拦不住。
一句话:PDB 是「维护操作时的护栏」,不是「高可用的银弹」。真正的高可用还得靠多副本 + 反亲和性把 Pod 打散到不同节点。
最小可用副本:minAvailable
假设有个 web Deployment 跑 4 个副本,你希望任何维护操作发生时,至少有 3 个在线:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-pdb
spec:
minAvailable: 3 # 至少留 3 个可用
selector:
matchLabels:
app: web # 必须和目标 Pod 的 label 对上
有了这个,当运维执行 kubectl drain node-1,而 node-1 上有 2 个 web Pod 时:集群会先驱逐 1 个(此时还剩 3 个,满足预算),然后卡住,直到别的节点上先把新 Pod 拉起来、可用数回到 4,才驱逐第二个。drain 命令会一直等,而不是粗暴地全踢掉。
最大不可用:maxUnavailable
另一种写法是从「最多能挂几个」的角度描述,更适合副本数会变的场景(比如配了 HPA):
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-pdb
spec:
maxUnavailable: 1 # 任何时刻最多 1 个不可用
selector:
matchLabels:
app: web
minAvailable: 3(4 副本时)和 maxUnavailable: 1 效果相同。区别在于副本数变化时:如果 HPA 把副本扩到 10,minAvailable: 3 允许同时驱逐 7 个(太激进),而 maxUnavailable: 1 始终只允许挂 1 个。副本数动态变化时,优先用 maxUnavailable 配百分比:
spec:
maxUnavailable: 25% # 按当前副本数的 25% 算,自动适配扩缩容
注意:minAvailable 和 maxUnavailable 二选一,不能同时写。
验证 PDB 真的在工作
配完别急着信,实测一遍。先看 PDB 状态:
kubectl get pdb web-pdb
# NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
# web-pdb 3 N/A 1 2m
ALLOWED DISRUPTIONS 是当前还允许驱逐几个,这里是 1,说明现在有 4 个健康副本、留 3 个、可以再挂 1 个。
然后模拟节点维护:
kubectl drain node-1 --ignore-daemonsets --delete-emptydir-data
如果 node-1 上的 Pod 一驱逐就会破坏预算,你会看到 drain 卡在这样的日志:
evicting pod default/web-xxxxx
error when evicting pods/"web-xxxxx" -n "default" (will retry after 5s):
Cannot evict pod as it would violate the pod's disruption budget.
这不是报错,是 PDB 在起作用 —— 它在等新副本先就位。这正是我们要的效果。
四个真实会踩的坑
坑一:selector 对不上,PDB 形同虚设。 PDB 的 selector 必须精确匹配目标 Pod 的 label。写错了 kubectl get pdb 里 ALLOWED DISRUPTIONS 会显示得很奇怪(比如始终等于副本数),drain 时根本不拦。配完一定要 kubectl describe pdb web-pdb 确认 Status 里的 Current/Desired/Expected pods 数字对得上。
坑二:minAvailable 设得等于副本数,drain 永远卡死。 比如 3 副本配 minAvailable: 3,意味着「一个都不许挂」。这样 kubectl drain 会永远等下去,因为它无法在不破坏预算的情况下驱逐任何 Pod,节点维护做不下去。留一点余量,别把预算顶满。
坑三:以为 PDB 能防节点宕机。 前面说过,节点硬件故障是非自愿中断,PDB 管不着。见过团队配了 minAvailable 就以为万事大吉,结果一台节点物理挂掉,上面的 Pod 全没了,PDB 一点忙没帮上。防这个得靠 topologySpreadConstraints 或 Pod 反亲和,把副本强制分散到不同节点/可用区。
坑四:单副本服务配 PDB 会锁死维护。 只有 1 个副本的服务,配 minAvailable: 1 会让节点永远无法排空。单副本本就没有高可用可言,要么先扩到多副本再配 PDB,要么就别给它配、接受维护时的短暂中断。
配套:把副本打散,PDB 才有意义
PDB 保证「留几个活着」,但如果副本全挤在一个节点上,一次 drain 还是会触发预算等待、拖慢维护。配合反亲和把副本摊开:
# Deployment.spec.template.spec 里
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: web
topologyKey: kubernetes.io/hostname # 尽量分到不同节点
副本分散 + PDB,才是「维护无感 + 意外少损」的组合拳。
小结
- PDB 只拦自愿中断(drain、缩容、升级),拦不住节点宕机、OOMKilled 这类非自愿中断。
minAvailable声明「至少留几个」,maxUnavailable声明「最多挂几个」,二选一;副本数会随 HPA 变化时优先用maxUnavailable配百分比。- 配完用
kubectl get pdb看ALLOWED DISRUPTIONS、describe核对 selector,再drain实测确认会卡等而不是全踢。 - 别把预算顶满(等于副本数会锁死 drain),别给单副本配 PDB,别指望它防硬件故障。
- PDB 要和 Pod 反亲和/拓扑分散一起用,副本先摊开,预算才真正省心。
一句话记忆点:PDB 是维护操作的护栏,不是高可用的替身 —— 它保证「主动动你的时候留几个活口」,防不了「机器自己挂」。
更多推荐
所有评论(0)