ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

K8s升级:kubeadm、kubelet与apt-mark hold避坑

K8s升级:kubeadm、kubelet与apt-mark hold避坑

1. 这道升级题真正卡人的地方不在命令本身

k8s 升级这道题在 CKA 里属于“看起来最简单、实际最容易丢分”的那一类。题目往往只给你一句话:把某个节点上的 kubeadm、kubelet、kubectl 升到指定版本,然后确认节点回到 Ready。很多人第一反应是翻文档抄命令,apt-get install kubeadm=1.xx.x-00敲下去,终端甩回来一句The following packages have been kept back或者干脆报找不到版本,人当场就懵了。问题的根子几乎从来不是命令写错,而是没搞明白 kubeadm、kubelet、kubectl 这三个东西在升级链路里的角色分工,也没搞明白apt-mark hold这个动作到底在跟谁较劲。

我见过不少人卡在第一步:apt-mark unhold kubeadm这行没执行,后面所有 install 全被包管理器拦下来。也有人反过来,unhold 完装完就跑了,忘了重新 hold 回去,结果第二天节点的 kubelet 被系统的自动升级顺手带到了一个新版本,整个集群的版本偏差瞬间超标。这两个错误方向相反,但本质是同一个认知缺口——你把包管理器的行为当成了黑盒,而它其实是整个升级流程里唯一一个有“记忆”的环节。

这一节我想先把三件套的关系和 hold 机制的底层逻辑摊开讲清楚。搞懂这部分,后面所有命令你都不需要背,顺着因果关系推就能推出来。

1.1 三件套版本关系:为什么 kubeadm 必须先动手

kubeadm、kubelet、kubectl 虽然经常被一起提起,但它们是三个完全独立的二进制包,各自有独立的版本号,也各自被不同的东西管理。

kubeadm是一次性工具,负责“生成配置、拉取镜像、引导集群、执行升级编排”。它不常驻,不跑在 systemd 里,你敲完命令它就退出了。kubelet是节点上的常驻守护进程,负责跟容器运行时打交道、汇报节点状态、拉起 Pod,它被 systemd 管理。kubectl只是一个客户端,负责把你的意图翻译成 HTTPS 请求发给 apiserver。

三者升级的先后顺序不是随便定的,而是由依赖关系倒逼出来的。kubeadm upgrade apply这个动作会重写控制平面组件的静态 Pod 清单、更新证书、迁移 etcd 的配置格式,它本身的行为是由 kubeadm 的版本决定的。如果你用旧版 kubeadm 去升级一个目标版本,它压根不认识新版本的配置字段和镜像 tag,生成出来的清单会直接让 apiserver 起不来。所以顺序必须是:先把 kubeadm 换成目标版本,再用新版 kubeadm 去驱动控制平面升级,最后把节点上的 kubelet 和客户端 kubectl 换成目标版本。

组件升级载体升级时机忘记升级的后果
kubeadmapt/yum 包全流程第一步upgrade plan报未知版本,配置字段不识别
控制平面(apiserver 等)由 kubeadm 驱动,镜像形式kubeadm upgrade apply版本停留在旧版,与新 kubelet 偏差超标
kubeadm 节点配置kubeadm upgrade node每个节点执行一次节点上的 kubelet 配置不同步,重启失败
kubeletapt/yum 包 + systemd 重启控制平面升级完成之后节点实际运行的容器引擎版本没变
kubectlapt/yum 包任意时机,建议紧跟 kubelet命令行报版本偏差警告,部分子命令行为异常

这里有个容易被忽略的点:控制平面的组件(apiserver、controller-manager、scheduler、etcd)不是通过包管理器升级的,它们是静态 Pod,镜像 tag 由 kubeadm 写进/etc/kubernetes/manifests/下的 yaml 文件里。你在节点上apt list --installed | grep kube看到的包列表里根本没有 apiserver。所以“升级 k8s”这件事必须拆成两半:一半是包管理器的活(三个二进制),一半是 kubeadm 编排的活(控制平面和节点配置)。把这两半混为一谈,就会出现“我明明装了新版本,为什么kubectl get nodes还是显示旧版本”的困惑——因为节点上报的 version 字段来自 kubelet,而 apiserver 的版本来自镜像。

1.2 apt-mark hold 与 unhold:被“锁住”的包到底锁的是什么

apt-mark hold做的事情非常朴素:在 dpkg 的状态数据库里给某个包打一个标记,告诉 apt“这个包你别碰,不管依赖怎么变、不管有没有新版本,都不许自动动它”。它不改版本,不卸包,只改一个状态位。你可以用apt-mark showhold随时查当前有哪些包被锁住。

集群安装完成后,初始化脚本或者 kubeadm 安装文档通常会把kubeadm、kubelet、kubectl、kubernetes-cni这几个包全部 hold 住。这么做的理由很实在:k8s 的版本偏差策略是有硬性上限的,kubelet 最多只能比 apiserver 落后三个小版本,kubectl 只能偏差一个小版本。如果放任包管理器跟着系统更新走,某天一次apt upgrade就可能把节点上的 kubelet 顶到最新版,整个集群瞬间变成不受支持的组合,而且这种漂移是静默发生的,等你发现时往往是某个节点莫名其妙 NotReady。

所以升级时的动作就变成了一个“解—装—锁”的三段式:

# 1. 解锁,让 apt 允许改动这个包 apt-mark unhold kubeadm # 2. 安装指定版本 apt-get install -y kubeadm=1.30.2-1.1 # 3. 立刻锁回去,防止后续误升级 apt-mark hold kubeadm

这三步里的第三步最容易被漏。很多人觉得“我在考试环境里就操作这一次,锁不锁无所谓”,但考试评分脚本经常会在升级完成后跑一次apt-mark showhold来确认你没有破坏集群的版本锁定策略。更实际的是,如果你在同一台机器上还要继续装 kubelet 和 kubectl,那么保持 kubeadm 处于 hold 状态能防止apt-get install kubelet=xxx时 apt 顺手把 kubeadm 的依赖关系重算一遍。

还有一个小细节值得说:unhold和install之间不要夹apt-get update之外的多余操作,也不要在这中间跑apt-get upgrade。因为一旦解了锁又跑了全量升级,apt 会把你解开的那个包直接顶到源里的最新版,你再想装指定版本就得先降级,而降级在 apt 里是个麻烦事,需要显式指定版本并处理依赖回退。

提示:如果apt-get install kubeadm=1.30.2-1.1报Version '1.30.2-1.1' for 'kubeadm' was not found,八成是你没把目标版本的软件源加进来,或者apt-get update没跑。这不是 hold 的问题,别在这儿死磕。

2. 动手之前把版本账算清楚

升级这种事,最贵的成本永远是“做到一半发现前提不成立”。我在实际环境里踩过最难受的一次坑,是目标版本号抄错了一位,kubeadm upgrade apply跑到一半报镜像拉取失败,控制平面处于半升级状态,apiserver 起不来,kubectl 直接失联,只能靠crictl和静态 Pod 的日志一点点往回捞。那次之后我养成了一个习惯:任何升级动作之前,先花三分钟把版本账算清楚,把三条信息确认到位——当前版本、目标版本、源里到底有没有目标版本。

这三分钟不是浪费时间,它能把后面 80% 的失败概率直接掐掉。

2.1 当前状态勘察:节点版本、可用版本、源配置

第一步是摸清现状。不要凭记忆,直接查。

# 看节点上报的版本,这个数字来自 kubelet kubectl get nodes -o wide # 看客户端和服务端的完整版本信息 kubectl version --output=yaml # 看 kubeadm 自己的版本 kubeadm version -o short # 看已安装的包版本 dpkg -l | grep -E 'kubeadm|kubelet|kubectl'

这四条命令输出的是四份不同的账。kubectl get nodes里的 VERSION 列是 kubelet 上报的,kubectl version里的 Server Version 是 apiserver 报的,kubeadm version是本地二进制自己的版本。三者不一致是常态,尤其在刚装完还没升级的机器上——apiserver 可能已经是新版,但 kubelet 还停在旧版。搞清楚哪个是哪个,你才知道下一步该动谁。

第二步是确认目标版本在源里存在。这一步用apt-cache madison最直接:

apt-get update apt-cache madison kubeadm

输出会列出该包在已配置源里的所有可用版本,形如:

kubeadm | 1.30.2-1.1 | https://pkgs.k8s.io/core:/stable:/v1.30/deb/ Packages kubeadm | 1.30.1-1.1 | https://pkgs.k8s.io/core:/stable:/v1.30/deb/ Packages kubeadm | 1.29.6-1.1 | https://pkgs.k8s.io/core:/stable:/v1.29/deb/ Packages

如果目标版本不在这份列表里,说明对应的软件源没配。k8s 从 1.24 之后官方源迁移到了独立的仓库地址,每个小版本一个路径。你需要检查/etc/apt/sources.list.d/下的配置文件,确认里面指向的是目标小版本的源。改完源必须重新apt-get update,否则apt-cache madison读的还是旧索引。

检查项命令期望结果
节点当前版本kubectl get nodes记录每个节点的 VERSION
客户端/服务端版本kubectl version --output=yaml确认 skew 是否已在边界
kubeadm 版本kubeadm version -o short与控制平面版本对齐
包是否被锁apt-mark showhold确认三件套在 hold 列表里
目标版本是否可得apt-cache madison kubeadm列表中出现完整版本串
源配置ls /etc/apt/sources.list.d/存在指向目标小版本的源文件

2.2 小版本跨度与组件版本偏差的硬约束

k8s 的版本号是major.minor.patch三段式,升级策略对这三段的处理完全不同。

patch 版本之间可以随便跳,1.30.1 到 1.30.6 直接升,没有任何限制。minor 版本之间不能跳,1.28 想到 1.30,必须先升到 1.29 再升到 1.30,kubeadm upgrade apply会明确拒绝跨 minor 的请求。这是一条硬约束,考试里如果题目要求从 1.28 升到 1.30,你必须老老实实走两轮,每轮都要完整跑一遍 kubeadm、控制平面、kubelet 的流程。major 版本目前不存在,k8s 还没到 2.0,所以不用操心。

除了 kubeadm 的跳跃限制,组件之间还有版本偏差(version skew)策略,这个在考试里不一定直接考,但它决定了你升级过程中集群会不会短暂进入不受支持的状态:

  • kubelet 相对 apiserver:kubelet 可以比 apiserver 旧最多三个 minor 版本,但绝对不能比 apiserver 新。这就是为什么必须先升控制平面再升 kubelet,反过来的话新版 kubelet 会拒绝连接旧版 apiserver。
  • kubectl 相对 apiserver:允许上下浮动一个 minor 版本。超了不会报错,但会打警告,个别子命令的行为可能有差异。
  • kube-controller-manager / kube-scheduler 相对 apiserver:必须同版本,因为它们是一起被 kubeadm 管理的静态 Pod。

把这些约束串起来,升级顺序就被唯一确定了:kubeadm 先换 → 控制平面跟着 kubeadm 走 → 各节点 kubelet 跟上 → kubectl 最后换。这条链路里任何一步乱序,都会撞上 skew 边界。

注意:kubeadm upgrade plan输出的表格里会明确列出“可以升级到哪些版本”以及“当前集群各组件版本”。这个输出在考试里是免费的提示,先跑一次再决定装哪个版本,比直接抄题目里的版本号稳妥得多。

3. 控制平面节点的升级链路

控制平面节点的升级是整条链路上唯一会短暂影响集群可用性的环节。kubeadm upgrade apply执行期间,apiserver 会被反复重启,具体表现为静态 Pod 的清单文件被逐个重写、容器被逐个重建。这个过程通常持续几十秒到一两分钟,期间kubectl可能报连接拒绝,属于正常现象,不需要慌张。

但“正常现象”和“真出问题了”在终端上的表现很像,所以这一节我想把每个阶段的预期输出和判读方法讲透,让你在命令跑的时候心里有底,知道什么该等、什么该断。

3.1 kubeadm 自身的替换与 upgrade plan 的判读

第一步永远是把 kubeadm 二进制换成目标版本。这一步只影响本地工具,不影响运行中的集群,所以可以放心操作。

apt-mark unhold kubeadm apt-get install -y kubeadm=1.30.2-1.1 apt-mark hold kubeadm kubeadm version -o short

最后一行输出v1.30.2就说明替换成功了。如果输出还是旧版本,检查一下which kubeadm是不是指向了/usr/local/bin下的旧二进制——有些环境里手工下载过 kubeadm,PATH 的优先级会让 apt 装的/usr/bin/kubeadm被覆盖掉。这种情况要么删掉旧二进制,要么把 PATH 顺序调整过来。

接下来跑计划。kubeadm upgrade plan是个只读操作,它不会改任何东西,但会做几件事:检查当前集群版本、检查 kubeadm 自身版本、拉取目标版本的镜像元信息、列出可用的升级目标、检查证书是否即将过期。

kubeadm upgrade plan

输出里要重点看三块。第一块是Components that must be upgraded manually,它会列出每个节点当前的 kubelet 版本和目标版本,这是你后续操作的清单。第二块是Upgrade to the latest stable version,给出建议的目标版本号。第三块是各组件(etcd、CoreDNS、kube-proxy)的版本变化。如果输出里出现证书过期的警告,别急着往下走,先处理证书,否则升级完 apiserver 会直接起不来。

这里有个很实际的坑:kubeadm upgrade plan会尝试访问镜像仓库拉取元数据,如果节点出不了网或者镜像仓库不可达,它会卡住或者报错。考试环境一般不会有这个问题,但真实环境里很常见。遇到这种情况,可以加--allow-experimental-upgrades之类的参数绕过一部分检查,或者提前把镜像拉好。

3.2 kubeadm upgrade apply 之后:kubelet 与 kubectl 的接力

确认计划没问题,执行升级:

kubeadm upgrade apply v1.30.2 -y

-y是跳过交互确认。整个过程会输出大量日志,关键节点有这么几个:[upgrade/staticpods]表示正在重写静态 Pod 清单,[upgrade/etcd]表示在迁移 etcd 数据格式,[upload-config]表示在更新集群里的 kubeadm 配置对象(ConfigMap 形式)。最后出现SUCCESS! Your cluster was upgraded to "v1.30.2"就说明控制平面这一半完成了。

但这时候节点还没升级完。kubectl get nodes看 VERSION 列还是旧的,因为 kubelet 还没动。升级输出里会明确提示你:

[kubelet] Downloading configuration for the kubelet from the "kubelet-config" ConfigMap [upgrade/successful] SUCCESS! Your cluster was upgraded to "v1.30.2". [upgrade/kubelet] Please manually upgrade the kubelet package on each node.

接下来是 kubelet 和 kubectl 的接力。这两个包可以一起装,但顺序上建议先 kubelet 再 kubectl,因为 kubectl 只是客户端,晚一点换不影响什么。

apt-mark unhold kubelet kubectl apt-get install -y kubelet=1.30.2-1.1 kubectl=1.30.2-1.1 apt-mark hold kubelet kubectl systemctl daemon-reload systemctl restart kubelet

daemon-reload这行绝对不能省。apt 安装新版 kubelet 时会替换/usr/lib/systemd/system/kubelet.service或对应的 drop-in 文件,如果不重新加载 systemd 的单元定义,restart跑的还是缓存在内存里的旧定义,可能指向一个已经不存在的配置路径。这个坑在真实环境里非常隐蔽——systemctl restart kubelet返回成功,systemctl status kubelet显示 active,但kubectl get nodes里节点就是不 Ready,日志里报failed to load bootstrap kubeconfig之类的错。

重启完之后验证一下:

systemctl status kubelet --no-pager kubectl get nodes -o wide

VERSION 列变成v1.30.2就说明这台机器走完了全程。

3.3 静态 Pod 的滚动与 apiserver 短暂不可用的窗口

kubeadm upgrade apply期间集群会发生什么,值得单独说一下,因为这段窗口里的现象很容易让人误判成故障。

kubeadm 升级控制平面的方式不是“停掉旧的、启动新的”,而是逐个重写/etc/kubernetes/manifests/下的 yaml 文件里的镜像 tag。kubelet 监听这个目录,文件一变就重建对应的静态 Pod。所以实际过程是:apiserver 的新旧两个实例短暂共存,然后旧的退出;controller-manager 和 scheduler 跟着滚动;etcd 视版本跨度可能会做数据迁移。

这段窗口期里,你的kubectl命令可能出现三种表现:连接超时、连接被拒绝、或者返回部分结果。这些都是正常的。判断是否真出问题的标准只有一个——等待时间。正常滚动一般 30 到 90 秒内完成,如果你的kubectl get nodes连续三分钟以上都连不上,那就要开始排查了,先看静态 Pod 的容器状态:

# 静态 Pod 不走 apiserver,需要用容器运行时直接查 crictl ps -a | grep -E 'kube-apiserver|etcd|kube-controller|kube-scheduler' crictl logs <容器ID> 2>&1 | tail -50

这里crictl是容器运行时的命令行工具,静态 Pod 在 apiserver 不可用时是查不到kubectl get pods的,因为 kubelet 自己都连不上 apiserver。这是排查控制平面故障的关键路径,很多人在这一步死磕kubectl get pods -n kube-system,然后被“连接拒绝”绕进死胡同。

etcd 的迁移是另一个需要留意的点。如果新旧版本跨度较大,kubeadm upgrade apply会做 etcd 的数据格式迁移,这期间 etcd 会短暂只读甚至不可用,表现是 apiserver 报etcdserver: request timed out。这个同样属于预期行为,等它跑完就行。

4. 工作节点的升级与调度腾挪

控制平面升完,剩下的工作节点升级在技术上是同一套动作,但多了一层调度层面的考虑:节点上的 kubelet 一重启,跑在上面的 Pod 会短暂失联,如果这台节点上有单副本的有状态服务,重启窗口里业务就直接断了。

考试环境里通常不用管这些,一道题给你一个 worker 节点,上面可能就跑着几个系统组件。但既然这篇是给真实场景写的,调度腾挪这部分还是得讲,因为它决定了你是“无感升级”还是“制造事故”。

4.1 drain 的时机、参数与 PDB 的干扰

标准动作是先标记不可调度,再驱逐 Pod,再升级,最后恢复。

kubectl cordon node-01 kubectl drain node-01 --ignore-daemonsets --delete-emptydir-data

两个参数的含义要搞清楚。--ignore-daemonsets是必须的,因为 DaemonSet 的 Pod 本来就是要跑在每个节点上的,你不忽略它,drain 会直接报错拒绝执行。--delete-emptydir-data是另一回事,它允许删除使用了 emptyDir 卷的 Pod,这个卷的数据在 Pod 重建后就没了,所以这个参数本质上是一次数据丢失的授权,加之前要想清楚上面跑的是什么。

真实环境里最容易卡住的是 PodDisruptionBudget。如果某个应用配了 PDB 且要求最少可用副本数等于当前副本数,drain 会一直卡在“等待 Pod 被驱逐”的状态,直到超时。这时候不是命令写错了,是你不能在不影响业务的前提下驱逐这个 Pod。处理方式要么是临时扩容副本数,要么是接受一次短暂的可用性下降,把 PDB 临时放宽。

还有一个细节:drain默认会设置一个超时时间,卡住的时候不会永远等下去,会报error when evicting pods退出。看到这个错别急着重跑,先kubectl get pods -o wide | grep node-01看看还剩哪些 Pod 没走,逐个确认原因。

提示:单节点集群或者控制平面同时承担工作负载的环境里,drain控制平面节点会把核心组件以外的 Pod 全赶走,如果上面跑着单副本的监控、日志组件,驱逐之后它们会被调度到其他节点重新拉起。这个过程本身没问题,但如果集群资源紧张,可能出现 Pod Pending 的情况。升级前看一眼kubectl describe node的 Allocated resources 会更稳妥。

4.2 工作节点上 kubeadm upgrade node 的差异

工作节点不需要跑kubeadm upgrade plan,也不需要kubeadm upgrade apply。它只需要一个命令:

kubeadm upgrade node

这个命令做的事情和控制平面上的apply不一样。在控制平面节点上,kubeadm upgrade node会做完整的控制平面升级;在工作节点上,它只做两件事:从集群里拉取最新的kubelet-configConfigMap,写到本地的/var/lib/kubelet/config.yaml;更新本地 kubeadm 的集群配置对象。它不碰静态 Pod,也不碰证书。

这个差异带来一个很实际的问题:工作节点升级时,本地的 kubelet 配置文件会被覆盖。如果你之前手工改过/var/lib/kubelet/config.yaml,比如调过maxPods、改过evictionHard阈值、或者加过自定义的resolvConf路径,这些改动在升级后全部丢失。真实环境里这个坑相当致命,因为丢的往往是之前为了解决某个特定问题打的补丁,而且丢得悄无声息,直到某天节点因为资源压力开始乱杀 Pod 才被发现。

所以正规做法是把自定义配置放到/var/lib/kubelet/config.yaml之外的 drop-in 目录里,或者通过 kubeadm 的KubeletConfigurationpatch 机制管理。考试环境不用管这么细,但知道这个差异,至少能让你在kubeadm upgrade node之前先备份一下原文件:

cp /var/lib/kubelet/config.yaml /root/kubelet-config.yaml.bak kubeadm upgrade node

完整的工作节点流程串起来是这样:

# 在控制平面节点上执行 kubectl drain node-01 --ignore-daemonsets --delete-emptydir-data # 登录到工作节点执行 apt-mark unhold kubeadm apt-get install -y kubeadm=1.30.2-1.1 apt-mark hold kubeadm kubeadm upgrade node apt-mark unhold kubelet kubectl apt-get install -y kubelet=1.30.2-1.1 kubectl=1.30.2-1.1 apt-mark hold kubelet kubectl systemctl daemon-reload systemctl restart kubelet # 回到控制平面节点 kubectl uncordon node-01 kubectl get nodes -o wide

注意uncordon这一步必须回控制平面节点执行,在工作节点上跑kubectl也可以,只要 kubeconfig 配好了。但考试里经常出现的情况是工作节点上根本没有 kubeconfig,kubectl一跑就报The connection to the server localhost:8080 was refused。这不是权限问题,是它不知道去哪找 apiserver。解决办法要么把控制平面的/etc/kubernetes/admin.conf拷过来,要么回控制平面节点执行。别在这儿浪费时间去改 kubeconfig 的 server 地址。

5. 升级后的验证清单与常见翻车现场

命令跑完、节点显示 Ready,很多人到这儿就交卷了。但 CKA 的评分脚本往往不只查版本号,它还会检查节点状态、系统组件 Pod 是否全部 Running、kubelet 服务是否 enabled。我见过版本号明明对了却被判错的案例,根因是 kubelet 服务虽然跑起来了但没有设成开机自启,或者某个系统组件 Pod 卡在 CrashLoopBackOff。

这一节把验证拆成五层,每一层的检查目的和常见异常都写清楚。

5.1 五层验证:节点、组件、Pod、kubelet、证书

第一层,节点版本与状态。kubectl get nodes -o wide看两列:STATUS 必须是 Ready,VERSION 必须是目标版本。如果 STATUS 是 NotReady,先别看别的,直接上节点systemctl status kubelet和journalctl -u kubelet -n 100。

第二层,系统组件 Pod。kubectl get pods -n kube-system里所有 Pod 应该都是 Running。重点看kube-apiserver-*、kube-controller-manager-*、kube-scheduler-*、etcd-*、kube-proxy-*、coredns-*。控制平面的静态 Pod 名字里带节点名,如果你的节点名改过,这里可能对不上。

第三层,kubectl 版本。kubectl version --output=yaml里 Client 和 Server 的 minor 版本差距不能超过 1。

第四层,kubelet 服务状态。这个必须上节点查,因为 apiserver 只能看到 kubelet 上报的结果,看不到服务本身的状态:

systemctl is-active kubelet systemctl is-enabled kubelet

两个命令都要看。is-active返回active说明在跑,is-enabled返回enabled说明开机自启配着。真实环境里出现过升级后is-enabled变成disabled的情况,节点当前没问题,但机器一重启就下线。

第五层,证书有效期。升级过程会顺带续期一部分证书,但 kubelet 的客户端证书和 apiserver 的 serving 证书不一定覆盖到:

kubeadm certs check-expiration

输出表格里如果有个别证书剩余时间不足 30 天,趁这次机会一起续了,别等到下次。

把这五层做成一张检查表,升级完照着过一遍,比凭感觉靠谱得多:

层级检查命令通过标准不通过时的第一反应
节点kubectl get nodes -o wideReady + 目标版本查 kubelet 日志
组件 Podkubectl get pods -n kube-system全部 Runningkubectl describe看事件
客户端kubectl version --output=yamlminor 差距 ≤ 1确认装的是哪个二进制
kubelet 服务systemctl is-active/is-enabledactive + enabledsystemctl enable --now
证书kubeadm certs check-expiration剩余 > 30 天立刻续期

5.2 几个真实感很强的失败场景与处置

场景一:apt-get install报held back。终端输出类似The following packages have been kept back: kubeadm kubelet。这就是 hold 没解。回去补apt-mark unhold再装。注意三个包都要解,只解一个另一个照样被拦。

场景二:kubeadm upgrade apply报版本不匹配。输出the desired version "v1.31.0" is not supported,或者提示跨 minor 不允许。这说明你要么跳了 minor,要么本地 kubeadm 版本和目标版本不一致。先kubeadm version -o short确认,再决定是换版本号还是先升 kubeadm。

场景三:节点升级完变 NotReady,日志报failed to run Kubelet: misconfiguration: kubelet cgroup driver: "systemd" is different from docker cgroup driver: "cgroupfs"。这是 cgroup driver 不一致的经典报错。新版 k8s 默认用 systemd 驱动,但容器运行时如果配的是 cgroupfs 就会冲突。处置方式是改容器运行时的配置,让它也用 systemd,然后重启容器运行时和 kubelet。这个坑在从旧版本升上来的环境里特别常见。

场景四:kubectl get nodes一直显示旧版本,但节点上kubelet --version已经是新的。这说明 kubelet 重启后上报的新版本没被 apiserver 接受,通常是 kubelet 和 apiserver 的 skew 超了。回到版本偏差策略那一节,检查是不是顺序搞反了——先升了 kubelet 后升控制平面。

场景五:apiserver 起来但一直 CrashLoop。常见原因是升级过程中证书出了问题,或者 etcd 的数据格式没迁移成功。crictl logs看 apiserver 容器的日志,如果是证书相关,kubeadm certs renew all然后重启静态 Pod;如果是 etcd 相关,可能需要从备份恢复。

我在实际操作中体会最深的一点是:升级这件事的最大风险不在命令,在于你动手前没有把“如果失败怎么回退”想清楚。控制平面节点的/etc/kubernetes/manifests/和/var/lib/etcd是必须提前备份的两个目录,备份一次只要几秒钟,但出了事能不能救回来,全看这两份东西在不在。考试环境有快照可以重置,真实环境没有。

返回列表