Kubernetes 1.37 带来了一个容易被忽略、却和升级安全性直接相关的变化:Storage Version Migration(SVM)已经进入 GA,内置的 StorageVersionMigration API 和 control plane controller 默认启用。它解决的是“资源已经切换到新存储版本,但 etcd 里还有旧对象”的问题。本文不讨论如何盲目升级集群,而是给出一套更稳的 CRD 版本迁移检查方法:确认存储版本、检查转换能力、创建迁移对象、观察状态,最后用 storedVersions 验收。

一、先分清三个“版本”
CRD 升级时,最容易混淆的是 API 版本、服务版本和存储版本。API 版本决定客户端请求使用什么字段;served: true 表示该版本仍能被 API Server 提供;storage: true 表示新写入对象优先按哪个版本保存。
例如同时提供 v1 和 v2 时,可让两者 served,但只把 v2 设为 storage。旧对象不会因“能读”就自动重写,SVM 负责将其转换并写回。
二、Kubernetes 1.37 的变化
官方发布说明显示,Kubernetes v1.37 于 2026 年 8 月 26 日发布,共有 16 项增强能力晋级 Stable。8 月 31 日官方博客说明,Storage Version Migration 已进入 GA,storagemigration.k8s.io/v1 API 和控制器在 v1.37 集群中默认启用。
这不等于所有对象会自动迁移。迁移仍要围绕具体 Resource 创建 SVM,并检查状态。Kubernetes 提供了稳定基础设施,但迁移目标、转换 webhook、备份和验收仍需运维负责。
三、升级前先保存证据
不要一上来就改 CRD。先记录集群版本、CRD 当前 storage 版本、可提供的 API 版本,以及目标资源能否正常读取。
# 集群版本
kubectl version
# 查看 CRD 的服务与存储版本
kubectl get crd selfierequests.example.com -o yaml
# 只打印当前 storage 版本
kubectl get crd selfierequests.example.com \
-o jsonpath='{.spec.versions[?(@.storage==true)].name}'
echo
# 确认资源是否能被 API Server 发现
kubectl api-resources | grep -i selfie
# 读取一条真实对象,确认转换 webhook 没有先天故障
kubectl get selfierequests.example.com -A -o yaml
示例名称是占位符,实际操作时替换为自己的资源。生产环境应保存 CRD YAML 和迁移前的资源数量。
四、CRD 版本切换顺序
先部署并验证 conversion webhook,再让新版本 served;确认 v1、v2 可以双向转换后,把 v2 设置为 storage;最后创建 SVM。
重点看两个字段:spec.versions[*].storage 表示首选存储版本,应该只有一个 true;status.storedVersions 表示仍可能存在对象的历史存储版本,迁移完成前可能同时包含 v1 和 v2。
不要只验证新版本创建成功,还要用旧版本对象做读取、更新和列表测试,避免 SVM 重写旧对象时才暴露转换问题。
五、创建 StorageVersionMigration
迁移对象通过资源的 group 和 resource 指定目标。以 example.com 组的 selfierequests 为例:
cat > migrate-crd.yaml <<'EOF'
apiVersion: storagemigration.k8s.io/v1
kind: StorageVersionMigration
metadata:
name: selfierequests-migration
spec:
resource:
group: example.com
resource: selfierequests
EOF
kubectl apply -f migrate-crd.yaml
kubectl get storageversionmigration.storagemigration.k8s.io \
selfierequests-migration -o yaml
如果集群只提供 v1beta1,以 Discovery 和对应官方文档为准,不要照抄示例版本。
六、状态怎么看才算完成
迁移对象的 status.conditions 是第一层证据。成功时,Succeeded 条件应为 True;Running 通常应为 False。可以这样等待:
kubectl wait \
--for=condition=Succeeded \
storageversionmigration.storagemigration.k8s.io/selfierequests-migration \
--timeout=10m
kubectl get storageversionmigration.storagemigration.k8s.io \
selfierequests-migration -o yaml
这里的 succeeded 只说明迁移控制器报告成功,不应该直接等同于“所有验收结束”。第二层证据是 CRD 的 status.storedVersions,确认旧版本已经从列表中消失:
kubectl get crd selfierequests.example.com \
-o jsonpath='{.status.storedVersions}'
echo
第三层证据是抽样读取、更新和列表。使用旧版本客户端读取时,API Server 仍可能按转换后的对象返回;这时要确认字段没有丢失,尤其是 webhook 自定义转换的字段。

七、卡住或失败时按证据排查
| 现象 | 优先证据 | 常见原因 | 处理方向 |
|---|---|---|---|
| 找不到 API | kubectl api-resources | 集群版本、API 注册或资源版本不匹配 | 以 Discovery 结果和控制面配置为准 |
| 一直 Running | SVM 的 conditions、事件 | 对象数量多、控制器异常或资源读取失败 | 查看控制器日志,先定位首个失败资源 |
| 成功但仍有 v1 | CRD 的 storedVersions | 转换 webhook、资源名或迁移范围不正确 | 核对 group/resource 和 webhook 双向转换 |
| 读取对象报错 | API Server 与 webhook 日志 | schema 不兼容、证书或网络问题 | 修复转换链路后再重试,不要反复创建同名对象 |
| 担心直接查 etcd | 备份、API 读回、审计记录 | etcd 二进制内容不便人工判断 | 优先使用 API 与 storedVersions 双重验收 |
排查先看“第一个失败的资源”。转换 webhook 重点检查 Service、Endpoint、证书和 API Server 到 webhook 的连通性。
八、回滚边界
迁移失败时先暂停变更、修复 webhook 或 schema,再根据状态决定是否重试。不要无备份地切回旧 storage 版本,更不要手工编辑 etcd。回滚前必须确认新字段能否转换回旧版本;“把 storage 改回 v1”不是完整回滚。
九、上线验收清单
- 集群版本和 StorageVersionMigration API 已通过 Discovery 确认。
- CRD 只有一个 storage 版本,目标版本为预期值。
- 旧版本和新版本的 conversion webhook 均能读取、创建、更新和列表。
- SVM 的
Succeeded=True,并保存了完整 status。 - CRD 的
status.storedVersions已不再包含待淘汰的旧版本。 - 抽样对象字段完整,业务读写和控制器日志无新增错误。
- 备份、回滚边界和变更记录已归档。
🔕 评论已关闭