维护K8s集群时间长了,你会发现“版本配置”这四个字,几乎把日常运维里容易翻车的地方全占了。很多人一开始只关心控制面镜像tag,拿到一个YAML就往上套,结果节点上的kubelet版本、本地kubectl版本、甚至kubeadm版本全都对不上,集群表面正常,一遇到资源变更或API调整就冒出一堆诡异报错。Kubernetes版本配置,本质上不是改一个数字,而是要把控制面组件、节点组件、客户端工具和各附加组件的版本统一放进一个兼容关系里管理。这篇文章我会按实际维护视角,把版本兼容规则、升级前体检、升级步骤、升级后校验和长期版本策略一次讲清楚,适合正在独立管理集群的运维工程师和平台开发同学参考。
1. Kubernetes版本配置到底在配什么
1.1 一个集群里至少存在四类“版本”
Kubernetes的版本体系不像普通软件那样只有一个版本号。我见过很多新手在排查问题时盯着kubectl version里的Server Version,以为那就是集群版本,实际上那只是控制面里kube-apiserver的版本。
第一类是控制面组件版本。包括kube-apiserver、kube-controller-manager、kube-scheduler,大多数由kubeadm管理的集群以静态Pod方式运行,镜像tag就是版本。第二类是etcd版本。etcd是集群的存储底座,升级Kubernetes时不可能完全绕开它,kubeadm在升级控制面时会有选择地升级etcd镜像。第三类是节点组件版本,主要是kubelet和容器运行时(一般是containerd)。kubelet是真正把Pod跑起来的代理,它和控制面之间的兼容关系最关键。第四类是客户端工具版本,包括kubectl和kubeadm。很多生产事故不是控制面坏了,而是操作人员的kubectl版本和集群对不上,用了不存在的命令或字段。
这四类版本各有各的发布节奏,所以“版本配置”需要建立一张表,而不是一个值。我建议每一个集群都要有一份版本清单,至少记录控制面、etcd、kubelet、容器运行时、kubectl、kubeadm每一类当前版本,以及它们的升级来源。
1.2 兼容窗口:一组可以抄下来的规则
官方版本偏斜策略是判断版本配置是否合理的依据。先把常用规则抄下来:
- kubelet版本不能高于kube-apiserver版本,最多可以比apiserver旧两个minor版本。
- 控制面组件之间(apiserver、controller-manager、scheduler)应该保持同一个minor版本,偏差不能超过一个minor。
- kubectl建议与apiserver保持同一个minor,官方允许最多相差一个minor。
- etcd、CoreDNS、CNI、CSI这些周边组件不适用同一个窗口,必须看各自的兼容矩阵。
这张表我每次升级前都会贴在任务说明里。注意“最多落后两个minor”指的是kubelet,不是kubeadm,也不是kubectl。kubeadm的版本要尽量和目标小版本一致,不是跟随集群版本就可以。还有一个容易忽略的点:apiserver的“两个小版本领先窗口”不是永久不变的,Kubernetes的计划是未来逐步收紧,所以最好不要卡着窗口边缘运行,长期保持在同版本最省心。
| 组件对 | 允许偏差 |
|---|---|
| kubelet / kube-apiserver | kubelet最多落后两个minor,不允许高于apiserver |
| 控制面组件之间 | 尽量同版本,偏差不超过一个minor |
| kubectl / kube-apiserver | 尽量同版本,允许相差一个minor |
| kubeadm / 集群目标版本 | 建议完全一致,尤其是执行升级时 |
1.3 版本错位的连锁反应
版本错位最危险的地方在于,它不会立刻让集群崩溃,而是把问题埋进日常操作的缝隙里。比如控制面升级到了新版本,节点kubelet还停在两个minor之前,集群可能仍然能调度Pod,但节点上报的状态字段里少了一些新字段;如果某个控制器开始使用新API下发配置,旧kubelet不一定能正确认领。你可能会在kubectl describe node里看到奇怪的Condition,或者在创建特定资源时收到ambiguous字段的报错。
再举一个模拟环境里的例子:某次巡检发现,集群里只有部分节点能创建新的StatefulSet,另外一部分节点表现正常但PVC挂载超时。查到最后是CSI Driver版本没跟上,新API需要的新特性在旧驱动里没有实现。这提醒我们,版本配置不只是Kubernetes组件,还包括所有通过CRD扩展的生态组件。版本错位的排查成本很高,因为表面问题在应用层,根因在版本差距里。
2. 升级前必做的版本体检:几条命令摸清现状
2.1 用命令盘点控制面与节点版本
在动任何升级动作前,先执行下面几条命令。
kubectl version -o json kubectl get nodes -o custom-columns='NAME:.metadata.name,KUBELET:.status.nodeInfo.kubeletVersion,KERNEL:.status.nodeInfo.kernelVersion,OS:.status.nodeInfo.osImage' kubeadm versionkubectl version -o json中clientVersion.gitVersion是本地kubectl,serverVersion.gitVersion是apiserver版本。如果你想看整个集群所有节点,用kubectl get nodes表格里的 VERSION 列也能看到kubelet版本,但用 custom-columns 能同时带出内核、操作系统,便于发现是否有人改了某节点内核导致版本曲线不同。
另外,检查kubelet运行状态时,systemctl status kubelet不会直接显示版本,在/usr/bin/kubelet --version能打印。也可以看journal:
journalctl -u kubelet --no-pager | grep -m1 'kubelet version'这里有个细节:kubectl version 的--short参数在新版中已经移除了,1.25之后别再用,否则只会得到“flag needs an argument”一类的报错。用-o json最稳。
2.2 记录当前的“版本拓扑”
我习惯在升级前把版本现状做成一张表,贴在集群的仓库或内部文档里。比如:
| 角色 | 期望版本 | 控制面节点 | 工作节点A | 工作节点B |
|---|---|---|---|---|
| kube-apiserver | v1.29.6 | v1.29.6 | - | - |
| kubelet | v1.29.6 | v1.29.6 | v1.29.3 | v1.28.4 |
| kubeadm | v1.29.6 | v1.29.6 | v1.29.3 | v1.28.4 |
| containerd | 按运行时矩阵 | 1.7.x | 1.7.x | 1.6.x |
这张表的真实价值在于,暴露“你以为所有节点都是同版本”的错觉。集群升级最怕的是节点版本参差不齐还直接跳过检查。记录完成后,至少要确认三件事:所有控制面节点版本一致;工作节点kubelet版本不超过支持窗口;kubeadm二进制和kubelet版本差在哪里。任何一项不满足,都要先纳入升级计划。
2.3 找到真正的版本“存储点”
Kubernetes的版本配置不是集中存储在一个文件里的,它是由一堆分散的事实共同构成的。
第一层是静态Pod清单。/etc/kubernetes/manifests/kube-apiserver.yaml等文件里的image: registry.../kube-apiserver:v1.30.3,这个镜像tag就是控制面实际运行版本。kubeadm升级时会替换这些清单,如果你手动改过镜像tag,下一次 kubeadm upgrade 可能会检测到差异。
第二层是kube-system下的 ConfigMapkubeadm-config。这里面保存着kubeadm初始化集群时的 ClusterConfiguration,包括kubernetesVersion。它记录的是“kubeadm认为集群应该处于的版本”,甚至可以作为校验目标。
第三层是节点上的二进制与systemd单元。kubelet是宿主机进程,它的版本不体现在任何kube-system资源里,只能通过节点状态status.nodeInfo.kubeletVersion或本地kubelet --version查看。
第四层是镜像仓库里的tag以及部署清单中的image字段。CNI、CSI、ingress等组件都以Deployment或DaemonSet形式运行,它们的版本同样存在于镜像tag中。版本“配置”本质上是一串镜像引用和二进制版本的集合,所以管理要落到“精确锁定tag”上。
3. 从控制面到工作节点:一次标准小版本升级实操
3.1 为什么要先升 kubeadm,再升其他组件
kubeadm本身不是控制面组件,而是负责初始化、证书签发和升级编排的命令行工具。执行kubeadm upgrade apply时,它需要理解目标版本的完整结构,包括新版本里新增的组件参数、证书有效期策略和etcd版本。如果你本机还是旧kubeadm,它甚至可能不认识目标版本的配置格式,于是直接抛错。
所以每个节点的标准顺序都是:先安装新版 kubeadm,再安装新版 kubelet 和 kubectl,然后执行kubeadm upgrade node或kubeadm upgrade apply,最后重启kubelet。有人会在控制面上只升其他工具,忘记升kubeadm,结果升级命令一直提示找不到合适的镜像或配置迁移失败,这个坑我踩过不止一次。
如果使用包管理器,要注意锁定版本。以apt为例:
apt-cache madison kubeadm apt-get install kubeadm=1.30.3-00 kubelet=1.30.3-00 kubectl=1.30.3-00不要光写apt-get install -y kubeadm,因为那会把kubeadm升到源里的最新版,可能远高于目标版本。
3.2 控制面升级流程(以基于 kubeadm 的集群为例)
升级前必须备份etcd。即使有多个控制面,也要先把etcd快照保存到外部存储。
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 /backup/etcd-snapshot-$(date +%F).db注意 etcdctl 版本要和 etcd 匹配,否则可能无法识别快照格式。另一个备份是/etc/kubernetes,我通常直接 tar 打包到备份目录,里面包含证书、kubeconfig和static Pod清单。
然后执行预检:
kubeadm upgrade plan v1.30.3这一步会输出当前版本、可升级目标、etcd版本和要更新的镜像列表。确认无误后执行:
kubeadm upgrade apply v1.30.3apply 会逐项更新控制面静态Pod。执行期间,如果只有一个控制面节点,API会出现短时间不可用;线上环境至少两个控制面,依次操作,并确保负载均衡健康检查放行。
控制面组件更新完成后,不要忘记更新该节点上的 kubelet 和 kubectl:
apt-get install kubelet=1.30.3-00 kubectl=1.30.3-00 -y systemctl daemon-reload systemctl restart kubelet然后验证节点版本:
kubectl get nodes -o wide如果该控制面节点之前被封禁,需要执行kubectl uncordon <node>恢复调度。如果你没有在升级前 cordon 节点,执行升级后要注意资源是否被调度到正在维护的节点上。
3.3 工作节点分批滚动升级
工作节点的升级思路和控制面类似,但更强调“分批”。我常用的批次是:先升级1个节点观察5分钟,再升级10%(1-2个),观察10分钟,最后处理剩余。如果节点数量很少,至少跑一轮业务探针再继续。
单个工作节点的操作:
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data apt-get install kubeadm=1.30.3-00 kubelet=1.30.3-00 kubectl=1.30.3-00 -y kubeadm upgrade node systemctl daemon-reload systemctl restart kubelet kubectl uncordon <node>kubectl drain时加--ignore-daemonsets是为了让 DaemonSet 的 Pod 不阻塞排空;--delete-emptydir-data是为了让带 emptyDir 的 Pod 允许被删除。如果某个 Pod 设置了非常严格的 PDB,drain 可能会长时间卡住,先找到原因再继续,不要随手加--force。kubeadm upgrade node在 worker 节点上不需要指定版本,它读取本机 kubeadm 配置并更新该节点相关的静态配置。你必须先确认节点上的 kubeadm 已经是目标版本,否则这条命令还是旧逻辑。
整个批次完成后,用版本检查命令扫一遍节点:
kubectl get nodes -o custom-columns='NAME:.metadata.name,VERSION:.status.nodeInfo.kubeletVersion' | sort | uniq -c如果出现多个不同版本,说明升级有遗漏。这里有个经验:不要等全部节点升级完才做校验,每个批次都要检查,别把“差不多”带进生产环境。
3.4 版本回滚预案:不要幻想一条命令降级
kubeadm没有提供kubeadm rollback这类命令。很多人大版本升级出问题后想改镜像tag把控制面拉回旧版本,结果发现apiserver不断重启,证书、RBAC、存储版本已经不同了。原因很简单:升级过程不是只换二进制,它还会调整RBAC规则、更新证书、迁移内置组件数据。
所以我建议把回滚预案当成“恢复到已知良好状态”来设计,而不是“把版本号改回去”。具体包含:
- etcd快照:这是最高优先级,数据问题只能用它恢复。
/etc/kubernetes备份:证书和Kubeconfig是控制面的命根子。- 节点组件版本:所有节点的 kubeadm、kubelet、kubectl 可安装版本要能重新安装。
如果升级后发现轻微问题,优先在当前版本上修复,不要急着回滚。跨两个minor以上的升级,数据可能已经做了不可逆迁移,回滚风险远大于继续向前。真要回滚,也要按“先恢复证书、再恢复组件版本、最后恢复etcd快照”的顺序小心执行,并在维护窗口内测试完整流程。
4. 升级后的版本校验与配置清理
4.1 验证“版本配置”的最后防线
版本升级完成后,需要确认的不只是节点版本一致。我的“黄金三连”:
kubectl get --raw='/livez' kubectl get --raw='/readyz' kubectl get nodes -o custom-columns='NAME:.metadata.name,KUBELET:.status.nodeInfo.kubeletVersion'/livez表示apiserver进程活着,/readyz表示它已经准备好接受请求。接着执行kubeadm upgrade plan。如果一切正常,它会显示当前版本已经是最新或者提示没有可用升级,不会报错。看到kubeadm upgrade plan输出的第一行,确认目标版本和集群版本一致。
还要检查kubeadm-config中的kubernetesVersion字段:
kubectl -n kube-system get cm kubeadm-config -o yaml如果在升级过程中手动改过静态Pod镜像,而ConfigMap里的kubernetesVersion没有同步,下一次升级就可能出现混乱。所以在校验阶段,我会同时核对这几个事实:镜像标签、ConfigMap版本、节点kubelet版本、kubectl版本。四者一致,才算版本配置闭环。
4.2 客户端版本同步:别把 kubectl 当成“默认最新”
多集群环境里,管理员本地的 kubectl 版本很难总是匹配所有集群。但建议每个人在连接某个集群前,先查看当前上下文指向的集群版本,再决定是否使用本机命令行。一个稳妥的方法是:在跳板机或CI里维护一个与目标集群版本一致的 kubectl 二进制,而不是全局装一个 latest。
如果只是临时操作,可以这样安装:
KUBE_VERSION=v1.30.3 curl -LO "https://dl.k8s.io/release/${KUBE_VERSION}/bin/linux/amd64/kubectl" install kubectl /usr/local/bin/ kubectl version --client更工程化的做法是,在代码仓库里定义一个versions.yaml,把kubectl版本作为参数传入CI,避免不同环境之间漂移。你不需要记住所有兼容规则,只要规则里统一“生产集群用什么版本,客户端就尽量用什么版本”,就能减少一大半莫名其妙的问题。
4.3 附加组件版本治理:最容易被忽略的一层
Kubernetes本体升级完成后,附加组件的版本配置还留在原地。CoreDNS、Metrics Server、ingress controller、CNI、CSI Driver这些组件往往会声明自己支持某个K8s版本范围。如果升级集群前没有同步这些组件,升级后可能会出现“控制面正常,Pod也能调度,但DNS解析延迟变高、监控数据拿不到、LoadBalancer服务创建失败”等情况。
维护一份组件版本清单非常有必要。例如:
| 组件 | 部署方式 | 当前app版本 | 需要关注的兼容性 |
|---|---|---|---|
| CoreDNS | Deployment | 按官方矩阵 | 随K8s版本更新,通常小版本跟随 |
| Metrics Server | Deployment | 按官方矩阵 | 注意APIService和证书 |
| CNI | DaemonSet | 按插件版本 | 必须与K8s发布矩阵匹配 |
| Ingress Controller | Deployment/DaemonSet | 按发行版 | 不要用latest标签 |
清单不要只记版本号,还要记录“上次升级时间”和“升级触发条件”。我的原则是:集群升级后,同一批次内把周边组件也一起排期,而不是等出了问题再逐个试。用Helm维护时,把chart版本与app版本都固定住,升级到新K8s版本时先看兼容矩阵,再决定chart版本升到什么位置。不要为了省事直接helm upgrade --install ... --version latest,latest会把版本配置的确定性完全扔掉。
5. 长期版本配置:什么时候升、怎么升、怎么管
5.1 用支持窗口倒推升级日期
Kubernetes的版本支持策略是最近三个minor版本都受支持,而每个minor的发布节奏大约是三个月。如果按这个节奏,一个版本在发布后最多一年多一点就会停止支持。我通常的做法是:每季度查看一次官方发布页,确认所有线上集群的版本是否还在支持窗口内,并提前一个minor开始准备升级。
这里的计算方式很简单:如果当前线上版本是1.28,最新支持版本已经到1.30,那么1.28还处于支持窗口;再等一两个发布周期,1.28就会进入停止支持名单。不要用“能跑就不升”的心态管理版本,安全补丁只发给受支持版本,旧版本一旦停止支持,遇到安全问题只能自己扛。
5.2 小步快跑 vs 大版本跳升
不要尝试从1.24直接跳到1.30,即使官方文档没有明确禁止,升级路径中会经过很多废弃API的移除点。Kubernetes每个minor都会移除一批已在更早版本标记为废弃的API,跨多个minor升级时,你可能根本不知道某类资源已经改到哪个新组里。小步快跑的意思是:每次最多升一个minor,升完做一轮审计,确认没有使用废弃API后再继续。
具体做法:
- 先在测试环境升到目标minor。
- 用
kubectl get --raw=/apis枚举集群中的资源组,或者更简单地跑一轮业务回归。 - 检查所有CRD、Helm chart、Operator是否声明了兼容版本。
- 再升下一个minor。
这种方式看着慢,实际最稳。版本配置不是“换一个标签”,而是一连串兼容性决策的累积。
5.3 把版本配置放进自动化与可观测
版本配置不应该是某位管理员脑子里的信息。我建议把所有集群的期望版本写在一个配置仓库里,例如versions.yaml:
clusters: production: targetKubernetesVersion: v1.30.3 targetEtcdVersion: 3.5.x kubectlVersion: v1.30.3 staging: targetKubernetesVersion: v1.30.3然后写一个最简单的检查脚本,把期望版本与实际集群节点版本做对比。例如:
#!/usr/bin/env bash set -euo pipefail target="v1.30.3" mapfile -t versions < <(kubectl get nodes -o jsonpath='{range .items[*]}{.status.nodeInfo.kubeletVersion}{"\n"}{end}') for v in "${versions[@]}"; do if [[ "$v" != "$target" ]]; then echo "版本漂移: 期望 $target, 实际 $v" exit 1 fi done echo "所有节点版本一致: $target"这里要留意,有些节点的版本号会带 build 后缀,比如v1.30.3+k3s1或者其他发行版标记。如果你的环境是这样,脚本要做前缀匹配,不要直接比较字符串。除了检查,还应该把“过期版本”作为一个指标上报监控系统,比如节点数里kubelet版本不一致的比例超过0就直接告警。这样版本配置就从一次性操作变成了持续可观测的手段。
6. 常见问题速查:版本配置坑与排查实录
6.1 kubectl 比集群新很多,命令直接报错
症状:执行kubectl get pod时出现error: the server doesn't have a resource type "pods",但用旧kubectl又可以。原因是本地kubectl按自己的API discovery请求集群,遇到旧apiserver没有的资源版本时就会失败。排查时先kubectl api-versions看apiserver当前提供哪些版本,然后换到与集群相同minor的kubectl。一个比较稳的习惯是,在~/.kube/config里为每个集群写清楚版本注释,肉眼提示。
6.2 kubeadm upgrade plan 到不了对应版本
症状:执行计划时输出类似couldn't fetch the Kubernetes version,或者提示 etcd 版本无法识别。先确认kubeadm二进制本身已更新到目标版本,再检查三个控制面节点之间的 etcd 健康,kubectl -n kube-system get pods | grep etcd,看看是否有一台etcd处于CrashLoopBackOff。etcd版本过旧也是常见原因。我在处理这类问题时,会先恢复到“控制面可读、etcd健康”的基线再动升级,不要在半健康状态下继续跑。
6.3 kubeadm upgrade apply 卡在旧配置结构
症状:升级过程报错,说当前 kubeadm 配置结构太旧,或者unexpected old bundle version。这通常发生在集群初始化版本与目标版本相隔太远,kubeadm 无法直接迁移配置。处理办法是先做配置迁移,kubeadm提供了kubeadm config migrate,把旧格式转换成当前格式;但要注意迁移后的配置要经过评审再写回。如果已经卡住,不要强行删除 etcd Pod 或控制面静态Pod,先从备份恢复/etc/kubernetes,再用匹配旧版 kubeadm 重建升级基线。
6.4 节点升级后 NotReady,kubelet 却在运行
症状:节点版本显示正常,但状态 NotReady,看kubelet日志没有明显panic。优先怀疑容器运行时和CNI。控制面升级后,如果CNI组件没有同步升级,节点可能与API通信异常,kubectl describe node会看到NetworkReady=false。排查步骤:
journalctl -u kubelet -f看错误关键字。crictl ps检查运行时容器是否正常。- 检查CNI DaemonSet Pod,必要时先滚动升级CNI。
这里要强调:不要一看到 NotReady 就重启节点。先确认是网络层还是kubelet配置层,然后针对修复。
6.5 回滚后控制面崩溃,问题比升级前更严重
症状:手动改镜像tag回滚后,apiserver起不来,证书报错。原因是没有同步恢复证书和RBAC。升级后的证书可能已经重新签发,旧版本apiserver不认新证书,或者新旧证书混淆。所以,回滚操作不能只改镜像。我的建议是:升级前把/etc/kubernetes/pki完整打包,回滚时整个/etc/kubernetes目录一起恢复,而不是只改image字段。并且要在测试环境先演习一次回滚,确定步骤没问题,别在生产环境第一次尝试。
6.6 版本号里的“假偏差”让监控误报
症状:版本检查脚本报“版本不一致”,人工登录节点发现明明是同一个版本。原因多是发行版在版本号后添加了后缀。比如v1.30.3和v1.30.3+custom,肉眼一样,脚本比较字符串却是两回事。解决方式是在检查脚本里提取主版本号再比较,比如用正则去掉+后面的部分。这个问题看似小,但会让告警淹没真实问题,值得提前处理。
最后说一个我自己的习惯。每次版本升级完成后,我都会顺手把当前集群的版本状态写进一个 VERSIONS.md,提交到配置仓库。版本配置的本质,不是让所有组件永远最新,而是让团队里的每个人都清楚当前处于什么版本、为什么在这个版本、下一步准备怎么升。代码和镜像可以自动化,版本信息如果只存在某个人的记忆里,迟早会变成事故。希望这篇基于实际维护经验的梳理,能帮你在下一次管理 Kubernetes 版本配置时少踩几个坑。