集群升级实战:原地升级与蓝绿集群迁移一句话定位:升级不是点个按钮,是一套有预案、有回滚、有节奏的手术——这篇把怎么升不停业务、翻车怎么回滚讲透。写在前面我见过最贵的故障,就是升级翻车。某团队周五晚上原地升级 1.28→1.29,没做兼容性测试,升级后 CNI 不兼容新 CRI,kubelet 全报错,Pod 重建后起不来,周末通宵抢救。升级这事,敬畏心比技术更重要。这篇我把在生产带过 N 次升级的 SOP 落成文字,从原地升级到蓝绿迁移,每一步都能照着跑。核心问题怎么升级不停业务?滚动升级控制面和节点,逐台 cordon/drain,业务有多副本就能零中断。升级翻车怎么回滚?原地升级可回滚 kubelet/控制面组件版本,但 etcd 数据不回滚;蓝绿迁移靠流量切回老集群。升级顺序是什么?etcd(可选)→ 控制面组件 → kubelet → kube-proxy,严格按依赖顺序。一、原理剖析1.1 K8s 版本兼容矩阵与升级路径K8s 的版本兼容靠 skew policy(偏差策略):┌──────────────────────────────────────────────┐ │ kube-apiserver: v1.30 │ │ kubelet: v1.30 / v1.29 / v1.28 │ ← kubelet 最多低 2 个 minor │ kube-proxy: v1.30 / v1.29 / v1.28 │ ← 与 kubelet 同版本或低 2 │ kubectl: v1.31 / v1.30 / v1.29 │ ← 可高/低 1 个 minor └──────────────────────────────────────────────┘升级路径铁律:规则说明一次只升一个 minor1.30→1.31,不能跳 1.30→1.32kube-apiserver 最高所有组件版本 ≤ apiserverkubelet 比 apiserver 最多低 2否则拒绝注册升级顺序:apiserver 先controller/scheduler 次之,kubelet 最后1.2 kubeadm 原地升级机制kubeadm 的升级分两层:kubeadm upgrade plan拉取新版本镜像kubeadm upgrade apply升级控制面 static podsetcd 不动或单独升级升级 kubelet/kube-proxy逐个 worker 节点验证集群健康关键点:etcd 版本由 kubeadm 管理,但升级 etcd 跨大版本(如 3.5→3.6)需单独操作,不能直接靠 kubeadm。static pod 升级:kubeadm 修改/etc/kubernetes/manifests/*.yaml的镜像版本,kubelet 自动重建 Pod。kubelet 升级:必须在每个节点手动apt install kubelet1.31.*并重启 kubelet。1.3 蓝绿集群迁移方案原地升级省机器但有风险,蓝绿迁移最安全:绿集群 v1.31蓝集群 v1.30切换应用层同步Master x3Worker x5Master x3Worker x5入口 LB/Ingress蓝绿迁移的核心是双集群并行 流量切换:部署新版本集群(绿)应用配置同步到新集群(Helm/GitOps)数据同步(数据库主从复制/对象存储同步)流量灰度切换到新集群观察,出问题切回老集群稳定后下线老集群二、实战操作2.1 升级前检查清单# 1. 备份 etcd(必做!)ETCDCTL_API3etcdctl snapshot save /backup/pre-upgrade-$(date%Y%m%d).db\--endpointshttps://127.0.0.1:2379\--cacert/etc/kubernetes/pki/etcd/ca.crt\--cert/etc/kubernetes/pki/etcd/peer.crt\--key/etc/kubernetes/pki/etcd/peer.key# 2. 检查当前版本kubeadm version kubectl version--shortkubelet--version# 3. 检查节点状态kubectl get nodes-owide# 确保所有节点 Ready,无 NotReady# 4. 检查组件健康kubectl get componentstatuses# 5. 检查 API 弃用(关键!)kubectl get--raw/metrics|grepapiserver_requested_deprecated_apis# 6. 验证所有 Pod 正常,有足够冗余kubectl get pods-A|grep-vRunning# 7. 检查 CNI/CRI 兼容性(查 CNI 项目 release notes)# Calico v3.26 支持 K8s 1.30,确认你的 CNI 版本# 8. 确认 kubeadm/kubelet 1.31 包已就绪apt-cachepolicy kubeadm升级前检查清单:etcd 快照已备份并验证可恢复集群所有节点 Ready,无异常 Pod关键 API 弃用已确认(CheckDeprecation)CNI/CSI/CRI 版本与新 K8s 兼容业务 Deployment 有 ≥2 副本,PDB 已配置维护窗口已通知业务方回滚预案已准备(版本包etcd 快照)升级目标版本包已下载,镜像已预拉取2.2 kubeadm 原地升级 SOP(1.30→1.31)Step 1:升级 kubeadm(所有 Master)# 在 master1 上先升级 kubeadmapt-mark unhold kubeadmapt-getupdateapt-getinstall-ykubeadm1.31.* apt-mark hold kubeadm# 验证kubeadm versionStep 2:升级计划检查kubeadm upgrade plan# 输出会显示:# Components that must be upgraded manually after upgrade# Upgrade the control plane and node componentsStep 3:升级第一个 Masterkubeadm upgrade apply v1.31.0# 这一步会:# - 拉取新版本控制面镜像# - 升级 etcd(小版本)# - 升级 apiserver/controller-manager/scheduler 静态 Pod# - 更新 kube-proxy 和 CoreDNS ConfigMap# - 更新证书(如果接近过期)Step 4:升级其余 Master# master2/master3 只需 diff,不用 applykubeadm upgradenodeStep 5:升级 Master 上的 kubelet 和 kube-proxy# 所有 Master 节点apt-mark unhold kubelet kubectlapt-getinstall-ykubelet1.31.*kubectl1.31.* apt-mark hold kubelet kubectl systemctl daemon-reload systemctl restart kubelet# 验证kubectl get nodes# 此时 master 版本应显示 v1.31Step 6:逐个升级 Worker 节点(零业务中断关键)# 升级 kubeadmapt-mark unhold kubeadmapt-getinstall-ykubeadm1.31.* apt-mark hold kubeadm# 调度升级(节点级)kubeadm upgradenode# 阻止调度kubectl cordon worker1# 驱逐业务 Pod(业务需有多副本PDB)kubectl drain worker1 --ignore-daemonsets --delete-emptydir-data# 升级 kubeletapt-mark unhold kubeletapt-getinstall-ykubelet1.31.* apt-mark hold kubelet systemctl daemon-reload systemctl restart kubelet# 恢复调度kubectl uncordon worker1# 验证节点版本kubectl getnodeworker1# 逐个节点重复,不要并行 drain 多个节点Step 7:升级后验证# 所有节点版本一致kubectl get nodes# 组件状态kubectl get componentstatuses# 关键 Pod 状态kubectl get pods-nkube-system# 证书有效期kubeadm certs check-expiration2.3 组件升级顺序详解Step1: kubeadm 升级Step2: 控制面 static podsapiserver→controller-manager→schedulerStep3: etcd 小版本kubeadm 自动处理Step4: kube-proxy DaemonSetStep5: kubelet 逐节点cordon→drain→upgrade→uncordonStep6: 验证健康为什么是 etcd→apiserver→controller/scheduler→kubelet?因为各组件向前兼容:apiserver 先升,能理解旧 kubelet 的请求(向下兼容)kubelet 最后升,避免新 kubelet 向旧 apiserver 发不兼容请求2.4 蓝绿集群迁移脚本框架迁移脚本骨架:#!/bin/bash# migrate-bluegreen.sh - 蓝绿集群迁移编排set-euopipefailOLD_CLUSTER_CONTEXTprod-blueNEW_CLUSTER_CONTEXTprod-greenNAMESPACE_LISTapp1 app2 app3TRAFFIC_RATIO10# 首次切 10% 流量# 1. 同步应用配置到新集群sync_manifests(){fornsin$NAMESPACE_LIST;dokubectl--context$OLD_CLUSTER_CONTEXTget ns$ns-oyaml|\kubectl--context$NEW_CLUSTER_CONTEXTapply-f-done}# 2. 数据库主从切换检查check_db_replica(){echo检查新集群数据库复制延迟...# mysql: SHOW SLAVE STATUS,检查 Seconds_Behind_Master# pgsql: SELECT pg_last_wal_receive_lsn()}# 3. 流量切换(基于 Ingress 权重或 DNS)switch_traffic(){localratio$1# 方案A: Ingress Canarykubectl--context$NEW_CLUSTER_CONTEXTsetweight prod-canary-nistio-system--weight$ratio# 方案B: DNS 权重# dig short app.example.com}# 4. 健康检查health_check(){localctx$1kubectl--context$ctxget nodes|grep-vReady|head{echo节点异常;return1}kubectl--context$ctxget pods-A--field-selectorstatus.phase!Running|head}# 主流程case$1insync)sync_manifests check_db_replica;;canary)switch_traffic$TRAFFIC_RATIOsleep300# 观察 5 分钟health_check$NEW_CLUSTER_CONTEXT;;full)switch_traffic100;;rollback)switch_traffic0# 全切回老集群;;cleanup)kubectl--context$OLD_CLUSTER_CONTEXTscale deployment--all--replicas0-napp1;;esac2.5 回滚预案原地升级回滚(有限场景):# 注意:etcd 数据无法回滚!只能回滚组件二进制版本# 回滚前务必确认:升级没产生新数据结构/资源版本# 1. 降级 kubeadmapt-getinstall-ykubeadm1.30.*# 2. 降级控制面(kubeadm 不支持降级 apply,需手动)# 修改 /etc/kubernetes/manifests/*.yaml 镜像版本回 v1.30# 3. 降级 kubeletapt-getinstall-ykubelet1.30.* systemctl restart kubelet# 4. 恢复 etcd 快照(如果数据被新版本改坏)ETCDCTL_API3etcdctl snapshot restore /backup/pre-upgrade-20260718.db --data-dir/var/lib/etcd原地降级是最后手段,优先用蓝绿回滚。蓝绿回滚(秒级):# 流量切回老集群./migrate-bluegreen.sh rollback# DNS/Ingress 权重切 0,1 分钟内生效三、踩坑与排查坑1:升级后 CNI Pod 起不来,节点 NotReady现象:kubelet 升级后,节点变 NotReady,CNI(calico)Pod CrashLoopBackOff。原因:Calico v3.25 不兼容 K8s 1.31 的 CNI spec 变化,taint 容忍度有问题。解决:升级前必须核对 CNI 兼容矩阵。Calico 升级:# 先升级 CNI(在新 K8s 版本前完成)kubectl apply-fhttps://docs.tigera.io/calico/3.27/manifests/calico.yaml# 确认 calico-node 全部 Running 后再升级 K8s坑2:drain 时卡住,PDB 拦截现象:kubectl drain worker1卡住,提示error when evicting pods/xxx: Too many pod failures。原因:业务没配 PodDisruptionBudget,或者 PDB minAvailable 设太高导致驱逐被挡。解决:# 1. 查看哪些 Pod 被拦kubectl get pdb-Akubectl describe pdb xxx-napp1# 2. 临时调低 PDB(协调业务方)kubectl patch pdb xxx-napp1--typemerge-p{spec:{minAvailable:1}}# 3. 或者强制(慎用,可能中断业务)kubectl drain worker1 --ignore-daemonsets --delete-emptydir-data--force--disable-eviction# 预防:业务必须配 PDB,minAvailable 副本数-1坑3:升级后 etcd 报错mvcc: database space exceeded现象:升级后 etcd 突然写满。原因:升级过程产生大量写(组件重新注册),本来快到 quota 的 db 被顶满。解决:升级前先压缩 etcd(见第18篇)。升级时监控 db size,提前留 30% 余量。坑4:升级后 kubelet 无法注册现象:worker 节点升级 kubelet 后一直 NotReady,日志failed to get node info: connection refused。原因:kubelet 版本比 apiserver 高 2 个 minor(比如 kubelet 1.32 连 apiserver 1.30),违反 skew policy。解决:严格按顺序,apiserver 先升,kubelet 后升。已发生的回滚 kubelet 版本。四、最佳实践生产升级走蓝绿,稳妥;测试环境可原地升级升级前 etcd 快照 异地备份一次只升一个 minor 版本严格按顺序:kubeadm→控制面→kubeletWorker 逐个升级,禁并行 drain业务 Pod ≥2 副本 PDB升级前核对 CNI/CSI/CRI 兼容矩阵升级后用kubectl get componentstatuses和节点版本验证保留老版本包(apt-mark hold),便于回滚选择业务低峰期升级升级后 24 小时密切监控准备回滚 Runbook,定期演练五、小结集群升级的本质是风险可控的变更管理。原地升级省钱但有风险,蓝绿迁移最稳但费资源——选哪个看你的业务对停机的容忍度。无论哪种,备份和回滚预案是底线。记住:升级前做兼容性测试,升级时按顺序、逐节点、慢推进,升级后验证健康。生产环境,慢就是快,稳就是赢。思考题原地升级从 1.30 升到 1.32(跳版本)会怎样?为什么 kubeadm 不支持?蓝绿迁移时,如何保证数据库零数据丢失地切换?drain 节点时,--delete-emptydir-data会不会丢数据?哪种场景安全?延伸阅读kubeadm 升级文档:https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-upgrade/版本偏差策略:https://kubernetes.io/releases/version-skew-policy/API 弃用检查:https://kubernetes.io/docs/reference/using-api/deprecation-guide/