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 中存储的规范版本 |
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 应能无损表示所有历史版本的数据
版本转换流程
下图展示了客户端使用不同 served 版本时,API Server 内部的版本转换与存储流程:
served 与 storage 的协同工作
在实际的 Operator 开发中,served 和 storage 的配置需要协同考虑。以下是一个版本演进的时间线示例:
版本迁移中的 Finalizer 注意事项
当 CRD 版本发生迁移时,Finalizer 的处理需要特别关注:
- Finalizer 与版本无关: Finalizer 是对象级别的元数据,不随 API 版本变化而改变
- storage 版本切换影响: 切换 storage 版本时,所有对象的 Finalizer 列表会随对象一起转换到新版本格式
- 版本废弃前清理: 在将某个版本设为
served: false之前,应确保该版本下的所有 Finalizer 已被正确处理 - 跨版本 Finalizer 兼容性: 如果不同版本的 Finalizer 名称或行为不同,需要在版本转换逻辑中处理兼容性
常见错误与排查
| 错误场景 | 现象 | 解决方案 |
|---|---|---|
| 多个 storage 版本 | CRD 创建/更新被拒绝 | 确保只有一个版本的 storage: true |
| storage 版本被设为 served:false | API 端点不可用,但 etcd 数据仍在 | 不要将 storage 版本设为 served:false,除非已迁移 |
| 版本转换未配置 | 使用非 storage 版本写入时数据丢失 | 配置 conversion 策略或确保所有版本 Schema 兼容 |
| 移除 served 版本后 Finalizer 残留 | 资源无法删除,Finalizer 无法被处理 | 手动清理 Finalizer 或恢复版本定义 |