Kubernetes 基础体系 · 第 64/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
Kubernetes API 弃用治理:发现、迁移、兼容测试和升级门禁
Kubernetes API 弃用治理解决的不是“把 YAML 里的版本字符串改掉”这一件事,而是保证以下链路在集群升级后仍然成立:
源码与模板
↓
渲染后的清单
↓
客户端请求
↓
kube-apiserver 发现、认证、准入与转换
↓
etcd 中的持久化对象
↓
控制器、Webhook、运维脚本读取并更新对象
任何一个环节仍然依赖即将移除的 API,都可能在升级后产生失败。典型结果包括:
kubectl apply返回no matches for kind;- 控制器启动后无法 list/watch 某个资源;
- Validating 或 Mutating Webhook 因
rules仍匹配旧版本而失效; - 旧对象暂时还能读取,但无法再通过旧版本更新;
- 升级成功,业务发布却因 Helm 模板渲染出旧
apiVersion而失败; - CRD 的存量对象仍以旧存储版本保存,后续转换 Webhook 或控制器升级后出现兼容问题。
因此,治理对象应定义为:
在目标 Kubernetes 版本集合中,所有客户端、清单、控制器、Webhook、存量对象和运维工具对 API 的使用都满足发现、语义、存储和运行时兼容条件。
一、先建立 API 版本模型:GVK、资源路径与对象存储
1. GVK 不是一个字符串,而是对象类型标识
Kubernetes 对象的 GVK 是 Group、Version、Kind 的组合:
- Group:API 组,例如
apps、networking.k8s.io; - Version:组内版本,例如
v1、v1beta1; - Kind:对象类型,例如
Deployment、Ingress。
清单中的:
apiVersion: apps/v1
kind: Deployment
对应:
Group = apps
Version = v1
Kind = Deployment
核心资源的 apiVersion 可能没有显式 Group。例如:
apiVersion: v1
kind: ConfigMap
这里的 Group 是核心组,通常表示为空字符串,而不是名为 core 的普通 API 组。
GVK 用于识别“对象是什么类型”,但客户端访问 HTTP API 时通常还会涉及 GVR,即 Group、Version、Resource:
apps/v1 → GVR 的 group=apps, version=v1
deployments → resource=deployments
例如:
/apis/apps/v1/namespaces/default/deployments
其中:
apps是 API Group;v1是 API Version;deployments是复数资源名;- URL 中的资源名不一定等于 Kind 的小写形式,真实资源名应以 Discovery 为准。
kubectl api-resources 展示的就是 Kind、短名称、Namespaced 属性和资源名之间的映射。
2. Metadata、Spec 和 Status 的职责不同
一个典型 Kubernetes 对象可以抽象为:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: default
labels:
app: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: nginx:1.27
status:
availableReplicas: 3
三个区域的性质不同:
metadata描述对象身份和管理信息,例如name、namespace、uid、resourceVersion、labels、annotations、ownerReferences;spec是用户或控制器声明的期望状态;status是控制器或 API Server 维护的观测状态。
迁移清单时,不能简单地把整个对象原样复制。原因包括:
metadata.resourceVersion是并发控制字段,不应被当作新的对象版本提交;metadata.uid、creationTimestamp等字段描述已有对象身份;status通常由控制器写入,用户提交它可能造成冲突或被忽略;- API 版本变化往往不仅改变
apiVersion,还会改变spec的字段结构和必填约束。
因此,迁移通常以“保留用户意图的声明式对象”为目标,而不是复制 API Server 返回的完整快照。
3. 三个“版本”必须分开讨论
API 弃用治理至少要区分以下版本:
API 版本
例如:
extensions/v1beta1
networking.k8s.io/v1
它决定请求如何被解析、验证和转换。
Kubernetes 集群版本
例如:
v1.21
v1.22
v1.30
它决定 kube-apiserver、控制器和内置资源支持哪些 API 版本。
对象存储版本
API Server 可以对外提供多个版本,但通常会选择一个内部存储版本写入 etcd。客户端请求的版本不一定就是 etcd 中的版本。
因此,下面两个事实可以同时成立:
- 客户端请求
networking.k8s.io/v1; - etcd 中对象仍以某个内部存储版本保存。
API Server 负责在外部版本与内部版本之间进行转换。kubectl get -o yaml 展示的是请求版本经过转换后的对象,不等价于“etcd 原始字节”。
这一区分直接影响迁移判断:
“现在还能读取”不等于“所有客户端都已迁移”,也不等于“存量对象已经完成存储版本迁移”。
二、弃用、停止服务和删除是三个不同阶段
一个 API 版本可能经历以下状态:
正常提供
↓
标记弃用,但仍 Served
↓
停止 Served
↓
必要时完成存储版本迁移
1. 弃用通常先表现为 Warning
当客户端请求已弃用但仍提供服务的 API 时,API Server 可以返回 HTTP Warning 响应头,常见内容类似:
Warning: 299 - "apps/v1beta1 is deprecated ..."
kubectl 可能将其显示为警告。这个阶段请求通常仍能成功,但它已经是迁移信号,不是可以忽略的普通日志。
Kubernetes 官方弃用策略会规定不同稳定级别 API 的弃用和移除约束,但具体移除版本必须以目标 Kubernetes 版本的发布说明和弃用 API 文档为准。不能根据某个旧版本文章推断当前集群一定还提供某个版本。
2. Served 与 Storage 是两个独立开关
对 CRD 来说,版本配置通常具有:
spec:
versions:
- name: v1
served: true
storage: true
- name: v1beta1
served: false
storage: false
含义是:
served: true:API Server 对外接受这个版本的请求;storage: true:这个版本作为该 CRD 的存储版本;- 同一个 CRD 必须且只能有一个存储版本;
- 设置新的版本为
storage: true,不会自动把 etcd 中所有旧对象立即重写一遍。
因此,下面的推理是错误的:
“我把 CRD 的 storage 改成 v1,所以旧对象已经全部迁移成 v1。”
真实情况是:
- 新写入对象可能使用新的存储版本;
- 旧对象仍可能保留在旧版本的存储形式;
- 只有经过读取再写入、专门的存储迁移工具或控制器处理,旧对象才可能被重写;
- 在确认存量对象迁移完成前,不能安全删除旧版本的转换路径。
对于内置资源,存储版本由 API Server 和集群实现决定,不能直接把“修改 CRD 版本”的方法套用于 Deployment、Ingress 等内置资源。
3. “资源还存在”不等于“旧 API 还存在”
以下命令:
kubectl get ingress -A
只说明 kubectl 找到了某个当前可用的 Ingress 资源映射。它不能证明:
extensions/v1beta1仍然 Served;- 仓库中的 Helm 模板没有旧版本;
- 控制器没有通过旧 GVK 注册 Informer;
- Webhook 没有匹配旧版本;
- etcd 中没有旧存储版本对象。
API 兼容性必须按调用方和 API 版本分别验证。
三、发现阶段:建立完整的 API 使用清单
发现的目标不是只扫描 YAML,而是建立如下关系:
调用方 → GVK/GVR → 使用方式 → 当前集群状态 → 目标集群状态 → 迁移动作
1. 先从 API Server Discovery 观察真实能力
kubectl api-versions 列出当前 API Server 对外提供的 Group/Version:
kubectl api-versions
示例输出可能包含:
apps/v1
batch/v1
networking.k8s.io/v1
policy/v1
kubectl api-resources 进一步显示资源名称和作用域:
kubectl api-resources
典型输出结构:
NAME SHORTNAMES APIVERSION NAMESPACED KIND
deployments deploy apps/v1 true Deployment
ingresses ing networking.k8s.io/v1 true Ingress
nodes v1 false Node
只查看可列出的 Namespaced 资源:
kubectl api-resources \
--verbs=list \
--namespaced \
-o name
查看集群级资源:
kubectl api-resources \
--verbs=list \
--namespaced=false \
-o name
这里的前置条件是当前身份具有相应资源的 list 权限。没有权限时,命令失败不代表资源不存在;它只代表当前身份不能观察该资源。
2. 用 Discovery 验证单个版本是否真的 Served
例如检查 Ingress:
kubectl api-resources --api-group=networking.k8s.io
也可以直接访问 Discovery 端点:
kubectl get --raw /apis/networking.k8s.io/v1 | jq .
对于核心组:
kubectl get --raw /api/v1 | jq .
如果某个版本不在 Discovery 响应中,面向该版本的请求通常会失败。但 Discovery 本身也受 API Server 版本、聚合 API、权限和升级过程影响,因此它是“当前观察结果”,不是对未来版本的承诺。
3. 扫描仓库时,必须扫描渲染结果
只执行:
grep -R "extensions/v1beta1" .
是不充分的,因为旧版本可能来自:
- Helm 模板;
- Kustomize patch;
- Jsonnet;
- Operator 生成器;
- CI/CD 中动态拼接;
- GitOps 控制器在集群内渲染;
- 第三方 chart 或远程 base。
Helm 应先渲染,再扫描:
helm template web ./chart \
--namespace production \
-f values-production.yaml \
> /tmp/web-rendered.yaml
grep -nE 'apiVersion:|extensions/v1beta1|networking.k8s.io/v1beta1' \
/tmp/web-rendered.yaml
对于已经部署的对象,可以查看 API Server 返回的当前版本:
kubectl get ingress web -n production -o yaml
但这只能发现当前对象,不能发现“创建它的客户端曾经使用过什么版本”。要补充检查:
- CI/CD 任务日志;
- Operator 或控制器镜像版本;
- Webhook 配置;
- 自定义脚本中的
kubectl、REST URL 和客户端库; - 审计日志;
- API Server 的弃用请求指标。
4. 使用弃用指标识别运行时调用方
常见 Kubernetes 实现会暴露弃用 API 请求指标,例如:
apiserver_requested_deprecated_apis
查询方式取决于监控系统,Prometheus 中可以按集群实际标签查询:
apiserver_requested_deprecated_apis > 0
该指标通常能帮助发现仍在请求弃用 API 的服务,并可能包含 group、version、resource、removed_release 等标签。标签名称和指标可用性应以当前 Kubernetes 版本和发行版暴露的指标为准。
指标的局限也很重要:
- 没有流量就没有样本;
- 只扫描清单但尚未部署的旧 API 不会出现在指标中;
- 请求可能被缓存,不能据此证明所有调用方已消失;
- 托管 Kubernetes 可能隐藏部分控制面指标。
因此,静态扫描和运行时观测必须同时存在。
5. 用弃用请求警告定位请求来源
对仍然 Served 的弃用 API 发起请求时,客户端可能收到 Warning。可以从以下位置收集:
- CI 日志;
- 控制器日志;
- API Gateway 或审计系统;
kubectl执行输出;- 集群监控告警。
如果请求由客户端库发起,日志可能只记录“list failed”,而没有完整 Warning。此时应结合:
- API Server 审计日志中的
userAgent; - ServiceAccount;
- source IP;
- 请求的 API path;
- 控制器 Deployment、Job 或 Pod 的归属关系。
这样才能从“有旧 API 请求”进一步定位到“哪个发布单元需要升级”。
四、迁移阶段:从旧 GVK 映射到新 GVK 和新字段语义
API 迁移至少包含三项工作:
- GVK 迁移;
- 字段结构迁移;
- 行为和权限迁移。
只改 apiVersion,往往只能完成第一项。
1. 完整算例:Ingress 从旧版本迁移到 networking.k8s.io/v1
旧版本 Ingress 常见写法如下:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: web
namespace: production
spec:
rules:
- host: web.example.com
http:
paths:
- path: /
backend:
serviceName: web
servicePort: 8080
迁移到 networking.k8s.io/v1:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
namespace: production
spec:
ingressClassName: nginx
rules:
- host: web.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 8080
变化不是单一版本替换:
| 旧写法 | 新写法 | 迁移原因 |
|---|---|---|
extensions/v1beta1 |
networking.k8s.io/v1 |
API 组和版本变化 |
serviceName |
service.name |
后端结构变化 |
servicePort |
service.port.number |
端口字段结构变化 |
无 pathType |
pathType: Prefix |
新版本要求明确路径匹配语义 |
| 可能由注解决定 Class | ingressClassName |
推荐使用结构化字段表达 IngressClass |
pathType 不是可随意填的占位字段:
Exact表示精确匹配;Prefix表示按路径段前缀匹配;ImplementationSpecific将行为交给 Ingress Controller 实现。
例如,旧控制器对 /foo 的匹配行为可能与新版本 Prefix 的路径段规则不同。迁移后必须验证实际路由,而不仅是 API Server 接受了对象。
执行迁移前先渲染并检查:
kubectl diff \
--server-side \
--field-manager=api-migration \
-f ingress-v1.yaml
然后进行服务端试运行:
kubectl apply \
--server-side \
--dry-run=server \
--field-manager=api-migration \
-f ingress-v1.yaml
参数含义:
--dry-run=server:请求发送到 API Server,但不持久化;- 服务端会执行 API 解析、schema 校验和适用的准入流程;
--server-side:使用 Server-Side Apply 的字段管理语义;--field-manager:为字段所有权记录提供稳定名称。
通过后再实际应用:
kubectl apply \
--server-side \
--field-manager=api-migration \
-f ingress-v1.yaml
风险包括:
- Server-Side Apply 可能报告字段所有权冲突;
- Webhook 可能修改对象;
- Ingress Controller 可能需要支持新 API 和新字段;
- 旧对象中的注解可能仍产生行为影响;
- 业务流量可能因路径或默认 Class 改变而路由到不同后端。
验证应同时检查对象和行为:
kubectl get ingress web -n production -o yaml
kubectl describe ingress web -n production
kubectl get endpointslice -n production \
-l kubernetes.io/service-name=web
还应通过真实或合成请求验证:
curl -i -H 'Host: web.example.com' http://<ingress-address>/
2. 不要把服务端转换当作完成迁移
如果旧 API 仍然 Served,下面的命令可能成功:
kubectl apply -f old-ingress.yaml
这只证明当前 API Server 能接收旧版本并进行转换,不证明目标版本仍然接受它。
真正的迁移应达到:
源码模板 → 新 GVK
客户端库 → 新 GVK
控制器 Informer → 新 GVK
Webhook rules → 新 GVK
RBAC apiGroups/resources → 兼容目标调用方式
运行时对象 → 已通过新版本读写验证
例如,控制器如果仍然注册:
extensions/v1beta1, Ingress
即使仓库中的 YAML 已经改成 networking.k8s.io/v1,控制器也可能在目标集群中启动失败,因为它仍对已删除的 GVR 执行 list/watch。
3. CRD 迁移需要考虑转换 Webhook
CRD 可以同时提供多个版本。若版本间结构不同,需要配置转换策略:
None:结构基本兼容,API Server 进行简单转换;Webhook:由用户提供转换服务。
转换 Webhook 的关键问题是:
- API Server 是否能访问 Webhook Service;
- TLS 证书是否有效;
- Webhook 是否能够处理所有声明的版本;
- 转换是否保持对象语义;
- 失败时
failurePolicy如何影响请求; - Webhook 自己依赖的 API 是否会在升级中消失。
Webhook 转换失败时,可能导致 CRD 对象无法读取或写入。若转换 Webhook 的 Service、证书或 Deployment 在升级期间不可用,影响面可能比单个业务发布更大。
CRD 迁移不能只修改:
spec.versions[].name
还要检查:
- 每个版本的 OpenAPI v3 schema;
served与storage;status.storedVersions;- 转换 Webhook 的
conversionReviewVersions; - 现有对象是否已被重新写入;
- 控制器客户端是否支持新版本。
查看 CRD 状态:
kubectl get crd widgets.example.com -o yaml
输出中的 status.storedVersions 表示 API Server 认为对象曾以哪些存储版本存在。它不是“所有对象当前都已经完成迁移”的充分证明,但可以作为迁移核查信号。
五、兼容测试:验证版本、结构和行为三个层次
兼容测试不能只依赖 kubectl apply --dry-run=client。
1. 客户端试运行与服务端试运行的差异
客户端试运行:
kubectl apply --dry-run=client -f manifest.yaml
主要检查本地命令行能够解析清单。它可能无法反映:
- 目标 API Server 是否 Served 该 GVK;
- 服务端 schema 是否接受字段;
- ValidatingWebhook 是否拒绝对象;
- MutatingWebhook 会如何修改对象;
- 当前身份是否有权限;
- Server-Side Apply 是否产生字段冲突。
服务端试运行:
kubectl apply \
--dry-run=server \
--server-side \
--field-manager=ci \
-f manifest.yaml
更接近真实提交,但它仍不是完整业务测试,因为:
- 不会持久化对象;
- 不会完整模拟控制器异步调谐;
- 不会证明节点、Ingress Controller、CSI 等数据面组件支持该对象;
- 目标集群必须真实运行对应 API 版本;
- 生产准入策略可能与测试集群不同。
2. 兼容性矩阵应覆盖支持范围
假设组织支持三个集群版本:
v1.28、v1.29、v1.30
至少应建立如下矩阵:
| 检查项 | v1.28 | v1.29 | v1.30 |
|---|---|---|---|
| 所有 GVK 被 Discovery 发现 | ✓ | ✓ | ✓ |
| 清单服务端试运行通过 | ✓ | ✓ | ✓ |
| Webhook 接收并返回成功 | ✓ | ✓ | ✓ |
| 控制器 list/watch 成功 | ✓ | ✓ | ✓ |
| 业务对象达到期望状态 | ✓ | ✓ | ✓ |
| 旧版本请求为零 | 迁移后 | 迁移后 | 迁移后 |
这不是要求所有旧 API 在所有版本都可用,而是要求发布产物使用的 API 位于支持矩阵的交集内。
设:
- 为发布系统可能生成的 GVK 集合;
- 为 Kubernetes 版本 当前 Served 的 GVK 集合;
- 为组织支持的集群版本集合。
仅从 API 发现角度,发布可以通过的必要条件是:
直觉是:每个发布产物中的 GVK,都必须在每一个受支持的目标集群版本中存在。
但这只是必要条件,不是充分条件。还需要满足:
其中 表示 GVK 在版本 上不仅能被解析,而且:
- schema 校验通过;
- 准入 Webhook 接受;
- 控制器能观察并调谐;
- 业务行为符合预期。
因此更完整的门禁条件可以写成:
如果 API 版本迁移改变了字段语义,还要增加语义等价条件:
其中:
- 是旧版本对象;
- 是迁移后的新版本对象;
- 是控制器和数据面观察到的业务行为;
- 表示满足组织定义的等价关系,例如路由、滚动更新、权限和可用性不发生非预期变化。
3. 用多版本临时集群做真实服务端测试
CI 可以使用与目标 Kubernetes 版本对应的临时集群,例如 kind、minikube 或其他测试环境。核心流程如下:
# 具体镜像标签应根据测试平台和目标 Kubernetes 版本选择
kind create cluster --name api-compat --image <目标版本对应的 kind 节点镜像>
kubectl cluster-info
kubectl version
kubectl apply \
--server-side \
--dry-run=server \
--field-manager=ci \
-f rendered.yaml
kubectl apply \
--server-side \
--field-manager=ci \
-f rendered.yaml
kubectl wait \
--for=condition=available \
deployment/web \
--timeout=180s
这里的 <目标版本对应的 kind 节点镜像> 不是可以随意填写的固定值。测试系统应根据实际支持版本维护镜像映射,因为 kind 节点镜像、容器运行时和 Kubernetes 版本之间存在对应关系。
测试应覆盖两类资源:
静态 API 兼容
验证:
- GVK 是否可发现;
- YAML 是否通过服务端 schema;
- 是否存在弃用 Warning;
- 是否通过准入;
- 是否有 RBAC 错误。
动态行为兼容
验证:
- Deployment 是否产生预期 ReplicaSet 和 Pod;
- Service 是否有 EndpointSlice;
- Ingress 是否完成地址分配并正确路由;
- Job 是否按预期结束;
- PDB、HPA、NetworkPolicy 等关联对象是否仍具有原语义;
- 控制器和 Webhook 是否持续运行。
只验证第一类,不能排除第二类故障。
4. Schema 工具可以提前发现错误,但不能替代 API Server
可以使用基于 Kubernetes OpenAPI schema 的工具检查渲染结果,例如在 CI 中引入 schema 校验工具。它们擅长发现:
- 字段拼写错误;
- 类型错误;
- 不允许的字段;
- 某版本资源不存在于指定 schema。
但工具使用的 schema 版本、CRD schema、Webhook 逻辑和云厂商差异可能与真实集群不同。因此推荐分层:
模板渲染
↓
静态 schema 检查
↓
目标版本 API Server dry-run
↓
实际 apply
↓
控制器与业务行为验证
每一层解决的问题不同,不应以某一层结果替代其他层。
六、升级门禁:把发现结果转化为可执行规则
升级门禁的作用是阻止“已知会在目标版本失败”的变更进入升级窗口,而不是阻止所有版本差异。
1. 门禁输入应来自四类数据
一次升级前至少收集:
- 目标版本清单:当前版本、目标版本、未来支持版本;
- 静态产物清单:所有 Helm、Kustomize、Operator 输出;
- 运行时请求清单:弃用指标、审计日志、控制器日志;
- 对象和 CRD 状态:当前 Served 版本、Storage 版本、存量迁移状态。
可以形成一张表:
| 来源 | GVK/GVR | 调用方 | 当前状态 | 目标版本状态 | 动作 |
|---|---|---|---|---|---|
| Helm chart | extensions/v1beta1/Ingress |
CI 发布 | 当前可用但弃用 | 不存在 | 阻断并迁移 |
| Operator | batch/v1beta1/CronJob |
controller | 运行时调用 | 不存在 | 升级 Operator |
| CRD | example.com/v1beta1 |
API Server | Served=true | 仍 Served | 迁移客户端并规划移除 |
| Webhook | admissionregistration.k8s.io/v1beta1 |
admission | 当前可用 | 不存在 | 修改配置 |
2. 推荐的门禁算法
可以把门禁实现为以下步骤:
第一步:计算目标版本的 Discovery 集合
对每个支持的 Kubernetes 版本 ,得到 。
第二步:解析交付产物
从渲染后的 YAML 中得到 。如果清单中包含未知或缺失 apiVersion、kind,直接失败。
第三步:检查交集
计算:
如果 非空,则说明至少有一个发布 GVK 不在所有目标版本中,门禁失败。
第四步:检查弃用窗口
即使 GVK 仍在交集内,如果目标版本已经将其标记为 Deprecated,也应产生高优先级告警,并根据组织策略决定:
- 允许合并但要求创建迁移任务;
- 只允许紧急修复;
- 直接阻断新使用。
第五步:检查运行时调用
如果 apiserver_requested_deprecated_apis 在迁移窗口内仍有请求,则不能宣称迁移完成。应按 ServiceAccount 或 userAgent 定位调用方。
第六步:检查动态行为
至少在一个目标版本测试集群中完成 apply、调谐和业务验证。对于跨多个 Kubernetes 版本运行的产品,则应在每个支持版本上执行关键测试。
3. 一个简单的 CI 门禁示意
下面的命令表达了门禁思想:
set -euo pipefail
helm template web ./chart \
-f values-ci.yaml \
> rendered.yaml
if grep -nE \
'extensions/v1beta1|apps/v1beta1|apps/v1beta2|batch/v1beta1|policy/v1beta1|networking.k8s.io/v1beta1' \
rendered.yaml; then
echo "发现禁止使用的 Kubernetes API 版本" >&2
exit 1
fi
kubectl apply \
--server-side \
--dry-run=server \
--field-manager=ci \
-f rendered.yaml
kubectl diff \
--server-side \
--field-manager=ci \
-f rendered.yaml
这个脚本适合作为一个初级门禁,但它有明显边界:
- 版本列表是人工维护的;
- 只扫描当前文件,不扫描控制器二进制中的 GVK;
- 不能识别所有动态生成的请求;
grep可能误报注释或文档;- 它依赖当前
kubectl指向的集群,而不是自动验证所有目标版本。
生产级门禁应使用结构化 YAML 解析、目标版本 Discovery、渲染矩阵和运行时指标,而不是只依赖正则表达式。
七、升级过程中的 Version Skew 与弃用风险
Version Skew 是 Kubernetes 组件之间的版本偏差。它不是“只要客户端版本不一样就一定失败”,而是需要遵守 Kubernetes 对控制面、节点、kubectl、控制器和扩展组件规定的兼容范围。
1. API Server 不是唯一需要升级的组件
一个 API 迁移可能涉及:
kube-apiserver
kube-controller-manager
kube-scheduler
kubelet
kubectl / 客户端库
Operator
Admission Webhook
Ingress Controller
CSI、CNI 等扩展组件
例如:
- API Server 已移除旧 GVK,但 Operator 还在使用旧客户端;
- API Server 接受新 GVK,但旧 Ingress Controller 不理解新字段;
- Webhook 配置使用新 API,但 Webhook 服务程序只支持旧 AdmissionReview 版本;
kubectl支持的资源类型与集群 Discovery 不一致;- 升级过程中 HA API Server 节点版本暂时不同,客户端观察到的 Discovery 结果短时间不一致。
升级期间不应把某一次 kubectl 成功看作整个 HA 控制面的长期保证。应使用目标版本的支持客户端,并在控制面升级完成后重新执行 Discovery、dry-run 和行为验证。
2. 先迁移消费者,再移除提供者
通常更安全的顺序是:
升级或改造客户端/控制器/Webhook
↓
迁移源码、模板和运行时对象
↓
观察弃用请求为零
↓
验证目标版本兼容性
↓
升级控制面
↓
升级节点和扩展组件
这里的“消费者”包括所有发起 API 请求的组件,不只是发布 YAML 的 CI。
但迁移顺序并非所有资源都一样:
- 内置 API 通常由 Kubernetes 控制面提供转换;
- CRD 需要先准备版本、schema 和转换 Webhook;
- Webhook 本身必须先支持目标 AdmissionReview 和对象版本;
- Operator 往往需要先升级到支持目标集群版本的版本;
- 托管 Kubernetes 可能由云厂商控制控制面升级时间和可用 API 集合。
八、常见失败表现与诊断路径
1. no matches for kind
错误示例:
error: resource mapping not found for name: web namespace: production
no matches for kind "Ingress" in version "extensions/v1beta1"
诊断顺序:
kubectl api-versions | grep -E 'extensions|networking.k8s.io'
kubectl api-resources | grep -i ingress
kubectl explain ingress --api-version=networking.k8s.io/v1
如果新版本存在,应迁移 GVK 和字段结构;如果新版本也不存在,可能是:
- 当前上下文连接到了错误集群;
- API Group 写错;
- CRD 未安装;
- 聚合 API 服务异常;
- 权限或 Discovery 缓存出现问题。
2. the server does not allow this method
常见原因是请求方法、资源作用域或子资源路径不符合目标 API。应检查:
kubectl api-resources -o wide
kubectl auth can-i list deployments -n production
kubectl auth can-i update deployments/status -n production
API 版本迁移可能改变了客户端访问的资源或子资源,RBAC 规则也必须随之核对。RBAC 的 apiGroups 和 resources 不是根据 Kind 自动转换的。
3. Webhook 阻断所有写请求
常见错误:
failed calling webhook
context deadline exceeded
诊断:
kubectl get mutatingwebhookconfiguration,validatingwebhookconfiguration
kubectl get svc,endpoints -n <webhook-namespace>
kubectl logs -n <webhook-namespace> deploy/<webhook-deployment>
重点检查:
- Webhook
rules是否仍引用旧 API 版本; clientConfig.service是否能解析;- TLS CA 与服务端证书是否匹配;
timeoutSeconds是否过短;failurePolicy是否为Fail;- 升级期间 Webhook Deployment 是否与 API Server 同时不可用。
failurePolicy: Ignore 可以降低升级时的阻断风险,但会降低准入保证,不能无条件当作修复方案。生产上应根据校验的重要性、升级窗口和回退路径做取舍。
4. 对象能读,控制器却不工作
可能出现:
unable to recognize ...
the server could not find the requested resource
failed to list ...
这通常说明:
kubectl使用的是新版本,但控制器仍使用旧 GVR;- 控制器的 discovery cache 过期;
- RBAC 缺少目标 API Group 或资源权限;
- CRD 的转换 Webhook 失败;
- 扩展控制器不支持目标版本。
诊断应从控制器日志中的 list/watch 路径、ServiceAccount 和镜像版本入手,而不是只查看对象是否存在。
5. 迁移后对象被意外重置
使用完整 YAML 回写对象时,可能产生:
- 字段所有权冲突;
- 省略字段被清除;
- 默认值变化;
- 注解和标签被覆盖;
status与spec混写;- 多个控制器互相争夺字段。
建议:
- 从声明式源文件重新生成迁移后的清单;
- 不直接把
kubectl get -o yaml的完整输出当作新源文件; - 先执行
kubectl diff --server-side; - 对重要对象使用独立字段管理器;
- 检查控制器日志和事件;
- 对生产资源先在灰度命名空间验证。
九、生产升级前后的验证与恢复
1. 升级前检查
升级前至少确认:
kubectl version
kubectl get nodes
kubectl get --raw /version | jq .
kubectl api-versions
kubectl api-resources
然后检查:
- 所有发布产物已完成目标版本 dry-run;
- 弃用 API 请求指标在观察窗口内为零或有明确豁免;
- Operator、Webhook、Ingress、CSI、CNI 等扩展组件支持目标版本;
- CRD 已完成版本规划;
- 关键对象和集群状态已经备份;
- 升级后验证脚本可以独立运行;
- 业务有明确的流量和功能探针。
备份的价值不仅是恢复业务 YAML,还包括理解对象关系、CRD 定义、RBAC、Webhook 和控制器配置。单纯备份几个 Deployment,无法还原完整控制面依赖。
2. 升级后检查
升级后重新执行 Discovery,因为 API 集合可能已经改变:
kubectl api-versions
kubectl api-resources
检查控制器和节点:
kubectl get nodes
kubectl get pods -A
kubectl get events -A --sort-by=.lastTimestamp
检查关键对象:
kubectl get deployment,service,ingress -A
kubectl get crd
检查运行时弃用请求:
sum by (group, version, resource) (
apiserver_requested_deprecated_apis
)
随后执行真实业务探针,例如:
- HTTP 请求和状态码;
- 消息队列生产与消费;
- Job 完成;
- 数据库连接;
- Ingress 路由;
- 自动扩缩容;
- 网络策略;
- 持久卷挂载和读写。
3. 回滚不能假设为普通版本回退
API 被移除后,回滚具有特殊风险:
- 新版本对象可能使用旧 API Server 不认识的字段;
- 新版本的默认值或字段语义可能已发生变化;
- 存储版本迁移可能使降级后的组件无法读取对象;
- 控制面降级本身通常不等价于业务版本回滚;
- 托管 Kubernetes 往往不支持用户任意降级控制面。
因此,升级回滚应优先设计为:
控制面升级前保留稳定版本的业务发布产物
↓
升级后发现问题
↓
回滚业务变更、流量和扩展组件
↓
必要时恢复备份或按发行版支持的灾难恢复流程处理
而不是临时把 apiVersion 改回旧版本。旧版本 API 已经被移除时,这个操作必然失败;即便旧 API 仍存在,也可能触发字段丢失或语义变化。
十、云厂商差异和真实边界
Kubernetes API 的基本对象模型、Discovery、API Server 转换和版本生命周期由 Kubernetes 规范定义,但生产环境仍可能存在发行版差异:
- 云厂商控制面升级窗口由平台决定;
- 某些控制面指标、审计日志或 API Server 参数不可见;
- 云厂商预装的 Ingress、LoadBalancer、CSI 和 Webhook 可能有独立兼容矩阵;
- 托管服务可能提前禁用或延后提供某些 API;
- Kubernetes 发行版可能携带额外的 API 组;
- 聚合 API Server 的 Discovery 和可用性不一定由主 API Server 单独决定。
所以目标版本不能只写成“上游 Kubernetes v1.x”,还应记录:
发行版/云厂商
控制面版本
节点版本
预装扩展组件版本
CRD 版本
Webhook 版本
支持的 kubectl 和客户端库范围
对于生产升级,最终兼容结论应来自目标发行版的实际测试,而不是只根据上游 YAML 示例推断。
十一、一个可执行的治理闭环
可以把整个过程收敛为以下状态机:
stateDiagram-v2
[*] --> Inventory: 收集清单、运行时请求和组件
Inventory --> Classified: 区分 Served、Deprecated、Storage
Classified --> Migration: 生成新 GVK 与字段映射
Migration --> StaticTest: 渲染和 schema 检查
StaticTest --> ServerDryRun: 目标集群服务端试运行
ServerDryRun --> BehaviorTest: 部署并验证控制器行为
BehaviorTest --> RuntimeObserve: 观察弃用请求和错误
RuntimeObserve --> GatePass: 满足目标版本门禁
RuntimeObserve --> Migration: 仍有旧调用或语义错误
GatePass --> Upgrade: 执行控制面和节点升级
Upgrade --> PostCheck: 重新 Discovery、验证业务
PostCheck --> [*]
PostCheck --> Recovery: 发现不可接受故障
Recovery --> PostCheck: 按支持的恢复方案处理
关键路径不是“文件改完就升级”,而是:
- Inventory:发现所有静态和运行时 API 使用;
- Classified:判断 API 是正常、弃用、停止 Served 还是存储版本问题;
- Migration:同时迁移 GVK、字段和依赖组件;
- ServerDryRun:在真实 API Server 上验证结构、准入和权限;
- BehaviorTest:验证控制器和数据面行为;
- RuntimeObserve:确认旧调用已消失;
- GatePass:将结论固化为升级门禁;
- PostCheck:升级后重新发现和验证;
- Recovery:按预先验证过的恢复流程处理,而不是临时降级。
十二、最终判断标准
一次 API 弃用迁移只有在以下条件同时满足时,才可以认为完成:
- 发布仓库和所有渲染产物不再生成目标版本不支持的 GVK;
- 控制器、Operator、Webhook 和脚本不再调用已弃用或即将移除的 GVR;
- 所有目标 Kubernetes 版本都能通过服务端 dry-run;
- API 版本变化涉及的字段语义已经验证;
- CRD 的
served、storage、转换 Webhook 和存量对象状态一致; - RBAC、审计、监控和准入规则已同步检查;
- 升级后 API Discovery 与升级前假设一致;
- 关键业务行为、扩展组件和数据面功能通过验证;
- 回滚或恢复路径不依赖已经被移除的旧 API。
可以把核心原则压缩成一句话:
API 弃用治理的完成标志,不是 YAML 中出现了新的
apiVersion,而是目标版本 API Server、客户端、控制器、Webhook、存储对象和业务行为共同证明迁移已经成立。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:Kubernetes 集群升级:Version Skew、API 弃用、节点灰度和回滚
- 下一篇:Kubernetes etcd 备份恢复:Snapshot、证书、Revision 和灾难演练
- 延伸:Kubernetes API 对象:GVK、Metadata、Spec、Status、版本和兼容
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论