3. CRD 版本管理:多版本支持与版本转换
在 Kubernetes 生态中,自定义资源(CR)的 API 版本管理是 Operator 长期运维的核心能力。随着资源定义演进,你可能需要同时维护 v1alpha1(实验版)、v1beta1(测试版)和 v1(稳定版)等多个版本。本章深入剖析多版本支持机制、存储版本与服务版本的区别,以及 Conversion Webhook 的实现原理。
3.1 多版本支持的必要性
Kubernetes 要求 CRD 在升级时不能破坏现有客户端。多版本支持允许你:
- 渐进式演进:新版本发布后,旧版本客户端仍可正常工作。
- 平滑迁移:用户可以在不修改 YAML 文件的情况下逐步迁移到新 API。
- 兼容性保障:通过版本转换,确保存储数据与最新服务版本一致。
最佳实践:始终从
v1alpha1 开始开发,稳定后升级到 v1beta1,最终发布 v1。每个版本至少保留一个次要版本周期,给用户足够的迁移时间。
3.2 存储版本与服务版本的核心区别
这是 CRD 版本管理中最容易混淆的概念。理解两者的区别是正确配置多版本的前提。
| 维度 | 存储版本(Storage Version) | 服务版本(Served Version) |
|---|---|---|
| 定义 | etcd 中实际持久化存储的版本 | API Server 对外提供读写服务的版本 |
| 数量 | 每个 CRD 只能有 1 个存储版本 | 可以同时有 多个服务版本 |
| 作用 | 决定数据在 etcd 中的序列化格式 | 决定客户端可以使用的 API 版本 |
| 转换 | 写入时自动转换到存储版本;读取时从存储版本转换到请求版本 | 无需转换,直接由 API Server 路由 |
| 配置位置 | spec.versions[].storage: true |
spec.versions[].served: true |
常见陷阱:如果修改了存储版本,etcd 中已有的旧版本数据不会自动迁移。你需要手动编写迁移 Job 或使用
kubectl get --v=8 导出后重新导入。建议在 Operator 启动时自动检测并触发存储版本迁移。
3.3 版本转换机制:Conversion Webhook
当客户端请求的版本与存储版本不一致时,Kubernetes 需要执行版本转换。转换方式有两种:
- None(默认):仅支持相同 schema 的版本,字段名和类型必须完全一致。
- Webhook:通过外部 HTTP 服务实现自定义转换逻辑,支持不同 schema 的版本。
Conversion Webhook 的工作流程如下:
3.4 配置多版本 CRD 示例
以下是一个完整的 CRD 定义,包含 v1alpha1、v1beta1 和 v1 三个版本,并启用 Conversion Webhook:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: applications.example.com
spec:
group: example.com
names:
kind: Application
plural: applications
singular: application
scope: Namespaced
versions:
- name: v1alpha1
served: true
storage: false
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
name:
type: string
replicas:
type: integer
- name: v1beta1
served: true
storage: false
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
displayName:
type: string
count:
type: integer
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
displayName:
type: string
replicas:
type: integer
conversion:
strategy: Webhook
webhook:
conversionReviewVersions: ["v1", "v1beta1"]
clientConfig:
service:
namespace: operator-system
name: conversion-webhook-service
path: /convert
caBundle: <base64-encoded-ca-cert>
3.5 实现 Conversion Webhook 服务
Webhook 服务需要实现 ConversionReview 接口。以下是一个 Go 语言的核心处理逻辑片段:
func (h *ConversionHandler) Handle(ctx context.Context, req *admissionv1.ConversionReview) (*admissionv1.ConversionReview, error) {
var convertedObjects []runtime.RawExtension
for _, obj := range req.Request.Objects {
// 反序列化请求对象
unstructuredObj := &unstructured.Unstructured{}
if err := json.Unmarshal(obj.Raw, unstructuredObj); err != nil {
return nil, err
}
// 根据目标版本执行转换
targetVersion := req.Request.DesiredAPIVersion
switch targetVersion {
case "example.com/v1":
convertedObj := convertToV1(unstructuredObj)
convertedObjects = append(convertedObjects, runtime.RawExtension{Object: convertedObj})
case "example.com/v1beta1":
convertedObj := convertToV1beta1(unstructuredObj)
convertedObjects = append(convertedObjects, runtime.RawExtension{Object: convertedObj})
case "example.com/v1alpha1":
convertedObj := convertToV1alpha1(unstructuredObj)
convertedObjects = append(convertedObjects, runtime.RawExtension{Object: convertedObj})
}
}
return &admissionv1.ConversionReview{
Response: &admissionv1.ConversionResponse{
UID: req.Request.UID,
ConvertedObjects: convertedObjects,
Result: metav1.Status{Status: metav1.StatusSuccess},
},
}, nil
}
3.6 版本转换的常见故障与排查
| 故障现象 | 可能原因 | 排查方法 |
|---|---|---|
创建 CR 时报 conversion webhook returned invalid object |
Webhook 返回的对象缺少必要字段或类型不匹配 | 检查 Webhook 日志,确认转换后的对象符合目标版本的 schema |
| 读取 CR 时字段丢失 | 存储版本与服务版本之间字段映射不完整 | 在 Webhook 中添加字段映射日志,对比两个版本的字段差异 |
| Webhook 超时 | 转换逻辑耗时过长或网络延迟 | 检查 Webhook 服务的响应时间,考虑增加 timeoutSeconds 配置 |
| 证书错误 | caBundle 配置错误或证书过期 |
使用 openssl s_client 验证证书链,重新生成 CA 和证书 |
排查命令速查:
kubectl get crd <crd-name> -o yaml— 查看 CRD 的版本配置和 Webhook 设置kubectl logs -n operator-system deployment/conversion-webhook— 查看 Webhook 日志kubectl get <resource> --v=8— 查看 API Server 与 Webhook 的完整 HTTP 交互kubectl describe crd <crd-name>— 查看 CRD 的状态和版本信息
3.7 存储版本迁移策略
当你需要更改存储版本时(例如从 v1beta1 迁移到 v1),推荐以下步骤:
- 准备阶段:确保所有版本都支持双向转换,Webhook 已稳定运行。
- 标记新存储版本:在 CRD 中将
v1的storage: true,同时将旧版本设为storage: false。 - 数据迁移:编写脚本遍历所有现有 CR 对象,通过 API Server 读取后重新写入,触发自动转换。
- 验证:使用
kubectl get <resource> -o yaml确认所有对象已转换为新存储版本。 - 清理:确认无误后,可以移除旧版本的服务版本(设置
served: false)。
重要提醒:存储版本迁移是高风险操作。建议先在测试环境完整演练,并确保有 etcd 快照备份。迁移过程中,旧版本客户端仍可正常读写,但写入的数据会以新存储版本保存。