Kubernetes 基础体系 · 第 3/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
Kubernetes API 对象:GVK、Metadata、Spec、Status、版本和兼容
Kubernetes 的大多数资源都通过 Kubernetes API Server 以“对象”的形式暴露。Deployment、Pod、Service、ConfigMap、Node、CustomResource 等对象虽然用途不同,但通常都遵循同一套基本结构:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
status:
availableReplicas: 3
这段 YAML 不只是配置文件。它同时包含:
- GVK:对象属于哪一种 API 类型;
- Metadata:对象的身份、归属、标签、版本和生命周期信息;
- Spec:用户期望的状态;
- Status:系统观察到的实际状态;
- 版本信息:对象由哪个 API 版本表示、如何存储和转换;
- 并发控制信息:多个客户端如何安全地读写同一个对象。
理解这些概念,是理解声明式配置、Controller、Server-Side Apply、滚动更新、Operator 和 API 兼容性的基础。
一、先区分四个容易混淆的概念
Kubernetes API 中至少有四个相关但不同的术语:
| 术语 | 含义 | 示例 |
|---|---|---|
| Group | API 组 | apps、batch、networking.k8s.io |
| Version | API 版本 | v1、v1beta1 |
| Kind | 对象类型 | Deployment、Job、Ingress |
| Resource | REST 资源名称 | deployments、jobs、ingresses |
1. GVK:描述对象类型
GVK 是 Group、Version、Kind 的组合:
例如:
apiVersion: apps/v1
kind: Deployment
对应:
Group = apps
Version = v1
Kind = Deployment
对于核心 API 组,apiVersion 只有版本部分:
apiVersion: v1
kind: Pod
这里的 Group 是 Kubernetes 约定的核心组,通常表示为空字符串,而不是名为 core 的组:
Group = ""
Version = v1
Kind = Pod
因此,下面两个对象不是同一种 GVK:
apiVersion: v1
kind: Service
apiVersion: v1
kind: ConfigMap
它们虽然都属于核心组和 v1 版本,但 Kind 不同。
2. GVR:描述 REST 访问路径
客户端访问 API 时通常使用 GVR,即 Group、Version、Resource:
例如,Deployment 的 GVK 和 GVR 分别是:
GVK = apps/v1, Deployment
GVR = apps/v1, deployments
REST 路径通常是:
/apis/apps/v1/namespaces/default/deployments/web
Pod 属于核心组,因此路径没有 /apis/<group>:
/api/v1/namespaces/default/pods/web
Kind 通常是单数、首字母大写;Resource 通常是复数、小写,但客户端不应该自行猜测所有资源名。API Discovery 会公开资源到 Kind 的映射,例如:
kubectl api-resources
输出中可能包含:
NAME SHORTNAMES APIVERSION NAMESPACED KIND
deployments deploy apps/v1 true Deployment
pods po v1 true Pod
这里的 deployments 是 Resource,Deployment 是 Kind。
3. GVK 为什么重要
Kubernetes 需要知道一段 JSON 或 YAML 应该按照什么结构解析、验证和转换。仅凭字段名无法可靠判断对象类型:
metadata:
name: example
这个片段可能属于 Pod、Service、ConfigMap 或自定义资源。apiVersion 和 kind 提供了类型上下文。
客户端工具也依赖 GVK:
kubectl根据 GVK 选择资源 REST 路径;- API Server 根据 GVK 选择版本化处理逻辑;
- Admission Webhook 可按资源组、版本和资源过滤请求;
- Controller 通常监听某个 Kind;
- 序列化和版本转换依赖 API 类型定义。
二、一个 Kubernetes API 对象的基本结构
并非每个对象都严格拥有 spec 和 status,但 Kubernetes API 对象通常可以抽象为:
apiVersion: <group>/<version>
kind: <kind>
metadata:
...
spec:
...
status:
...
例如一个 Deployment:
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: nginx
image: nginx:1.27
status:
replicas: 3
readyReplicas: 3
需要区分“用户发送的对象”和“API Server 返回的完整对象”:
- 创建时,用户通常只提交
apiVersion、kind、metadata和所需的spec; - API Server 会填充 UID、时间戳、resourceVersion 等字段;
- Admission、默认值、Controller 可能继续修改对象或相关对象;
status往往由 Controller 异步写入,因此创建响应不一定立即包含最终状态。
三、Metadata:对象的身份、索引和生命周期
metadata 不是普通业务配置。它包含 Kubernetes 管理对象所需的控制信息。
1. name、generateName 和 namespace
最基本的身份字段是:
metadata:
name: web
namespace: production
对于命名空间作用域的资源,唯一身份通常是:
也就是说,production 命名空间中的 web Deployment,与 staging 命名空间中的 web Deployment 不是同一个对象。
对于集群作用域资源,例如 Node、Namespace、PersistentVolume,不使用命名空间:
apiVersion: v1
kind: Node
metadata:
name: worker-1
generateName 用于请求 API Server 自动生成名称:
metadata:
generateName: job-
API Server 可能生成类似:
job-x7k2m
name 和 generateName 通常不能同时使用。使用 generateName 时,客户端不能预先知道最终名称,后续操作需要读取创建响应中的 metadata.name。
2. uid:对象的稳定身份
metadata.uid 是 API Server 为对象分配的唯一标识:
metadata:
uid: 7e6f...
同名对象删除后重新创建,会得到新的 UID:
旧对象:namespace=default, name=web, uid=U1
删除并重建后:namespace=default, name=web, uid=U2
因此,UID 可以区分“曾经存在过的对象实例”。Controller 使用 UID 进行 OwnerReference 校验,避免把旧对象误认为新对象。
3. resourceVersion:并发控制版本
metadata.resourceVersion 是 API Server 用于并发控制和观察一致性的版本标识:
metadata:
resourceVersion: "18421"
它不是业务版本号,也不应被应用程序自行递增。
典型的乐观并发控制过程如下:
- 客户端 A 读取对象,得到
resourceVersion: "10"; - 客户端 B 先更新对象,API Server 将版本推进到
"11"; - 客户端 A 携带旧的
"10"提交更新; - API Server 检测到版本不匹配,通常返回
409 Conflict; - 客户端 A 重新读取对象,合并自己的修改后重试。
使用 HTTP PUT 或 Kubernetes 客户端更新对象时,过期对象可能产生类似错误:
the object has been modified; please apply your changes to the latest version
这是一种保护机制,而不是 API Server 故障。它阻止客户端用旧快照覆盖其他客户端刚刚写入的字段。
4. generation:期望配置的代数
metadata.generation 表示对象期望配置的代数,通常由 API Server 在对象的期望状态发生变化时递增:
metadata:
generation: 4
Controller 常在 Status 中记录自己已经处理到哪一代:
status:
observedGeneration: 4
于是可以形成判断:
表示 Controller 至少已经观察到当前这代配置;如果:
通常表示用户刚修改了 spec,Controller 还没有完成处理。
需要注意:
generation不是所有资源都以完全相同方式使用;- 具体递增条件取决于资源实现;
generation与resourceVersion不等价;- 更新 Status 通常不会被当作用户修改 Spec 的新 generation。
一个对象可能出现:
generation = 5
observedGeneration = 4
resourceVersion = "900"
这表示对象已经有新配置,但 Controller 还没有处理完成;resourceVersion 只是 API 存储和并发观察层面的版本。
5. creationTimestamp、deletionTimestamp 和 finalizers
API Server 会记录创建时间:
metadata:
creationTimestamp: "2025-01-10T08:00:00Z"
删除对象时,API Server 通常先设置:
metadata:
deletionTimestamp: "2025-01-10T08:10:00Z"
如果对象没有阻止删除的 Finalizer,它随后会消失;如果存在 Finalizer,删除会进入一种“删除中但对象仍可读取”的状态。
metadata:
finalizers:
- example.com/cleanup
Finalizer 的语义是:
- 客户端请求删除对象;
- API Server 设置
deletionTimestamp; - Controller 观察到删除请求;
- Controller 完成外部清理;
- Controller 移除自己负责的 Finalizer;
- 当 Finalizer 列表为空时,对象才真正删除。
Finalizer 必须由能够完成清理的 Controller 负责移除。随意删除 Finalizer 可能留下云资源、磁盘、DNS 记录等外部残留;但 Controller 已经永久失效时,Finalizer 也可能导致对象长期卡在 Terminating。
6. labels:可选择、可筛选的索引信息
Label 是结构化的键值对:
metadata:
labels:
app.kubernetes.io/name: web
app.kubernetes.io/component: frontend
Label 可用于:
- Service 选择 Pod;
- Deployment 的 Pod Selector;
kubectl get -l过滤;- Controller 的队列索引;
- NetworkPolicy、监控和调度规则中的选择条件。
例如:
kubectl get pods -l app.kubernetes.io/name=web
Label 适合表达“这个对象属于哪一类”或“应被哪些组件选择”。
Label 的值和键有语法限制,不能把任意长文本都放进去。高基数、较大的 JSON、描述性文档通常不适合放在 Label 中,因为 Label 经常参与索引、选择和列表过滤。
7. annotations:不用于选择的附加信息
Annotation 也是键值对,但语义不同:
metadata:
annotations:
example.com/config-hash: "abc123"
Annotation 通常用于:
- 工具写入的额外配置;
- Webhook、Ingress Controller 等组件的行为开关;
- 变更原因或外部系统引用;
- 不适合 Label 的较长元数据。
Annotation 不应该被当作通用数据库。过大的 Annotation 可能导致对象超过 API 请求或 etcd 对象大小限制,也可能增加每次读写对象的开销。具体限制和行为受 Kubernetes 版本及 API Server 配置影响。
8. ownerReferences:对象之间的所有权
OwnerReference 用于表达 Kubernetes 对象之间的拥有关系:
metadata:
ownerReferences:
- apiVersion: apps/v1
kind: ReplicaSet
name: web-7d8f...
uid: 1a2b...
controller: true
blockOwnerDeletion: true
Deployment 创建并管理 ReplicaSet,ReplicaSet 再管理 Pod。删除拥有者时,Garbage Collector 可以依据 OwnerReference 执行级联删除。
OwnerReference 中的 UID 很重要。仅凭名称可能误把“删除后重建的新对象”当成原来的拥有者,因此 Kubernetes 通常同时检查名称和 UID。
四、Spec 和 Status:期望状态与观察状态
1. Spec 表达用户期望
spec 是声明式 API 的核心。以 Deployment 为例:
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27
这表达的是:
- 希望有 3 个副本;
- 这些副本由指定的标签选择器管理;
- 每个 Pod 使用
nginx:1.27镜像。
它不是“立即执行三次创建 Pod 的命令”。API Server 接受对象后,Deployment Controller 才根据 Spec 计算需要创建或更新的 ReplicaSet,ReplicaSet Controller 再创建 Pod,Scheduler 为 Pod 选择节点,Kubelet 最终在节点上启动容器。
2. Status 表达系统观察结果
status 表示当前观察到的结果,例如:
status:
availableReplicas: 2
readyReplicas: 2
replicas: 3
updatedReplicas: 3
observedGeneration: 4
这里可以推导出:
- Controller 已经处理到 generation 4;
- 当前有 3 个副本对象;
- 其中 2 个已经 Ready;
- 用户期望是 3 个,因此系统尚未完全达到期望状态。
声明式控制的基本闭环可以表示为:
Controller 不一定执行一个简单的数值减法,但它会根据期望状态和观察状态计算差异,再采取动作。
完整路径通常是:
flowchart LR
U[用户或客户端] -->|创建/更新 Spec| A[API Server]
A --> E[(etcd)]
A -->|Watch 事件| C[Controller]
C -->|创建或更新子资源| A
A --> S[Scheduler]
S -->|绑定 Pod 到 Node| A
A --> K[Kubelet]
K -->|启动容器并上报结果| A
A -->|Status| U
关键点是:API Server 负责 API、认证、鉴权、准入、持久化和事件分发;它通常不负责让 Deployment 的 Pod 真正运行起来。实际的收敛动作由 Controller、Scheduler 和 Kubelet 等组件完成。
3. Status 不是 Spec 的回显
错误的理解是把 Status 当作 Spec 的复制品:
spec:
replicas: 3
status:
replicas: 3
status.replicas 表示当前 Controller 统计到的副本数,不是对 spec.replicas 的机械回显。两者可能长期不同,例如:
- 节点资源不足;
- 镜像拉取失败;
- Pod 被调度到不可用节点;
- Readiness Probe 失败;
- Controller 尚未处理新配置。
因此,判断系统是否“可用”,通常需要结合 Conditions、事件、Pod 状态和底层节点,而不能只比较某一个字段。
4. Conditions:状态的可扩展表达
许多资源使用 Conditions:
status:
conditions:
- type: Available
status: "True"
reason: MinimumReplicasAvailable
message: Deployment has minimum availability.
lastTransitionTime: "2025-01-10T08:15:00Z"
Condition 通常包含:
type:条件类型;status:True、False或Unknown;reason:机器可读的原因标识;message:面向人的补充信息;- 时间字段:条件何时改变或何时被观察。
status.conditions 不是所有资源都使用完全相同的 Condition 类型。应用程序必须参考具体资源的 API 文档,不能假设所有对象都有 Ready=True。
5. Status 子资源和权限边界
许多资源支持独立的 Status 子资源:
PUT /apis/apps/v1/namespaces/default/deployments/web/status
这样可以让 Controller 更新 Status,而不覆盖用户管理的 Spec。普通更新与 Status 更新通常具有不同的 API 路径和 RBAC 权限:
resources:
- deployments
- deployments/status
verbs:
- get
- update
对于自定义资源,如果 CRD 声明了:
spec:
subresources:
status: {}
则可以启用 /status 子资源。Controller 应使用专门的 Status 更新机制,而不是把整个对象的修改结果直接写回主资源。
但即使启用 Status 子资源,写入 Status 仍然受到 resourceVersion 并发控制,可能返回冲突。
6. 并非所有对象都有 Spec 和 Status
例如 ConfigMap 的主要内容在 data 和 binaryData:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: info
它没有典型的 spec。Secret 也主要使用 data 或 stringData。因此,“所有 Kubernetes 对象都必须有 Spec 和 Status”是不正确的。
在自定义资源设计中,通常将:
- 用户声明的输入放入
spec; - Controller 观察到的输出放入
status; - 对象身份和控制元信息放入
metadata。
这是一种 API 设计惯例,但具体资源仍由其类型定义决定。
五、创建一个对象时,字段如何变化
下面以 Deployment 为例。
1. 用户提交最小配置
保存为 web.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: default
labels:
app.kubernetes.io/name: web
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: web
template:
metadata:
labels:
app.kubernetes.io/name: web
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- name: http
containerPort: 80
前置条件:
kubectl cluster-info
kubectl auth can-i create deployments --namespace default
应用对象:
kubectl apply -f web.yaml
可能输出:
deployment.apps/web created
apply 的含义不是“无条件覆盖整个对象”,而是根据声明内容、现有对象和字段管理信息计算一次合并更新。后文会说明字段所有权。
2. API Server 处理请求
API Server 处理创建请求时,通常会经过以下阶段:
- 解析
apiVersion和kind; - 确定 REST 资源和命名空间;
- 认证请求;
- 鉴权;
- 执行 Mutating Admission;
- 设置默认值;
- 执行 schema 和字段验证;
- 执行 Validating Admission;
- 写入存储;
- 返回对象或异步触发 Watch 事件。
返回对象通常比原始 YAML 多出:
metadata:
creationTimestamp: "..."
generation: 1
resourceVersion: "..."
uid: "..."
这些字段由服务器或控制器管理,不应把一次 kubectl get -o yaml 的完整输出原样当作新的配置文件提交。
3. Controller 异步创建子资源
Deployment Controller 观察到 Deployment 后,会创建或更新 ReplicaSet。随后 ReplicaSet Controller 创建 Pod。
检查对象:
kubectl get deployment web
kubectl get replicaset -l app.kubernetes.io/name=web
kubectl get pods -l app.kubernetes.io/name=web
查看完整对象:
kubectl get deployment web -o yaml
查看状态:
kubectl rollout status deployment/web
预期是在镜像可拉取、节点有资源且探针没有阻塞的情况下看到:
deployment "web" successfully rolled out
这不是 API Server 在创建请求中同步完成的保证。kubectl apply 成功只说明对象写入 API 成功,不等于应用已经可用。
4. 观察变化而不是轮询
可以使用 Watch:
kubectl get deployment web --watch
或者观察 Pod:
kubectl get pods -l app.kubernetes.io/name=web --watch
Watch 返回的是对象变化事件,例如 ADDED、MODIFIED、DELETED。实际客户端还必须处理:
- Watch 连接断开;
- 资源版本过旧;
- API Server 返回
410 Gone; - 需要重新 List 后再 Watch;
- 事件流中对象可能因并发更新快速变化。
因此,可靠 Controller 一般采用“List-then-Watch”模式:
- List 当前对象集合;
- 记录返回的 resourceVersion;
- 从该版本开始 Watch;
- 收到事件后更新本地缓存;
- Watch 结束或版本过旧时重新 List。
六、默认值、验证和未知字段
用户提交的 YAML 不是最终存储形态。API Server 可能通过默认值、Admission 和类型转换改变对象。
1. 默认值不是客户端猜测
例如某些字段在未填写时会使用 API 默认值。默认值可能来自:
- Kubernetes 内置类型的默认逻辑;
- CRD 的 OpenAPI v3 schema 默认值;
- Mutating Admission Webhook;
- 其他版本转换过程。
因此,不能只看本地 YAML 判断 API Server 最终保存了什么,应检查服务端对象:
kubectl get deployment web -o yaml
也可以用服务器端 dry-run 查看处理结果而不持久化:
kubectl apply --dry-run=server -f web.yaml -o yaml
这个命令仍可能执行认证、鉴权、准入和服务器端验证,但不会真正创建对象。它适合检查默认值和验证结果,但不应被误认为完全等价于生产集群中的最终运行状态。
2. 字段验证
使用严格字段验证:
kubectl apply --validate=strict -f web.yaml
如果写入不存在的字段,API Server 可能返回类似:
unknown field "spec.replicass"
实际行为取决于资源 schema、客户端版本和服务器版本。对结构化内置资源与 CRD,现代 Kubernetes 通常会依据 OpenAPI schema 进行字段验证和未知字段处理。
3. 未知字段不能依赖“服务器保留”
在声明式配置中,以下写法可能造成误判:
spec:
replicas: 2
repicas: 3
如果未知字段被拒绝,应用会失败;如果某类资源或旧版本处理逻辑丢弃未知字段,应用可能成功但 repicas 不会产生任何效果。
生产配置应使用服务器端验证,并将 API schema、资源版本和客户端版本纳入测试。尤其是 CRD,结构化 schema 会影响:
- 未知字段是否被裁剪;
- 默认值是否生效;
- Server-Side Apply 是否能正确计算字段;
- OpenAPI 文档和验证行为;
- 版本转换是否可靠。
七、字段所有权、Update、Patch 和 Apply
多个客户端同时修改对象时,“更新整个对象”和“声明自己负责哪些字段”是不同模型。
1. Update:基于完整对象的替换式写入
传统 Update 通常是:
- GET 当前对象;
- 修改本地对象;
- PUT 或 Update 整个对象。
伪代码:
obj = GET /apis/apps/v1/namespaces/default/deployments/web
obj.spec.replicas = 3
PUT obj
如果期间其他客户端修改了对象,旧的 resourceVersion 可能导致 409 Conflict。如果客户端忽略冲突并重新构造对象,还可能覆盖其他字段。
Update 适合一个组件拥有整个资源,或客户端能够可靠地基于最新对象合并修改的场景。
2. Patch:只描述局部变更
Kubernetes 支持多种 Patch 语义,不能把它们混为一谈:
- JSON Patch:按操作数组修改路径;
- JSON Merge Patch:对 JSON 对象做合并,某些值设为
null表示删除; - Strategic Merge Patch:针对部分内置资源提供基于类型的合并语义;
- Server-Side Apply:基于字段管理器和声明式字段集合合并。
例如 JSON Merge Patch:
kubectl patch deployment web \
--type=merge \
-p '{"spec":{"replicas":3}}'
它只表达将 spec.replicas 设置为 3,但具体合并行为仍由 Patch 类型定义。列表字段尤其需要小心:有的列表按键合并,有的列表整体替换。
3. Server-Side Apply 和 managedFields
Server-Side Apply 使用字段管理器记录字段所有权:
kubectl apply \
--server-side \
--field-manager=wr-blog \
-f web.yaml
对象中可能出现:
metadata:
managedFields:
- manager: wr-blog
operation: Apply
apiVersion: apps/v1
fieldsType: FieldsV1
fieldsV1:
...
managedFields 记录哪些管理器声明或修改了哪些字段。它支持这样的协作模型:
- GitOps 管理 Deployment 的镜像和副本数;
- 某个 Controller 管理 Status;
- 另一个工具管理特定 Annotation;
- 各方尽量不覆盖彼此拥有的字段。
如果两个管理器对同一个字段进行 Apply,API Server 可能返回字段冲突:
conflict: ... field is managed by ...
可以选择:
kubectl apply \
--server-side \
--field-manager=wr-blog \
--force-conflicts \
-f web.yaml
--force-conflicts 会强制转移字段所有权。它不是“解决冲突”的无害选项,而是明确接受覆盖其他管理器声明的字段,必须确认不会破坏 Controller 或其他自动化系统。
4. 删除字段的语义
声明式配置中,删除一个字段通常意味着“我不再声明或希望该字段存在”,但最终效果取决于:
- Apply 还是 Update;
- 当前字段由谁管理;
- API 默认值是否会重新填充;
- 字段是否有继承或控制器写入逻辑;
- Patch 的具体类型。
因此,把 YAML 中删掉一个字段简单理解为“服务器必然清空该字段”是不正确的。应结合 managedFields、资源 schema 和实际 GET 结果诊断。
八、API 版本:served、storage 与转换
apiVersion: apps/v1 至少涉及三个层面:
- 客户端对外请求的版本;
- API Server 对外提供的版本;
- etcd 内部存储的版本。
这三者可以相同,也可以不同。
1. served 和 storage
一个 API 组可能在 API Server 中声明多个版本:
- served:是否接受客户端对该版本的请求;
- storage:是否作为内部持久化存储版本。
例如某个资源可能:
v1beta1: served=true, storage=false
v1: served=true, storage=true
客户端仍可请求 v1beta1,API Server 将其转换为内部版本或存储版本后保存。读取时再转换为客户端请求的版本。
“客户端能请求某版本”不等于“etcd 以该版本保存对象”。
2. 版本转换
转换可能发生在:
客户端版本
↓
API Server 外部版本
↓
内部对象表示
↓
存储版本
读取时方向相反。
内置资源的转换由 Kubernetes 代码实现。CRD 可以声明多个版本,并通过 CRD 的 schema 和转换策略处理;更复杂的字段变换可以使用 Conversion Webhook。
安全的转换必须尽量保持语义:
convert(v1beta1 object) -> internal object
convert(internal object) -> v1 object
理想情况下,往返转换不会丢失用户可观察的重要信息:
这里的“约等于”表示允许版本间字段名称、默认值或表示形式不同,但不应无意丢失业务语义。若旧版本包含新版本无法表达的字段,转换可能需要额外字段保存这些信息;否则写入和再读取会产生数据丢失。
3. 查询 API 版本和存储版本
查看服务端支持的 API 版本:
kubectl api-versions
查看资源发现信息:
kubectl api-resources
kubectl explain deployment --api-version=apps/v1
kubectl explain deployment.spec
kubectl explain 展示的是当前连接到的 API Server 提供的 schema,不应把本地编辑器中的静态提示当作最终权威。
对于 CRD,可以查看版本配置:
kubectl get crd <crd-name> -o yaml
重点关注:
spec:
versions:
- name: v1
served: true
storage: true
生产迁移时还要检查集群中的存储版本,而不能只看客户端 YAML 中写了哪个 apiVersion。不同 Kubernetes 版本提供的存储版本迁移工具和流程可能不同,不能直接假定所有集群都能用同一条命令完成迁移。
九、兼容性:版本、字段和客户端都可能产生偏差
Kubernetes 的“兼容”不是一个单一概念,至少包括以下几类。
1. API 组版本兼容
例如:
apiVersion: extensions/v1beta1
kind: Ingress
在较新的 Kubernetes 集群中可能失败,因为该版本已经被移除。当前稳定写法应使用:
apiVersion: networking.k8s.io/v1
kind: Ingress
而且这不只是修改 apiVersion:
pathType在稳定 Ingress API 中是重要字段;- 后端字段结构发生过变化;
- 某些旧字段没有一一对应的稳定字段;
- Controller 对 IngressClass 和默认行为的支持也可能不同。
因此,API 升级需要阅读目标 Kubernetes 版本的迁移说明并重新验证清单,不能用字符串替换完成。
2. 客户端与 API Server 的版本偏差
kubectl、client-go、Operator SDK、Helm 或其他客户端都可能与 API Server 版本不同。Kubernetes 官方对 kubectl 有受支持的版本偏差范围,通常要求客户端与 API Server 处于相近的 minor 版本;精确支持范围应以当前版本文档为准。
偏差可能表现为:
- 客户端使用了服务器不支持的 API 版本;
- 客户端不了解服务器新增字段;
- 客户端生成旧格式对象;
- OpenAPI schema 缓存过期;
- Apply 或 Patch 的列表合并语义不符合预期。
常用检查:
kubectl version
kubectl cluster-info
kubectl api-resources
kubectl explain deployment --api-version=apps/v1
这里需要区分:
kubectl version显示客户端和服务器版本;api-resources显示服务器实际发现到的资源;explain显示当前服务器的字段 schema。
3. Stable、Beta 和 Alpha
API 版本中的稳定性通常通过名称表达:
v1
v1beta1
v1alpha1
一般含义是:
v1:稳定 API,兼容承诺更强;v1beta1:接近稳定但仍可能有变更或弃用;v1alpha1:实验性,字段和行为可能变化。
这不是绝对的“所有 v1 都永不变化”。稳定 API 仍可能增加字段、标记字段弃用,某些整体 API 也可能在遵循弃用策略后被替换。但稳定 API 通常提供更强的长期兼容预期。
4. 字段兼容不等于行为兼容
同一份 YAML 在不同版本中可能都能通过验证,但行为仍可能变化。例如:
- 默认调度行为变化;
- Admission 插件配置不同;
- Ingress Controller 对同一字段的实现不同;
- 云厂商对 Service、LoadBalancer、磁盘和网络字段有额外解释;
- CRD Controller 自己的版本改变了状态机。
所以兼容性验证至少要覆盖:
- API Server 是否接受对象;
- 对象是否以预期字段保存;
- Controller 是否创建预期的子资源;
- Status 是否进入预期状态;
- 实际运行行为是否符合预期。
5. 云厂商差异
Kubernetes API 的核心对象由 Kubernetes 规范定义,但托管集群可能在以下部分存在差异:
- API Server 版本和升级节奏;
- Admission Webhook;
- 默认 StorageClass;
- LoadBalancer Service 的云资源实现;
- Ingress Controller;
- CNI、CSI 和节点运行时;
- 节点标签、污点和拓扑信息;
- 资源配额、策略和安全限制。
例如:
apiVersion: v1
kind: Service
metadata:
name: web
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 80
这份对象在不同云环境中可能都能创建,但 status.loadBalancer 的填充方式、外部 IP、健康检查和安全组行为可能不同。Service API 的结构兼容,不代表底层云负载均衡器行为完全一致。
十、API 对象与 API Server、etcd 的关系
一个对象从提交到运行,涉及多个组件,但它们负责的事情不同。
1. API Server 是控制平面入口
API Server 通常负责:
- 接收 HTTP 请求;
- 认证和鉴权;
- Admission;
- API 版本处理和转换;
- schema 验证;
- 读写持久化存储;
- 向 Watch 客户端发送事件。
API Server 接受 Deployment,并不代表 Deployment 已经运行。它只表示期望状态已经被写入 API。
2. etcd 保存对象数据
Kubernetes 使用 etcd 持久化 API 对象及相关状态。Controller、Scheduler 和其他组件通常通过 API Server 访问对象,而不是直接访问 etcd。
直接操作 etcd 绕过 API Server 会绕过:
- 认证和 RBAC;
- Admission;
- schema 验证;
- resourceVersion 处理;
- 版本转换;
- Watch 语义。
因此,直接修改 Kubernetes 的 etcd 数据属于高风险恢复操作,不是普通运维手段。
3. Watch 连接控制平面和控制器
Controller 通常不是不断执行全量扫描,而是:
- 从 API Server List 初始对象;
- 建立 Watch;
- 收到对象变化;
- 将对象键放入工作队列;
- Reconcile 读取最新对象;
- 计算差异并写回资源或子资源。
由于事件可能重复、乱序地到达或被重新同步,Controller 必须设计为幂等。可靠逻辑不应假设“每个事件只处理一次”,而应以当前 API 对象和外部实际状态为依据反复收敛。
十一、常见错误与诊断方法
错误一:把 apiVersion 当成集群版本
apiVersion: apps/v1
表示对象 API 的组和版本,不表示集群是 Kubernetes 1.31 还是 1.32。
集群版本应通过:
kubectl version
或管理平台查询。一个 Kubernetes 版本可以同时提供多个 API 组版本。
错误二:kubectl apply 成功等于应用可用
kubectl apply -f web.yaml
成功只证明请求被接受并写入 API。继续检查:
kubectl rollout status deployment/web
kubectl get pods -l app.kubernetes.io/name=web
kubectl describe deployment web
kubectl describe pod <pod-name>
kubectl get events --sort-by=.lastTimestamp
如果 Deployment 存在但 Pod 没有 Ready,常见原因包括镜像拉取失败、探针失败、资源不足、调度约束冲突和应用自身退出。
错误三:手动修改 Status 解决故障
直接修改 Status 通常不能让实际系统变好。即使某些资源允许写 Status,Controller 下一次 Reconcile 也可能把它改回真实观察结果。
正确诊断路径是:
kubectl get deployment web -o yaml
kubectl get rs -l app.kubernetes.io/name=web
kubectl get pods -l app.kubernetes.io/name=web
kubectl describe pod <pod-name>
kubectl logs <pod-name> -c nginx
如果是自定义资源,还要检查对应 Controller 的日志和权限:
kubectl auth can-i update <resource>/status \
--as=system:serviceaccount:<namespace>:<serviceaccount>
错误四:把 resourceVersion 当作 generation
对比:
resourceVersion = "200"
generation = 3
resourceVersion用于 API 存储、并发和 Watch;generation用于表达期望配置的变更代数;observedGeneration表示 Controller 已处理到的代数。
它们递增的原因不同,不能互换。
错误五:把 Labels 和 Annotations 混用
Service 的选择器:
spec:
selector:
app: web
匹配的是 Pod Label,不是 Annotation:
metadata:
annotations:
app: web
如果把 app: web 放到了 Annotation,Service 不会选择该 Pod,最终表现可能是 Service 没有 Endpoints。
检查方式:
kubectl get pods --show-labels
kubectl get endpointslice -l kubernetes.io/service-name=web
错误六:把完整 kubectl get -o yaml 当成可移植清单
完整输出通常包含:
uid;resourceVersion;creationTimestamp;managedFields;- 运行时 Status;
- Controller 写入的字段;
- 服务器默认值。
直接提交可能导致冲突、污染字段所有权,或者把环境相关状态带到另一个集群。迁移时应提取所需的声明字段,并重新确认命名空间、存储、网络和云厂商相关配置。
错误七:只修改 apiVersion 来“升级”资源
例如从旧 Ingress API 升级到 networking.k8s.io/v1 时,必须同时检查:
spec:
ingressClassName: nginx
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
字段结构、必填项和 Controller 行为都可能变化。应在目标集群执行:
kubectl apply --dry-run=server -f ingress.yaml
kubectl explain ingress.spec --api-version=networking.k8s.io/v1
再进行实际发布。
十二、生产环境中的兼容策略
1. 以目标 API Server 为验证权威
开发机上可以安装多个版本的 kubectl,但最终验证应针对目标集群:
kubectl apply --dry-run=server --validate=strict -f manifest.yaml
这能发现很多本地静态检查无法发现的问题,但仍不能验证 Controller 和云资源的最终行为,因此还需要测试环境中的实际 Reconcile。
2. 优先使用稳定 API
新配置应优先选择目标 Kubernetes 版本支持的稳定 API。对旧版本对象,应建立迁移计划:
- 确认目标集群支持的 API;
- 阅读弃用和迁移说明;
- 转换字段结构;
- 使用服务器端 dry-run;
- 在测试集群实际运行;
- 检查 Status、事件和子资源;
- 再发布到生产。
不要仅因为对象“还能创建”就认为旧 API 可以长期使用。某些版本会先标记 deprecated,之后停止 served,最终请求会直接失败。
3. 让自动化工具明确声明身份
使用 Apply 时指定稳定的 Field Manager:
kubectl apply \
--server-side \
--field-manager=platform-gitops \
-f manifest.yaml
这样可以通过 managedFields 识别字段来源,并在多个自动化系统并存时定位冲突。
4. 处理并发冲突和重试
客户端更新对象时,应把 409 Conflict 当作正常并发结果处理:
读取最新对象
↓
重新计算修改
↓
提交更新
↓
若 409,则回到读取步骤
不能无条件重试旧对象,也不应简单使用强制覆盖来隐藏冲突。对于 Controller,通常应使用 API 客户端的冲突重试机制,并保证 Reconcile 幂等。
5. 区分规范保证、实现行为和环境行为
可以这样分类:
- 规范保证:对象通过 Kubernetes API 访问,遵循 GVK、RBAC、资源版本和 API schema;
- 常见实现:Deployment Controller 通过 ReplicaSet 管理 Pod,Controller 使用 List-Watch 和工作队列;
- 环境行为:LoadBalancer 的外部 IP、云磁盘状态、Ingress Controller 的注解和网络实现。
设计故障处理和兼容测试时,不应把第三类行为误认为 Kubernetes 核心 API 的统一保证。
十三、用一个对象完成端到端检查
以下命令可以形成一条完整的检查链:
# 1. 确认目标集群和客户端版本
kubectl version
# 2. 确认服务器提供 Deployment
kubectl api-resources | grep -i deployment
# 3. 查看服务器 schema
kubectl explain deployment --api-version=apps/v1
kubectl explain deployment.spec
# 4. 服务器端验证和查看默认结果
kubectl apply --dry-run=server --validate=strict \
-f web.yaml -o yaml
# 5. 写入对象
kubectl apply -f web.yaml
# 6. 查看身份和期望配置
kubectl get deployment web \
-o jsonpath='{.metadata.uid}{"\n"}{.metadata.generation}{"\n"}{.spec.replicas}{"\n"}'
# 7. 查看当前状态
kubectl get deployment web \
-o jsonpath='{.status.observedGeneration}{"\n"}{.status.readyReplicas}{"\n"}'
# 8. 等待控制器完成收敛
kubectl rollout status deployment/web
# 9. 出现异常时查看事件和子资源
kubectl describe deployment web
kubectl get rs,pods -l app.kubernetes.io/name=web
kubectl get events --sort-by=.lastTimestamp
这些步骤分别验证了不同层次:
version:客户端和 API Server 的版本关系;api-resources:服务端是否实际提供资源;explain:服务端 schema 和字段结构;dry-run=server:服务器端默认值、准入和验证;apply:对象是否写入;generation与observedGeneration:Controller 是否追上最新期望;status.readyReplicas:实际可用副本;describe和 Events:失败原因和控制器动作。
十四、最终建立一套对象阅读方法
阅读一个 Kubernetes API 对象时,可以按以下顺序推导:
-
先看 GVK
通过apiVersion和kind确定类型、API 组和版本。 -
再看作用域和身份
检查资源是否 namespaced,再结合namespace、name和uid确定对象身份。 -
看 Spec 的期望状态
找出用户或上层系统要求 Kubernetes 达到什么状态。 -
看 Status 的观察状态
检查实际副本、条件、错误原因和observedGeneration。 -
看 Metadata 的控制信息
关注 Label、Annotation、OwnerReference、Finalizer、generation、resourceVersion 和 managedFields。 -
看版本与来源
确认 API 是否仍被 served,是否存在弃用,字段由哪个管理器维护,是否发生过版本转换。 -
沿控制链验证结果
从 API Server 到 Controller、子资源、Scheduler、Node 和容器逐层检查,而不是只看kubectl apply的返回值。
Kubernetes API 对象的核心不是一份静态 YAML,而是一种由类型、期望状态、观察状态、元数据和控制循环共同构成的协议。GVK 解决“这是什么对象”,Metadata 解决“它是谁、由谁管理、处于哪个生命周期”,Spec 解决“希望系统变成什么样”,Status 解决“系统目前实际是什么样”,版本机制解决“不同客户端和不同 API 表示如何沟通”,而 resourceVersion、generation、Watch 和字段所有权则保证多个组件能够在并发变化中持续收敛。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:Kubernetes 架构:API Server、Scheduler、Controller Manager、etcd 和 Node
- 下一篇:Kubernetes 声明式配置:YAML、默认值、字段所有权、Apply 和 Diff
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论