k8s 集群升级这道 CKA 真题,看起来只是几条 apt 命令加一次 kubeadm upgrade,真上手做才知道坑全藏在细节里。标题里那个apt-mark unhold kubeadm括号注释,恰恰是很多人第一次做这道题时卡住的地方——明明apt-get install敲下去了,终端却回一句"软件包已经是 locked 状态",或者干脆装了个旧版本上去。这篇文章就把这道题从头到尾拆一遍:先讲清楚这道题在考什么、评分点落在哪里,再给出控制平面节点和工作节点各自的完整操作序列,最后整理一份排查速查表。不管你是准备考证的考生,还是日常要维护测试集群的运维,都可以照着这个顺序走一遍。文中涉及的版本号、包后缀、命令参数都基于常见实践补充,你在自己的环境里落地时把版本号替换成实际目标即可。
1. 真题拆解:这道升级题到底在考什么
1.1 任务描述还原与评分点分布
这类题目的标准形态大致是这样:给定一个已经跑起来的小型集群,包含一到两个控制平面节点和一到两个工作节点,要求把整组节点从当前小版本升级到指定的小版本,只允许使用 kubeadm 工具链完成,升级完成后所有节点状态必须是 Ready,且kubectl get nodes里显示的 kubelet 版本要和目标版本一致。题目通常会明确写出目标版本号,比如从 1.28.x 升到 1.29.x,同时给出待升级的节点清单。
评分点通常拆得很细,常见的有这么几块:kubeadm 包是否升级到目标版本、kubeadm upgrade plan是否执行过、控制平面节点的kubeadm upgrade apply是否成功、工作节点的 kubeadm 与 kubelet 是否同步升级、节点是否正确执行了 drain 和 uncordon、升级后 kubelet 服务是否处于 active 状态。有些题目还会额外给分给"升级后节点未被污染",也就是没有被随手改动过标签、污点或者调度的状态。
这里有个容易被忽视的点:很多人只盯着 kubelet 版本,忘了 kubectl。题目里如果写了"kubectl 也要升级到目标版本",就必须在控制平面节点上单独处理 kubectl 包,并且用kubectl version --client或者kubectl version --short确认版本号。缺这一步,扣分是静默的,你根本感觉不到自己漏了。
1.2 版本偏差策略:为什么升级顺序不能乱
Kubernetes 的组件版本不是随便配的,官方有一套版本偏差策略,核心约束是:kube-apiserver 永远是最新版本的那一个,其他组件不允许比它更新。具体到几个关键组件,kubelet 允许比 kube-apiserver 落后,当前策略允许落后三个小版本;kube-proxy 跟着 kubelet 走,偏差范围一致;kubectl 允许与 kube-apiserver 相差正负一个小版本;kube-scheduler 和 kube-controller-manager 则不允许超过 kube-apiserver 的版本。
这套约束直接决定了操作顺序:必须先把控制平面的 kubeadm 升上去,再执行 upgrade apply 把 apiserver、controller-manager、scheduler、etcd 这些静态 Pod 拉起来,然后才能在各个节点上升级 kubelet。如果你反过来,先在节点上把 kubelet 升到目标版本,那么 kubelet 版本就会短暂地高于 apiserver,kubectl get nodes里会看到版本号跳了,但控制平面那边的 preflight 检查很可能会报错,甚至出现 kubelet 无法正常上报状态的情况。
用生活化的类比:apiserver 是公司里的主管,kubelet 是组员。组员的版本可以比主管旧一点,但不能比主管还新——否则主管看不懂组员提交的汇报格式。理解了这个关系,你就不用死记硬背升级顺序,自己就能推导出"先控制面、后工作节点"的流程。
1.3 包管理器锁:apt-mark hold 这个细节为什么是高频坑
Debian 系发行版上,kubeadm、kubelet、kubectl 这三个包在安装时通常会被apt-mark hold锁住。加锁的初衷是防止系统例行apt upgrade时把集群组件顺手升级,导致版本失控——这在生产环境里是好事,但在考试或者计划内升级时就成了障碍。
具体现象是:你执行apt-get install -y kubeadm=目标版本,apt 会告诉你该软件包被 hold 住了,要么跳过,要么直接失败。更迷惑的一种情况是,命令执行完没有明显报错,但kubeadm version一看还是旧版本。原因就是 apt 在遇到 held 包时的行为差异,加上有些环境会配置--allow-change-held-packages之类的参数,导致你以为装上了。
所以标题里那句apt-mark unhold kubeadm不是废话,它是整个升级流程的第一步。操作上要先apt-mark unhold kubeadm,装完新版本,再apt-mark hold kubeadm把锁加回去,避免后续被自动升级。kubelet 和 kubectl 同理,只是它们的 unhold 时机分布在不同节点、不同阶段。记住一个原则:谁要升级,就先给谁解锁;装完立刻加锁,锁的状态是这道题的隐性检查项。
2. 升级前的准备:把不确定性收敛到最小
2.1 环境体检:五条命令摸清家底
动手之前先做体检,这一步花五分钟,能省掉后面半小时的排错。我习惯跑这么几条命令,按顺序看:
kubectl get nodes -o wide kubectl version --short kubeadm version apt-mark showhold cat /etc/os-release第一条看节点列表、角色、状态和当前 kubelet 版本,顺便记下哪些节点是控制平面、哪些是工作节点。第二条看客户端和服务端的版本,确认 kubectl 与 apiserver 的偏差在允许范围内。第三条确认 kubeadm 的当前版本。第四条列出当前被锁住的软件包,这就是你后面要逐个解锁的对象清单。第五条确认发行版和版本代号,决定你用 apt 还是 yum/dnf 的语法。
体检时还有两个地方值得顺手看一眼:kubectl -n kube-system get pods确认核心组件都健康,没有 CrashLoopBackOff 的 Pod 挂着;kubectl get pdb -A看看有没有 PodDisruptionBudget,这个直接影响后面 drain 节点会不会卡住。如果集群里跑着带 PDB 的业务负载,drain 时可能被拦住,得提前想好是临时调整 PDB 还是接受排水失败后手工处理。
2.2 备份:etcd 快照和配置目录双保险
升级是有回退需求的,虽然考试环境往往不要求你真回滚,但备份动作本身是职业习惯,也是防止手滑的安全网。两件事:etcd 快照和 /etc/kubernetes 目录拷贝。
etcd 快照的命令在控制平面节点上执行,路径和证书按实际情况替换:
ETCDCTL_API=3 etcdctl snapshot save /opt/etcd-backup-$(date +%F-%H%M).db \ --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执行完用etcdctl snapshot status看一眼文件大小和哈希,确认快照是可用的而不是个空壳。配置目录的备份更简单,cp -a /etc/kubernetes /opt/kubernetes-backup-$(date +%F-%H%M),把整个目录连同 kubeconfig、静态 Pod 清单、PKI 证书一起打包。这个目录不大,但一旦升级过程中静态 Pod 清单被改坏,它就是最快的恢复来源。
注意:备份目录不要放在会被 drain 影响的本地临时路径上,也不要把快照文件塞进 /tmp,某些系统重启会清理。选一个固定目录,命名带上时间戳,方便回查。
2.3 仓库与版本号确认:别让 apt 帮你选版本
升级前必须确认目标版本在软件源里确实存在。用apt-cache madison kubeadm可以列出仓库里所有可安装的版本,输出里会带上类似1.29.0-1.1这样的包后缀。这个后缀很重要:Kubernetes 的 deb 包版本号是"上游版本-打包修订号"的格式,你写apt-get install kubeadm=1.29.0可能匹配不上,得写全kubeadm=1.29.0-1.1。
另外要确认软件源指向的仓库地址与你当前集群的版本线匹配。如果源里只有旧版本,apt-get update之后仍然看不到目标版本,那就得先调整源配置再继续。这一步在考试环境里通常不用操心,题目会给好源;但在自建环境里,源配置错误导致的"版本不存在"报错非常常见,而且报错信息不直观,容易让人以为是网络问题。
还有一个细节:apt-get update要单独执行一次,不要指望 install 命令自动刷新索引。仓库索引过期时,你看到的可用版本列表是旧的,install 会告诉你找不到指定版本,实际原因只是索引没更新。
3. 控制平面节点升级实操全流程
3.1 第一步:解锁并安装目标版本的 kubeadm
控制平面节点的第一件事永远是 kubeadm 本身。命令序列如下,注意 unhold 和 hold 的配对:
apt-mark unhold kubeadm apt-get update apt-get install -y kubeadm=1.29.0-1.1 apt-mark hold kubeadm kubeadm version最后那条kubeadm version是必须的验证动作,输出里会明确写着GitVersion:"v1.29.0"。看不到这个版本号,后面的步骤全都白搭。我见过有人 unhold 之后忘了apt-get update,结果装回去的还是旧版本,然后对着kubeadm upgrade plan的输出发懵,以为集群状态有问题。
这里解释一下为什么先只升 kubeadm,不把 kubelet 和 kubectl 一起装。原因在于 kubeadm 是"升级动作的执行者",它需要先具备识别目标版本、生成新静态 Pod 清单、执行 preflight 检查的能力。而 kubelet 是"被升级的对象",必须等控制平面的 apiserver 先升上去、版本偏差允许之后才能动。kubectl 则是纯客户端工具,放最后处理没有风险。
3.2 第二步:读透 kubeadm upgrade plan 的输出
kubeadm upgrade plan是只读操作,不改任何东西,但它的输出信息量很大。典型输出会包含三块内容:当前集群版本与 kubeadm 版本、可升级到的目标版本列表、以及各核心组件的版本对照,比如 etcd、CoreDNS、kube-proxy 分别会升到什么版本。
读这块输出的时候重点关注两件事。一是目标版本是否出现在"可以升级到"的列表里,如果没有,说明 kubeadm 的版本或者仓库里的版本信息不对。二是输出末尾有没有 preflight 相关的警告,比如 kubelet 版本与目标版本不匹配、某个节点不可达、swap 未关闭之类。这些警告在upgrade apply阶段会变成阻塞项。
如果 plan 阶段报出 "the kubelet version is higher than the control plane version" 这类错误,说明之前有人把某个节点的 kubelet 提前升上去了,得先把控制平面升到位再处理。如果是 "cannot determine the node name" 之类的报错,通常是 kubeconfig 指向不对,检查/etc/kubernetes/admin.conf或者当前用户的~/.kube/config是否可用。
提示:plan 的输出建议用
| tee存一份到文件里,升级出问题时可以对照着看哪些组件本该升级却没升。
3.3 第三步:apply 执行与静态 Pod 滚动重建
确认 plan 没问题,执行 apply:
kubeadm upgrade apply v1.29.0 -y-y参数省掉交互确认,考试环境里有用,因为有些终端会把确认提示吞掉导致看起来卡住。执行过程中会看到它依次做这些事:检查集群健康状态、拉取新版本的控制平面镜像、更新/etc/kubernetes/manifests下的静态 Pod 清单、等待 apiserver、controller-manager、scheduler、etcd 依次重启并就绪、升级 CoreDNS 和 kube-proxy。
中间会出现一段"等待"的时间,因为静态 Pod 重建后 kubelet 需要重新拉起容器,apiserver 重启期间 kubectl 会短暂连不上。不要在这个阶段慌着敲命令,等它自己走完。全流程走完会输出 "SUCCESS! Your cluster was upgraded to v1.29.0" 之类的字样。
如果集群有多个控制平面节点,第一个节点用kubeadm upgrade apply,其余控制平面节点用kubeadm upgrade node。后者只做本节点的静态 Pod 清单和 kubelet 配置更新,不执行集群级别的升级动作。顺序上每个控制平面节点都要走一遍"unhold kubeadm、装新版本、hold、upgrade node"这套组合。
3.4 第四步:kubelet 与 kubectl 收尾
控制平面的 kubeadm 升级完成后,接着处理本节点的 kubelet 和 kubectl:
apt-mark unhold kubelet kubectl apt-get install -y kubelet=1.29.0-1.1 kubectl=1.29.0-1.1 apt-mark hold kubelet kubectl systemctl daemon-reload systemctl restart kubelet systemctl status kubelet --no-pagerdaemon-reload不能省,因为 kubelet 的 systemd unit 文件或者配置可能在新包里发生了变化,不重载的话重启会用到旧配置。重启后看状态是否 active (running),同时用kubectl get nodes确认控制平面节点的 kubelet 版本已经刷新。
这里有个实操心得:kubelet 重启后节点状态会短暂变成 NotReady 或者 SchedulingDisabled,等十几秒会自动恢复 Ready。如果你在读秒阶段看节点状态就急着重启服务,反而可能把一次正常的滚动弄成反复重启。我的做法是重启完sleep 20再看,或者用kubectl get nodes -w盯着状态变化。
4. 工作节点升级与集群回归验证
4.1 drain 的正确姿势与 PDB 拦截处理
工作节点升级前必须排水,让上面的业务 Pod 迁移到其他节点。标准命令是:
kubectl drain node01 --ignore-daemonsets --delete-emptydir-data--ignore-daemonsets不能省,因为每个节点上都有 DaemonSet 类型的 Pod,比如 kube-proxy 和网络插件,drain 默认会拒绝处理这些 Pod。--delete-emptydir-data用于清理使用 emptyDir 的 Pod,在较新版本里这个参数替代了旧的--delete-local-data,写错参数名会直接报错。
drain 卡住是最常见的问题之一。表现是命令一直不返回,或者报 "Cannot evict pod as it would violate the pod's disruption budget"。遇到 PDB 拦截,先kubectl get pdb -A找到对应的预算对象,看它的 minAvailable 和当前健康 Pod 数。如果集群里副本本来就少,PDB 会拦住驱逐,这时候要么临时调低 PDB,要么接受这个节点暂时无法完全排空。考试场景里一般不会有复杂业务负载,但日常环境里这一步值得多留个心眼。
还有一个容易忽略的点:如果节点上有裸 Pod(没有控制器管理的 Pod),drain 会直接拒绝,提示需要加--force。加--force意味着这个 Pod 会被直接删除且不会重建,操作前得确认它是不是关键负载。
4.2 工作节点的三步升级序列
排水完成后,工作节点上的操作顺序是:升 kubeadm、跑 upgrade node、升 kubelet 和 kubectl。具体命令:
apt-mark unhold kubeadm apt-get update apt-get install -y kubeadm=1.29.0-1.1 apt-mark hold kubeadm kubeadm upgrade node注意工作节点用的是kubeadm upgrade node,不是apply。这个子命令在工作节点上的作用是更新 kubelet 的配置文件和证书,不会去动静态 Pod。执行完会看到类似 "kubelet configuration updated" 的输出。
接着处理 kubelet 和 kubectl:
apt-mark unhold kubelet kubectl apt-get install -y kubelet=1.29.0-1.1 kubectl=1.29.0-1.1 apt-mark hold kubelet kubectl systemctl daemon-reload systemctl restart kubelet工作节点上装不装 kubectl 取决于题目要求,有些环境只在控制平面节点保留 kubectl。如果题目没要求,装了也没坏处,但别忘了跟着锁回去。
节点恢复调度用kubectl uncordon node01,然后在控制平面节点上确认节点状态和版本。这里要注意:uncordon 之前先确认 kubelet 已经是 active 状态,否则节点会以 NotReady 的状态回到调度池,后续调度上去的 Pod 会启动失败。
4.3 升级后的回归验证清单
全部节点走完之后,做一轮验证,清单如下:
| 检查项 | 命令 | 期望结果 |
|---|---|---|
| 节点版本 | kubectl get nodes -o wide | 所有节点 kubelet 版本为目标版本 |
| 节点状态 | kubectl get nodes | 全部 Ready,无 SchedulingDisabled |
| 核心组件 | kubectl -n kube-system get pods | 控制平面组件和 CoreDNS 均 Running |
| 客户端版本 | kubectl version --short | 客户端与服务端版本符合偏差策略 |
| kubeadm 版本 | kubeadm version | 目标版本 |
| 包锁状态 | apt-mark showhold | kubeadm、kubelet、kubectl 均在锁列表中 |
| 证书状态 | kubeadm certs check-expiration | 关键证书未过期 |
表格里最后一项容易被忽略。升级过程本身不会自动续期证书,但如果集群运行时间较长,升级后恰好赶上证书临近过期,后续操作会莫名其妙失败。顺手检查一次,成本很低。另外kubectl get pods -A看一遍全局 Pod 状态,确认没有因为升级导致的异常重启,网络插件和存储插件这类依赖 kubelet 接口的组件尤其值得关注。
5. 常见问题与排查速查表
5.1 版本与包管理类报错
这类问题的根子大多在 hold 状态和版本号写法上。典型表现是 install 成功但版本没变,或者直接报"找不到指定版本"。排查顺序:先apt-mark showhold看锁,再apt-cache madison kubeadm看可用版本,最后核对包后缀是否写全。版本号里少写一个-1.1后缀,apt 就会认为你要装的是一个不存在的版本。
另一类是版本偏差报错。比如在控制平面还没升级时就去升工作节点的 kubelet,kubeadm upgrade node会报 kubelet 版本高于控制平面的错误。处理办法是把顺序倒回来,先完成控制平面升级。
5.2 镜像拉取与静态 Pod 启动失败
kubeadm upgrade apply过程中如果卡在拉镜像,通常是镜像仓库不可达。先手动执行kubeadm config images pull --kubernetes-version v1.29.0看具体哪个镜像失败,再检查仓库地址配置。kubeadm 的配置存在名为kubeadm-config的 ConfigMap 里,可以用kubectl -n kube-system get cm kubeadm-config -o yaml查看当前的 imageRepository 设置。
静态 Pod 起不来还有一个隐蔽原因:清单文件权限或路径被改动。检查/etc/kubernetes/manifests下四个核心清单是否存在、属主是否正确。kubelet 只认这个默认目录,路径改了它就不管了。
5.3 证书、配置与节点状态异常
升级后节点长时间 NotReady,先看 kubelet 日志:journalctl -u kubelet -n 100 --no-pager。常见原因有 kubelet 配置文件里的 cgroup driver 与容器运行时不一致、证书过期、以及 kubelet 无法连接 apiserver。cgroup driver 不一致的表现是 Pod 能调度上去但起不来,日志里会有明显的驱动名称对比。
如果kubectl get nodes显示的版本号还是旧的,但节点上的kubelet --version已经是新的,那多半是 kubelet 没重启或者 apiserver 缓存未刷新。先确认服务状态,再等半分钟重新查询。
下面这张表把高频问题集中列一下,方便对照:
| 现象 | 可能原因 | 处理方向 |
|---|---|---|
| install 后版本未变 | 包被 hold 住 | apt-mark unhold后重装 |
| 找不到指定版本 | 包后缀写错或索引过期 | 补全-1.1后缀,重跑apt-get update |
| upgrade apply 卡住 | 镜像拉取失败 | 手动 pull 镜像,检查仓库配置 |
| drain 无法完成 | PDB 拦截或存在裸 Pod | 调整 PDB 或加--force |
| 节点 NotReady | kubelet 未启动或配置错误 | 看 journalctl,核对 cgroup driver |
| 节点版本未刷新 | kubelet 未重启 | daemon-reload后重启服务 |
| 静态 Pod 缺失 | 清单目录被改动 | 检查/etc/kubernetes/manifests |
6. 我在实操里总结的几条经验
6.1 建立一个"版本心智模型"而不是背命令
刚接触这道题的时候,我习惯把命令序列抄在纸上,按顺序敲。这种方式在环境干净时能用,一旦中间某步报错就完全懵了,因为不知道当前处在流程的哪个位置。后来我换了个思路:把集群想象成一条从 apiserver 往下的版本链条,apiserver 在最顶端,kubelet 在下面,kubectl 是旁挂的客户端。升级的本质是把这条链条从下往上、从执行者到被执行者逐段替换。
带着这个模型,很多判断就变得自然:为什么先升 kubeadm,因为它是执行升级的工具;为什么控制平面先于工作节点,因为 apiserver 必须先就位;为什么 kubectl 放最后,因为它不参与集群运行,只影响你敲命令的体验。遇到报错时也能快速定位:报错说版本偏差,那就是链条顺序错了;报错说包被锁,那就是执行者没解锁。这种理解方式比死记命令可靠得多,换个大版本或者换个发行版也能迁移过去。
6.2 考场节奏:先保流程完整,再抠细节
考试环境有时间压力,这道题如果按部就班做,顺利的话十几分钟能完成,但如果卡在某个报错上,很容易把后面的题时间挤掉。我的建议是先把主流程走通:控制平面 apply 成功、工作节点 upgrade node 成功、kubelet 全部重启、节点全部 Ready。这四件事做完,基本分已经拿到了。然后再回头补细节,比如检查包锁状态、确认 kubectl 版本、看一眼证书有效期。
另一个节奏上的经验是:每个节点升级完立刻验证,不要攒到最后一起查。节点多了之后,你很难记住哪台机器上做过哪一步,而kubectl get nodes -o wide是随时可以跑的低成本检查。发现某台机器版本没刷新,当场补一条 restart,比最后统一排查省事得多。
还有一个容易被忽略的收尾动作:升级完成后把 shell 的 kubectl 补全重新加载一次。大版本升级后补全脚本可能变化,不重载会出现命令补全失效或者补全到已废弃子命令的情况,虽然不影响功能,但在紧张的考试节奏里挺烦人。执行source <(kubectl completion bash)就行,几秒钟的事。