Kubernetes生产运维14:集群升级怎么做到业务无感,从API兼容到逐节点驱逐的完整流程
写在前面
集群升级是运维里少数"做对了没人夸,做错了全公司都知道"的操作。它最怕两件事:
- 废弃API:新版本删掉了某些API版本,你的工作负载还在用,升级后突然失效
- 节点驱逐:升级节点时要把上面的Pod赶走,如果驱逐不当,业务会被打断
做到业务无感的核心不是运气,而是一套有顺序、有保护、有回退的流程:先查兼容、先升控制面、节点逐批升、每批验证、随时能退。
这篇给出这套完整流程和一份可直接用的Runbook,并结合一段EKS逐节点升级的真实脱敏经验,说明实际会踩的坑。
本文按照Kubernetes官方机制整理,并结合作者管理多云集群、做过EKS逐节点升级的脱敏经验。由于当前没有连接可验证的实验集群,命令和输出均为说明性重建,不是某次升级的原始记录。不同Kubernetes版本、托管服务(EKS/GKE/ACK等)和自建方式的升级步骤差异很大,务必以对应版本和平台的官方文档为准。升级是高风险操作,每步都要有验证和回退。
写在前面的真实经验
先说一个我在EKS升级中反复确认的经验,它定了这篇的基调:
升级前我会先借助工具审查新版本是否调整了API版本或CRD,看当前工作负载要不要改;升级时先升控制面(托管集群由云厂商升),再逐个升级Node Pool,一台一台来,每升一批观察Pod是否正常。曾经踩过的坑是:有些Pod没有指定nodegroup或调度约束,节点升级驱逐后飘到了错误的节点,所以每次升级都要特别注意Pod的调度约束。
这段是A级脱敏经验:先查兼容、控制面先升、节点逐台升、注意调度约束。下面把它展开成完整流程。文中的具体命令输出属于说明性重建,不是那次升级的原始记录。
一、升级前:查兼容,别让API废弃埋雷
2.1 看版本变更和废弃API
升级前先读目标版本和中间版本的Release Notes,重点是废弃(deprecated)和移除(removed)的API版本。Kubernetes会在若干版本前标记废弃,到某个版本正式移除。
如果你的工作负载还在用被移除的API版本,升级后这些资源会无法创建或更新。
2.2 扫描现有工作负载用了哪些废弃API
不要靠人工翻YAML。用工具扫描集群里实际在用的API版本,例如社区的废弃API检测工具,或云厂商EKS/GKE提供的升级前检查(insights)。
原则:
- 升级前扫描出所有使用废弃或将被移除API的资源
- 提前把它们迁移到新API版本,在升级前就改好
- 不只看自己的应用,还要看Ingress Controller、监控组件、CRD、Operator等第三方组件是否兼容目标版本
2.3 检查组件兼容性
除了API,还要确认:
- Ingress Controller、CNI、CSI、监控栈是否支持目标版本
- CRD和Operator是否兼容
- kubelet与控制面的版本偏差是否在支持范围内
二、升级顺序:先控制面,再节点
3.1 为什么控制面先升
Kubernetes支持控制面比节点版本高一定范围(版本偏差策略)。所以顺序是先升控制面,再升节点,反过来不行。
- 托管集群(EKS/GKE/ACK):控制面由云厂商升级,你点一下或调API,云厂商负责。没有master/slave这种自己管的概念。
- 自建集群:按kubeadm等工具的流程,先升控制面组件,多控制面节点逐个升。
3.2 节点升级:逐批而不是一起
节点升级不能一次全升,否则大量Pod同时被驱逐会打断业务。做法是逐批(甚至逐台):
对每个节点:
cordon 该节点(标记不可调度,新Pod不再进来) → drain 该节点(驱逐上面的Pod,让它们在别处重建) → 升级/替换该节点 → 节点Ready后uncordon(重新可调度) → 验证,再进行下一批托管集群常用的是替换式升级:创建新版本节点,把旧节点上的Pod迁过去,再删旧节点。原理一样,关键都是逐批、有驱逐、有验证。
3.3 cordon和drain的作用
kubectl cordon'<node>'kubectl drain'<node>'--ignore-daemonsets --delete-emptydir-datacordon:只标记节点不可调度,不动现有Pod,让新Pod不再进来drain:驱逐节点上的Pod,触发它们在其他节点重建;--ignore-daemonsets因为DaemonSet每节点都有、不需迁移,--delete-emptydir-data表示接受emptyDir数据丢失
drain是有序驱逐,会尊重PDB。
三、用PDB保证驱逐期间的可用性
4.1 PDB是什么
PodDisruptionBudget(PDB)声明一个工作负载在自愿驱逐(如drain)期间,最少要保留多少可用副本,或最多容忍多少不可用。
apiVersion:policy/v1kind:PodDisruptionBudgetmetadata:name:web-pdbnamespace:appspec:minAvailable:2# 驱逐期间至少保留2个可用selector:matchLabels:app:web4.2 PDB如何让升级无感
drain驱逐Pod时会遵守PDB:如果驱逐某个Pod会导致可用副本低于PDB的要求,drain会等待,直到有足够副本就绪才继续。
这样即使在逐节点升级、不断驱逐Pod的过程中,每个工作负载始终保持足够的可用副本,业务不中断。
设计要点:
- 关键服务都应配PDB,
minAvailable按能容忍的最小可用副本设 - 单副本服务无法在驱逐时保持可用,PDB也救不了,要先多副本化
- PDB配得太严(如minAvailable等于总副本数)会让drain卡住,永远驱逐不动
4.3 配合readiness和优雅关闭
驱逐时Pod会被终止并在别处重建。要做到无感,还需要:
- readiness探针正确,新Pod就绪后才接流量(第11篇)
- 优雅关闭,旧Pod先摘流量再终止
- 足够副本和合理的滚动,避免瞬时容量不足
四、C级生产化重建案例:升级卡在一个drain不动的节点
5.1 先说明哪些是真的,哪些是重建的
下面结合真实的EKS逐节点升级经验,但具体场景和输出是根据机制构造的生产化重建,不是某次升级的原始记录。
| 内容 | 证据属性 |
|---|---|
| 先查兼容、控制面先升、节点逐台升、注意调度约束 | A级脱敏经验 |
| cordon、drain、PDB和驱逐的机制 | Kubernetes官方机制 |
| drain卡住、PDB阻止驱逐的具体场景 | 机制一致的重建场景 |
| Namespace、资源名、副本数和终端输出 | 为讲解构造的说明性信息 |
| 某服务PDB配置过严导致drain卡住 | 模拟根因,不是某次升级原始记录 |
所有输出按真实对象关系编排,但没有连接实际集群采集,读者不能把下面的数值引用为某次升级的真实数据。
5.2 现场卡片
逐节点升级进行到某个节点时,drain一直卡住不动,节点上的一个Pod始终驱逐不掉,升级停滞。团队最初怀疑节点或Pod卡死。
重建的相对时间线:
| 相对时间 | 观察或动作 | 当时能够得出的结论 |
|---|---|---|
| T+00 | drain某节点长时间不返回 | 驱逐受阻,原因未知 |
| T+02 | 怀疑Pod卡死 | 待验证假设 |
| T+04 | drain输出提示违反PDB | 转向PDB |
| T+06 | 发现该服务PDB的minAvailable等于总副本数 | 得到可验证的主假设 |
| T+验证 | 调整PDB或先扩副本后观察drain | 单变量验证 |
相对时间只表示排查顺序,不代表真实数值。
5.3 看drain为什么卡住
kubectl drain worker-5 --ignore-daemonsets --delete-emptydir-data机制一致的说明性输出:
evicting pod app/web-xxxx error when evicting pods/"web-xxxx" -n "app": Cannot evict pod as it would violate the pod's disruption budget.drain明确说:驱逐这个Pod会违反它的PDB。不是Pod卡死,是PDB在阻止。
5.4 查PDB配置
kubectl get pdb-napp kubectl describe pdb web-pdb-napp机制一致的说明性输出:
NAME MIN AVAILABLE ALLOWED DISRUPTIONS web-pdb 3 0 (web Deployment副本数为3)关键发现:PDB的minAvailable是3,而该Deployment总共就3个副本。这意味着任何时候都不允许有Pod不可用,ALLOWED DISRUPTIONS为0,drain永远无法驱逐任何一个Pod。
证据链:
drain提示违反PDB,不是Pod卡死 + PDB minAvailable=3 + Deployment副本数也是3 + ALLOWED DISRUPTIONS=0 = 强烈支持PDB配置过严导致drain无法驱逐这仍是主假设,验证前不写成根因已闭环。
5.5 止损与单变量验证
有两种单变量修正,选影响最小的:
- 方案一:临时把副本从3扩到4,这样允许1个被驱逐,drain能继续
- 方案二:把PDB的minAvailable从3改成2,允许1个不可用
方案一不降低可用性,更稳妥。只改副本数这一项:
kubectl scale deployment web-napp--replicas=4kubectl rollout status deployment/web-napp--timeout=5m然后重试drain:
kubectl drain worker-5 --ignore-daemonsets --delete-emptydir-data kubectl get pdb web-pdb-napp机制一致的说明性输出:
node/worker-5 drained NAME MIN AVAILABLE ALLOWED DISRUPTIONS web-pdb 3 1扩到4副本后,PDB允许1个被驱逐,drain顺利完成,升级继续。
这些输出分别证明不同范围的事实:
| 输出 | 能够支持 | 不能单独证明 |
|---|---|---|
| ALLOWED DISRUPTIONS变1 | 有了可驱逐余量 | 所有节点都能顺利drain |
| node drained成功 | 该节点已安全腾空 | 业务完全无感 |
| 副本扩到4 | 驱逐期间仍满足minAvailable | 长期需要4副本 |
5.6 根因闭环与永久修复
- 临时:扩副本让drain继续,完成本次升级
- 永久:修正PDB设计,
minAvailable不应等于总副本数,要留出至少1个可驱逐余量;或按可用性需求同时提高副本数和PDB - 升级完成后如果临时扩了副本,按需恢复
这也印证了那段真实经验:升级前的准备(包括PDB和副本设计)比升级动作本身更重要。PDB配错不是升级时才暴露,而是升级时才被"用到"。
五、完整升级Runbook
一、升级前(准备) [ ] 阅读目标版本和中间版本的Release Notes,列出废弃和移除的API [ ] 用工具扫描集群在用的废弃API,提前迁移到新版本 [ ] 确认Ingress Controller、CNI、CSI、监控、CRD、Operator兼容目标版本 [ ] 确认关键服务多副本且配了合理的PDB [ ] 检查Pod的调度约束,避免驱逐后飘到错误节点 [ ] 在测试集群先完整升一遍,观察一段时间 [ ] 准备回退预案,确认能退到什么程度 二、升级控制面 [ ] 选低峰窗口 [ ] 托管集群触发控制面升级,自建集群逐个升控制面节点 [ ] 验证控制面组件健康、API正常 三、逐批升级节点 [ ] 一批一台或少量节点:cordon → drain(遵守PDB)→ 升级/替换 → Ready后uncordon [ ] 每批后验证:Pod正常调度、无异常重启、关键服务可用 [ ] 观察调度约束,确认Pod没飘到错误节点 [ ] 有问题立即暂停,不继续下一批 四、升级后验证 [ ] 核心业务功能验证 [ ] 监控指标、错误率、延迟对比升级前 [ ] 组件和CRD工作正常 [ ] 恢复升级期间的临时调整(如临时扩的副本) 五、回退 [ ] 若控制面升级异常,按平台回退能力处理 [ ] 若某批节点异常,暂停并排查,必要时回退该批 [ ] 记录问题,复盘后再继续这份Runbook可以直接改成你集群的检查项。
六、监控与治理
7.1 升级期间监控什么
- 每批节点升级后Pod的调度和就绪情况
- 关键服务的可用副本数是否始终满足PDB
- 业务错误率、延迟与升级前对比
- 是否有Pod因调度约束飘到错误节点
- 控制面组件健康
7.2 治理
- 升级前检查(API扫描、兼容性、PDB、调度约束)标准化成清单
- 关键服务默认多副本加PDB,PDB不设成等于总副本数
- 固定升级节奏(如跟随社区版本每隔一到两个小版本升一次),不跨大版本跳升
- 每次升级后复盘,把踩的坑回补进Runbook
- 测试集群先行,生产低峰窗口执行
七、常见误区
误区1:不查废弃API直接升
工作负载用了被移除的API,升级后失效。
误区2:节点一次全升
大量Pod同时驱逐会打断业务,要逐批。
误区3:不配PDB就drain
没有PDB保护,驱逐可能瞬间打掉过多副本。
误区4:PDB设成等于总副本数
ALLOWED DISRUPTIONS为0,drain永远卡住。
误区5:单副本服务指望升级无感
单副本驱逐必然中断,要先多副本化。
误区6:忽略Pod调度约束
驱逐后Pod可能飘到错误节点,升级前要检查。
误区7:先升节点再升控制面
违反版本偏差策略,顺序必须控制面先。
误区8:不在测试集群预演
生产直接升,遇到兼容问题就是事故。
八、面试怎么说
60秒版本
集群升级我按固定流程:升级前先查废弃和移除的API,用工具扫描集群在用的版本,提前迁移,还要确认Ingress、CNI、CSI、监控和CRD兼容目标版本。顺序是控制面先升,托管集群由云厂商升,然后节点逐批升,不能一次全升。每个节点cordon加drain,drain会遵守PDB保证关键服务始终有足够可用副本。每批升完验证Pod调度和业务,有问题就暂停。我踩过的坑是有些Pod没设调度约束,驱逐后飘到错误节点,所以每次都要检查。最后测试集群先行、低峰执行、有回退预案。
3分钟场景版本
假设逐节点升级时drain卡在一个节点不动。我先看drain输出,提示"会违反PDB",说明不是Pod卡死,是PDB在阻止。查这个服务的PDB,minAvailable设成了3,而Deployment副本也是3,导致ALLOWED DISRUPTIONS为0,任何驱逐都不允许,drain永远卡住。根因是PDB配置过严。我选影响最小的方案,临时把副本扩到4,让PDB允许1个被驱逐,drain随即完成,升级继续。永久修复是改PDB设计,minAvailable不能等于总副本数,要留可驱逐余量。这件事说明升级的坑往往在准备阶段就埋下了:PDB、副本、调度约束这些不是升级时才配,而是升级时才被用到。所以我把这些检查都放进升级前的Runbook。
九、延伸问答
1. 为什么控制面要先升
版本偏差策略允许控制面比节点高一定范围,反过来不行,所以控制面先升。
2. 节点为什么要逐批升
一次全升会同时驱逐大量Pod打断业务,逐批加PDB才能无感。
3. cordon和drain区别
cordon只标记不可调度不动现有Pod,drain会驱逐Pod让它们在别处重建。
4. PDB怎么保证升级无感
drain遵守PDB,驱逐会导致可用副本不足时会等待,保证始终有足够副本。
5. PDB设成minAvailable等于总副本会怎样
ALLOWED DISRUPTIONS为0,drain永远无法驱逐,升级卡住。
6. 废弃API怎么提前发现
用社区废弃API检测工具或云厂商升级前检查扫描集群实际在用的版本。
7. 托管集群升级和自建有什么不同
托管集群控制面由云厂商升,你不用管master;自建要自己按kubeadm等流程升控制面。
8. 升级出问题怎么回退
控制面按平台回退能力处理,节点批次异常就暂停排查,必要时回退该批并复盘。
小结
- 升级最怕废弃API和节点驱逐打断业务,靠流程而非运气解决。
- 升级前查废弃API并提前迁移,确认组件兼容。
- 顺序是控制面先升,再节点逐批升。
- 节点cordon加drain,drain遵守PDB保证可用副本。
- 关键服务多副本加PDB,PDB不能等于总副本数。
- 检查Pod调度约束,避免驱逐后飘到错误节点。
- 测试集群先行,低峰执行,每批验证,有回退预案。
- 升级的坑多在准备阶段埋下,把检查标准化成Runbook。
下一篇预告
下一篇是本专栏最后一篇:监控、告警与故障复盘。我们会讲清RED和USE方法、SLO、告警降噪、故障时间线和Postmortem,把前面所有排障经验收敛成稳定性闭环。
参考资料
- Kubernetes官方文档:Version Skew Policy
- Kubernetes官方文档:Upgrade A Cluster
- Kubernetes官方文档:Deprecated API Migration Guide
- Kubernetes官方文档:Kubernetes Deprecation Policy
- Kubernetes官方文档:Specifying a Disruption Budget for your Application
- Kubernetes官方文档:Disruptions
- Kubernetes官方文档:Safely Drain a Node
- Kubernetes官方文档:Upgrading kubeadm clusters
- Kubernetes官方文档:kubectl drain
- Kubernetes官方文档:PodDisruptionBudget API