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,复杂度高 |
| 版本别名(不推荐) | 临时过渡 | 快速实现 | 容易造成数据不一致,难以调试 |
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子资源的权限。