服务覆盖:昆明·曲靖·玉溪·保山·昭通·丽江·普洱·临沧·楚雄·红河·文山·西双版纳·大理·德宏·怒江·迪庆

K8s集群升级翻车实录:从v1.28到v1.31的现血泪教训

eycit 2026-04-24 1 次阅读 网络故障
---

theme: default themeName: "默认主题" title: "K8s集群升级翻车实录:从v1.28到v1.31的血泪教训"


K8s集群升级翻车实录:从v1.28到v1.31的现血泪教训

今年初,我们做了一次K8s集群大版本升级,从v1.28直接跳到v1.31。升级方案评审时大家信心满满,觉得自己经验丰富不会有问题。结果升级过程翻了三次车,生产环境中断了4个小时。

事后复盘,三次翻车全是"以为不会出问题"的地方。今天把整个过程写出来,希望你能从我们的教训中学到东西,而不是自己踩一遍。

背景:为什么要跨3个大版本升级

我们的集群从2023年搭建后一直运行在v1.28上。2025年底,v1.28进入EOL(End of Life),不再接收安全补丁。安全审计要求必须升级。

本来应该按顺序升级:v1.28 → v1.29 → v1.30 → v1.31。但项目组觉得跨版本升级"效率更高",加上官方文档说支持n-2版本跳转,就决定一步到位。

这是第一个错误。 官方说的"支持"不等于"推荐",更不等于"没有风险"。

翻车一:API版本废弃导致的灾难

升级开始很顺利,kubeadm upgrade一路绿灯。但在切换流量到新版本master后,一堆Pod开始报CrashLoopBackOff。

查看Pod事件:

kubectl describe pod my-app-7d8f9c6b5-x2k4j

错误信息:

Warning  Failed  3s  kubelet  Error: failed to create containerd task: failed to create shim task: 

APIVersion "k8s.io/api/core/v1" is not supported by this version

不对,v1是稳定版API,不可能不支持。仔细看日志,发现真正的错误是:

error: unable to recognize "deployment.yaml": no matches for kind "Deployment" in version "apps/v1beta1"

`apps/v1beta1`在v1.16就已经废弃了,但我们有几个老旧的Helm Chart还在用这个版本。之前一直没出问题是因为K8s做了兼容保留,v1.31彻底移除了。

排查过程:
# 找出所有使用废弃API的资源
kubectl get --raw /metricsgrep apiserver_requested_deprecated_apis

更直接的方法:审计集群中的废弃API

kubectl api-resourcesgrep -i deprecated

扫描所有Helm Release

helm list -Aawk '{print $1, $2}'while read ns release; do
helm get values "$release" -n "$ns"grep -i "apiVersion"

done

修复: 把所有Helm Chart更新到支持`apps/v1`的版本。问题是有些Chart已经没人维护了,只能自己fork修改。
# 修改前

apiVersion: apps/v1beta1 kind: Deployment

修改后

apiVersion: apps/v1 kind: Deployment

这个坑花了2小时排查和修复。

教训:升级前一定要用`pluto`或`kubent`扫描废弃API。
# 用pluto检测废弃API

pluto detect-helm -o wide pluto detect-api-resources -o wide

用kubent检测

kubent

翻车二:CNI插件不兼容

API问题修好后,业务Pod能创建了,但互相之间无法通信。服务发现也挂了——CoreDNS无法解析任何域名。

# 测试Pod间通信

kubectl exec -it test-pod -- ping 10.244.1.5

PING 10.244.1.5: 56 data bytes

--- 10.244.1.5 ping statistics ---

5 packets transmitted, 0 received, 100% packet loss

测试DNS解析

kubectl exec -it test-pod -- nslookup kubernetes.default

;; connection timed out; no servers could be reached

问题出在CNI插件上。我们用的是Calico v3.25,而v3.25不支持K8s v1.31的新特性——结构性鉴权(Structural Authentication)变更导致CNI插件无法正确配置iptables规则。

排查过程:
# 查看Calico Pod状态

kubectl get pods -n kube-system -l k8s-app=calico-node

查看Calico日志

kubectl logs -n kube-system -l k8s-app=calico-node --tail=50

关键错误

calico-node: "Failed to program iptables rules: exit status 2"

修复: 升级Calico到v3.28+。
# 升级Calico

kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/calico.yaml

重启Calico Pod

kubectl rollout restart daemonset calico-node -n kube-system

这个坑花了1.5小时。

教训:升级K8s前,一定要确认所有组件(CNI、CSI、Ingress Controller、Cert Manager等)的版本兼容性矩阵。 我后来整理了一份检查清单:
组件v1.28兼容版本v1.31兼容版本需要升级
Calicov3.25+v3.28+
Nginx Ingressv1.8+v1.10+
Cert Managerv1.13+v1.14+
CSI Driverv1.8+v1.9+
Metrics Serverv0.6+v0.7+

翻车三:etcd数据迁移的坑

前两个问题解决后,业务基本恢复了。但监控发现etcd的响应延迟从5ms飙升到200ms,集群性能明显下降。

查看etcd状态:

# 查看etcd成员

kubectl get pods -n kube-system -l component=etcd

查看etcd性能

etcdctl endpoint status --cluster -w table

关键指标

DB SIZE: 8.2GB ← 这个异常大

RAFT APPLIED INDEX: 落后leader很多

8.2GB的etcd数据库——正常不应该超过2GB。原因是我们在v1.28上跑了很多Lease没有正确释放的CronJob,etcd里积攒了大量过期的key-value。

更严重的是,kubeadm upgrade过程中etcd做了数据格式转换,但没有自动执行压缩(compaction)。8GB的未压缩历史数据让etcd慢如蜗牛。

修复:
# 1. 先备份(非常重要!)

ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-snapshot.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

2. 获取当前版本号

REVISION=$(ETCDCTL_API=3 etcdctl endpoint status --write-out=jsonpython3 -c "import sys,json; print(json.load(sys.stdin)[0]['Status']['header']['revision'])")

3. 执行压缩

ETCDCTL_API=3 etcdctl compact "$REVISION"

4. 执行碎片整理

ETCDCTL_API=3 etcdctl defrag --cluster

5. 验证

ETCDCTL_API=3 etcdctl endpoint status --cluster -w table

DB SIZE: 1.8GB ← 恢复正常

这个坑花了30分钟排查,但压缩操作本身很快。

教训:升级前先清理etcd。 清理废弃的Lease和过期的key,再执行compaction+defrag,然后才能升级。

升级后的正确姿势

经过这次教训,我总结了一套"零翻车"升级流程:

升级前(至少提前2周)

1. 兼容性扫描:

# 废弃API检测

pluto detect-helm -o wide pluto detect-api-resources -o wide kubent

组件版本兼容性检查

kubeadm config images list --kubernetes-version=v1.31.0

2. etcd清理:

# 清理过期Lease

ETCDCTL_API=3 etcdctl lease list

压缩和碎片整理(见上文)

3. 在测试环境完整演练一遍升级流程。

4. 准备回滚方案。 用Velero备份整个集群。

升级中

1. 一次只跨一个大版本。 v1.28 → v1.29 → v1.30 → v1.31,不要跳版本。 2. 先升级控制面,再升级节点。 每次只升级一个master节点,确认etcd健康后再升级下一个。 3. 逐节点升级worker。 使用`kubectl cordon` + `kubectl drain`逐个升级,确保业务Pod有足够副本在其他节点运行。

# 标记节点不可调度

kubectl cordon node-1

驱逐Pod

kubectl drain node-1 --ignore-daemonsets --delete-emptydir-data

升级节点

kubeadm upgrade node

恢复调度

kubectl uncordon node-1

升级后

1. 全面验证: 所有Pod状态、Service可达性、Ingress路由、PVC挂载。 2. 监控观察48小时。 3. 写升级报告,记录遇到的问题和解决方案。

做IT这么多年,见过太多"早知道就好了"的情况。

希望这篇文章能帮你少走弯路。如果真的遇到问题,别一个人扛着——易云城IT服务随时待命。

📞 服务热线:13708730161 💬 微信:eyc1689 📧 邮箱:service@eycit.com 🌐 https://www.eycit.com

您身边的IT专家。

上一篇
MySQL主从延迟飙升到600秒,一个不起眼的参数救了命...
下一篇
网络突然断了?这份排查清单帮你5分钟找到问题...