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 组 appsbatchnetworking.k8s.io
Version API 版本 v1v1beta1
Kind 对象类型 DeploymentJobIngress
Resource REST 资源名称 deploymentsjobsingresses

1. GVK:描述对象类型

GVK 是 Group、Version、Kind 的组合:

GVK=(Group,Version,Kind)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:

GVR=(Group,Version,Resource)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 或自定义资源。apiVersionkind 提供了类型上下文。

客户端工具也依赖 GVK:

  • kubectl 根据 GVK 选择资源 REST 路径;
  • API Server 根据 GVK 选择版本化处理逻辑;
  • Admission Webhook 可按资源组、版本和资源过滤请求;
  • Controller 通常监听某个 Kind;
  • 序列化和版本转换依赖 API 类型定义。

二、一个 Kubernetes API 对象的基本结构

并非每个对象都严格拥有 specstatus,但 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 返回的完整对象”:

  • 创建时,用户通常只提交 apiVersionkindmetadata 和所需的 spec
  • API Server 会填充 UID、时间戳、resourceVersion 等字段;
  • Admission、默认值、Controller 可能继续修改对象或相关对象;
  • status 往往由 Controller 异步写入,因此创建响应不一定立即包含最终状态。

三、Metadata:对象的身份、索引和生命周期

metadata 不是普通业务配置。它包含 Kubernetes 管理对象所需的控制信息。

1. name、generateName 和 namespace

最基本的身份字段是:

metadata:
  name: web
  namespace: production

对于命名空间作用域的资源,唯一身份通常是:

(namespace,name,GVK)(namespace, name, GVK)

也就是说,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

namegenerateName 通常不能同时使用。使用 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"

它不是业务版本号,也不应被应用程序自行递增。

典型的乐观并发控制过程如下:

  1. 客户端 A 读取对象,得到 resourceVersion: "10"
  2. 客户端 B 先更新对象,API Server 将版本推进到 "11"
  3. 客户端 A 携带旧的 "10" 提交更新;
  4. API Server 检测到版本不匹配,通常返回 409 Conflict
  5. 客户端 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

于是可以形成判断:

observedGeneration=generationobservedGeneration = generation

表示 Controller 至少已经观察到当前这代配置;如果:

observedGeneration<generationobservedGeneration < generation

通常表示用户刚修改了 spec,Controller 还没有完成处理。

需要注意:

  • generation 不是所有资源都以完全相同方式使用;
  • 具体递增条件取决于资源实现;
  • generationresourceVersion 不等价;
  • 更新 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 的语义是:

  1. 客户端请求删除对象;
  2. API Server 设置 deletionTimestamp
  3. Controller 观察到删除请求;
  4. Controller 完成外部清理;
  5. Controller 移除自己负责的 Finalizer;
  6. 当 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 个,因此系统尚未完全达到期望状态。

声明式控制的基本闭环可以表示为:

error=desired(spec)observed(status)error = desired(spec) - observed(status)

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:条件类型;
  • statusTrueFalseUnknown
  • 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 的主要内容在 databinaryData

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  LOG_LEVEL: info

它没有典型的 spec。Secret 也主要使用 datastringData。因此,“所有 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 处理创建请求时,通常会经过以下阶段:

  1. 解析 apiVersionkind
  2. 确定 REST 资源和命名空间;
  3. 认证请求;
  4. 鉴权;
  5. 执行 Mutating Admission;
  6. 设置默认值;
  7. 执行 schema 和字段验证;
  8. 执行 Validating Admission;
  9. 写入存储;
  10. 返回对象或异步触发 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 返回的是对象变化事件,例如 ADDEDMODIFIEDDELETED。实际客户端还必须处理:

  • Watch 连接断开;
  • 资源版本过旧;
  • API Server 返回 410 Gone
  • 需要重新 List 后再 Watch;
  • 事件流中对象可能因并发更新快速变化。

因此,可靠 Controller 一般采用“List-then-Watch”模式:

  1. List 当前对象集合;
  2. 记录返回的 resourceVersion;
  3. 从该版本开始 Watch;
  4. 收到事件后更新本地缓存;
  5. 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 通常是:

  1. GET 当前对象;
  2. 修改本地对象;
  3. 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 至少涉及三个层面:

  1. 客户端对外请求的版本;
  2. API Server 对外提供的版本;
  3. 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

理想情况下,往返转换不会丢失用户可观察的重要信息:

decodev1(encodeinternal(object))objectdecode_{v1}(encode_{internal}(object)) \approx 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 自己的版本改变了状态机。

所以兼容性验证至少要覆盖:

  1. API Server 是否接受对象;
  2. 对象是否以预期字段保存;
  3. Controller 是否创建预期的子资源;
  4. Status 是否进入预期状态;
  5. 实际运行行为是否符合预期。

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 通常不是不断执行全量扫描,而是:

  1. 从 API Server List 初始对象;
  2. 建立 Watch;
  3. 收到对象变化;
  4. 将对象键放入工作队列;
  5. Reconcile 读取最新对象;
  6. 计算差异并写回资源或子资源。

由于事件可能重复、乱序地到达或被重新同步,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。对旧版本对象,应建立迁移计划:

  1. 确认目标集群支持的 API;
  2. 阅读弃用和迁移说明;
  3. 转换字段结构;
  4. 使用服务器端 dry-run;
  5. 在测试集群实际运行;
  6. 检查 Status、事件和子资源;
  7. 再发布到生产。

不要仅因为对象“还能创建”就认为旧 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:对象是否写入;
  • generationobservedGeneration:Controller 是否追上最新期望;
  • status.readyReplicas:实际可用副本;
  • describe 和 Events:失败原因和控制器动作。

十四、最终建立一套对象阅读方法

阅读一个 Kubernetes API 对象时,可以按以下顺序推导:

  1. 先看 GVK
    通过 apiVersionkind 确定类型、API 组和版本。

  2. 再看作用域和身份
    检查资源是否 namespaced,再结合 namespacenameuid 确定对象身份。

  3. 看 Spec 的期望状态
    找出用户或上层系统要求 Kubernetes 达到什么状态。

  4. 看 Status 的观察状态
    检查实际副本、条件、错误原因和 observedGeneration

  5. 看 Metadata 的控制信息
    关注 Label、Annotation、OwnerReference、Finalizer、generation、resourceVersion 和 managedFields。

  6. 看版本与来源
    确认 API 是否仍被 served,是否存在弃用,字段由哪个管理器维护,是否发生过版本转换。

  7. 沿控制链验证结果
    从 API Server 到 Controller、子资源、Scheduler、Node 和容器逐层检查,而不是只看 kubectl apply 的返回值。

Kubernetes API 对象的核心不是一份静态 YAML,而是一种由类型、期望状态、观察状态、元数据和控制循环共同构成的协议。GVK 解决“这是什么对象”,Metadata 解决“它是谁、由谁管理、处于哪个生命周期”,Spec 解决“希望系统变成什么样”,Status 解决“系统目前实际是什么样”,版本机制解决“不同客户端和不同 API 表示如何沟通”,而 resourceVersion、generation、Watch 和字段所有权则保证多个组件能够在并发变化中持续收敛。


系列导航与关联阅读

官方资料

本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。