一次由资源配额与滚动策略叠加引发的发布停滞复盘(匿名化合成案例)。
某业务集群中,一个 20 副本的 Deployment 发布后长时间停在“等待滚动更新完成”。流水线的 kubectl rollout status 超时退出,旧版本 Pod 仍在提供服务,新 ReplicaSet 没有产生可用副本。与此同时,计划中的节点维护被 PDB 拒绝,kubectl drain 报错无法驱逐 Pod。对外没有明显 5xx,但发布和节点维护同时卡住。
maxSurge: 25%、maxUnavailable: 0,命名空间内 Pod 配额和 CPU 配额都没有为新副本预留空间。ALLOWED DISRUPTIONS 为 0,又看到 drain 被拒绝,判断是 PDB 阻止了滚动更新。FailedCreate,原因是创建 Pod 超出 ResourceQuota;这才是发布的直接阻塞点。maxSurge: 0、maxUnavailable: 1。新版本延迟约 20 多分钟上线,旧版本持续服务,未出现整站不可用。恢复阶段短暂减少一个副本,接口成功率和延迟没有明显变化。真正的次生影响是节点维护窗口被拖延:需要升级的节点在多轮发布期间一直无法排空,增加了集群版本和硬件维护的堆积。配额拒绝只发生在这个命名空间,其他业务未受影响。
最初的两个信号都指向 PDB:ALLOWED DISRUPTIONS 为 0,而且 kubectl drain 明确报“would violate the pod's disruption budget”。但 PDB 只约束 Eviction API,kubectl drain、descheduler 和节点缩容会走驱逐接口;Deployment 的滚动更新由 ReplicaSet 直接创建和删除 Pod,根本不查询 PDB。把 PDB 当成发布停滞原因,是把两个独立约束混成了一个。
第二个误判是怀疑调度器没有资源。检查 Pod 列表后却发现根本没有新 Pod:配额准入在调度之前就拒绝了创建请求,所以不会出现 Pending。推翻前一个假设的证据是新 ReplicaSet 的 FailedCreate 事件,以及 kubectl rollout status 只报“waiting”而不报驱逐失败。kubectl describe deployment 的状态比较笼统,必须继续查看 ReplicaSet 事件才能看到配额拒绝。
PDB 的独立问题是真实存在的:它的可用下限等于副本数,维护时没有任何驱逐余量。但它解释的是 drain 为什么失败,不是发布为什么没有新 Pod。
发布策略要求先创建 25% 的额外副本,20 副本即需要 5 个新 Pod;命名空间的 pods 配额已经用满,requests.cpu 也没有同等余量。ReplicaSet 创建 Pod 的请求被配额准入拒绝,滚动更新无法推进。Deployment 没有在发布前计算“当前用量 + maxSurge 需求”是否小于配额上限,流水线也只在超时后报错,没有把 FailedCreate 作为失败原因提取出来。
PDB 的 minAvailable 被设置成与副本数相同,导致任何自愿驱逐都没有许可。它和配额共同造成了“发布走不动、节点也排不空”的局面,但作用点完全不同。
先停止重复重试,避免控制器持续打配额接口,也避免掩盖原始事件:
kubectl rollout pause deployment/<deploy> -n <ns>
kubectl get deployment <deploy> -n <ns> -o jsonpath='{.spec.strategy}{"\n"}'
kubectl get rs -n <ns> -l app=<app>
kubectl describe rs <new-rs> -n <ns>
kubectl describe resourcequota -n <ns>
kubectl get events -n <ns> --field-selector reason=FailedCreate --sort-by=.lastTimestamp
确认是可接受的短暂容量下降后,把策略改成不需要额外 Pod 的滚动方式,然后恢复发布:
kubectl patch deployment <deploy> -n <ns> --type=merge \
-p '{"spec":{"strategy":{"type":"RollingUpdate","rollingUpdate":{"maxSurge":0,"maxUnavailable":1}}}}'
kubectl rollout resume deployment/<deploy> -n <ns>
kubectl rollout status deployment/<deploy> -n <ns> --timeout=10m
如果业务不能少一个副本,正确做法是申请临时配额或先腾出真实余量,而不是把 maxUnavailable 强行改成大于业务承受能力的值。也不要用手动删除旧 Pod 的方式“腾配额”:ReplicaSet 仍按期望副本数补位,删除会造成服务容量波动,新 Pod 还可能继续被配额拒绝。需要释放空间时,应先确认资源归属,再由发布系统统一回收。
发布完成后再处理 PDB:在确认反亲和、冗余和流量承载能力后,把 minAvailable 降到低于副本数,或改用 maxUnavailable;不要为了这一次 drain 直接删除 PDB。修改后先看 ALLOWED DISRUPTIONS 是否大于 0,再执行 drain。
ceil(期望副本数 × maxSurge) 的 Pod 数和资源请求,并设置 80% 左右的用量告警。FailedCreate、Pod Pending、发布超时纳入不同告警。发布超时只说明现象,必须自动附上 ReplicaSet 事件和配额用量。minAvailable 等于副本数、maxUnavailable: 0 且为单副本、选择器误选多个工作负载,都会让维护季无法推进。把“drain 演练”纳入集群例检。kubectl rollout status --timeout 快速失败,并在失败时暂停而不是无限重试;恢复动作由明确的 runbook 驱动。FailedCreate 的关键对象合上页面,先回答,再展开对照。
maxSurge: 0 与 maxUnavailable: 1 的取舍是什么?FailedCreate。还要检查 ResourceQuota 的 hard/used 和 Deployment 的有效策略;如果根本没有新 Pod,问题多半在配额或准入,而不是调度器。
小程序版
微信扫码,随时查命令