3. 自定义资源定义设计

CRD(CustomResourceDefinition)是 Operator 扩展 Kubernetes API 的入口。它决定了用户能写什么 YAML、apiserver 会做哪些校验、以及后续版本如何演进。设计 CRD 时,核心是三件事:用 OpenAPI v3 schema 把字段约束表达清楚,用版本演进策略保证兼容性,并遵循 OpenShift 的 API 约定让资源融入集群生态。

一、OpenAPI v3 schema:把校验前移到 apiserver

CRD 的 spec.versions[].schema.openAPIV3Schema 使用结构化 schema 描述资源结构。apiserver 会在写入时按 schema 校验,非法字段直接拒绝,Operator 无需在 Reconcile 中重复做基础校验。一个典型示例如下:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: webapps.example.com
spec:
  group: example.com
  scope: Namespaced
  names:
    kind: WebApp
    plural: webapps
    singular: webapp
    shortNames: [wa]
  versions:
  - name: v1
    served: true
    storage: true
    schema:
      openAPIV3Schema:
        type: object
        required: [spec]
        properties:
          spec:
            type: object
            required: [image, replicas]
            properties:
              image:
                type: string
                minLength: 1
              replicas:
                type: integer
                minimum: 1
                maximum: 20
                default: 2
              port:
                type: integer
                minimum: 1
                maximum: 65535
                default: 8080
              env:
                type: array
                items:
                  type: object
                  required: [name, value]
                  properties:
                    name: {type: string}
                    value: {type: string}
            x-kubernetes-preserve-unknown-fields: false
          status:
            type: object
            properties:
              readyReplicas: {type: integer}
              conditions:
                type: array
                items:
                  type: object
                  properties:
                    type: {type: string}
                    status: {type: string}
                    reason: {type: string}
                    lastTransitionTime: {type: string, format: date-time}

几个关键点:required 明确必填字段;minimum/maximum 约束取值范围;default 让 apiserver 在缺省时自动填充,避免 Operator 处理空值;enum 可限定取值集合。默认情况下 x-kubernetes-preserve-unknown-fields 为 false,未声明字段会被裁剪,这能防止用户写入拼写错误的字段却被静默接受。若确实需要透传任意结构(如透传给底层组件的配置),应显式开启并清楚其代价。

status 子资源建议通过 subresources.status 单独暴露,使 spec 与 status 分离:用户只改 spec,Operator 只写 status,避免互相覆盖。同时可启用 subresources.scale,让 HPA 或 kubectl scale 直接作用于 CR。

二、版本演进策略:served 与 storage 的分离

CRD 支持多版本共存,每个版本有 served 和 storage 两个开关。served=true 表示该版本可通过 API 读写;storage=true 表示该版本是 etcd 中的持久化格式,且每个 CRD 有且仅有一个 storage 版本。演进时通常遵循:新增版本先 served,待 Operator 支持后再切换 storage,最后下线旧版本。

版本间字段变化需要转换策略。若字段只是新增且可选,可依赖默认值,无需转换;若字段重命名或语义变化,则必须提供 conversion。Kubernetes 支持 None 和 Webhook 两种转换。None 仅适用于各版本 schema 完全兼容的情况;否则应配置 Conversion Webhook,由 Operator 实现版本间双向转换。转换必须满足往返一致性:v1→v2→v1 后对象语义不变。

一个稳妥的演进顺序是:v1 稳定运行 → 引入 v2 并 served → Operator 同时处理 v1/v2 → 将 storage 切到 v2 → 观察一段时间 → 将 v1 标记 served=false → 最终移除。切勿直接删除仍在 storage 中的版本,否则会导致数据无法读取。

三、OpenShift API 约定

在 OpenShift 上,CRD 还需遵循若干约定。首先是 group 命名,建议使用 <domain> 形式,避免与核心组冲突;OpenShift 自身资源多用 *.openshift.io,第三方 Operator 应使用自有域名。其次是 scope,命名空间级资源更利于多租户隔离,集群级资源需谨慎授予权限。

条件(Conditions)应遵循标准结构:type、status、reason、message、lastTransitionTime,并优先使用 Available、Progressing、Degraded 等通用类型,便于 oc 与 Console 统一展示。此外,CRD 应声明 categories(如 all)和 shortNames,提升 oc get 的可用性。若资源需要被 Console 识别,还应提供相应的 UI 描述或使用 OpenShift 的插件机制。

设计 CRD 时,先问三个问题:字段是否都能被 schema 表达?未来版本如何转换?资源在 OpenShift 中如何被用户发现和操作?回答清楚,CRD 就成功了一半。
CRD 版本演进与 schema 校验关系 用户 YAML spec.image spec.replicas spec.port apiserver 校验 required / type minimum / maximum default 填充 etcd 存储 storage 版本 v1 或 v2 版本演进时间线 v1 served storage v2 served storage 切换 v1 served=false 保留 storage v1 移除 Conversion Webhook v1 ⇄ v2 双向转换 保证往返一致性 OpenShift 约定 group 使用自有域名 Conditions 标准结构 shortNames / categories schema 要点 required 必填 default 自动填充 status 子资源分离

把 schema、版本和约定三者结合起来,CRD 才真正成为 Operator 的稳定契约。schema 负责“写进来的数据是对的”,版本策略负责“旧数据能平滑迁移”,OpenShift 约定负责“用户和工具能自然使用”。在实现 Operator 时,Reconcile 只需关注业务逻辑,基础校验和版本转换交给 apiserver 与 webhook,整体设计会更清晰、更易维护。