1.5. 集群升级、证书与节点维护

升级不是「换一个 kubelet 二进制」,而是一次涉及控制平面、etcd、证书、CNI 与入口控制器的协同变更。这一章把 kubeadm 的升级路径、version skew 约束、drain/uncordon 与回滚方案串成一条可演练的流水线,并在三节点集群上完成 v1.30 → v1.31 的实操。

层级:

L2 · 核心

难度:

进阶

预计:

70 分钟

前置:

集群部署、etcd 与 PKI 基础

你将学到

  • kubeadm 的逐 minor 升级路径与控制平面 → worker 的顺序约束。

  • version skew 的硬性边界,以及为什么不能先升 kubelet。

  • kubeadm upgrade plan/apply、kubeadm upgrade node 各自改了什么。

  • drain/cordon/uncordon 与 PDB 的配合,证书轮换与 etcd 快照恢复。

  • 升级前检查表、验证清单、失败回滚与排错路径。

1.5.1. 升级到底改了什么

kubeadm 管理的集群里,版本不是一个数字,而是一组组件的组合:静态 Pod 形式的 kube-apiserver、kube-controller-manager、kube-scheduler、etcd,DaemonSet 形式的 kube-proxy,节点上的 kubelet 与 kubectl,以及 addon 层的 CoreDNS。kubeadm upgrade apply 负责前四类与 addon 的镜像和配置,kubelet 必须由你在每台节点上单独升级,CNI、Ingress、CSI 则各有自己的发布节奏。

升级的动机通常有三类:跟进安全补丁、使用新特性、以及保持版本仍在社区支持窗口内。Kubernetes 每个 minor 大约支持 14 个月,跨过三个 minor 就会同时失去补丁与文档支持,所以生产集群应把「每年至少两次 minor 升级」写进维护计划,而不是等到 EOL 再一次性跳版。

1.5.1.1. version skew 的三条硬约束

Kubernetes 只承诺有限范围内的版本偏差,超出范围的行为没有保证。规划升级时先对照这张表:

组件

与 kube-apiserver 的允许偏差

说明

kube-apiserver

基准

集群里版本最高的组件

controller-manager / scheduler

不得更新,最多旧 1 个 minor

新 API 由 apiserver 提供,控制器可以稍旧

kubelet

最多旧 3 个 minor,不得更新

先升控制平面再升 kubelet 的根因

kube-proxy

与 kubelet 同 minor,最多旧 3 个 minor

通常随 kubelet 一起换镜像

kubectl

新旧各 1 个 minor 以内

客户端兼容窗口,超出会报字段错误

由此得到两条操作纪律:第一,控制平面必须先升,让 apiserver 成为最新版本,旧的 kubelet 仍在支持窗口内;第二,kubeadm 不能跨 minor,v1.29 → v1.31 必须经过 v1.30,但同一个 minor 内的 patch 可以自由选择。

1.5.1.2. 顺序不能变

架构图

图:一次 minor 升级的固定顺序。每个 worker 内部又是 drain → 升包 → kubeadm upgrade node → uncordon 的循环。

多控制平面集群先升第一个节点,其余控制平面用 kubeadm upgrade node 升级;worker 一次只动一台,确保业务副本始终有地方调度。

1.5.2. 升级前的准备

1.5.2.1. 备份 etcd

etcd 是集群唯一的事实来源,升级前必须有一份可验证的快照。把备份做成脚本,避免每次手敲参数出错:

etcd-backup.sh
#!/usr/bin/env bash
set -euo pipefail
SNAP="/var/backups/etcd-$(date +%F-%H%M).db"
sudo mkdir -p /var/backups
sudo ETCDCTL_API=3 etcdctl \
  --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 \
  snapshot save "$SNAP"
sudo ETCDCTL_API=3 etcdctl snapshot status "$SNAP" -w table

snapshot status 会输出 hash、revision 与总 key 数;只看到文件生成不算成功,revision 非零才说明快照完整。快照应复制到集群外的对象存储,和集群放在同一块磁盘上的备份在磁盘故障时一起消失。

1.5.2.2. 检查证书与升级计划

sudo kubeadm certs check-expiration
sudo kubeadm upgrade plan

第一条列出每张证书的残存时间,重点看 ca(默认 10 年)与 apiserver、etcd 证书(默认 1 年)。第二条会打印当前版本、可升级到的 patch/minor、以及「必须手动升级的组件」清单。kubeadm upgrade plan 不会检查 CNI 与 Ingress,那部分要靠发布说明。

警告

控制平面升级会短暂中断 API

kubeadm upgrade apply 会重建 apiserver、controller-manager、scheduler 的静态 Pod,API 通常抖动 30–60 秒。请从集群外的跳板机执行,并确认控制平面 VIP 后面不止一个健康端点;单控制平面集群只能接受这段停机窗口。

1.5.2.3. API 与插件兼容性

升级前扫一遍集群里实际使用的 API 组,确认没有落在下一个版本移除清单里的资源:

kubectl api-resources --verbs=list --namespaced -o name | sort -u > /tmp/resources.txt
kubectl get ingress,networkpolicy,poddisruptionbudget -A
kubectl get --raw '/metrics' | grep apiserver_requested_deprecated_apis

第三行依赖 apiserver 的 deprecated API 指标,能直接看出哪些客户端还在调用已废弃的接口。控制器、Operator、CRD 的 conversion webhook 也要一并确认,它们往往比工作负载更晚适配新版本。

升级前检查表

检查项

命令或动作

通过标准

etcd 快照

etcdctl snapshot status

revision 非零,且已复制到集群外

证书有效期

kubeadm certs check-expiration

剩余时间覆盖升级窗口

节点健康

kubectl get nodes

全部 Ready,无长期 NotReady

工作负载

kubectl get pods -A

无 CrashLoop / Pending

PDB

kubectl get pdb -A

允许至少一个副本被驱逐

版本偏差

kubeadm upgrade plan

当前版本与目标只差一个 minor

插件矩阵

CNI / Ingress / CSI 发布说明

明确声明支持目标 minor

回滚预案

快照 + 旧版本 deb 包 + 演练记录

能在测试环境 30 分钟内恢复

1.5.3. 演练:三节点集群 v1.30 → v1.31

实验环境:master01(控制平面 + etcd)、node01、node02,OS 为 Ubuntu 22.04,包管理使用 Kubernetes 官方 apt 源。所有节点先确认 kubeadm version -o short 为 v1.30.x。

1.5.3.1. 步骤 1 · 升级第一个控制平面

sudo apt-mark unhold kubeadm
sudo apt-get update && sudo apt-get install -y kubeadm='1.31.4-1.1'
sudo apt-mark hold kubeadm

sudo kubeadm upgrade plan
sudo kubeadm upgrade apply v1.31.4 --yes

upgrade apply 会依次完成:预检(API 可达、版本偏差、证书、etcd 健康)、拉取新镜像、重建静态 Pod、升级 CoreDNS 与 kube-proxy 的 addon 清单、续签受管证书。输出末尾出现 SUCCESS! Your cluster was upgraded to "v1.31.4" 才算控制平面完成。

接着升级本节点的 kubelet 与 kubectl:

sudo apt-mark unhold kubelet kubectl
sudo apt-get install -y kubelet='1.31.4-1.1' kubectl='1.31.4-1.1'
sudo apt-mark hold kubelet kubectl
sudo systemctl daemon-reload && sudo systemctl restart kubelet
kubectl get nodes -o wide

此时 master01 已是 v1.31.4,两个 worker 仍是 v1.30.x,这是 skew 允许的中间状态,但不应超过一个维护窗口。

1.5.3.2. 步骤 2 · 逐台升级 worker

在 node01 上先由控制面驱逐业务 Pod,再升级:

kubectl drain node01 --ignore-daemonsets --delete-emptydir-data --timeout=180s
ssh node01
sudo apt-mark unhold kubeadm && sudo apt-get install -y kubeadm='1.31.4-1.1'
sudo kubeadm upgrade node
sudo apt-mark unhold kubelet kubectl
sudo apt-get install -y kubelet='1.31.4-1.1' kubectl='1.31.4-1.1'
sudo systemctl daemon-reload && sudo systemctl restart kubelet
exit
kubectl uncordon node01

worker 上的 kubeadm upgrade node 不带版本号:它从集群读取 kubelet 配置并写入 /var/lib/kubelet/config.yaml,不会碰控制平面。node02 重复同一组命令,不要并行,否则 PDB 可能同时卡住两台的驱逐。

1.5.3.3. 步骤 3 · 收尾验证

$ kubectl get nodes
NAME       STATUS   ROLES           AGE    VERSION
master01   Ready    control-plane   412d   v1.31.4
node01     Ready    <none>          412d   v1.31.4
node02     Ready    <none>          412d   v1.31.4
kubectl -n kube-system get pods
kubectl -n kube-system get deploy coredns -o jsonpath='{.spec.template.spec.containers[0].image}'
kubectl -n kube-system get ds kube-proxy -o jsonpath='{.spec.template.spec.containers[0].image}'
kubectl run dnstest --image=busybox:1.36 --rm -it --restart=Never -- nslookup kubernetes.default

CoreDNS 与 kube-proxy 的镜像 tag 应随之变为 v1.31 对应版本;如果它们仍停在旧 tag,说明 addon 被 Helm 或手工清单接管,需要按各自的方式单独升级。

1.5.3.4. 插件兼容性核对

控制平面升级不会替你升级 CNI、Ingress 与 CSI。按这张表逐项确认:

组件

升级方式

检查点

CoreDNS

kubeadm upgrade apply 更新 addon

是否自建 ConfigMap 覆盖,ready 插件是否保留

kube-proxy

随 kubeadm 更新 DaemonSet

是否被 Helm 接管、是否启用了 nftables 模式

CNI

按 Calico/Cilium 发行版单独升级

发布说明中的 Kubernetes 支持矩阵

Ingress

Helm 或清单单独升级

ingress-nginx 1.12+ 才声明支持 1.31

CSI

驱动与 sidecar 分开升级

external-provisioner 与快照 CRD 的兼容表

运行时

containerd / CRI-O 与 k8s minor 对齐

crictl version 与 kubelet --version

CNI 与 Ingress 的选型细节见 CNI 网络 与 负载均衡与 Ingress。

1.5.4. 证书轮换与 etcd 恢复

1.5.4.1. 证书轮换

kubeadm 默认签发 1 年期的叶子证书,upgrade apply 会顺带续签。如果集群长期不升级,需要单独轮换:

sudo kubeadm certs check-expiration
sudo kubeadm certs renew all
sudo kubeadm init phase kubeconfig admin --kubeconfig-dir /etc/kubernetes
sudo systemctl restart kubelet

renew all 只重写 /etc/kubernetes/pki 下的文件,而 admin.conf、controller-manager.conf、scheduler.conf 内嵌了旧的客户端证书,必须用 kubeadm init phase kubeconfig 重新生成,然后重启静态 Pod。kubelet 的客户端证书默认开启轮换(rotateCertificates: true),服务端证书需要显式打开 serverTLSBootstrap 并手动批准 CSR。

1.5.4.2. etcd 快照恢复

单节点 stacked etcd 的恢复流程是「停 etcd → 换数据目录 → restore → 放回静态 Pod 清单」:

restore-etcd.sh
#!/usr/bin/env bash
set -euo pipefail
SNAP="${1:?usage: restore-etcd.sh <snapshot>}"
sudo mv /etc/kubernetes/manifests/etcd.yaml /root/etcd.yaml
sudo mv /var/lib/etcd /var/lib/etcd.broken
sudo ETCDCTL_API=3 etcdctl snapshot restore "$SNAP" --data-dir /var/lib/etcd
sudo mv /root/etcd.yaml /etc/kubernetes/manifests/etcd.yaml

恢复后 etcd 会以快照时刻的状态启动,快照之后写入的对象全部丢失。三节点 etcd 不能只恢复一个成员就宣布完成:要么恢复全部成员并重建 quorum,要么恢复一个成员后用 etcdctl member add 重新加入其余成员。恢复前先确认 apiserver 已停止写入,否则会制造新的不一致。

警告

回滚不是 downgrade

kubeadm 不支持把控制平面降回旧 minor。真正的回滚只有两条路:用升级前的 etcd 快照恢复并重装旧版本二进制,或者在测试环境验证后重建集群。worker 可以重装旧版 kubelet 包回退,但一旦控制平面已写入新版本数据,降级 etcd 才是唯一一致的选择。

1.5.5. 升级失败与回滚

现象

可能原因

处理方式

upgrade apply 预检失败 etcd is not healthy

etcd 成员异常或磁盘满

先修 etcd;crictl ps 看静态 Pod,df -h 看 /var/lib/etcd

升级后 apiserver 起不来

证书过期、--service-account-issuer 冲突、静态 Pod 清单被改

crictl logs 看 apiserver;kubeadm certs check-expiration

kubelet CrashLoop,日志报 cgroup driver 不一致

containerd 与 kubelet 配置不一致

统一为 systemd,改 /var/lib/kubelet/config.yaml 后重启

drain 卡住,报 PDB 阻止驱逐

副本数不足或 PDB minAvailable 过高

先扩容副本或临时放宽 PDB,记录变更

worker 升级后 NotReady,CNI Pod 异常

CNI 版本不兼容新 kubelet

升级 CNI;crictl logs 看 sandbox 创建失败原因

Ingress 返回 404 / 503

控制器版本过旧,或 Endpoints 尚未收敛

升级 ingress-nginx;观察 kubectl get endpoints

kubectl 报 no matches for kind

客户端与 apiserver 版本偏差超 1 个 minor

升级 kubectl 到 apiserver ±1 范围

升级后 CoreDNS CrashLoop

自定义 Corefile 与新版 CoreDNS 插件不兼容

回退 ConfigMap,按新版语法修正后滚动重启

回滚的决策点要提前定:如果 30 分钟内无法让控制平面恢复 Ready,直接走快照恢复,而不是继续在故障集群上试命令。恢复完成后先验证 kubectl get nodes、etcd endpoint health 与关键 Namespace 的对象数量,再恢复业务流量。

验证清单

  • 三个节点 VERSION 均为目标版本,STATUS 全 Ready。

  • kubectl -n kube-system get pods 无非 Running 的静态 Pod 与 addon。

  • CoreDNS 能解析 kubernetes.default,跨节点 Pod 互通。

  • Ingress 入口返回预期状态码,证书链有效。

  • kubeadm certs check-expiration 中所有证书剩余时间大于 30 天。

  • etcd endpoint health 为 healthy,快照可 restore 到测试目录。

  • 关键 Deployment 副本数恢复,PDB 不再阻止驱逐。

  • 监控里 apiserver 5xx、etcd leader change、节点 NotReady 无异常抬升。

1.5.6. 常见问题与排错

问题

排查入口

升级顺序能不能反过来?

不能。先升 kubelet 会违反「kubelet 不得新于 apiserver」,API 会拒绝注册或行为未定义

能不能跳过 v1.30 直接到 v1.31?

不能。kubeadm 只支持逐 minor;跨版需要多次 apply

控制平面节点要不要 drain?

第一个控制平面通常直接 apply,接受 API 抖动;其余控制平面先 drain 再 kubeadm upgrade node

升级后旧镜像能删吗?

确认回滚窗口结束后,用 crictl rmi --prune 清理未使用镜像,保留一份旧版本 deb 包

托管集群也要做这些吗?

控制平面由云厂商升级,节点池升级仍需 drain、PDB 与插件兼容检查,命令换成各云的 CLI

1.5.7. 进阶挑战

  1. 在测试集群故意把 worker 的 kubelet 升到比 apiserver 新一个 minor,记录 kubectl get nodes 与 kubelet 日志的表现,再按正确顺序修复。

  2. 用 kubeadm upgrade apply --dry-run 观察预检项,给每一项写一条对应的自动化检查命令。

  3. 把 etcd 快照恢复演练做成 CronJob 之外的 runbook:在独立目录 restore 一份快照,用 etcdutl snapshot status 校验后删除。

  4. 关闭一个节点的 kubelet 证书自动轮换,手动走完 CSR 申请与批准流程,比较两种方式的运维成本。

1.5.8. 小结

  • 升级的主线是「控制平面先行、worker 逐台、插件单独跟进」,version skew 决定了这个顺序不能随意调整。

  • upgrade plan 负责告诉你目标版本与手动项,upgrade apply 负责控制平面,upgrade node 负责其余节点,三者不要混用。

  • 回滚的底气来自可验证的 etcd 快照与旧版本安装包,而不是 kubeadm 的降级参数——它并不存在。

延伸阅读