K8s 升级别靠祈祷:兼容性检查和回滚演练,我踩坑踩出来的一套土办法

简介: K8s 升级别靠祈祷:兼容性检查和回滚演练,我踩坑踩出来的一套土办法

K8s 升级别靠祈祷:兼容性检查和回滚演练,我踩坑踩出来的一套土办法

作者:Echo_Wish · Nov 6, 2026 · @Echo_Wish

先说结论:升级不可怕,没演练过回滚才可怕

K8s 升级出事,十有八九不是升级步骤写错了,而是出事之后发现没人真的试过怎么回滚。这种场面我见过不止一次:控制面刚升完,业务一个接一个报错,Ingress 起不来,Helm 里一堆老 API 被直接拒掉,群里一片“回滚回滚”,然后所有人面面相觑——回滚到底怎么回?

我一直觉得,大家对升级这件事的注意力分配是反的。九成的时间花在“怎么升”上:读文档、抄命令、挑窗口、通知业务;剩下一成,在方案最后补一句“如有问题则回滚”,就算预案了。可 K8s 偏偏有个很扎心的事实:控制面没有“降级”这个按钮,真要退回去,靠的是 etcd 快照加旧版本组件。这条路你不提前走一遍,出事的时候基本走不通。

所以这篇文章我只想讲一个观点:回滚能力不是“出事后的预案”,而是“升级前必须交付的产物”。后面我按“查兼容性、搭演练环境、演练升级、演练回滚”的顺序,把自己这套土办法掰开了讲,代码都是改改就能用的。

第一步:兼容性要查三层,别只查第一层

兼容性不是一个问题,是三个问题,很多人只查了最上面那层。

  1. 版本偏差:各组件之间到底能差几个版本。
  2. API 弃用:你的 YAML、Helm Chart、Operator 还在用的老 API,目标版本里还在不在。
  3. 周边生态:Ingress Controller、CNI、CSI、cert-manager、自研 Webhook,它们各自声明支持的 K8s 版本范围。

先说版本偏差,这张表建议直接贴在升级方案里(以社区文档为准,升级前再核对一遍你的目标版本):

组件 与 kube-apiserver 的关系
kubelet、kube-proxy 不能比 apiserver 新,最多落后 3 个小版本
kube-controller-manager、kube-scheduler 不能比 apiserver 新,最多落后 1 个小版本
kubectl 前后各差 1 个小版本以内
控制面升级节奏 一次只升一个小版本,不能跳

这张表有个隐含的好消息:控制面先升、节点后升,中间有一段“混合版本”的安全窗口。但它也藏着一个坑,后面讲回滚的时候我会回来说。

再说 API 弃用,这是最容易踩雷的地方。我的做法是“动态加静态”两手抓:

# 1. 动态:线上集群里,谁还在调用即将被删除的 API
kubectl get --raw /metrics | grep apiserver_requested_deprecated_apis
# 看 removed_release 这个标签,就是该 API 会在哪个版本被删掉

# 2. 存量:扫描集群里已有的资源和 Helm release(kubent、pluto 二选一)
kubent --context prod
pluto detect-helm -o wide --target-versions k8s=v1.34.0

# 3. 静态:扫描代码仓库里的清单,连 CI 模板也别放过
pluto detect-files -d ./manifests --target-versions k8s=v1.34.0

注意那个 metrics 只能抓到“被调用过”的 API,所以观察时间要拉长,至少覆盖一个完整的发布周期;而半年才部署一次的那些东西,最容易被动态手段漏掉,必须靠静态扫描兜底。这是我吃过亏的地方。

最后是周边组件,这块没有银弹,只能一个个翻它们的兼容矩阵。有一条经验:重点盯那些 failurePolicy: Fail 的 Admission Webhook。它要是在升级期间起不来,整个集群的创建和更新操作都会被它卡死,到时候你连修它的 Pod 都建不出来。升级前要么确认它高可用,要么临时改成 Ignore,并且把这件事写进升级清单。

第二步:演练环境别太干净,要“长得像生产”

演练环境最大的坑是太干净:一个空集群,升一百次都不会出事,你演练完信心满满,上生产照样翻车。所以我习惯分两层来搭。

  • 轻量层,用 kind 拉一个目标版本的集群,专门校验清单兼容性,十分钟就能出结果,适合每次改完清单就跑。
  • 完整层,用 kubeadm 在几台测试机或虚机上,按生产的版本、CNI、CSI、Ingress 原样搭一套。规模可以小,但配置一定要像,这一层才是真正演练升级和回滚的地方。

先看轻量层,拿目标版本的 kind 集群,让 API server 帮你做一次服务端校验:

# kind-v134.yaml(镜像 tag 以 kind 发布页为准)
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    image: kindest/node:v1.34.0
  - role: worker
    image: kindest/node:v1.34.0
kind create cluster --name upgrade-drill --config kind-v134.yaml

# 先把你们用到的 CRD 装上,否则 CRD 类资源会报 no matches for kind
# 然后把仓库里所有清单,对着“目标版本”的 API server 校验一遍
fail=0
for f in $(find ./manifests -name '*.yaml'); do
  if ! kubectl apply --dry-run=server -f "$f" >/dev/null 2>/tmp/err.log; then
    echo "FAIL: $f"; cat /tmp/err.log; fail=1
  fi
done

# Helm 的同理,渲染完直接丢给 API server 校验
helm template my-app ./charts/my-app -f values-prod.yaml \
  | kubectl apply --dry-run=server -f -

exit $fail

这段脚本我建议直接塞进 CI。以后谁提交了一个目标版本不认的清单,流水线当场就红,根本不用等到升级那天才发现。

至于完整层,有条件的话可以把生产的 etcd 快照恢复进去,这样演练集群里的对象、命名空间、各种历史包袱都是真的。但要记住快照里有 Secret,先做脱敏,或者至少把演练环境的访问权限收紧。这一步不是必须的,但我试过一次,光是靠它就翻出了两个“谁也记不得是谁建的”的老资源,当场省了一次线上事故。

第三步:升级演练的目标是跑通、记时间、找坑

演练升级别当走流程,它要交付三样东西:一份跑通的步骤、每一步的实际耗时、一张踩坑清单。耗时尤其重要,它直接决定你生产窗口要申请多久。

动手之前,先给集群拍张“现场照片”,升级后才有东西可比:

kubectl get nodes -o wide   > before-nodes.txt
kubectl get pods -A -o wide > before-pods.txt
kubectl get pdb -A          > before-pdb.txt
# 重点看 pdb 里 ALLOWED DISRUPTIONS 为 0 的,它们会让 drain 卡住不动

那个 PDB 是演练里最常翻出来的问题。有的业务副本数是 1,还配了 minAvailable: 1,drain 的时候就会一直等,等到超时。这种东西不演练,生产窗口里就是半夜十二点对着一个转圈的终端发呆。

然后按“控制面先、节点后”的顺序来,第一台控制面节点(版本号按实际替换):

apt-mark unhold kubeadm && apt-get update
apt-get install -y kubeadm='1.34.x-*' && apt-mark hold kubeadm

kubeadm upgrade plan          # 看看能升到哪、哪些组件会变
kubeadm upgrade apply v1.34.x

kubectl drain <cp-node> --ignore-daemonsets --delete-emptydir-data --timeout=300s
apt-mark unhold kubelet kubectl
apt-get install -y kubelet='1.34.x-*' kubectl='1.34.x-*'
apt-mark hold kubelet kubectl
systemctl daemon-reload && systemctl restart kubelet
kubectl uncordon <cp-node>

其余控制面节点用 kubeadm upgrade node,Worker 节点也是,一台一台来:

# 在 worker 上
apt-get install -y kubeadm='1.34.x-*'
kubeadm upgrade node

# 在管理机上
kubectl drain <worker> --ignore-daemonsets --delete-emptydir-data --timeout=300s

# 回到 worker 上,升 kubelet 并重启
apt-get install -y kubelet='1.34.x-*' kubectl='1.34.x-*'
systemctl daemon-reload && systemctl restart kubelet

# 管理机上
kubectl uncordon <worker>

光有步骤不够,每一步后面都得有一个“门禁”,不过就停,别硬着头皮往下升。我自己用的是这样一个脚本,土,但好使:

#!/usr/bin/env bash
# gate.sh —— 每升完一台就跑一遍,不过就停
set -uo pipefail
fail=0

kubectl get --raw='/readyz' >/dev/null 2>&1 || {
    echo "apiserver /readyz 不通"; fail=1; }

not_ready=$(kubectl get nodes --no-headers | awk '$2!="Ready"' | wc -l)
[ "$not_ready" -eq 0 ] || {
    echo "有 $not_ready 个节点不是 Ready"; fail=1; }

bad=$(kubectl get pods -A --no-headers | awk '$4!="Running" && $4!="Completed"' | wc -l)
[ "$bad" -eq 0 ] || {
    echo "有 $bad 个 Pod 状态异常"; kubectl get pods -A | grep -Ev 'Running|Completed'; fail=1; }

kubectl run dns-test --rm -i --restart=Never --image=busybox:1.36 -- \
  nslookup kubernetes.default >/dev/null 2>&1 || {
    echo "集群 DNS 解析失败"; fail=1; }

exit $fail

演练时我会故意干几件“坏事”:升级到一半把某个节点的网络掐掉,或者把一个 Webhook 的 Pod 停掉,看看门禁能不能拦得住、你的人能不能反应过来。演练的价值不在于一切顺利,而在于你提前见过它不顺利的样子。

第四步:回滚演练,三层回滚各走一遍

这一步最容易被跳过,也最值钱。先把话说在前面:kubeadm 不支持控制面降级,K8s 也没有官方的“一键降级”。所以回滚不是一个动作,而是三层,从轻到重:

  1. 应用层:绝大多数“升级事故”其实是应用和新版本不兼容,回滚应用就够了。
  2. 节点层:个别 worker 升级后异常,把它摘掉处理,不动整个集群。
  3. 控制面:etcd 快照加旧版本组件,这是最后的手段,也是最重的一刀。

先说最轻的应用层,简单到不用多说,但要确认你的 Helm 历史版本确实留着:

kubectl rollout undo deployment/my-app -n prod
helm history my-app -n prod
helm rollback my-app 12 -n prod

节点层我的建议是别原地降级 kubelet,直接摘掉、用旧版本镜像重建,更干净:

kubectl drain <bad-node> --ignore-daemonsets --delete-emptydir-data
kubectl delete node <bad-node>
# 用升级前的旧版本镜像重装该节点,然后在控制面上生成加入命令
kubeadm token create --print-join-command

最重的是控制面。升级之前必须做的事,是把 etcd 快照和配置目录都备下来,两样缺一不可:

ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-pre-upgrade.db \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

etcdutl snapshot status /backup/etcd-pre-upgrade.db -w table   # 验证快照没坏
cp -a /etc/kubernetes /backup/kubernetes-pre-upgrade          # 清单、证书、kubeconfig 一起备

恢复这件事,要在演练集群上真刀真枪地做一遍,单控制面节点的流程大致是这样:

# 1) 停掉控制面静态 Pod:把清单挪走,kubelet 会自动把它们停掉
mkdir -p /backup/manifests-stopped
mv /etc/kubernetes/manifests/*.yaml /backup/manifests-stopped/

# 2) 把快照恢复到一个新目录
etcdutl snapshot restore /backup/etcd-pre-upgrade.db --data-dir /var/lib/etcd-restored

# 3) 把升级前备份的静态 Pod 清单放回去(旧版本的 apiserver、etcd 等)
cp /backup/kubernetes-pre-upgrade/manifests/*.yaml /etc/kubernetes/manifests/
#    然后手动改 etcd.yaml:volumes 里 etcd-data 的 hostPath.path 指向 /var/lib/etcd-restored

# 4) 把 kubelet、kubeadm 的软件包也装回旧版本,再重启 kubelet

演练时你会撞上三个现实问题,这也是我说“必须演练”的原因:

  • 快照之后写进去的数据全部丢失。Deployment 改了、ConfigMap 更新了,统统回到快照那一刻。所以回滚窗口里必须冻结发布,并且提前说好这件事谁来拍板。
  • 前面那张版本偏差表里埋的坑在这儿:kubelet 不能比 apiserver 新。控制面一旦回退,已经升级过的 worker 也得跟着退,或者重建。
  • 备份文件在哪、谁有权限拿、恢复用的 etcdutl 版本对不对,这些细节只有真做一遍才知道。

最后,回滚要有“触发条件”,别等到出事了再开会讨论要不要回滚。下面这张表是我的写法,阈值仅为举例,按你们的业务来:

现象 先做什么 什么时候升级为整体回滚
单个业务异常 应用层回滚 同类异常扩散到多个核心业务
个别节点 NotReady 摘除并重建该节点 NotReady 节点超过总数的 20%
apiserver 的 /readyz 持续失败 查日志、重启组件 持续 10 分钟仍无法恢复

以上三层,每一层都得有人真的执行过一遍、有人拿着秒表记录过,才叫“有回滚能力”。

最后聊几句心里话,再送你一份清单

写到这儿,我想说几个自己的感受,不一定对,供你参考。

第一,升级要“小步快跑”,别攒。K8s 一年大约出三个小版本,社区只维护最近三个,你要是拖上一年半载,最后就得连升好几级,每一级都是一次赌博。我宁愿一个小版本一个小版本地升,每次的变化量小,出了问题也好定位。

第二,演练要有“对抗性”。看着别人敲命令、自己点头说“嗯嗯没问题”,那叫观摩,不叫演练。真正有用的是有人专门负责“捣乱”:突然断个节点、停个 Webhook、把快照路径藏起来。你不吓自己一下,生产会帮你吓。

第三,把演练结果写成 Runbook,而不是留在某个人的脑子里。每一步多久、卡在哪、谁来拍板回滚,全部落成文字。人会离职、会休假、会半夜睡得很沉,文档不会。

最后一点最实在:我越来越觉得,一个团队的运维成熟度,不看它升级有多快,而看它回滚有多从容。能从容回滚的团队,反而敢更勤地升级,因为他们知道自己有退路。有退路的人,走得才稳。

下面这份清单,你可以直接复制到自己的升级方案里,每一项都打上勾再动手:

  • [ ] 版本偏差表已核对,控制面升级路径是逐个小版本,没有跳级
  • [ ] 已用 metrics、kubent 或 pluto 扫描集群存量,已用 pluto 扫描仓库清单
  • [ ] Ingress、CNI、CSI、cert-manager 等周边组件已确认兼容目标版本
  • [ ] failurePolicy: Fail 的 Webhook 已确认高可用,或已有临时处理方案
  • [ ] kind 服务端校验已接入 CI,所有清单对目标版本通过
  • [ ] 完整演练环境按生产配置搭建,升级全流程已跑通并记录每步耗时
  • [ ] PDB 已检查,没有会让 drain 卡死的配置
  • [ ] 每一步升级后都有门禁脚本,且演练时验证过它能拦得住问题
  • [ ] 升级前已备份 etcd 快照与 /etc/kubernetes,并验证过快照可用
  • [ ] 应用层、节点层、控制面三层回滚,都在演练环境真实执行过
  • [ ] 回滚触发条件和拍板人已写进 Runbook,窗口期已约定冻结发布

这套办法不高级,甚至有点笨,但它救过我。如果你照着做完觉得哪里不对劲,或者踩了我没提到的坑,欢迎来跟我聊聊,咱们一起把它补全。

目录
相关文章
|
7天前
|
JavaScript API iOS开发
DeepSeek Harness下载、安装、配置一篇搞定(2026整理,送常用插件)
DeepSeek Harness(DSH)是DeepSeek于2026年开源的MIT协议AI Agent框架,支持插件化组装模型、工具、沙箱与UI,兼容主流大模型。本文提供免Node.js的桌面端安装包(Win/macOS),含详细图文教程及热门插件推荐。
3437 33
|
7天前
|
Linux 开发工具 iOS开发
Codex保姆级教程(下载+安装+登录+使用,含所有版本,2026最新)
Codex是OpenAI推出的开源AI编程智能体,本地运行,可理解整个代码库,支持自然语言指令改代码、执行命令、跑测试、修Bug,兼容Win/macOS/Linux。(239字)
|
2天前
|
云安全 人工智能 安全
|
8天前
|
iOS开发 Docker MacOS
【2026实测】Docker Desktop下载、安装、汉化、配置教程(附官网安装包)
Docker Desktop 是 Docker 官方推出的桌面容器管理工具,集成引擎、CLI、图形界面与 Kubernetes,支持 Win/macOS/Linux。2026 最新版 4.88.1 强化 ARM 原生运行、镜像加速及资源可视化,附中文汉化与国内镜像配置指南。
1530 5
|
云安全 安全 网络安全
|
8天前
|
安全 算法 定位技术
《高性价比人生指南》完整原版:普通人用最少的资源,过好这一生的底层逻辑~How To Live Better
📖《高性价比人生指南》是一份基于实证、拒绝焦虑的生存手册,倡导以“人生时薪”为视角,重新计算时间、金钱与精力的成本与收益。它不承诺逆袭,只教人算清账:省下的钱若耗掉时间便亏;买体验比买物品更值;睡眠是最高杠杆的健康投资;关系贵在情绪价值而非表面热络;学习重在改变行为而非自我安慰。核心理念:用低成本、高回报的微小行动,换取长期的选择权——不是成为谁,而是掌控自己人生的主动权。
|
21天前
|
人工智能 API 开发工具
DeepSeek Harness 神插件:白嫖 13 家大厂模型
`dsh-our-free-model` 是 DeepSeek Harness 神级插件:零配置、零API Key、零账号,聚合13家大厂免费模型(含DeepSeek V4.1 Flash、Kimi K3等),支持本地网关、用量看板与自动降级,真正实现“白嫖即用”。
3695 54
|
13天前
|
人工智能 JSON Linux
【保姆级】ComfyUI部署教程(下载+安装+配置+工作流,2026最新)
ComfyUI 是一款开源节点式AI生成工具,支持图像、视频、音频及3D内容创作。通过拖拽连线组合模型加载、提示词、采样等节点,构建可复用、易修改的工作流,兼容SD1.5、SDXL、Flux、Qwen Image等主流模型。

热门文章

最新文章