2. CRD 基础:CustomResourceDefinition 定义、版本管理与结构验证

CustomResourceDefinition(CRD)是 Kubernetes 扩展机制的核心,它允许用户在不修改核心代码的前提下,向集群注册自定义资源类型。本章将系统讲解 CRD 的声明式定义、多版本管理策略以及结构验证规则,为后续使用 Operator SDK 开发控制器奠定基础。

2.1 CRD 声明式定义

CRD 本身是一个标准的 Kubernetes API 资源,通过 YAML 清单向集群注册。一个完整的 CRD 定义包含以下关键字段:

字段 说明 是否必填
metadata.name CRD 名称,必须为 <plural>.<group> 格式 是
spec.group API 组,如 example.com 是
spec.names 资源名称配置(plural、singular、shortNames、kind) 是
spec.scope 作用域:Namespaced 或 Cluster 是
spec.versions 版本列表,每个版本包含 schema、subresources 等 是

以下是一个典型的 CRD 定义示例,注册名为 MyApp 的自定义资源:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: myapps.example.com
spec:
  group: example.com
  names:
    kind: MyApp
    plural: myapps
    singular: myapp
    shortNames:
    - ma
  scope: Namespaced
  versions:
  - name: v1
    served: true
    storage: true
    schema:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            properties:
              replicas:
                type: integer
                minimum: 1
                maximum: 10
              image:
                type: string
    subresources:
      status: {}
关键提示:CRD 名称中的 plural 必须与 spec.names.plural 一致,且 group 不能与核心 API 组(如 core、apps)冲突。建议使用公司域名作为 group 前缀,避免命名空间污染。

2.2 CRD 版本管理

Kubernetes 1.16+ 支持 CRD 的多版本管理,允许同一资源在不同 API 版本间共存和转换。版本管理涉及三个核心概念:

  • served:该版本是否在 API Server 上提供服务,客户端可通过该版本读写资源。
  • storage:集群持久化存储使用哪个版本,只能有一个版本标记为 storage: true。
  • conversion:不同版本间的转换策略,支持 None(无转换)和 Webhook(自定义转换)。

版本管理的最佳实践遵循“先增后弃”原则:

  1. 新增版本时,将新版本标记为 served: true,旧版本保持 storage: true。
  2. 通过 Webhook 实现版本间字段映射,确保数据不丢失。
  3. 确认所有客户端迁移到新版本后,将旧版本 served 设为 false。
  4. 最终移除旧版本定义,并将 storage 切换到新版本。

下图展示了 CRD 版本管理的完整生命周期:

CRD 版本管理生命周期 v1alpha1 served: true, storage: true v1 served: true, storage: false v1 served: true, storage: true v1alpha1 served: false, storage: false 版本转换 Webhook 要求 • 必须实现 v1alpha1 ↔ v1 的双向字段映射 • 转换 Webhook 需部署为 Service,配置 CA 证书 • 使用 conversionReview 请求/响应模型
版本转换注意事项:当 CRD 包含多个 served 版本时,API Server 会自动将存储版本转换为请求版本返回给客户端。如果字段在不同版本间有差异(如字段重命名或类型变更),必须通过 Webhook 实现自定义转换逻辑,否则会导致数据丢失或解析错误。

2.3 CRD 结构验证

CRD 通过 OpenAPI v3 Schema 定义资源的结构约束,API Server 在写入 etcd 前会自动校验。结构验证支持以下规则:

验证类型 关键字 示例
类型约束 type type: integer
必填字段 required required: ["name", "version"]
数值范围 minimum / maximum minimum: 1, maximum: 100
字符串模式 pattern pattern: "^[a-z0-9-]+$"
枚举值 enum enum: ["dev", "staging", "prod"]
数组长度 minItems / maxItems minItems: 1, maxItems: 10
嵌套对象 properties 支持多层嵌套和 additionalProperties: false

以下是一个包含完整验证规则的 CRD Schema 示例:

schema:
  openAPIV3Schema:
    type: object
    required:
    - spec
    properties:
      spec:
        type: object
        required:
        - image
        - replicas
        properties:
          image:
            type: string
            pattern: "^[a-z0-9]+(?:[._-][a-z0-9]+)*/[a-z0-9]+(?:[._-][a-z0-9]+)*:[a-zA-Z0-9._-]+$"
          replicas:
            type: integer
            minimum: 1
            maximum: 20
          env:
            type: array
            items:
              type: object
              required:
              - name
              properties:
                name:
                  type: string
                  pattern: "^[A-Z_]+$"
                value:
                  type: string
            maxItems: 50
          tier:
            type: string
            enum:
            - dev
            - staging
            - production
常见验证陷阱:
  • additionalProperties 默认 true:如果不显式设置为 false,用户可以在对象中写入未定义的字段,导致验证形同虚设。
  • 整数与浮点数混淆:Kubernetes 1.25 之前 type: integer 会接受浮点数(如 3.0),建议配合 format: int32 或 format: int64 使用。
  • 嵌套 required 遗漏:required 只在当前对象层级生效,嵌套对象需要单独声明 required 字段。

2.4 CRD 结构验证与 PreserveUnknownFields

当 CRD 定义中未显式声明某些字段时,API Server 的行为由 spec.preserveUnknownFields 控制:

  • false(默认):拒绝包含未定义字段的请求,严格遵循 Schema。
  • true:允许未定义字段通过,但会绕过结构验证,适用于需要灵活扩展的场景。

从 Kubernetes 1.21 开始,推荐使用 spec.versions[*].schema.openAPIV3Schema.x-kubernetes-preserve-unknown-fields: true 替代顶层字段,实现更细粒度的控制。

下图对比了严格验证与宽松模式的处理流程:

结构验证模式对比 严格模式(preserveUnknownFields: false) 1. 客户端提交 YAML 请求 2. API Server 校验 Schema 3. 未定义字段 → 拒绝请求(400) 4. 通过校验 → 写入 etcd 宽松模式(preserveUnknownFields: true) 1. 客户端提交 YAML 请求 2. API Server 校验 Schema 3. 未定义字段 → 保留并写入 4. 控制器需自行处理未知字段 对比
生产建议:在 Operator 开发初期可使用 preserveUnknownFields: true 快速迭代,但进入生产环境前必须切换到严格模式,并补全所有字段的 Schema 定义。Operator SDK 默认生成的 CRD 模板采用严格模式,建议保持此配置。

2.5 CRD 状态子资源与结构验证联动

CRD 支持 subresources.status 子资源,允许控制器独立更新资源状态而不触发完整的校验逻辑。状态子资源的结构验证规则如下:

  • 状态字段在 spec.versions[*].schema.openAPIV3Schema 中定义,但更新状态时只校验状态部分。
  • 状态子资源默认只接受 status 字段的变更,spec 和 metadata 的修改会被忽略。
  • 控制器通过 UpdateStatus 方法更新状态,避免与用户对 spec 的修改产生冲突。

以下是一个包含状态子资源的 CRD 定义片段:

versions:
- name: v1
  served: true
  storage: true
  schema:
    openAPIV3Schema:
      type: object
      properties:
        spec:
          type: object
          properties:
            replicas:
              type: integer
        status:
          type: object
          properties:
            availableReplicas:
              type: integer
            conditions:
              type: array
              items:
                type: object
                properties:
                  type:
                    type: string
                  status:
                    type: string
                    enum: ["True", "False", "Unknown"]
  subresources:
    status: {}
结构验证与控制器协作:CRD 的结构验证是 API 层的“第一道防线”,控制器应在业务逻辑层进行二次校验(如依赖关系检查、集群资源配额验证)。Operator SDK 的 ValidateCreate 和 ValidateUpdate 接口提供了自定义校验入口,建议在控制器中实现更严格的业务规则。