CRD 版本化机制:versions 字段详解、served 与 storage 版本的区别

在 Kubernetes 自定义资源(CRD)的生命周期中,版本管理是最核心的治理手段之一。CRD 的 versions 字段定义了资源可以使用的所有 API 版本,而 served 与 storage 两个标志位则决定了每个版本在集群中的实际角色。理解这两个标志位的区别,是进行版本迁移和 Finalizer 清理的前提。

versions 字段结构

一个典型的 CRD 定义中,versions 是一个数组,每个元素包含版本名称、Schema、子资源开关以及 served 和 storage 两个布尔字段。以下是一个包含 v1alpha1、v1beta1 和 v1 三个版本的示例:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: applications.example.com
spec:
  group: example.com
  names:
    kind: Application
    plural: applications
  scope: Namespaced
  versions:
  - name: v1alpha1
    served: true
    storage: false
    schema:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            properties:
              replicas:
                type: integer
  - name: v1beta1
    served: true
    storage: false
    schema:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            properties:
              replicas:
                type: integer
              version:
                type: string
  - name: v1
    served: true
    storage: true
    schema:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            properties:
              replicas:
                type: integer
              version:
                type: string
              features:
                type: array
                items:
                  type: string

served 与 storage 的核心区别

这两个标志位虽然都是布尔值,但职责完全不同。下表从多个维度对比两者的差异:

对比维度 served storage
定义 该版本是否可通过 API Server 对外提供服务 该版本是否用于将对象持久化存储到 etcd
影响范围 REST API 端点是否可用 etcd 中存储的对象版本
读写行为 客户端可通过该版本 API 读写对象 所有写入操作最终都会转换为 storage 版本存储
集群中数量 可以同时有多个 served 版本 同一 CRD 只能有一个 storage 版本
转换要求 如果 served 版本与 storage 版本不同,需要配置 conversion 所有非 storage 版本在读写时都会自动转换为此版本
典型场景 允许旧版本客户端继续使用旧 API 指定 etcd 中存储的规范版本
关键原则: 一个 CRD 只能有一个 storage: true 的版本。如果尝试将多个版本设置为 storage 版本,API Server 会拒绝创建或更新 CRD。

served 版本:API 可见性控制

当 served: true 时,该版本的 REST 端点会在 API Server 中注册。客户端可以通过 /apis/example.com/v1alpha1/ 等路径访问资源。当 served: false 时,该版本的 API 端点会被移除,但 etcd 中已存储的对象不会自动删除。

设置 served: false 的典型场景:

  • 版本废弃: 某个版本不再推荐使用,但需要给客户端迁移时间
  • 版本移除前准备: 先停止服务,确认没有客户端使用后,再移除版本定义
  • 灰度迁移: 新版本稳定后,逐步关闭旧版本的 API 端点

storage 版本:etcd 持久化锚点

storage: true 的版本决定了对象在 etcd 中的实际存储格式。无论客户端使用哪个 served 版本写入数据,API Server 都会在写入 etcd 前将对象转换为 storage 版本。读取时,API Server 也会将 storage 版本的对象转换为客户端请求的 served 版本。

storage 版本的选择策略:

  • 选择最稳定的版本: 通常选择 GA(General Availability)版本作为 storage 版本
  • 避免频繁切换: 切换 storage 版本需要执行全量存储迁移,成本较高
  • 考虑 Schema 兼容性: storage 版本的 Schema 应能无损表示所有历史版本的数据
注意: 如果 CRD 只有一个版本,该版本必须同时是 served 和 storage 版本。删除或修改 storage 版本会导致 etcd 中已存储的对象无法正确读取。

版本转换流程

下图展示了客户端使用不同 served 版本时,API Server 内部的版本转换与存储流程:

CRD 版本转换与存储流程 客户端 v1alpha1 客户端 v1beta1 客户端 v1 API Server 版本转换层 v1alpha1 → v1 v1beta1 → v1 v1 → v1 (storage 版本) etcd v1 格式存储 (storage 版本) 说明: 所有客户端请求都会经过 API Server 的版本转换层,统一转换为 storage 版本(v1)后写入 etcd。 读取时,API Server 将 etcd 中的 v1 格式数据转换为客户端请求的 served 版本返回。

served 与 storage 的协同工作

在实际的 Operator 开发中,served 和 storage 的配置需要协同考虑。以下是一个版本演进的时间线示例:

CRD 版本演进时间线 v1alpha1 served:true storage:true v1beta1 served:true storage:false v1 served:true storage:true v1 (稳定) served:true storage:true T1: 初始版本 T2: 加入 beta T3: GA 版本 T4: 清理旧版 版本演进关键操作: 1. T1→T2: 添加 v1beta1,storage 仍为 v1alpha1,两个版本都 served 2. T2→T3: 添加 v1 并设为 storage,v1alpha1 设为 served:false 准备废弃 3. T3→T4: 确认无客户端使用后,从 CRD 定义中移除 v1alpha1

版本迁移中的 Finalizer 注意事项

当 CRD 版本发生迁移时,Finalizer 的处理需要特别关注:

  • Finalizer 与版本无关: Finalizer 是对象级别的元数据,不随 API 版本变化而改变
  • storage 版本切换影响: 切换 storage 版本时,所有对象的 Finalizer 列表会随对象一起转换到新版本格式
  • 版本废弃前清理: 在将某个版本设为 served: false 之前,应确保该版本下的所有 Finalizer 已被正确处理
  • 跨版本 Finalizer 兼容性: 如果不同版本的 Finalizer 名称或行为不同,需要在版本转换逻辑中处理兼容性
最佳实践: 在 Operator 中,建议将 Finalizer 的处理逻辑与 CRD 版本解耦。Finalizer 的名称和清理行为应保持跨版本稳定,避免在版本迁移时出现残留 Finalizer 导致资源无法删除。

常见错误与排查

错误场景 现象 解决方案
多个 storage 版本 CRD 创建/更新被拒绝 确保只有一个版本的 storage: true
storage 版本被设为 served:false API 端点不可用,但 etcd 数据仍在 不要将 storage 版本设为 served:false,除非已迁移
版本转换未配置 使用非 storage 版本写入时数据丢失 配置 conversion 策略或确保所有版本 Schema 兼容
移除 served 版本后 Finalizer 残留 资源无法删除,Finalizer 无法被处理 手动清理 Finalizer 或恢复版本定义