Kubernetes 基础体系 · 第 63/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
Kubernetes 集群升级:Version Skew、API 弃用、节点灰度和回滚
Kubernetes 升级不是简单地把所有节点上的二进制文件替换成新版本。一次升级同时改变了三类状态:
- 组件版本状态:API Server、Controller Manager、Scheduler、kubelet、kube-proxy、kubectl 以及云厂商控制面可能处于不同版本。
- API 契约状态:某些 API 可能仍能读取但已经弃用,随后在更高版本中被移除。
- 持久化数据状态:etcd 中保存的是对象及其 API 版本信息。升级后,旧对象是否能继续被读取、转换和更新,取决于 API Server、CRD 转换和存储版本。
因此,安全升级的目标不是“版本号全部一致”,而是验证以下条件同时成立:
下文以从版本 升级到 为例。具体可升级路径、支持窗口和参数必须以目标 Kubernetes 版本的官方文档及云厂商文档为准。
一、先区分 Kubernetes 中的几个“版本”
1. Kubernetes 版本不是一个单独的版本
集群中至少存在以下版本:
- kube-apiserver:提供 Kubernetes API,并负责认证、授权、准入、对象持久化和 watch。
- kube-controller-manager:运行各种控制器,例如 Deployment、Node、Job 和 EndpointSlice 控制器。
- kube-scheduler:为未绑定节点的 Pod 选择节点。
- kubelet:运行在每个节点上,负责把 PodSpec 转换为容器运行时操作。
- kube-proxy:通常负责 Service 流量规则;具体实现也可能由云厂商网络组件替代。
- kubectl:客户端工具,不是集群组件。
- 扩展组件:CNI、CSI、Ingress Controller、监控组件、Admission Webhook、Operator 和 CRD。
kubectl version 的结果不能代表所有组件版本。例如:
kubectl version
可能输出:
Client Version: v1.31.2
Server Version: v1.30.6
这只能说明客户端和 API Server 的版本,不能说明每个节点的 kubelet 版本。应分别检查:
kubectl get nodes \
-o custom-columns=NAME:.metadata.name,KUBELET:.status.nodeInfo.kubeletVersion,OS:.status.nodeInfo.osImage,RUNTIME:.status.nodeInfo.containerRuntimeVersion
kubectl get --raw='/version'
预期结果类似:
NAME KUBELET OS RUNTIME
worker-a v1.30.6 Ubuntu 24.04.1 LTS containerd://1.7.x
worker-b v1.30.6 Ubuntu 24.04.1 LTS containerd://1.7.x
这里的 status.nodeInfo.kubeletVersion 是 kubelet 向 API Server 报告的版本;它不一定等于节点上所有 Kubernetes 相关软件包的版本。
2. 版本偏差 Version Skew 是什么
Version Skew 指同一个集群内不同 Kubernetes 组件之间存在版本差异。版本偏差本身并不一定是错误,因为 Kubernetes 的滚动升级依赖短时间的混合版本状态。
但“能够通信”不等于“受到官方支持”。必须同时满足目标版本对应的 Version Skew Policy。
常见规则可以概括为:
| 组件关系 | 通常要求 |
|---|---|
| 高可用集群中的多个 kube-apiserver | 不应跨越超过一个 minor 版本 |
| kube-controller-manager、kube-scheduler | 通常不应比 kube-apiserver 更新,升级过程中允许有限的旧版本偏差 |
| kubelet | 通常不应比 kube-apiserver 更新;可在官方策略允许的范围内落后 |
| kube-proxy | 通常遵循与 kubelet 类似的偏差约束,但实现和支持范围需核对目标版本 |
| kubectl | 官方支持范围通常为 API Server 前后一个 minor 版本 |
这里的“通常”很重要:精确的最大偏差、补丁版本要求和特殊例外属于版本敏感规则,不能用一张永久不变的表替代目标版本的官方 Version Skew Policy。
3. 为什么 kubelet 不能随意先升级到更高版本
API Server 是整个控制面的 API 契约中心。若 kubelet 比 API Server 新,可能出现以下问题:
- 新 kubelet 发送旧 API Server 不认识的字段或行为。
- 新 kubelet 依赖 API Server 尚未提供的资源或语义。
- 节点状态、Lease、Pod status 的字段解释出现差异。
- 新版本默认行为改变,但旧控制器仍按旧语义作出决策。
所以常见的安全顺序是:
旧 kubelet
↓
升级 API Server / controller-manager / scheduler
↓
验证控制面稳定
↓
逐批升级 kubelet 和节点组件
这不是因为新 kubelet 永远不能连接旧 API Server,而是因为官方支持通常要求 kubelet 不得领先于 API Server。
二、升级顺序:先改变控制面契约,再改变节点执行环境
一次典型的自建集群升级路径如下:
flowchart TD
A[冻结变更并确认目标版本] --> B[扫描弃用 API]
B --> C[验证扩展组件与 CRD]
C --> D[备份 etcd 并演练恢复]
D --> E[升级第一个控制面实例]
E --> F[升级其余控制面实例]
F --> G[验证 API Server、控制器和调度器]
G --> H[灰度升级一个节点池或一个节点]
H --> I[验证业务、网络、存储和监控]
I --> J{验证通过?}
J -- 是 --> K[继续分批升级节点]
J -- 否 --> L[停止扩散并恢复节点]
K --> M[升级后清理与复盘]
在高可用控制面中,不应同时停止所有 API Server。控制面升级的实际操作取决于部署方式:
- kubeadm 管理的集群有明确的升级流程和版本检查。
- 托管 Kubernetes 的控制面由云厂商升级,用户通常只能选择版本、维护窗口和节点池策略。
- 静态 Pod、systemd、二进制安装或发行版管理的集群,其控制面替换方式不同。
不要把 kubeadm 命令直接套到托管集群或自研发行版上。命令形式相似,不代表控制面生命周期相同。
1. 升级前检查控制面健康
kubectl get --raw='/readyz?verbose'
kubectl get nodes
kubectl get pods -A
kubectl get events -A --sort-by=.lastTimestamp | tail -n 100
/readyz?verbose 用来检查 API Server 的就绪检查。输出中应看到各项检查通过,例如:
[+]ping ok
[+]log ok
[+]etcd ok
[+]informer-sync ok
readyz check passed
如果 etcd、informer-sync 或认证相关检查已经失败,此时升级会把既有故障扩大为版本变更故障。
还要检查:
kubectl get --raw='/livez?verbose'
kubectl get nodes -o wide
kubectl get pdb -A
Ready 节点数量、不可调度节点、PodDisruptionBudget 和正在进行的发布都会直接影响后续排空。
2. 高可用控制面的并发限制
假设有三个 API Server:
api-1: N
api-2: N
api-3: N
升级期间可暂时变为:
api-1: N+1
api-2: N
api-3: N
但不应直接变成:
api-1: N+1
api-2: N+1
api-3: N-1
因为 API Server 之间的版本偏差可能超过支持范围,而且不同实例处理 discovery、watch、准入和对象读写时可能有不一致行为。升级一个实例后,应确认它能够:
- 通过负载均衡接收请求;
- 正常访问 etcd;
- 完成认证、授权和准入;
- 正常提供 discovery;
- 支持控制器和客户端建立 watch。
三、API 弃用不是“看到 warning 再改 YAML”
1. API Group、Version 和 Kind 的关系
一个 Kubernetes 对象通常由以下信息确定:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
这里:
apps是 API Group;v1是 API Version;Deployment是 Kind。
客户端操作时还存在资源名,例如:
kubectl get deployments.apps
API 弃用通常表示某个 Group/Version 不再推荐使用;API 移除则表示 API Server 不再提供该版本的读写接口。
例如,当前稳定 API 中应使用:
apiVersion: networking.k8s.io/v1
kind: Ingress
而不是已经移除的旧版本:
apiVersion: extensions/v1beta1
kind: Ingress
将 apiVersion 机械替换为新值并不总是正确,因为字段结构和语义也可能变化。Ingress 从旧版本迁移到 networking.k8s.io/v1 时,后端结构、路径匹配和 pathType 都需要按目标版本规范调整。
2. 弃用 API 的风险路径
一个弃用 API 可能经历如下过程:
仍可用
↓
标记为 deprecated,调用产生 warning
↓
新版本不再提供该 API
↓
旧 YAML 无法创建或更新
↓
旧对象可能仍存在,但无法通过旧接口管理
最容易误判的是:“集群里现在已经有旧对象,所以升级一定没问题。”实际上对象可能在 etcd 中继续存在,但升级后的 API Server 不再暴露读取或更新它的旧版本接口。
还存在第二个问题:即使资源已经迁移到新 API,发布系统、Operator、Admission Webhook 或备份工具仍可能使用旧 API。升级检查必须覆盖运行时调用者,而不只是 Git 仓库中的 YAML。
3. 发现正在使用的弃用 API
可以先检查 API Server 的弃用指标:
kubectl get --raw='/metrics' \
| grep 'apiserver_requested_deprecated_apis'
指标中通常会包含 group、version、resource 等标签。它反映的是 API Server 观测到的请求,不是整个历史周期的完整审计记录。因此至少应覆盖:
- 正常工作时间;
- 定时任务执行时间;
- 发布窗口;
- 备份和扫描窗口;
- Operator 的 reconcile 周期。
如果集群启用了审计日志,还应按 group/version/resource 搜索审计事件。仅执行一次 kubectl get 不能证明没有弃用调用。
检查集群提供的 API:
kubectl api-versions
kubectl api-resources
kubectl api-versions 展示当前 API Server 提供的 Group/Version;它不能告诉你哪些版本即将在下次升级中移除。弃用和移除列表必须与目标版本发行说明、弃用策略和 API 文档交叉核对。
4. 迁移清单和运行时调用
例如旧的 Deployment 清单:
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: web
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: nginx:1.27
迁移为稳定 API:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: nginx:1.27
apps/v1 要求 Deployment 的 .spec.selector 明确存在,并且必须与 Pod Template 的标签匹配。下面这个清单虽然 apiVersion 正确,但会被拒绝:
spec:
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: backend
原因是 Deployment 控制器无法根据 selector 正确管理预期 Pod。API 迁移必须验证字段约束和语义,而不是只验证 YAML 能否解析。
在应用前进行客户端校验:
kubectl apply --dry-run=server -f deployment.yaml
预期结果是:
deployment.apps/web configured (server dry run)
如果目标集群仍未升级到目标版本,这个检查只能验证当前 API Server 的能力,不能替代目标版本测试。因此应在隔离测试集群上使用目标版本进行同样的校验。
5. CRD 的特殊风险:served、storage 和 storedVersions
自定义资源定义 CRD 至少涉及三个概念:
served: true:API Server 是否提供该版本接口;storage: true:新写入对象在 etcd 中使用哪个版本作为存储版本;status.storedVersions:该 CRD 对象曾经以哪些版本存储过。
一个 CRD 可以暂时同时提供两个版本:
spec:
versions:
- name: v1beta1
served: true
storage: false
- name: v1
served: true
storage: true
这并不自动完成所有对象的迁移。若需要停止提供 v1beta1,必须确认:
- 控制器和客户端已经改用
v1; - 转换 webhook 能处理所有旧对象;
- 现有对象已经被读取并重新写入,使其完成存储迁移;
status.storedVersions不再保留不允许移除的旧版本;- 备份工具和恢复工具支持目标版本。
CRD 的版本转换不是简单字段重命名。若 v1beta1 和 v1 的结构不同,必须提供正确的 conversion webhook,并测试双向转换是否丢失数据。特别要验证默认值、枚举、列表顺序、未知字段和状态字段。
四、节点灰度:Drain 改变的是调度状态,不是“立刻杀掉节点”
1. 节点升级的基本状态变化
节点灰度通常包含四步:
kubectl cordon worker-a
kubectl drain worker-a --ignore-daemonsets --delete-emptydir-data
# 在节点上升级 kubelet、kube-proxy 和容器运行时
kubectl uncordon worker-a
每一步的含义不同:
cordon将节点标记为不可调度,阻止新的普通 Pod 被调度到该节点;drain通过 Eviction API 或删除操作驱逐现有 Pod;- 节点软件升级改变 kubelet、容器运行时或网络代理;
uncordon恢复调度资格。
cordon 不会迁移已有 Pod,drain 也不保证所有 Pod 都能立即退出。
2. 为什么 drain 可能失败
下面的命令可能失败:
kubectl drain worker-a \
--ignore-daemonsets \
--delete-emptydir-data
常见失败原因包括:
- Pod 被
local数据或emptyDir保护; - Pod 没有被控制器管理;
- PDB 不允许同时驱逐足够多的副本;
- Pod 使用本地卷;
- 容器长期不响应终止;
- 目标节点上的 DaemonSet Pod 被刻意保留;
- 节点处于 NotReady,Eviction 流程无法正常完成。
--force、--grace-period=0、--disable-eviction 等参数会改变安全边界,不能作为“命令失败后的固定补救”。例如绕过 PDB 可能导致服务副本低于可用阈值,立即产生容量或可用性事故。
先检查:
kubectl get pods -A -o wide --field-selector spec.nodeName=worker-a
kubectl get pdb -A
kubectl describe node worker-a
如果某个 PDB 为:
NAME MIN AVAILABLE ALLOWED DISRUPTIONS
web-pdb 2 0
而当前只有两个可用副本,则驱逐任何一个副本都会违反 minAvailable: 2。此时增加业务副本、临时调整 PDB 或延后升级,通常比强制删除更可控。
3. 灰度批次不是固定数字,而是容量约束
假设某服务需要至少 个可用副本,当前有 个副本,批次内最多同时不可用 个,则至少需要满足:
如果节点升级会同时影响 个服务副本,那么节点批次还必须受拓扑分布约束。例如:
- 服务有 6 个副本;
- PDB 要求至少 5 个可用;
- 则最多只能让 1 个副本因升级暂时不可用;
- 如果两个副本恰好集中在同一节点,升级该节点就可能违反 PDB。
因此节点灰度至少要观察:
kubectl get pods -A -o wide
kubectl get deploy,statefulset -A
kubectl get nodes
kubectl get events -A --sort-by=.lastTimestamp
升级一个节点后,应验证:
- 节点重新
Ready; - kubelet 版本符合预期;
- Pod 能创建、终止和重启;
- Service、DNS、Ingress 和网络策略正常;
- CSI 能挂载和卸载卷;
- 关键工作负载的可用副本和延迟恢复正常。
示例:
kubectl get node worker-a \
-o jsonpath='{.status.nodeInfo.kubeletVersion}{"\n"}'
kubectl wait --for=condition=Ready node/worker-a --timeout=5m
kubectl wait 成功只证明节点 Ready,不证明应用已经恢复。应用还应通过就绪探针、业务健康检查、错误率和依赖访问进行验证。
4. StatefulSet 和本地存储的额外边界
无状态 Deployment 通常可以依赖副本和 PDB 完成迁移,但 StatefulSet 可能绑定:
- PersistentVolume;
- 节点本地盘;
- 固定网络身份;
- 单实例数据库;
- 严格的启动和停止顺序。
对这类工作负载,drain 成功不等于数据服务安全。必须确认卷的拓扑约束、存储插件版本和重新挂载行为。升级节点前,先在同类节点和同类卷上验证“卸载—迁移—重新挂载—应用恢复”完整路径。
五、etcd 备份:回滚的基础,但不是普通升级按钮
1. 为什么必须备份 etcd
etcd 保存 Kubernetes 的控制面状态,包括:
- API 对象;
- Secret;
- ConfigMap;
- Service 和 EndpointSlice;
- CRD 和自定义资源;
- RBAC;
- Lease;
- 部分集群配置。
节点磁盘快照不能等价替代 etcd 备份。一个节点故障恢复可能修复该节点,但不能恢复已经被错误 API 迁移、误删或批量更新的集群对象。
使用 etcdctl snapshot save 前,必须明确:
- etcd 访问地址;
- 客户端证书、私钥和 CA;
- etcdctl 与 etcd 的兼容要求;
- 快照文件的完整性;
- 快照保存位置和权限。
示例:
export ETCDCTL_API=3
etcdctl \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/peer.crt \
--key=/etc/kubernetes/pki/etcd/peer.key \
snapshot save /backup/etcd-$(date +%Y%m%d%H%M%S).db
证书路径仅适用于某些 kubeadm 或自建布局,不能直接假设适用于云厂商控制面。
验证快照:
etcdutl snapshot status /backup/etcd-20250101010000.db
不同 Kubernetes/etcd 版本中使用的工具名称和参数可能不同,应以随 etcd 提供的工具为准。验证至少应确认快照可读取、文件大小合理、哈希或状态信息可记录。
2. Snapshot 恢复意味着什么
etcd snapshot 恢复通常是灾难恢复操作,而不是升级失败后的“撤销按钮”。假设快照时间为 ,升级后在 到 之间产生了对象变更,那么恢复 会丢失:
这包括可能刚刚创建的 Secret、Job、Service、CRD 对象和业务配置。
恢复后还要处理:
- API Server 证书和访问地址;
- etcd 集群成员关系;
- 数据目录;
- 控制面静态 Pod 或 systemd 配置;
- 云厂商托管控制面无法由用户自行恢复的问题;
- 外部数据库、对象存储和消息队列与 Kubernetes 状态的不一致。
3. Kubernetes 版本降级的边界
升级过程中写入新版本字段或新资源后,直接把控制面二进制降回旧版本通常不受支持。即使旧 API Server 能启动,也可能无法正确解释新版本对象。
因此应区分:
- 节点回滚:通常可通过节点镜像、软件包或实例组版本回退完成;
- 控制面回滚:往往不是普通的二进制降级,而是恢复兼容版本的控制面和 etcd 数据;
- API 移除后的回滚:如果升级前没有准备快照和旧版本环境,回滚难度显著增加。
生产环境的回滚计划必须写出“恢复到哪个时间点、会丢失什么数据、如何冻结写入、如何验证恢复结果”,不能只写“执行 downgrade”。
六、升级前的兼容性验证与门禁
1. 用目标版本测试真实对象
应在隔离测试集群中部署:
- 生产使用的 CRD;
- 所有 Operator;
- Admission Webhook;
- CNI 和 CSI;
- Ingress Controller;
- 监控与日志组件;
- 生产中实际的 Helm Chart 和 Kustomize 输出;
- 典型 StatefulSet、DaemonSet、Job 和 CronJob。
渲染清单后进行服务端校验:
helm template myapp ./chart -n production > rendered.yaml
kubectl apply --dry-run=server -f rendered.yaml
这一步可以发现目标 API Server 无法识别的资源、字段或校验规则,但无法发现所有运行时问题。例如 Webhook 超时、CSI 挂载失败、网络策略行为改变,都需要真实部署和业务测试。
2. 兼容性测试应覆盖“更新”而不只是“创建”
很多弃用问题在创建阶段不暴露,而在更新阶段出现。原因是控制器会:
- 读取现有对象;
- 修改某个字段;
- 以特定 API 版本重新提交;
- 触发资源版本冲突或转换。
因此测试应执行:
kubectl apply -f manifests/
kubectl rollout status deployment/web
kubectl scale deployment/web --replicas=3
kubectl patch deployment/web \
--type='merge' \
-p '{"spec":{"template":{"metadata":{"labels":{"revision":"test"}}}}}'
每一步都应检查对象是否仍能被控制器管理、Pod 是否能滚动更新、status 是否持续刷新。
3. Admission Webhook 是常见的隐藏故障点
Webhook 配置中常见字段包括:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
升级后,如果 Webhook 服务证书过期、Service 不可达、CA Bundle 错误或 failurePolicy: Fail,大量 API 写操作可能被拒绝:
failed calling webhook "...": context deadline exceeded
此时表现可能像“Deployment 不能创建”或“节点升级卡住”,但根因在扩展组件。诊断路径包括:
kubectl get validatingwebhookconfiguration,mutatingwebhookconfiguration
kubectl describe validatingwebhookconfiguration <name>
kubectl get svc,endpoints -n <webhook-namespace>
kubectl logs -n <webhook-namespace> <webhook-pod>
修改 failurePolicy 可以降低阻断风险,但也可能绕过安全策略或合规校验,不能在不了解策略含义的情况下全局改为 Ignore。
七、升级过程中的观测和停止条件
升级不是只看 kubectl get nodes。应为每一批定义明确的停止条件。
1. 控制面停止条件
出现以下情况之一,应停止继续升级:
- API Server
/readyz持续失败; - etcd 延迟、错误率或 leader 变化异常;
- controller-manager 大量重启或队列持续堆积;
- scheduler 无法为新 Pod 分配节点;
- discovery 结果不完整;
- API 请求出现大量 5xx 或 timeout;
- 审计或弃用指标显示新版本仍在被旧客户端调用。
2. 节点批次停止条件
节点灰度后,应停止继续扩散的典型信号包括:
- 新 kubelet 节点无法保持 Ready;
- Pod sandbox 创建失败;
- CNI 初始化失败;
- CSI 挂载、卸载或扩容失败;
- DNS、Service 或 Ingress 流量异常;
- 关键业务错误率、延迟或可用副本超阈值;
- PDB 频繁阻止驱逐,说明容量边界不足。
事件诊断:
kubectl get events -A \
--field-selector type=Warning \
--sort-by=.lastTimestamp
节点和 Pod 详细信息:
kubectl describe node worker-a
kubectl describe pod <pod-name> -n <namespace>
若出现 FailedCreatePodSandBox,应优先检查 CNI 和容器运行时;若出现 FailedMount,应检查 CSI 控制器、节点插件、卷拓扑和存储后端,而不是重复执行 drain。
八、回滚设计:优先回退扩散,不要假设可以撤回全部变化
1. 节点回滚
若某一批节点升级后异常,首先停止后续节点升级,并避免把更多工作负载调度到故障节点:
kubectl cordon worker-a
如果节点仍能运行 kubelet,可尝试将业务迁移到健康节点:
kubectl drain worker-a \
--ignore-daemonsets \
--delete-emptydir-data
随后使用经过验证的节点镜像或软件包恢复 kubelet、kube-proxy、容器运行时和 CNI/CSI 节点组件。恢复后:
kubectl uncordon worker-a
kubectl wait --for=condition=Ready node/worker-a --timeout=5m
若问题来自业务镜像或配置,而不是节点版本,回退节点版本不会解决问题。必须通过事件、日志和指标区分故障层次。
2. 控制面回滚
控制面已经升级并写入新对象后,不能默认执行:
# 不应把它当作通用回滚方案
apt install kube-apiserver=<old-version>
正确做法取决于集群发行方式和目标版本支持的降级路径。若必须恢复,通常需要:
- 停止或隔离控制面写入;
- 确认恢复点和数据丢失范围;
- 使用兼容版本的 etcd 工具恢复快照;
- 恢复与该快照匹配的控制面配置和证书;
- 启动兼容版本的 API Server、Controller Manager 和 Scheduler;
- 验证 API、控制器、节点和关键业务;
- 重新处理恢复后与外部系统之间的状态差异。
这属于灾难恢复流程,应该在升级前演练,而不是在生产故障时首次尝试。
3. API 迁移失败时的处理
如果新版本已移除旧 API,通常不能通过把旧清单重新 apply 来恢复。应根据失败类型处理:
- 旧客户端调用失败:升级客户端、Operator 或发布工具;
- 旧清单字段不兼容:改写为目标 API 结构;
- CRD 转换失败:恢复 conversion webhook、修复转换逻辑并重新测试;
- 对象无法读取:检查 API Server 提供的版本、CRD 的 served/storage 配置和
storedVersions; - 误删除或错误迁移:从 etcd 快照恢复,或从声明式配置和备份中重建。
API 兼容性问题与节点软件问题不能用同一种回滚动作解决。
九、云厂商 Kubernetes 的差异
托管 Kubernetes 通常把控制面升级、etcd 管理、证书轮换和高可用实现交给云厂商,但这并不消除用户责任:
- 控制面版本支持窗口由云厂商定义;
- 节点池可能支持滚动替换、蓝绿替换或原地升级;
- CNI、CSI、Ingress 和负载均衡控制器有云厂商专属版本矩阵;
- 云厂商可能自动升级控制面,但不会自动迁移用户镜像、Helm Chart 或 Operator;
- 托管控制面通常不能由用户直接执行 etcd snapshot restore;
- 云厂商提供的“回滚”可能只回滚节点池,不回滚控制面 API 和对象数据。
因此升级前必须明确四个边界:
谁升级控制面?
谁负责 etcd 备份与恢复?
谁替换节点?
谁验证扩展组件兼容性?
如果云平台只提供“升级到目标版本”,却没有控制面降级能力,那么回滚策略必须在升级前通过蓝绿集群、备份恢复或重建流程实现。
十、一个可执行的升级门禁示例
下面的门禁不是完整平台,只展示如何把关键事实自动化检查。它假设目标版本是 v1.31,实际使用时应替换为计划目标,并把版本策略与目标版本文档核对。
#!/usr/bin/env bash
set -Eeuo pipefail
target_minor="v1.31"
echo "== API Server =="
kubectl get --raw='/version'
kubectl get --raw='/readyz?verbose'
echo "== Nodes =="
kubectl get nodes
kubectl get nodes \
-o custom-columns=NAME:.metadata.name,VERSION:.status.nodeInfo.kubeletVersion
echo "== PDB =="
kubectl get pdb -A
echo "== Warning events =="
kubectl get events -A \
--field-selector type=Warning \
--sort-by=.lastTimestamp | tail -n 50
echo "== Deprecated API metrics =="
deprecated="$(
kubectl get --raw='/metrics' \
| grep 'apiserver_requested_deprecated_apis' || true
)"
if [[ -n "$deprecated" ]]; then
echo "$deprecated"
echo "Deprecated API requests were observed; migration is required."
exit 1
fi
echo "== Server-side dry run =="
kubectl apply --dry-run=server -f manifests/
echo "All automated gates passed for preliminary review."
这个脚本仍有明确边界:
- 它不能判断目标版本上的 API 是否即将移除;
- 它不能证明定时任务和冷门 Operator 没有调用弃用 API;
- 它不能替代 etcd 恢复演练;
- 它不能证明业务流量、存储和网络正常;
- 它不能决定 PDB 是否足以承受某种节点批次。
脚本适合做门禁的一部分,而不是把复杂的升级判断压缩成一个成功退出码。
十一、常见误解与对应故障
误解一:kubectl 版本一致,集群就升级完成了
kubectl 只是客户端。真正决定 API 行为的是 API Server,真正决定节点能否运行 Pod 的是 kubelet、容器运行时和网络/存储插件。必须分别检查各类组件。
误解二:kubectl apply 成功,API 迁移就完成了
apply 成功只说明这次请求被接受。旧对象、后台控制器、Webhook、定时任务和第三方工具仍可能使用旧 API。必须观察完整运行周期,并扫描 API Server 指标和审计记录。
误解三:节点全部 Ready,业务就没有风险
节点 Ready 只代表 kubelet 报告节点满足基本条件。它不证明:
- Service 流量规则正确;
- DNS 正常;
- PVC 已挂载;
- Ingress 能转发;
- 应用依赖可访问;
- 业务延迟和错误率正常。
误解四:etcd snapshot 等于随时可回滚
快照只有在能够恢复、版本兼容、证书和成员配置匹配,并且团队知道数据丢失范围时才具有回滚价值。没有恢复演练的快照,只能称为“已生成备份文件”,不能称为可验证的恢复方案。
误解五:强制 drain 可以提高升级速度
强制 drain 只是绕过保护条件,不会消除容量、数据和业务依赖。它可能把一个可诊断的“无法安全驱逐”变成多个副本同时中断、数据丢失或卷无法重新挂载的问题。
十二、最终验收
升级完成后,至少应验证以下状态:
kubectl get nodes
kubectl get pods -A
kubectl get --raw='/readyz'
kubectl api-versions
kubectl get crd
kubectl get pdb -A
kubectl get events -A --field-selector type=Warning
还应验证声明式和运行时行为:
- 部署、更新、扩缩容和回滚一个代表性 Deployment;
- 创建和完成一个 Job;
- 执行一次 CronJob;
- 重启一个使用持久卷的工作负载;
- 验证 Service、DNS、Ingress 和网络策略;
- 检查 Webhook、Operator、CNI、CSI 和监控告警;
- 确认弃用 API 指标在观察窗口内没有新增请求;
- 保存升级前后版本、事件、配置和备份记录。
一次合格的 Kubernetes 升级应能回答四个问题:
- 哪些组件暂时处于混合版本,是否符合 Version Skew Policy?
- 哪些 API 已弃用,所有调用者和存储对象是否完成迁移?
- 节点按什么批次灰度,PDB、容量、网络和存储是否承受得住?
- 控制面或节点出现故障时,究竟是回退节点、停止扩散,还是执行 etcd 灾难恢复?
只有把这四个问题分别验证清楚,版本升级才不是一次高风险的批量替换,而是一个具有边界、观测和恢复路径的状态迁移。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:Kubernetes 节点维护:Cordon、Drain、Eviction、DaemonSet 和恢复验证
- 下一篇:Kubernetes API 弃用治理:发现、迁移、兼容测试和升级门禁
- 延伸:Kubernetes etcd 备份恢复:Snapshot、证书、Revision 和灾难演练
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论