3. CRD 版本控制原理
CRD(Custom Resource Definition)的版本控制是 Operator 开发中最容易被忽视但影响深远的环节。当自定义资源从 v1alpha1 演进到 v1beta1 再到 v1,或者需要同时对外暴露多个版本时,Kubernetes 的版本管理机制决定了资源数据的存储形态、对外接口以及版本间的数据迁移方式。理解 CRD 版本控制的核心三要素——版本定义、存储版本与服务版本、版本转换——是编写生产级 Operator 的必备能力。
3.1 CRD 版本定义
每个 CRD 可以声明一个或多个版本(versions),每个版本包含独立的 Schema(OpenAPI v3 结构)、子资源(subresources)以及附加的校验规则。Kubernetes 要求 CRD 必须至少声明一个版本,且所有版本必须共享同一个 group 和 kind。
| 字段 | 说明 | 是否必填 |
|---|---|---|
name |
版本名称,如 v1alpha1、v1beta1、v1 | 是 |
served |
是否作为服务版本对外暴露 | 是 |
storage |
是否作为存储版本,整个 CRD 只能有一个版本标记为 storage: true |
是 |
schema |
该版本的 OpenAPI 结构定义 | 是 |
subresources |
是否启用 /status 和 /scale 子资源 | 否 |
additionalPrinterColumns |
自定义 kubectl 输出列 | 否 |
storage: true。如果未显式指定,默认第一个声明的版本即为存储版本。一旦 CRD 创建后,存储版本不可更改(除非通过 CRD 更新流程并配合转换 Webhook)。
# 一个声明了三个版本的 CRD 示例(简化)
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: applications.example.com
spec:
group: example.com
names:
kind: Application
plural: applications
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
env:
type: string
3.2 存储版本与服务版本
这是 CRD 版本控制中最核心的概念区分。存储版本决定了资源数据在 etcd 中以何种结构持久化;服务版本决定了用户通过 API 读写资源时使用哪个版本的 Schema。两者可以相同,也可以不同。
served: true 的版本对外暴露 API 端点。用户可以通过 /apis/example.com/v1alpha1/ 或 /apis/example.com/v1/ 等路径访问资源。服务版本可以同时存在多个,但每个版本必须提供完整的 Schema 定义。
3.3 版本转换(Conversion)机制
当存储版本与服务版本不同时,Kubernetes 需要一种机制在两者之间进行数据转换。CRD 支持两种转换方式:None(无转换) 和 Webhook(转换 Webhook)。
| 转换方式 | 适用场景 | 实现复杂度 | 性能影响 |
|---|---|---|---|
| None | 所有服务版本的 Schema 与存储版本完全一致(字段名、类型、结构相同) | 无 | 无 |
| Webhook | 服务版本与存储版本存在字段差异、重命名、结构调整 | 高(需编写 HTTPS 服务) | 每次读写触发 Webhook 调用 |
None 策略下且所有版本 Schema 完全一致时才能正常工作。
3.3.1 转换 Webhook 的工作原理
转换 Webhook 是一个 HTTPS 端点,由 Operator 开发者自行实现并部署。当 API Server 需要将资源对象从一个版本转换为另一个版本时,会向 Webhook 发送 ConversionReview 请求,Webhook 返回转换后的对象。
# CRD 中声明转换 Webhook
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: applications.example.com
spec:
conversion:
strategy: Webhook
webhook:
conversionReviewVersions: ["v1", "v1beta1"]
clientConfig:
service:
namespace: operator-system
name: conversion-webhook
path: /convert
caBundle: <base64-encoded-ca-cert>
versions:
- name: v1alpha1
served: true
storage: false
# ...
- name: v1
served: true
storage: true
# ...
request.objects(需要转换的对象列表),响应中必须包含 response.convertedObjects(转换后的对象列表)。如果转换失败,返回 response.result.status: Failure 并附带错误信息。
// ConversionReview 请求示例(Go 结构)
type ConversionReview struct {
APIVersion string `json:"apiVersion"`
Kind string `json:"kind"`
Request *ConversionRequest `json:"request,omitempty"`
Response *ConversionResponse `json:"response,omitempty"`
}
type ConversionRequest struct {
UID string `json:"uid"`
DesiredAPIVersion string `json:"desiredAPIVersion"`
Objects []runtime.RawExtension `json:"objects"`
}
type ConversionResponse struct {
UID string `json:"uid"`
ConvertedObjects []runtime.RawExtension `json:"convertedObjects"`
Result StatusResult `json:"result"`
}
3.3.2 版本转换的常见模式
- 字段重命名: v1alpha1 中的
replicaCount在 v1 中改为replicas,Webhook 负责字段映射。 - 字段类型变更: v1alpha1 中
version为字符串,v1 中改为枚举结构体,Webhook 需要做类型转换。 - 字段废弃与移除: v1alpha1 中的废弃字段在 v1 中删除,Webhook 在转换时丢弃该字段。
- 默认值注入: v1 新增字段在 v1alpha1 中不存在,Webhook 在反向转换(v1 → v1alpha1)时移除该字段;正向转换时注入默认值。
3.4 版本演进策略与存储版本迁移
当 CRD 需要变更存储版本时(例如从 v1beta1 升级到 v1),必须执行存储版本迁移(Storage Version Migration)。Kubernetes 1.29+ 提供了内置的 StorageVersionMigration API,但更通用的做法是使用 Operator 自身的迁移逻辑或社区工具。
| 迁移方式 | 适用版本 | 操作复杂度 | 数据一致性 |
|---|---|---|---|
| 手动 re-create 所有资源 | 所有版本 | 低(但风险高) | 需停机 |
使用 kubectl replace 逐个重写 |
所有版本 | 中 | 逐对象一致 |
| Operator 内置迁移 Controller | 推荐生产环境 | 高(需开发) | 在线迁移 |
| Kubernetes StorageVersionMigration API | ≥1.29 | 中 | 自动处理 |
3.5 版本控制实战要点
- 版本命名规范: 遵循
v[主版本][alpha|beta][序号]模式,如 v1alpha1、v1beta2、v1。alpha 表示不稳定,beta 表示基本稳定,无后缀表示 GA。 - 存储版本选择: 始终选择最成熟的版本作为存储版本。如果 CRD 只有一个版本,该版本自动成为存储版本。
- Webhook 高可用: 转换 Webhook 是读写路径上的关键组件,必须部署多副本并配置健康检查。Webhook 故障会导致所有跨版本读写操作失败。
- 转换幂等性: Webhook 的转换逻辑必须是幂等的——多次转换同一对象应得到相同结果。避免在转换过程中修改非版本相关的业务状态。
- 测试覆盖: 为每个版本组合编写转换测试,包括正向转换(旧→新)和反向转换(新→旧),确保数据不丢失、不损坏。