4. 自定义资源定义(CRD):定义CRD的Spec和Status字段、版本管理(v1alpha1/v1beta1/v1)
自定义资源定义(Custom Resource Definition,CRD)是 Kubernetes 扩展能力的核心。它允许你定义新的资源类型,就像内置的 Pod、Service 一样,可以被 kubectl 管理、被 API Server 验证、被 Controller 协调。本章将深入 CRD 的 Spec 与 Status 字段设计、版本管理策略,以及从 v1alpha1 到 v1 的演进路径。
4.1 CRD 的结构与核心字段
一个 CRD 本质上是一个 OpenAPI v3 Schema 的封装。Kubernetes 通过 CRD 的 spec 字段定义资源的期望状态(Spec),通过 status 字段定义资源的实际状态(Status)。Operator 的核心工作就是不断将 Status 调整为 Spec 所描述的状态。
以下是一个典型的 CRD YAML 结构,展示了 Spec 和 Status 的顶层定义:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: memcacheds.cache.example.com
spec:
group: cache.example.com
names:
kind: Memcached
listKind: MemcachedList
plural: memcacheds
singular: memcached
shortNames:
- mc
scope: Namespaced
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
size:
type: integer
minimum: 1
maximum: 100
image:
type: string
required:
- size
status:
type: object
properties:
readyReplicas:
type: integer
conditions:
type: array
items:
type: object
properties:
type:
type: string
status:
type: string
lastTransitionTime:
type: string
format: date-time
subresources:
status: {}
scale:
specReplicasPath: .spec.size
statusReplicasPath: .status.readyReplicas
4.2 Spec 字段设计:声明式期望
Spec 字段是用户与 Operator 交互的主要接口。设计良好的 Spec 应该遵循以下原则:
- 最小化原则:只暴露用户需要关心的配置项,隐藏实现细节。
- 不可变字段:使用
x-kubernetes-immutable: true标记创建后不可修改的字段(如集群名称)。 - 验证规则:利用 OpenAPI v3 的
minimum、maximum、pattern、enum等约束,在 API 层拦截非法输入。 - 默认值:通过 Webhook 或 Operator 内部逻辑为未设置的字段提供默认值。
| 字段类型 | 示例 | 说明 |
|---|---|---|
| 整数 | size: integer |
副本数、端口号等数值配置 |
| 字符串 | image: string |
镜像名称、域名、标签等 |
| 数组 | nodes: array |
节点列表、环境变量列表 |
| 对象 | tls: object |
复杂配置,如 TLS 证书引用 |
| 引用 | configMapRef: object |
引用集群内已有资源 |
4.3 Status 字段设计:观测状态与条件
Status 字段是 Operator 向用户报告资源当前状态的通道。一个健壮的 Status 设计包含两部分:
- 可观测指标:如
readyReplicas、currentVersion、observedGeneration等具体数值。 - 条件(Conditions):一组标准化的状态描述,遵循
type、status、reason、message、lastTransitionTime结构。
observedGeneration 字段,记录 Status 最后一次同步时对应的 Spec 版本号。这能帮助用户判断 Status 是否滞后于 Spec 变更。
常见的 Condition 类型包括:
| Condition 类型 | Status 值 | 含义 |
|---|---|---|
Available |
True / False / Unknown | 资源是否对外提供服务 |
Progressing |
True / False / Unknown | 是否正在执行变更操作 |
Degraded |
True / False / Unknown | 是否处于降级状态(部分功能不可用) |
Stopped |
True / False / Unknown | 是否被用户主动暂停 |
4.4 版本管理:从 v1alpha1 到 v1 的演进
CRD 版本管理是 Operator 生命周期管理的核心。Kubernetes 支持在同一 CRD 中定义多个版本,并通过 served 和 storage 字段控制版本的服务与存储行为。
storage: true 的版本就不能被删除或修改其 Schema 中的已有字段。只能通过新增版本来演进。
版本命名约定:
- v1alpha1:内部测试版本,Schema 可能频繁变更,不保证向后兼容。
- v1beta1:公开测试版本,Schema 趋于稳定,但可能仍有少量非破坏性变更。
- v1:正式发布版本,Schema 稳定,保证向后兼容。
多版本共存时,Kubernetes 通过内部版本(Internal Version)和外部版本(External Version)实现转换。Operator SDK 自动生成版本转换函数(Hub & Spoke 模式)。
4.5 多版本 CRD 的转换实现
Operator SDK 使用 Hub & Spoke 模式处理版本转换。你需要选择一个版本作为 Hub(通常是 v1),其他版本作为 Spoke。每个 Spoke 版本需要实现两个转换方法:
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:storageversion
// Memcached is the Schema for the memcacheds API
type Memcached struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec MemcachedSpec `json:"spec,omitempty"`
Status MemcachedStatus `json:"status,omitempty"`
}
// Hub 标记:v1 版本作为 Hub
func (m *Memcached) Hub() {}
// Spoke 版本(v1alpha1)的转换方法
func (src *MemcachedV1Alpha1) ConvertTo(dst Hub) error {
// 将 v1alpha1 转换为 v1(Hub)
memcachedV1 := dst.(*Memcached)
memcachedV1.Spec.Size = src.Spec.Size
memcachedV1.Spec.Image = src.Spec.Image
// 处理默认值
if memcachedV1.Spec.Image == "" {
memcachedV1.Spec.Image = "memcached:1.6"
}
return nil
}
func (dst *MemcachedV1Alpha1) ConvertFrom(src Hub) error {
// 将 v1(Hub)转换为 v1alpha1
memcachedV1 := src.(*Memcached)
dst.Spec.Size = memcachedV1.Spec.Size
dst.Spec.Image = memcachedV1.Spec.Image
return nil
}
4.6 版本管理策略对比
| 策略 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 单版本演进 | 早期快速迭代 | 实现简单,无需转换逻辑 | 无法同时服务多个 API 版本 |
| 多版本共存 | 生产环境,需要向后兼容 | 客户端可以逐步迁移 | 需要维护转换代码和测试 |
| 版本废弃 | 移除旧版本 | 减少维护负担 | 需要通知用户并给出迁移窗口 |
4.7 版本管理最佳实践清单
- 尽早定义 Hub 版本:即使当前只有 v1alpha1,也建议在代码中预留 Hub 标记,方便后续扩展。
- 使用
+kubebuilder:storageversion注解:明确标记存储版本,避免多个版本同时存储导致数据混乱。 - 编写版本转换测试:确保 v1alpha1 → v1 → v1alpha1 的往返转换不丢失数据。
- 避免破坏性变更:在 v1 版本中,不要删除已有字段或改变字段类型。如需移除,先标记为
deprecated。 - 利用
preserveUnknownFields: false:在 v1 CRD 中关闭未知字段保留,强制 Schema 验证。
make generate 和 make manifests 自动生成 CRD YAML 和转换代码。手动修改 CRD YAML 后务必运行 make generate 保持代码与 YAML 同步。
4.8 常见陷阱与避坑指南
- 陷阱:修改已有字段的
type或format,导致已存储数据无法反序列化。
对策:新增字段替代,旧字段标记为deprecated。 - 陷阱:多个版本同时设置
storage: true,导致数据存储混乱。
对策:始终只将一个版本设为storage: true。 - 陷阱:版本转换函数中丢失字段值。
对策:编写单元测试验证往返转换(Round-Trip)的数据完整性。 - 陷阱:忘记在 CRD 中启用
subresources.status,导致无法更新 Status。
对策:在 CRD YAML 的versions[*].subresources中显式声明status: {}。