Kubernetes 发布保护:PDB、Surge 和 Unavailable 怎么配
把 Deployment 滚动升级参数与 PodDisruptionBudget 配合起来,降低发布与节点维护带来的可用性风险。
很多“例行发布变事故”的根因来自以下几个参数之间的假设不一致:
- 发布时允许同时下线多少 Pod(Deployment rollingUpdate)
- 维护/驱逐时允许同时被赶走多少 Pod(PDB)
- 实际副本数与容量是否足够(请求资源、节点池 headroom)
- readiness 与优雅下线是否可靠(摘流量是否及时)
先看事故通常怎么发生
发布保护出问题时,现场往往表现为:
- 滚动发布卡住:新 Pod 起不来,旧 Pod 又不能下线,Deployment 长时间停在中间态。
- 节点维护赶走太多副本:PDB 没挡住关键服务,或者 selector 没选中真正的 Pod。
- 扩容看起来成功但流量仍然抖:readiness 太早变绿,Pod 还没准备好就被接入。
- 设置都很保守但发布很慢:
maxUnavailable: 0没错,但没有足够 surge 容量。
我通常先把发布和驱逐分开看:Deployment 管的是滚动升级过程,PDB 管的是自愿驱逐过程,readiness 管的是流量是否该进来。三者混在一起排查,很容易改错旋钮。
最容易误判的地方
- PDB 不保护节点故障、进程崩溃这类非自愿中断。
- PDB 也不约束 Deployment 或 StatefulSet 控制器主动删除 Pod;滚动升级由各自更新策略控制。
maxUnavailable: 0需要额外容量配合,否则只是把风险换成发布卡顿。- PDB 的 selector 写错时,看起来有保护,实际上没有保护任何业务 Pod。
- readiness 不可靠时,PDB 和 rollingUpdate 都救不了过早接流量的问题。
Deployment rollingUpdate:最常用的两个旋钮
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25%
maxUnavailable: 0
含义:
maxSurge:升级时允许额外创建多少新 Pod(加速替换,但需要容量)maxUnavailable:升级时允许不可用的 Pod 数量(越小越稳,但可能更慢)
对于必须保持可用的服务,maxUnavailable: 0 常常是一个强默认值。
百分比会取整:Deployment 的 maxSurge 向上取整,maxUnavailable 向下取整。小副本服务不要只看百分比,先换算成实际 Pod 数。
PDB(PodDisruptionBudget):保护“自愿”驱逐
PDB 主要保护:
kubectl drain节点- 平台升级/维护时的 cordon + drain
- 一些自动化维护流程
示例:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-pdb
namespace: app
spec:
minAvailable: 2
unhealthyPodEvictionPolicy: AlwaysAllow
selector:
matchLabels:
app: api
注意:PDB 只对通过 Eviction API 发起的 voluntary disruptions 生效,不对节点崩溃、网络分区、节点压力驱逐、内核 OOM 或工作负载控制器滚动更新兜底。
unhealthyPodEvictionPolicy: AlwaysAllow 从 Kubernetes 1.31 起稳定。它允许 drain 驱逐长期不 Ready 的 Pod,避免坏 Pod 把节点维护永久卡住;健康 Pod 仍受预算保护。极端严格的有状态系统可以保留默认 IfHealthyBudget,但要接受维护阻塞风险。
policy/v1 中 selector: {} 会选中 namespace 内全部 Pod,selector: null 才是不选任何 Pod。生产清单应始终写明确标签,避免一个空 selector 意外覆盖整库工作负载。
选 minAvailable 还是 maxUnavailable
minAvailable:副本数固定时更直观maxUnavailable:比例型预算更方便(大规模服务)
PDB 的百分比会向上取整。7 个副本配置 maxUnavailable: "30%" 时,可能允许 3 个 Pod 同时不可用,实际比例超过 30%;小规模服务优先使用整数并做 drain 演练。
经典坑:副本数太少 + PDB 太严格
如果你只有 2 个副本,同时 minAvailable: 2:
- drain 节点可能被卡住(一个都不让走)
- 维护会很痛苦
这通常意味着你的 SLA、成本、副本数与维护策略需要对齐。
让数字变得清晰:rollout 的“数学题”
例子:8 副本,maxSurge: 25%,maxUnavailable: 0
maxSurge允许额外最多 2 个 Pod(25% of 8)- 理论上可用 Pod 维持 8 个不掉(更稳)
但这依赖两个前提:
- 集群有容量调度出 surge Pod(requests 合理,节点池有余量)
- readiness 真的代表“可接流量”(否则 surge 再多也没用)
如果 surge Pod 因容量不足一直 Pending,rollout 会卡住。
把副本“铺开”:否则 PDB 也救不了你
如果所有副本都落在同一个节点/同一个可用区:
- 一个节点 drain/故障就会把你打到不可用
建议使用 topologySpreadConstraints(现代方式)或 pod anti-affinity。
例如跨可用区分布:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: api
“可用性”的另一半:优雅下线
即使 maxUnavailable: 0,你仍可能在发布时看到错误率升高,原因往往是旧 Pod 下线过快:
- readiness 未及时失败,仍在 endpoints 里
- 连接被强行断开(长连接/gRPC/队列 worker)
terminationGracePeriodSeconds太短
实用模式:
- 收到 SIGTERM,服务立刻让 readiness 失败(快速摘流量)
- 等待 in-flight 请求完成(grace period)
- 再退出
三个 Deployment 参数值得一起看
minReadySeconds:新 Pod 需要保持 ready 一段时间才算 available,避免 readiness 抖动导致发布误判。progressDeadlineSeconds:超过多久标记 rollout failed,方便接告警和自动化回滚。revisionHistoryLimit:保留多少历史 ReplicaSet,别为了“干净”把回滚能力删掉。
StatefulSet:目标类似,机制不同
StatefulSet 通常按 Pod 序号滚动。PDB 仍然保护自愿中断,但还要确认应用是否能接受顺序重启。数据库类工作负载优先看 Operator 的发布约束,尤其要验证副本和 leader 行为。
StatefulSet 控制器自己的滚动删除不经过 Eviction API,因此 PDB 也不会阻止 StatefulSet 更新。PDB 保护的是 drain 等外部自愿中断,不能替代 partition、更新顺序和数据库 quorum 设计。
发布卡住时的 Runbook
先看状态,再看事件,最后看单个 Pod:
kubectl rollout status deploy/<name> -n <ns>
kubectl describe deploy/<name> -n <ns>
kubectl get pdb -n <ns>
kubectl describe pdb -n <ns> <pdb>
kubectl get events -n <ns> --sort-by=.lastTimestamp | rg -n "FailedScheduling|Insufficient"
kubectl describe pod -n <ns> <pod>
kubectl logs -n <ns> <pod> -c <container> --previous
确认不能继续时,回滚比等待更有价值:
kubectl rollout undo deploy/<name> -n <ns>
发布期间看什么信号
不要只看 rollout status 成功。同步观察:
- 错误率(HTTP 5xx、gRPC 错误)
- 延迟(p95/p99)
- 饱和度(CPU throttling、内存压力)
- readiness 失败和重启次数
这些指标一旦回退,先暂停或回滚,不要等“发布完成”再处理。
和 HPA / Cluster Autoscaler 的交互
rollout 时 surge 会让 Pod 数暂时增多:
- 如果节点有余量,发布会很顺
- 如果没余量,Cluster Autoscaler 可能会扩节点,但节点启动需要时间
同时如果 HPA 正在因流量扩容,也会叠加 surge,造成更大的容量需求。
建议:
- 高峰期减少发布频率或采用 canary
- 让节点池保留一定 headroom
- 确保 requests 准确(否则你以为有余量,实际上没有)
kubectl drain 被卡住怎么办(PDB 视角)
当 drain 时 PDB 阻止驱逐,你通常会看到:
- drain 命令一直等待
- 事件里提示 disruption budget 不允许
这时应检查:
- 副本数是否足够、是否能在其他节点快速重建
- 是否因亲和性/节点选择器太严格导致无法调度
- readiness 是否一直不通过导致可用副本不足
不要用 kubectl drain --disable-eviction 绕过卡住的 PDB,除非已经明确接受可用性后果;该参数改用 Delete API,PDB 不再生效。
进阶:当 rollingUpdate 不够稳(canary/blue-green)
某些变更风险更高:
- 大版本升级
- 配置大改
- 数据库迁移
可考虑渐进交付:
- canary:先 1% 再 10% 再 50%
- blue/green:两套环境切流量
即使没有完整平台,也可以通过“第二个 Deployment + 流量规则”近似 canary,关键是 把爆炸半径变小。
推荐组合(可以先采用的保守值)
3 副本的典型无状态 API
- Deployment:
maxUnavailable: 0,maxSurge: 1 - PDB:
minAvailable: 2 - 跨 zone 分布(spread constraints)
10+ 副本的大流量服务
- Deployment:
maxUnavailable: 10%,maxSurge: 10% - PDB:用比例预算对齐维护窗口和 SLO,不要把它当作 rollout 的共享预算
- 配合监控与自动回滚策略
最后怎么判断
PDB + rollout 参数不是“开了就万事大吉”,它们在以下条件成立时效果最好:
- 副本数足够
- 副本分布在不同故障域
- readiness/liveness/优雅下线靠谱
- 节点容量足够(或 autoscaler 配置合理)
把这些前提补齐,发布会明显更稳。
发布策略的三个判断
Q: PDB 保护什么? A: 只保护“自愿中断”(如 drain/升级),不防节点宕机。
Q: 发布卡住怎么办?
A: PDB 不会阻塞 Deployment 控制器滚动更新。优先检查 surge Pod Pending、readiness、镜像拉取和 progressDeadlineSeconds;只有 drain/eviction 被卡时才调整 PDB。
Q: surge/unavailable 怎么配?
A: 按发布期间最少需要多少 Ready Pod 配 maxSurge/maxUnavailable;PDB 另按节点维护期间允许多少自愿中断独立计算。