3. 自定义资源定义:CRD 设计原则、CRD YAML 编写、版本管理、字段校验

自定义资源定义(Custom Resource Definition, CRD)是 Operator 开发的核心基石。它决定了你的自定义资源(CR)在 Kubernetes 集群中的“长相”和“行为”。一个设计糟糕的 CRD 会导致 Operator 难以维护、版本升级困难、用户使用体验差。本章将深入 CRD 的设计原则、YAML 编写规范、版本管理策略以及字段校验机制。

3.1 CRD 设计原则

在设计 CRD 时,应遵循以下核心原则,以确保资源的可扩展性、可维护性和用户友好性。

原则 说明 反例 / 注意事项
扁平化 vs. 嵌套 优先使用扁平结构,避免过深(超过 3 层)的嵌套。扁平结构便于字段校验和用户理解。 将 spec.config.database.connection.host 设计为 4 层嵌套,增加复杂度。
Spec 与 Status 分离 spec 定义期望状态,status 定义实际状态。Operator 只应修改 status,用户只应修改 spec。 在 spec 中放置只读状态字段,或在 status 中放置用户可配置字段。
最小化 Spec 只暴露用户必须配置的字段。能自动推导的字段(如镜像版本、副本数默认值)应通过 Operator 逻辑填充。 要求用户填写 imagePullPolicy,而不是默认使用 IfNotPresent。
向前兼容 新增字段时,必须提供默认值或标记为可选。永远不要删除或重命名已有字段。 将 replicas 字段重命名为 count,导致旧 CR 无法解析。
命名规范 使用驼峰式(camelCase)命名。字段名应清晰表达其用途,如 storageClassName 而非 scn。 使用缩写 pvcSize 而非 persistentVolumeClaimSize。
设计提示: 在编写 CRD 之前,先画出你的资源模型图。明确哪些是用户关心的配置(放入 spec),哪些是 Operator 运行后的结果(放入 status)。一个好的模型图能节省大量调试时间。

3.2 CRD YAML 编写实战

下面是一个典型的 CRD YAML 示例,定义了一个名为 MyApp 的自定义资源。注意 openAPIV3Schema 部分定义了字段的结构和校验规则。

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: myapps.example.com
spec:
  group: example.com
  names:
    kind: MyApp
    listKind: MyAppList
    plural: myapps
    singular: myapp
    shortNames:
      - ma
  scope: Namespaced
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                image:
                  type: string
                  pattern: '^[a-zA-Z0-9._/-]+:[a-zA-Z0-9._-]+$'
                  description: "容器镜像地址,例如 nginx:1.25"
                replicas:
                  type: integer
                  minimum: 1
                  maximum: 100
                  default: 3
                  description: "Pod 副本数"
                configMapRef:
                  type: string
                  description: "引用的 ConfigMap 名称"
              required:
                - image
            status:
              type: object
              properties:
                availableReplicas:
                  type: integer
                  description: "当前可用副本数"
                conditions:
                  type: array
                  items:
                    type: object
                    properties:
                      type:
                        type: string
                      status:
                        type: string
                        enum: ["True", "False", "Unknown"]
                      lastTransitionTime:
                        type: string
                        format: date-time
      additionalPrinterColumns:
        - name: Replicas
          type: integer
          jsonPath: .spec.replicas
        - name: Image
          type: string
          jsonPath: .spec.image
      subresources:
        status: {}
关键点解析:
  • group/names: 定义了 API 组和资源名称。短名称(shortNames)能提升 kubectl 使用体验。
  • scope: 选择 Namespaced 或 Cluster。大多数 Operator 资源是命名空间级别的。
  • versions: 可以定义多个版本,但必须指定一个 storage: true 的版本用于持久化存储。
  • additionalPrinterColumns: 让 kubectl get myapp 输出更直观的信息。
  • subresources: 启用 /status 子资源,确保 spec 和 status 的更新路径分离。

3.3 版本管理策略

CRD 的版本管理是 Operator 长期维护的关键。Kubernetes 支持通过 Conversion Webhook 实现不同版本之间的数据转换。

版本管理警告: 不要轻易将 storage 版本从 v1 切换到 v2。一旦切换,所有已存储的 v1 资源都需要通过 Webhook 转换为 v2 格式,这会带来巨大的迁移成本和风险。推荐的做法是:始终将最初的版本作为 storage 版本,后续版本只作为 served 版本。

以下是版本管理的推荐策略对比:

策略 适用场景 优点 缺点
单存储版本 Operator 早期阶段,API 变化不频繁 实现简单,无需 Webhook 版本演进困难,旧版本字段难以废弃
多版本 + Conversion Webhook Operator 成熟期,需要支持多个 API 版本 平滑升级,用户无感 需要开发和维护 Webhook,复杂度高
版本别名(不推荐) 临时过渡 快速实现 容易造成数据不一致,难以调试
CRD 版本管理流程(Conversion Webhook) 用户提交 v2 CR API Server etcd (存储 v1) Conversion Webhook 流程说明: 1. 用户提交 v2 版本的 CR → 2. API Server 发现存储版本为 v1 → 3. 调用 Conversion Webhook 将 v2 转为 v1 → 4. 将 v1 数据存入 etcd

3.4 字段校验机制

Kubernetes 1.25+ 支持在 CRD 的 openAPIV3Schema 中声明丰富的校验规则,无需编写 Admission Webhook 即可实现大部分常见校验。

3.4.1 内置校验规则

校验类型 关键字 示例 说明
字符串模式 pattern pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$' 使用正则表达式校验字符串格式
数值范围 minimum / maximum minimum: 1; maximum: 100 限制整数或浮点数的取值范围
枚举值 enum enum: ["Small", "Medium", "Large"] 字段只能取列表中的值
必填字段 required required: ["image", "replicas"] 在对象级别声明哪些字段必须存在
默认值 default default: 3 用户未提供时自动填充
数组长度 minItems / maxItems minItems: 1; maxItems: 10 限制数组元素个数

3.4.2 高级校验:x-kubernetes-validations

从 Kubernetes 1.25 开始,CRD 支持使用 Common Expression Language (CEL) 编写复杂的跨字段校验逻辑。这大大减少了编写 Admission Webhook 的需求。

schema:
  openAPIV3Schema:
    type: object
    properties:
      spec:
        type: object
        properties:
          replicas:
            type: integer
          maxReplicas:
            type: integer
        # CEL 校验:replicas 必须小于等于 maxReplicas
        x-kubernetes-validations:
          - rule: "self.replicas <= self.maxReplicas"
            message: "replicas 不能大于 maxReplicas"
          - rule: "self.replicas >= 1"
            message: "replicas 必须大于等于 1"
调试技巧: 当 CRD 校验失败时,kubectl 会返回详细的错误信息。使用 kubectl apply -f cr.yaml --validate=true 可以强制进行校验。如果遇到 unknown field 错误,请检查 CRD 中是否遗漏了该字段的定义,或者字段名拼写错误。

3.5 CRD 调试常见问题

在调试 CRD 时,以下问题最为常见:

  • CRD 未注册: 使用 kubectl get crd 确认 CRD 是否已成功创建。如果 CRD 处于 Established 状态,说明已就绪。
  • 字段校验失败: 检查 openAPIV3Schema 中的类型定义是否与 CR 中的字段类型一致。例如,replicas 定义为 integer,但 CR 中写成了 "3"(字符串)。
  • 版本不匹配: 确保 CR 的 apiVersion 与 CRD 中定义的 versions[].name 完全匹配。
  • Status 更新失败: 如果 Operator 无法更新 status,检查 CRD 是否启用了 subresources.status。同时,确保 Operator 使用的 ServiceAccount 有 update 该 CR 的 /status 子资源的权限。
CRD 调试流程:从定义到使用 1. 编写 CRD YAML 2. kubectl apply 3. 检查 Established 4. 编写 CR YAML 失败:检查 CRD 日志 关键命令:kubectl get crd myapps.example.com -o yaml | grep -A 10 "conditions" # 查看 CRD 注册状态