Kubernetes v1.37 中,Storage Version Migration(SVM)正式达到 GA,并在所有 v1.37 集群中默认启用。它解决了一个容易被忽略但会直接影响 API 兼容性的运维问题:API 版本已经升级,etcd 中却可能仍然保存着旧版本序列化的数据。
对于 CRD 作者、集群管理员以及负责加密密钥轮换的团队来说,SVM 将过去依赖脚本或额外组件完成的数据重写,变成了一个可以声明式管理、查询状态并纳入发布流程的 Kubernetes API。
为什么“新写入使用新版本”还不够
Kubernetes 资源在写入存储时,会按照某个 storage version 进行序列化。假设一个 CRD 曾经支持 v1alpha1 和 v1beta1,现在准备升级到 v1,并将 v1 标记为新的存储版本:
versions:
- name: v1alpha1
served: false
storage: false
- name: v1beta1
served: true
storage: false
- name: v1
served: true
storage: true
这只能保证之后的新写入使用 v1。已经存在于存储中的对象不会自动被重写,它们可能仍以 v1alpha1 或 v1beta1 的形式保存。
这会带来两个实际问题:
- 在所有对象完成迁移前,不能安全地从 CRD 的
.status.storedVersions中移除旧版本。 - 如果直接删除旧版本的 serving 支持,API Server 可能无法正确读取仍使用旧版本序列化的数据。
类似问题也会出现在静态加密和密钥轮换中。启用 encryption at rest 或更换加密密钥后,已有对象通常要经过 API Server 的主动重写,才能从未加密状态或旧密钥迁移到新状态。
过去,管理员通常需要编写 kubectl get、kubectl replace 脚本,或者部署集群外的 kube-storage-version-migrator。这些方式不仅容易漏掉资源,也不容易观察进度和判断是否真正完成。
用声明式对象启动迁移
Kubernetes v1.37 提供了稳定的 StorageVersionMigration API。内置的 StorageVersionMigrator controller 会监听这类对象,并将目标资源的已有实例重写为该 API 当前使用的默认存储版本。
下面的例子迁移 example.com API group 下的 crontabs 资源。运行前,请确认对应 CRD 已经将 v1 设置为 storage: true,并根据实际资源名称修改 manifest:
apiVersion: storagemigration.k8s.io/v1
kind: StorageVersionMigration
metadata:
name: crontabs-migration
spec:
resource:
group: example.com
resource: crontabs
保存为 crontabs-migration.yaml 后执行:
kubectl apply -f crontabs-migration.yaml
kubectl get storageversionmigration.storagemigration.k8s.io/crontabs-migration -o yaml
这里的 resource 是资源的复数形式,也就是 API 路径中的名称;它不是 CRD 的 kind。例如,CronTab 对应的资源通常是 crontabs。
如果需要先确认 API Server 识别到的资源名称,可以运行:
kubectl api-resources --api-group=example.com
如何判断迁移是否完成
迁移对象的 status 会反映控制器处理进度。成功时,应看到 Succeeded condition 的 status 为 True。可以用 JSONPath 做一个适合脚本和 CI 检查的判断:
kubectl get storageversionmigration.storagemigration.k8s.io/crontabs-migration \
-o jsonpath='{range .status.conditions[*]}{.type}={.status}{"\n"}{end}'
预期结果类似:
Running=False
Succeeded=True
也可以直接查看完整状态:
kubectl get storageversionmigration.storagemigration.k8s.io/crontabs-migration -o yaml
对于 CRD,迁移成功后,应该检查 .status.storedVersions 是否只剩下当前需要保留的版本:
kubectl get crd crontabs.example.com \
-o jsonpath='{.status.storedVersions}{"\n"}'
如果迁移成功后仍然看到旧版本,不要直接删除旧版本。一个常见原因是 CRD 在迁移过程中又发生了更新。此时应重新检查 CRD 的 storage version 配置,并重新创建或重试对应的迁移任务,确认对象已经在最新版本下完成重写。
把迁移纳入 CRD 发布流程
StorageVersionMigration 本身是标准的声明式 Kubernetes API,因此 CRD 作者可以把 CRD 更新和迁移对象放在同一个发布清单中:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: crontabs.example.com
spec:
group: example.com
scope: Namespaced
names:
plural: crontabs
singular: crontab
kind: CronTab
shortNames:
- ct
versions:
- name: v1beta1
served: true
storage: false
schema:
openAPIV3Schema:
type: object
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
---
apiVersion: storagemigration.k8s.io/v1
kind: StorageVersionMigration
metadata:
name: crontabs-migration
spec:
resource:
group: example.com
resource: crontabs
不过,清单放在一起并不代表两个操作会以一个事务完成。生产发布流程仍应分别验证:CRD 更新是否成功、迁移对象是否成功、.status.storedVersions 是否符合预期,以及旧版本 serving 支持是否可以安全移除。
升级时的检查清单
可以把下面的步骤加入 CRD 版本升级或集群加密配置变更流程:
- 确认目标 API 版本已经设置为唯一或主要的 storage version。
- 检查 API group、resource 名称和 CRD 配置是否一致。
- 创建
StorageVersionMigration对象。 - 等待
Succeeded=True,并记录迁移结果。 - 检查 CRD 的
.status.storedVersions。 - 在确认旧版本对象已经完成重写后,再移除旧版本的 serving 支持。
- 在大规模集群中观察 API Server、etcd 和控制器负载,安排合适的变更窗口。
SVM 的价值不在于让版本升级变成“无感操作”,而在于提供了一条 Kubernetes 原生、可观察、可重复执行的资源重写路径。升级 CRD 时,storage version、历史对象和 serving 兼容性应该被视为同一个发布问题,而不是三个互不相关的配置项。