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