系统设计面试:如何设计尊重 PDB 与拓扑分布的维护编排器?
题干与适用场景
集群需要滚动升级节点,但工作负载设置了 PodDisruptionBudget 和 topology spread constraints。请设计维护编排器,说明候选节点选择、驱逐并发、不可调度、容量预留、故障恢复和可观测性。
Kubernetes 将 PDB 定义为限制自愿中断同时影响的 Pod 数量;维护工具应使用 Eviction API,让 PDB 参与准入。拓扑分布约束则控制 Pod 在 zone、node 等故障域中的偏斜。高质量设计要把两个约束放进同一个控制循环,不能只检查当前 Pod 数量后批量删 Pod。
面试官考察点
- 是否区分自愿中断、硬件故障等非自愿中断及其保证边界。
- 是否通过 Eviction API 尊重 PDB,而不是直接删除 Pod。
- 是否按 topology spread 的
maxSkew、whenUnsatisfiable和标签选择器评估影响。 - 是否设计逐节点、逐故障域的并发和容量预留。
- 是否处理 PDB 阻塞、Pod 未就绪、重试、超时和回滚。
- 是否能用事件、指标和审计记录解释每次维护决策。
回答前需要澄清的问题
- 维护是节点 OS 升级、实例类型替换,还是紧急修复?紧急故障不一定受 PDB 保护。
- 工作负载的副本数、PDB、拓扑域和容量余量是多少?
- 目标是最短总时长、最小用户风险,还是严格保持每个 zone 的冗余?
- 集群是否有 Cluster Autoscaler、临时节点池或跨 zone 容量限制?
- 允许多长时间等待 PDB,超时后是暂停、降级还是人工批准?
30 秒回答框架
我会把维护编排器做成声明式控制器:先读取节点、Pod、PDB、拓扑约束和可调度容量,计算单次驱逐对健康副本和拓扑偏斜的影响,再通过 Eviction API 小批量执行。控制器以节点和故障域为并发边界,等待替代 Pod Ready 后再继续;PDB 阻塞或拓扑约束无法满足时暂停并给出原因,不直接删除 Pod。所有操作带幂等键、超时、回滚和指标,紧急非自愿故障走单独的风险路径。
分步骤深入解答
1. 定义状态与约束输入
维护任务包含目标节点、升级批次、最大并发、超时时间和回滚策略。控制器缓存节点标签、Pod owner、Ready 状态、PDB disruptionsAllowed、拓扑约束及可用容量;每次决策都重新读取关键状态,避免只依赖旧快照。
2. 计算候选节点和安全批次
先过滤已封锁、承载系统关键 Pod 或没有替代容量的节点,再按 zone、rack 和 workload 分组。单批次不能同时影响同一工作负载超过 PDB 允许值,也要模拟驱逐后新 Pod 的拓扑偏斜。优先选择有充足余量且不会让某个故障域成为单点的节点。
3. 使用 Eviction API
对自愿维护调用 Eviction API,让 Kubernetes 根据 PDB 判断是否允许。返回冲突或不可用时记录具体 PDB、工作负载和重试时间。直接删除 Pod 会绕过预算,应该仅在明确的紧急破坏性流程中使用,并要求更高权限和人工确认。
读取约束 -> 选择一个节点 -> 创建 eviction 请求
-> PDB 允许?否:退避并重新评估
-> 是:等待替代 Pod Ready 与拓扑恢复
-> 成功:标记节点完成;失败:暂停批次并回滚或升级4. 处理拓扑与容量反馈
驱逐不是终点。新 Pod 可能因 DoNotSchedule、缺少 topology key、亲和性或容量不足而 Pending。控制器监控调度事件,必要时先扩容临时节点池或改变批次顺序;不能为了完成升级而放宽工作负载约束。ScheduleAnyway 也要记录最终偏斜和成本影响。
5. 设计并发、租约与幂等
用任务租约锁定一个节点和控制器世代,避免多个维护器重复驱逐。默认每个 zone 一个小并发或每个工作负载一个令牌桶,只有替代 Pod Ready、PDB 预算恢复且拓扑偏斜回到阈值内才领取下一个令牌。重复执行同一 eviction 应识别为已处理,控制器重启后从 API 状态恢复。
6. 失败、超时和回滚
如果 Pod 长时间 Pending、PDB 长期为零、节点排空超时或新节点不健康,暂停后续批次并解除未必要的 cordon。已经升级的节点不能简单“回滚”成旧版本时,要保留旧节点池或切换到已验证镜像。回滚目标是恢复服务冗余,不是强行让维护百分比达到 100%。
7. 可观测性与演练
记录每次选择的节点、受影响工作负载、PDB 前后预算、拓扑计数、eviction 响应、Pending 原因和耗时。指标包括成功驱逐率、PDB 阻塞时长、拓扑最大偏斜、恢复 Ready 延迟、跨 zone 流量和任务重试。演练至少覆盖单 zone 容量不足、PDB 为零、调度器故障、控制器重启和节点突然消失。
高质量示范回答
我会把编排器建模为按节点和故障域推进的声明式控制器。它读取 Pod owner、Ready、PDB、topology spread、节点标签和可调度容量,先模拟一次驱逐对健康副本与 maxSkew 的影响,再通过 Eviction API 发起小批次请求。每个 zone 设并发令牌,只有替代 Pod Ready、PDB 预算恢复且拓扑约束满足,才继续下一节点。
PDB 返回冲突、Pod 进入 Pending、容量不足或等待超时都导致暂停并记录原因;控制器可以扩容临时节点池或调整顺序,但不直接删除 Pod,也不偷偷放宽约束。租约和世代号保证重试幂等,重启后从 API 状态恢复。指标覆盖预算阻塞、最大拓扑偏斜、Ready 延迟、重试与跨 zone 成本,演练通过后再扩大批次。
常见错误
- 直接删除 Pod 加快排空 → 绕过 PDB → 对自愿维护使用 Eviction API。
- 只看
disruptionsAllowed→ 新 Pod 可能无法按拓扑约束调度 → 先模拟偏斜和容量。 - 一次驱逐多个 zone 的副本 → 失去故障域冗余 → 以 zone 和 workload 设置并发令牌。
- PDB 阻塞就临时修改预算 → 可能把维护风险转给用户 → 暂停、扩容或请求批准。
- 只等待 Pod 被删除 → 服务可能长期没有 Ready 替代 → 以 Ready、调度事件和超时驱动状态机。
- 没有控制器租约 → 多个维护任务重复操作 → 用租约、世代号和幂等状态恢复。
追问及应对
PDB 能保护节点突然宕机吗?
不能完全保护。PDB主要限制自愿中断;硬件故障和资源耗尽等非自愿中断可能直接减少副本。设计仍需多故障域、副本和容量冗余。
为什么不能用 kubectl delete pod?
直接删除绕过 PDB 的驱逐准入。维护编排器应调用 Eviction API,让 API Server 根据预算返回允许或冲突。
PDB 允许但新 Pod 一直 Pending 怎么办?
暂停批次,读取调度事件和拓扑计数,扩容或更换候选节点;不要继续驱逐造成更多冗余缺口。
ScheduleAnyway 是否可以忽略偏斜?
不能忽略。它表示调度器会偏好降低偏斜的节点,但仍可能产生偏斜。控制器要记录实际分布、成本和冗余风险。
控制器重启如何避免重复驱逐?
用任务租约、节点锁、控制器世代号和持久状态;恢复时以 API 中的 Pod、Eviction 和节点状态为准,而不是重放内存队列。
什么时候允许紧急强制删除?
仅在确认自愿维护路径无法处理的紧急风险、具备更高权限和人工批准时,并明确记录可能违反 PDB 和损失冗余的后果。