1. 升级前的准备工作与版本策略
1.1 先想清楚:为什么升级 kubeadm 集群要卡版本
kubeadm 集群升级,最容易踩的坑就是不看版本路线,直接往新版冲。Kubernetes 官方的支持策略是:kubeadm 只能从一个次版本升级到相邻的下一个次版本,比如 1.28 到 1.29,不支持跨多个次版本直接跳。原因在于 API 资源的废弃节奏、kubelet 与 kube-apiserver 的版本偏差容忍范围都是固定设计的,跨版本跳很容易撞上不兼容的接口和已废弃的 API 资源,一旦撞上,整个集群的稳定性都会受影响。
实际操作中,我的习惯是把升级路线拆成三步走:先确认当前集群版本,再确定目标版本,最后规划每个节点的操作顺序。需要确认的东西包括 etcd 版本和镜像的兼容性、CoreDNS 是否需要同步升级、控制平面与 worker 节点之间镜像拉取策略、以及本地存储 PV/PVC 在节点重启后能否自动恢复。这些信息看起来繁琐,但缺一项都可能在升级过程中变成事故。
还有一点必须提前讲清楚:kubeadm 升级不会自动帮你完成所有事。它负责更新控制平面组件的静态 Pod 清单和集群级配置,但 kubelet 的二进制版本、kubelet.conf 配置、节点的隔离和恢复等操作,都需要手动介入。很多人以为跑一条命令就万事大吉,实际上升级是一个由多个环节组成的过程,任何一个环节被跳过,都会留下隐患。
1.2 环境检查与备份清单
升级前必须做的检查项,我一般列成这样一张清单,逐项打勾:
- 所有节点状态是否为 Ready,是否有节点处于 NotReady 或内存压力状态
- 核心插件是否健康,包括 coredns、metrics-server、网络插件(flannel 或 calico)
- etcd 集群是否有异常,有没有成员掉线或 leader 切换频繁的情况
- 是否有 Pending 状态的 Pod,是否有大量 CrashLoopBackOff
- 工作负载的副本数是否达到预期,特别是 Deployment 和 StatefulSet
这些检查做完后,备份是绝对不能跳过的一步。备份至少要做两件事:etcd 快照和 kubeadm 配置备份。etcd 快照命令是:
ETCDCTL_API=3 etcdctl --endpoints=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 \ snapshot save /backup/etcd-snapshot-$(date +%Y%m%d).dbkubeadm 配置一般在/etc/kubernetes/kubeadm-config.yaml,直接拷贝备份就行。我建议把备份文件同时放在控制平面节点本地和远端存储,避免节点硬件故障导致备份也丢。
这里有个我踩过坑的细节:如果集群里用了自定义的 admission webhook,升级前一定要确认新版本对admissionregistration.k8s.io/v1API 的兼容性。否则可能出现所有创建请求都被 webhook 拦截的情况,影响范围远比想象的大。检查方式很简单,先看一下 webhook 配置里的 apiVersion 和新旧版本对应关系,再确认 webhook 服务的可用性。
注意:如果集群中有使用PodDisruptionBudget(PDB)的工作负载,drain 节点时会受到 PDB 限制,导致驱逐速度极慢甚至卡住。升级前要评估业务是否允许中断,必要时先调整 PDB 策略。
2. 升级控制平面节点的完整流程
2.1 升级顺序:先控制平面还是先 worker
这个顺序非常关键,我的建议是严格按“控制平面节点优先、worker 节点其次”的顺序来。控制平面升级后,kube-apiserver、kube-controller-manager、kube-scheduler 都会运行在新版本上,而 kubelet 可以暂时停留在旧版本。Kubernetes 官方文档明确说:kubelet 版本不能高于 kube-apiserver 版本,但可以低一个次版本。所以先升控制平面,再逐个升级 worker 节点,是安全且符合规范的路线。
反过来,如果先升 worker 节点,就可能出现 worker 节点的 kubelet 版本比 API Server 还新,导致 kubelet 上报节点状态、Pod 状态时接口不兼容,最直接的现象是节点状态不稳定、Pod 一直卡在 ContainerCreating。我之前在一个测试环境里试过先升级 worker,结果节点间歇性 NotReady,排查了半天才发现是版本顺序的问题。
此外,控制平面内部也有顺序讲究。如果集群里有多个控制平面节点,官方推荐的做法是先升级第一个控制平面节点,然后逐步升级其余控制平面节点。这样能保证在升级过程中集群始终有可用的 API Server 端点,不会出现整个控制面不可用的情况。
2.2 主节点升级实测:kubeadm upgrade 三步
我用 kubeadm 1.28 升级到 1.29 的例子来说明完整流程。第一步,先更新软件源,然后安装目标版本的 kubeadm、kubelet 和 kubectl:
apt update apt install -y kubeadm=1.29.0-00 kubelet=1.29.0-00 kubectl=1.29.0-00这里要注意:最好一次性把三个包都指定版本装好,不能只装 kubeadm,忘记升级 kubelet。我就犯过这个错误,只升级了 kubeadm,忘记升级 kubelet,最后在kubeadm upgrade node阶段报版本不匹配,额外花了不少时间排查。如果使用的是国内镜像源,注意确认源里确实有对应版本,否则执行安装时会提示找不到包。
第二步,查看升级计划:
kubeadm upgrade plan这个命令会输出当前集群版本、可升级到的最高版本,以及升级过程中需要拉取的镜像列表。如果输出里显示 “can be upgraded to v1.29.0”,说明升级路径没问题。如果出现 “unsupportedVersion” 之类的提示,说明软件源里没有对应版本,或者当前 kubeadm 版本无法识别目标版本。
第三步,执行升级:
kubeadm upgrade apply v1.29.0这个命令会自动处理证书轮换、关键的配置更新、控制平面静态 Pod 清单的更新。执行过程中,通常会卡在 “Waiting for the kubelet to boot” 或类似的阶段,这属于正常现象,因为系统在等待静态 Pod 拉起并进入健康状态。看到 “upgrade completed successfully” 才算成功。
注意:执行
kubeadm upgrade apply时必须使用 root 权限,而且命令需要在一个空闲终端中执行,过程中如果有其他 kubectl 命令干扰,可能导致验证阶段误判。
2.3 控制平面升级后的收尾动作
升级完成后,手动收尾动作不能省。第一,更新本地 kubeconfig,让 kubectl 能继续访问新的 API Server:
sudo cp /etc/kubernetes/admin.conf $HOME/.kube/config这一步是为了确保本地客户端的访问凭据是最新的。如果跳过,后续kubectl get nodes可能因为证书或 endpoint 变化而报错。
第二,检查控制平面 Pod 的状态。重点看 kube-apiserver、etcd、kube-controller-manager、kube-scheduler 这些静态 Pod 是否处于 Running 状态。因为这些组件由 kubelet 直接拉起,如果新版本镜像拉不下来,会一直卡在 ImagePullBackOff。建议在升级前就确认镜像仓库的可达性,最好提前用kubeadm config images pull预拉取一遍目标版本镜像:
kubeadm config images pull --kubernetes-version v1.29.0第三,在第一个控制平面节点上,还要执行:
kubeadm upgrade node很多人以为这个命令只用于 worker 节点,但控制平面节点在apply之后也需要在本地执行kubeadm upgrade node来更新 kubelet 配置。原因是kubeadm upgrade apply主要负责控制平面组件的更新和集群级配置,而本节点的 kubelet systemd 配置、kubelet.conf 和证书文件不会自动写入,需要通过kubeadm upgrade node触发。
执行完这一步后,重启 kubelet 服务:
systemctl restart kubelet到这里,第一个控制平面节点才算真正升级完成。
3. worker 节点的升级与业务迁移
3.1 先腾空节点:cordon 和 drain 的正确姿势
worker 节点升级时,最怕的是 Pod 直接在原地被拉起,导致正在处理的业务请求中断。标准做法是先标记节点为不可调度,再把 Pod 驱逐到其他节点:
kubectl cordon node-worker-1 kubectl drain node-worker-1 --ignore-daemonsets --delete-emptydir-datacordon会标记节点为不可调度,新的 Pod 不会调度到这个节点上。drain会驱逐节点上的 Pod,触发优雅终止流程,等 Pod 迁移到其他节点后再进入维护状态。如果节点上运行的是 StatefulSet,且使用了本地存储,drain可能无法成功,这时候需要评估业务可接受的停机窗口,或者提前在存储层做好迁移方案。实际项目中,我遇到最多的问题是 PVC 绑定在本地节点上的 Pod,驱逐后 PVC 无法 attach 到其他节点,导致 Pod 一直 Pending。
还有一个必须记住的细节:如果节点上有 DaemonSet 的 Pod,drain默认不会驱逐它们,所以要加--ignore-daemonsets;如果某些工作负载使用了 emptyDir 临时存储,需要加--delete-emptydir-data,否则 drain 会因为无法删除 emptyDir 而卡住。这两个参数是高频使用的,建议直接写进操作文档里。
3.2 worker 节点执行升级命令
腾空节点并确认节点上的 Pod 都已经迁移走之后,在 worker 节点上执行升级。先更新软件源并安装目标版本组件:
apt update apt install -y kubeadm=1.29.0-00 kubelet=1.29.0-00 kubectl=1.29.0-00然后执行:
sudo kubeadm upgrade node sudo systemctl restart kubeletkubeadm upgrade node会读取本地的 kubelet 配置,并生成新版本的 kubelet.conf 和证书文件。执行完之后必须重启 kubelet,否则新配置不会生效。我见过不少人在这一步忘记重启,然后发现节点的 kubelet 版本还是旧的,节点状态变成 NotReady,最后一通排查才发现只是没重启服务。
这一整套流程做完后,可以执行kubectl get nodes确认该节点的 VERSION 已经变成新版本,同时 STATUS 是 Ready。
3.3 解除维护状态:uncordon
升级完成后,需要解除节点的不可调度标记:
kubectl uncordon node-worker-1这里有个节奏问题:如果集群里有多个 worker 节点,我建议一台一台来,每台升级完、uncordon、确认业务 Pod 调度回该节点后,再操作下一台。不要把所有 worker 节点一次性全部 drain,那样会让集群调度压力突然增大,甚至出现资源不足导致大量 Pod Pending。
整个升级过程中,集群调度器会重新为被驱逐的 Pod 分配节点,这实际上就是一次集群调度的实战演练。如果集群里配置了 PriorityClass,要注意高优先级 Pod 会抢占其他 Pod 的资源,避免因为短时间内多个节点同时维护引发大规模重调度风暴。生产环境里,跨节点迁移大流量业务时还要特别注意网络带宽和存储性能的瓶颈,必要时提前扩容相关节点资源。
4. 升级后的验证与常见问题排查
4.1 集群健康检查的完整验证清单
升级完成后不能只看控制平面 Pod 状态,还要从多个维度验证。先看节点状态:
kubectl get nodes所有节点应该显示 Ready,并且 VERSION 列已经变成新版本。然后看核心插件和系统组件:
kubectl get pods -n kube-system -o wide重点关注 kube-apiserver、kube-controller-manager、kube-scheduler、etcd、coredns、flannel 或 calico 这些 Pod。另外,我习惯再执行两条命令:
kubectl cluster-info kubectl get componentstatusescomponentstatuses这条命令虽然现在已经属于半废弃状态,但很多老运维还是会用它快速评估控制平面组件是否健康,输出结果里 controller-manager、scheduler、etcd 都应该是 Healthy。如果集群里启用了 metrics-server,还可以确认kubectl top nodes是否能正常返回数据,这一步能验证 kubelet 的 metrics 采集链路没有在升级中损坏。
除此之外,建议验证一下工作负载是否真的恢复了。随便挑几个核心业务应用,看一下 Pod 的启动时间和所在节点,确认 Pod 已经迁回目标节点并且 Running 状态稳定。如果业务有自建的监控告警系统,此时要确认数据采集没有中断,告警通道没有因为节点重启而静默。
4.2 常见升级问题速查表
我把升级过程中遇到的高频问题整理成速查表,方便排查:
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| kubeadm upgrade plan 报无法解析镜像 | 软件源未更新 | 先apt update并确认目标版本已安装 |
| 升级后 node 一直 NotReady | kubelet 未重启 | 查看systemctl status kubelet,重启 kubelet |
| Pod 一直 ContainerCreating | 镜像仓库不可达或新版本镜像不存在 | 检查 kubelet 日志,确认镜像 tag 是否匹配目标版本 |
| drain 卡住无法完成 | 有未配置容忍的 Pod 或 PDB 限制 | 用kubectl describe pod查看卡住的 Pod,评估强制驱逐或调整 PDB |
| API Server 启动失败 | 证书过期或配置不匹配 | 查看/var/log/kube-apiserver日志,检查证书有效期和路径 |
| 升级后部分节点网络异常 | 网络插件版本与集群不一致 | 检查 flannel/calico Pod 状态,按插件的升级文档同步更新 |
遇到问题不要先想着回滚,先看日志。kubelet 日志在 worker 节点上用journalctl -u kubelet -f查看,控制平面组件日志可以直接用kubectl logs -n kube-system查看,如果控制平面组件本身起不来,则需要在对应节点上查看静态 Pod 的容器日志,路径一般是/var/log/pods/kube-system_xxx/。
4.3 关于回滚方案的思考
很多人问升级失败能不能回滚。说实话,kubeadm 集群的升级回滚比升级本身还麻烦,尤其是控制平面已经执行了apply之后,etcd 数据可能已经被新版本写入,旧的二进制版本重新放回去也不一定兼容。我的经验是:如果只是 worker 节点升级失败,直接把 kubelet 和 kubeadm 的二进制版本降回去,重启 kubelet,大概率能恢复;如果控制平面升级失败,就要优先考虑是否有可用的 etcd 快照,用备份做恢复。
所以,升级前的备份不是走流程,而是真正决定你能不能安全回滚的关键。建议把 etcd 快照文件单独存放,并且至少保留升级前和业务稳定后的两份快照,这样即使稳定运行一段时间后发现隐患,也能有一个恢复点。多看几遍官方文档里关于备份 etcd 的章节,比自己临时摸索靠谱得多。
5. 升级kubeadm集群的两个额外实践建议
5.1 小集群练手,再上生产
升级 kubeadm 集群这件事,我做得越多越觉得关键不在命令本身,而在节奏。我实际测试下来最稳的顺序是:先在一套小集群里完整走一遍升级流程,包括验证、回滚、再升级,确认没有意外情况后,再在生产环境动手。小集群的节点少、业务量小,即使操作失误,修复成本也低。生产环境升级前,我会把所有的命令和步骤整理成一份操作单,里面包含每个节点的 IP、操作顺序、预计耗时、回滚入口,然后逐条执行,执行一项就打勾一项。
5.2 操作窗口与告警处理
升级时间尽量安排在业务低峰期。这个不用多说,但一定要提前在运维群同步操作窗口,避免在升级过程中被业务方的告警打扰。升级过程中会有节点重启、Pod 迁移,期间部分服务可能出现短暂不可用,如果业务方不知情,他们可能会误报故障,甚至有人会主动重启 Pod 导致操作动作冲突。
另一个很实用的建议是:在升级前把 kubectl 命令行工具提前下载到本地,并验证其版本与目标集群版本的兼容性。新版 kubectl 通常能兼容旧版 API Server,但反过来不一定成立。我习惯在正式操作前先跑一遍kubectl version --client和kubectl version --server,把版本输出记录下来,再开始动集群。这样做的好处是,一旦升级后出现兼容性问题,能快速判断是 kubectl 的问题还是集群组件的问题。
永远不要在没有备份、没有验证方案的情况下直接升级,记住这一点,很多坑就都绕开了。