Kubernetes 1.20发布了,带来了很多新特性,同时也废弃了一些旧功能。我们公司把K8s集群从1.16升级到了1.20,过程中踩了不少坑。本文完整记录这次迁移实战,包括迁移前的准备、迁移步骤、遇到的问题和解决方案、迁移后的验证和优化,希望对正在做K8s升级迁移的朋友有帮助。

一、为什么要迁移

我们的K8s集群一直跑在1.16版本,用了一年多,一直很稳定。但随着业务发展,1.16的一些问题越来越明显:

  1. 版本太老,社区支持减弱:K8s 1.16已经发布很久了,社区的支持和补丁越来越少,很多新的工具和组件不再支持老版本
  2. 缺少新特性:1.17之后的版本引入了很多有用的特性,比如IPv4/IPv6双栈、CSI迁移、EndpointSlice、Process Namespace Sharing等,这些特性对我们的业务有帮助
  3. 安全漏洞:老版本存在一些已知的安全漏洞,虽然暂时没被利用,但始终是个风险
  4. 生态兼容性:很多新的云原生工具(如新版Istio、Knative等)不再支持1.16,要使用这些工具必须升级

综合考虑之后,我们决定把集群从1.16升级到1.20。1.20是2020年底的版本,比较稳定,新特性也比较丰富,而且是很多企业的目标版本。

二、迁移前的准备

K8s版本迁移不是一件小事,准备工作非常重要。准备充分,迁移就顺利;准备不足,迁移就会出各种问题。

1. 了解版本变化

首先要详细了解从1.16到1.20每个版本的变化,特别是破坏性变更和废弃功能。

我们重点关注了这些变化:

  • 1.17:IPv4/IPv6双栈alpha、CSI迁移beta、拓扑感知服务路由alpha、EndpointSlice beta
  • 1.18:服务端应用kubectl alpha、CSI内联卷beta、节点问题检测器beta、HPA扩展行为字段
  • 1.19:Ingress扩展到networking.k8s.io/v1、EndpointSlice稳定、CSI存储容量alpha、Seccomp默认开启
  • 1.20:kubectl debug alpha、API优先级和公平性alpha、Docker作为运行时被废弃(dockershim deprecation)、IPv4/IPv6双栈beta、Process Namespace Sharing稳定

特别要注意的是废弃功能:

  • dockershim被废弃:1.20开始废弃Docker作为容器运行时,未来版本会移除,需要迁移到containerd或CRI-O
  • 旧的Ingress API:extensions/v1beta1的Ingress在1.22会被移除,需要迁移到networking.k8s.io/v1
  • 旧的RBAC API:rbac.authorization.k8s.io/v1beta1在1.22会被移除
  • 一些alpha特性的API变化

了解这些变化,才能在迁移前做好准备,避免迁移后出现兼容性问题。

2. 评估应用兼容性

接下来要评估集群上运行的所有应用,看看它们对K8s版本有没有依赖,会不会因为版本升级而出问题。

我们的做法是:

  • 列出所有运行中的应用和它们的YAML配置
  • 检查是否使用了被废弃的API(如旧的Ingress、旧的RBAC)
  • 检查是否依赖了特定版本的行为(如某些alpha特性)
  • 检查自定义控制器和Operator的兼容性
  • 检查Helm Chart的兼容性

我们发现有几个应用还在用旧的extensions/v1beta1 Ingress,需要先迁移到新的API。还有几个Helm Chart版本太老,不支持1.20,需要升级。

3. 准备测试环境

不要直接在生产环境升级,一定要先在测试环境验证。我们搭建了一个和生产环境配置相同的测试集群,先在测试环境做升级,验证所有应用都能正常运行,然后再在生产环境操作。

测试环境要尽可能和生产环境一致:相同的K8s版本、相同的节点配置、相同的网络插件、相同的存储类、相同的应用。这样测试结果才有参考价值。

4. 备份数据

升级前一定要做好备份,包括:

  • etcd的备份(最重要,集群的所有状态都存在etcd里)
  • 重要的PV数据备份
  • 所有YAML配置和Helm Chart的备份
  • 集群配置信息(kubeadm配置、网络配置等)

备份是最后的保障,如果升级失败,可以从备份恢复。etcd备份一定要验证可恢复性,不能只备份不验证。

5. 制定回滚计划

升级可能失败,一定要有回滚计划。我们的回滚计划是:

  • 如果升级过程中出现严重问题,立即停止升级
  • 用备份的etcd数据恢复集群到升级前的状态
  • 如果集群无法恢复,用备份的配置重建集群,重新部署应用
  • 回滚时间控制在1小时以内

有了回滚计划,升级的时候心里就有底了,出了问题也不慌。

三、迁移步骤

准备工作做好之后,就可以开始迁移了。我们采用的是滚动升级的方式,先升级控制平面,再逐个升级工作节点,尽量减少对业务的影响。

第一步:升级kubeadm和kubectl

首先在所有节点上升级kubeadm和kubectl到1.20版本。注意:kubeadm的版本要先于kubelet升级,而且kubeadm的版本不能比kubelet低超过一个小版本。

我们用的是Ubuntu系统,通过apt安装:

apt-mark unhold kubeadm kubectl
apt-get update && apt-get install -y kubeadm=1.20.x-00 kubectl=1.20.x-00
apt-mark hold kubeadm kubectl

第二步:升级控制平面

在第一个master节点上执行:

kubeadm upgrade plan
kubeadm upgrade apply v1.20.x

kubeadm upgrade plan会检查升级是否可行,列出需要升级的组件和配置变更。kubeadm upgrade apply会实际执行升级,升级etcd、API Server、Controller Manager、Scheduler等控制平面组件。

升级第一个master节点之后,在其他master节点上执行:

kubeadm upgrade node

控制平面升级完成后,验证控制平面组件是否正常:

kubectl get nodes
kubectl get pods -n kube-system
kubectl get componentstatuses

第三步:升级工作节点

控制平面升级完成后,逐个升级工作节点。升级工作节点的时候,要先把节点上的Pod驱逐出去,再升级,避免影响业务。

在每个工作节点上:

# 1. 驱逐节点上的Pod
kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data

# 2. 升级kubelet和kubeadm
apt-mark unhold kubelet
apt-get install -y kubelet=1.20.x-00
apt-mark hold kubelet

# 3. 升级节点配置
kubeadm upgrade node

# 4. 重启kubelet
systemctl daemon-reload
systemctl restart kubelet

# 5. 节点重新可调度
kubectl uncordon <node-name>

逐个节点升级,确保每个节点升级完成、Pod正常运行后,再升级下一个节点。这样即使某个节点出问题,也不会影响整个集群。

第四步:升级网络插件和其他组件

K8s本身升级完成后,还要升级网络插件(Calico/Flannel/Cilium等)、存储插件(CSI驱动)、Ingress Controller、监控组件(Prometheus/Grafana)、日志组件(EFK/Loki)等,确保它们和1.20兼容。

这些组件的升级要参考各自的官方文档,注意版本兼容性。有些组件的新版本可能有破坏性变更,需要调整配置。

第五步:迁移容器运行时(从Docker到containerd)

K8s 1.20废弃了dockershim,虽然1.20还能用Docker,但未来版本会移除。我们趁这次升级,把容器运行时从Docker迁移到了containerd。

迁移步骤:

  1. 在节点上安装containerd
  2. 配置containerd,设置cgroup驱动为systemd(和kubelet一致)
  3. 修改kubelet配置,指定容器运行时为containerd
  4. 重启kubelet
  5. 验证容器运行时是否切换成功

迁移过程中要注意:

  • 切换运行时会重启节点上的所有容器,要先drain节点
  • 镜像要重新拉取(containerd和Docker的镜像不共享)
  • 有些依赖Docker socket的应用(如Docker-in-Docker、某些CI工具)需要调整

四、遇到的问题和解决方案

迁移过程中,我们遇到了不少问题,这里记录几个典型的。

问题1:旧的Ingress API不兼容

现象:升级后,几个用旧的extensions/v1beta1 Ingress的应用,路由不生效了。

原因:1.20虽然还支持extensions/v1beta1的Ingress,但行为有变化,而且某些字段(如pathType)的处理方式不同。

解决方案:

  • 把所有旧的Ingress迁移到networking.k8s.io/v1版本
  • 显式指定pathType(Prefix/Exact/ImplementationSpecific)
  • 检查backend service的配置是否正确
  • kubectl convert命令自动转换旧的YAML

迁移后,所有Ingress都正常工作了。

问题2:dockershim废弃导致的问题

现象:升级到1.20后,kubelet日志里有大量的警告,说dockershim被废弃了。而且有些依赖Docker的工具出问题了。

原因:1.20开始废弃dockershim,虽然还能用,但有警告。我们的CI工具依赖Docker socket,切换到containerd后找不到Docker了。

解决方案:

  • 长期方案:迁移到containerd,我们已经做了
  • 短期方案:如果暂时不能迁移,可以继续用Docker,1.20还支持,只是有警告
  • 依赖Docker socket的工具:改成用containerd的API,或者用nerdctl(containerd的Docker兼容CLI)
  • CI/CD工具:升级到支持containerd的版本,或者用Kaniko、Buildah等不需要Docker daemon的构建工具

问题3:Calico网络插件版本不兼容

现象:升级后,部分Pod网络不通,Calico的日志里有报错。

原因:我们用的Calico版本太老(3.10),不支持K8s 1.20。

解决方案:

  • 升级Calico到支持1.20的版本(3.17+)
  • 升级前先看Calico的官方文档,确认版本兼容性
  • 升级Calico的时候,注意配置文件的变化,有些参数可能改了
  • 升级后验证网络连通性:Pod之间通信、Service访问、NetworkPolicy是否生效

问题4:自定义Controller的API不兼容

现象:我们自己写的一个自定义Controller,升级后不工作了,日志里有API调用错误。

原因:Controller用的client-go版本太老,和1.20的API不兼容。

解决方案:

  • 升级client-go到支持1.20的版本
  • 检查Controller里用到的API,有没有被废弃或变更的
  • 重新编译Controller,部署新版本
  • 建议:自定义Controller要跟上K8s版本,不要用太老的client-go

问题5:etcd升级失败

现象:升级第一个master节点的时候,etcd升级失败,控制平面起不来。

原因:etcd的数据目录权限有问题,而且etcd的版本跨度太大(从3.3直接到3.4),中间有兼容性问题。

解决方案:

  • 立即停止升级,用备份的etcd数据恢复
  • 检查etcd数据目录的权限,确保etcd用户能读写
  • 先升级etcd到中间版本,再升级到目标版本,不要跨太大版本
  • 升级前先做etcd的健康检查,确保etcd集群状态正常
  • 恢复后重新升级,这次成功了

这个问题提醒我们:etcd是集群的心脏,升级etcd一定要谨慎,做好备份和验证。

问题6:节点升级后NotReady

现象:某个工作节点升级后,状态一直是NotReady,Pod调度不上去。

原因:kubelet启动失败,因为cgroup驱动配置不一致。kubelet用的是systemd cgroup驱动,但容器运行时(containerd)用的是cgroupfs驱动,两者不一致导致kubelet启动失败。

解决方案:

  • 统一cgroup驱动,都用systemd(推荐)
  • 修改containerd配置:SystemdCgroup = true
  • 修改kubelet配置:cgroupDriver: systemd
  • 重启containerd和kubelet
  • 验证节点状态是否恢复正常

这个问题很常见,特别是从Docker迁移到containerd的时候,一定要注意cgroup驱动的一致性。

五、迁移后的验证

升级完成后,不要以为就完事了,一定要做全面的验证,确保集群和所有应用都正常运行。

1. 集群状态验证

# 节点状态
kubectl get nodes -o wide
# 所有Pod状态
kubectl get pods --all-namespaces
# 控制平面组件状态
kubectl get componentstatuses
# 系统事件
kubectl get events --all-namespaces --sort-by='.lastTimestamp'

确保所有节点都是Ready状态,所有Pod都是Running状态,没有异常的事件。

2. 应用功能验证

逐个验证所有应用的功能:

  • Web应用:访问页面,测试核心功能
  • API服务:调用接口,验证返回结果
  • 后台任务:检查任务是否正常执行
  • 定时任务:验证CronJob是否正常触发
  • 有状态应用:检查数据是否完整,主从是否正常

不要只看Pod是Running就认为正常,一定要验证业务功能。有些应用Pod是Running的,但内部已经出问题了。

3. 网络验证

  • Pod之间通信:在不同节点的Pod之间ping和curl
  • Service访问:测试ClusterIP、NodePort、LoadBalancer类型的Service
  • Ingress:测试外部域名访问是否正常
  • NetworkPolicy:验证网络策略是否生效
  • DNS:测试集群内DNS解析是否正常

4. 存储验证

  • PV/PVC:检查持久卷是否正常挂载
  • 数据完整性:验证有状态应用的数据是否完整
  • 存储类:测试动态存储供给是否正常
  • 快照:测试存储快照功能(如果用了)

5. 监控和日志验证

  • Prometheus:检查监控指标是否正常采集
  • Grafana:检查仪表盘是否正常显示
  • 告警:测试告警是否正常触发
  • 日志:检查日志是否正常采集和查询
  • 链路追踪:检查分布式追踪是否正常

6. 性能验证

  • 集群资源使用:CPU、内存、网络、磁盘是否正常
  • 应用响应时间:和升级前对比,有没有变慢
  • 压测:对核心应用做简单的压测,确保性能没有下降
  • 调度性能:测试Pod调度速度,和升级前对比

六、迁移后的优化

迁移完成后,还可以利用新版本的特性做一些优化。

1. 启用新特性

1.20有很多新特性可以用:

  • EndpointSlice:替代老的Endpoints,更高效,支持大规模Service
  • kubectl debug:方便地调试运行中的Pod
  • Process Namespace Sharing:Pod内容器共享进程命名空间,方便sidecar模式
  • IPv4/IPv6双栈:如果需要IPv6支持,可以启用
  • HPA扩展行为:更灵活的HPA扩缩容配置

根据业务需要,选择性地启用这些新特性,提升集群的能力和效率。

2. 清理废弃资源

迁移后,清理一些不再需要的资源:

  • 删除旧的、不再使用的API资源
  • 清理废弃的ConfigMap和Secret
  • 合并重复的Deployment和Service
  • 清理不再使用的PV和PVC

清理后,集群更整洁,管理起来也更方便。

3. 优化资源配置

利用新版本的特性优化资源配置:

  • 用HPA的新行为字段,更精细地控制扩缩容
  • 用LimitRange和ResourceQuota,更合理地限制资源使用
  • 用PriorityClass,确保关键应用优先调度
  • 优化Pod的resources request和limit,提高资源利用率

4. 完善监控和告警

迁移后,完善监控和告警:

  • 添加新版本组件的监控指标
  • 设置关键指标的告警(如节点NotReady、Pod异常重启、API Server延迟)
  • 配置etcd的监控和告警(etcd是集群的心脏,一定要重点监控)
  • 建立集群健康巡检机制,定期检查集群状态

七、经验和教训

这次迁移,我们总结了一些经验和教训。

1. 不要跨太多版本升级

我们从1.16直接升到1.20,跨了4个版本,中间的变化太多,遇到的问题也多。建议每次升级跨1-2个版本,比如1.16→1.18→1.20,这样每次的变化小,问题少,风险低。

2. 测试环境一定要充分验证

不要在测试环境简单跑一下就去生产环境升级。要在测试环境做全面的验证,包括所有应用的功能测试、性能测试、故障演练。测试环境发现的问题越多,生产环境出问题的概率就越小。

3. 备份和回滚计划是生命线

升级前一定要做好备份,而且要验证备份可恢复。回滚计划要详细,包括回滚步骤、回滚时间、责任人。有了备份和回滚计划,升级的时候心里才有底,出了问题也能快速恢复。

4. 逐个节点升级,不要贪快

工作节点升级的时候,一定要逐个来,确保一个节点升级完成、验证正常后,再升级下一个。不要同时升级多个节点,那样出了问题影响面大,而且不好定位是哪个节点的问题。

5. 关注废弃功能,提前迁移

K8s每个版本都会废弃一些功能,要提前关注,在升级前就完成迁移。不要等功能被移除了才着急,那时候就被动了。特别是API版本的废弃(如Ingress、RBAC),一定要提前迁移到新的API。

6. 文档很重要

迁移过程中,要做好文档记录:升级步骤、遇到的问题、解决方案、验证结果。这些文档不仅对本次迁移有帮助,对以后的升级也有参考价值。而且团队成员可以通过文档了解集群的状态和变化。

八、写在最后

K8s版本迁移是一项复杂但必要的工作。它不是简单地敲几个命令就完事了,而是需要充分的准备、谨慎的操作、全面的验证。

我们这次从1.16升级到1.20,前后花了两周时间(包括准备、测试、生产升级、验证优化),遇到了不少问题,但最终都解决了。升级完成后,集群更稳定、功能更丰富、性能也有提升,这次迁移是值得的。

如果你正在做K8s升级迁移,希望这篇文章能帮到你。记住:准备充分、操作谨慎、验证全面、有回滚计划,就能大大降低迁移的风险。

K8s在快速发展,新版本不断推出,升级迁移会是一个持续的工作。建立规范的升级流程,积累迁移经验,就能让每次升级都顺利完成。

最后,愿每一个K8s运维工程师,都能从容面对每一次版本升级,让集群稳定运行,让业务无忧。