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 所描述的状态。

核心原则: Spec 是用户声明的“目标”,Status 是系统观测到的“现实”。Operator 通过协调循环(Reconcile Loop)弥合两者之间的差距。

以下是一个典型的 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 字段控制版本的服务与存储行为。

版本演进铁律:一旦 CRD 被创建并存储了数据,storage: true 的版本就不能被删除或修改其 Schema 中的已有字段。只能通过新增版本来演进。

版本命名约定:

  • v1alpha1:内部测试版本,Schema 可能频繁变更,不保证向后兼容。
  • v1beta1:公开测试版本,Schema 趋于稳定,但可能仍有少量非破坏性变更。
  • v1:正式发布版本,Schema 稳定,保证向后兼容。

多版本共存时,Kubernetes 通过内部版本(Internal Version)和外部版本(External Version)实现转换。Operator SDK 自动生成版本转换函数(Hub & Spoke 模式)。

CRD 版本演进与转换架构 v1alpha1 内部测试版本 v1beta1 公开测试版本 v1 正式发布版本 内部版本(Hub) 所有外部版本转换的目标版本 版本转换规则: • 每个外部版本(v1alpha1/v1beta1/v1)都实现 ConvertTo/ConvertFrom 方法 • 所有外部版本统一转换为内部版本(Hub)进行协调,再转回各自外部版本

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 版本管理最佳实践清单

  1. 尽早定义 Hub 版本:即使当前只有 v1alpha1,也建议在代码中预留 Hub 标记,方便后续扩展。
  2. 使用 +kubebuilder:storageversion 注解:明确标记存储版本,避免多个版本同时存储导致数据混乱。
  3. 编写版本转换测试:确保 v1alpha1 → v1 → v1alpha1 的往返转换不丢失数据。
  4. 避免破坏性变更:在 v1 版本中,不要删除已有字段或改变字段类型。如需移除,先标记为 deprecated。
  5. 利用 preserveUnknownFields: false:在 v1 CRD 中关闭未知字段保留,强制 Schema 验证。
工程建议:在 Operator SDK 项目中,使用 make generate 和 make manifests 自动生成 CRD YAML 和转换代码。手动修改 CRD YAML 后务必运行 make generate 保持代码与 YAML 同步。
CRD 版本生命周期管理流程 v1alpha1 v1beta1 v1 v1(废弃) 版本生命周期关键动作: 1. v1alpha1 → v1beta1:Schema 趋于稳定,添加验证规则和默认值 2. v1beta1 → v1:Schema 冻结,保证向后兼容,添加存储版本标记 3. v1 → v1(废弃):设置 deprecated: true 和 deprecationWarning 字段 4. 废弃版本至少保留一个次要版本周期,然后设置 served: false

4.8 常见陷阱与避坑指南

  • 陷阱:修改已有字段的 type 或 format,导致已存储数据无法反序列化。
    对策:新增字段替代,旧字段标记为 deprecated。
  • 陷阱:多个版本同时设置 storage: true,导致数据存储混乱。
    对策:始终只将一个版本设为 storage: true。
  • 陷阱:版本转换函数中丢失字段值。
    对策:编写单元测试验证往返转换(Round-Trip)的数据完整性。
  • 陷阱:忘记在 CRD 中启用 subresources.status,导致无法更新 Status。
    对策:在 CRD YAML 的 versions[*].subresources 中显式声明 status: {}。
核心总结:CRD 的 Spec 定义“用户想要什么”,Status 报告“系统实际是什么”,版本管理确保“演进不破坏兼容”。这三者共同构成了 Operator 与用户之间的契约基础。