1. 存储版本漂移概述

1.1 定义:什么是存储版本漂移

在 Kubernetes 集群中,存储版本漂移(Storage Version Drift) 是指同一 CustomResourceDefinition(CRD)或内置资源(如 Deployment、Pod)在 etcd 中持久化的序列化版本,与集群中当前运行的 API Server 所支持的存储版本不一致的现象。这种不一致会导致 API Server 在读取旧版本对象时产生解码失败、字段丢失、控制器异常等连锁故障。

核心判断标准: 当集群中存在多个 API 版本(如 v1beta1、v1)且 etcd 中实际存储的对象版本与 kube-apiserver 的 --storage-versions 配置或 CRD 的 spec.versions[].storage 字段指向的版本不同时,即发生了存储版本漂移。

1.2 漂移产生的三大原因

存储版本漂移并非随机发生,其根因集中在以下三类典型场景中。下表从触发时机、影响范围和典型表现三个维度进行对比:

原因类别 触发时机 影响范围 典型表现
升级中断 集群升级过程中 API Server 重启或版本切换 全局 / 特定 CRD 部分对象无法通过 kubectl get 读取,etcd 中残留旧版本数据
回滚不一致 集群回滚后 API Server 版本低于 etcd 中对象的存储版本 全局 API Server 启动失败或持续报 unable to decode 错误
API 版本过期 CRD 或内置资源废弃旧版本后未执行存储迁移 特定 CRD 旧版本对象在 etcd 中成为“孤儿”,无法被新版本控制器处理

1.3 原因一:升级中断

Kubernetes 集群升级(尤其是跨大版本升级)时,kube-apiserver 会经历滚动重启。如果升级流程被意外中断(如节点宕机、网络分区、超时强制终止),可能出现以下状态:

  • 部分 API Server 实例已升级,开始使用新存储版本写入 etcd;
  • 部分 API Server 实例仍为旧版本,继续以旧版本格式读取/写入;
  • etcd 中同一资源的不同对象可能分别以新旧两种版本存储,形成混合存储状态。

当集群恢复后,新版本的 API Server 尝试读取旧版本对象时,若转换器(conversion)缺失或转换失败,就会触发 InternalError 或 DecodeError,导致该对象不可用。

1.4 原因二:回滚不一致

集群回滚是比升级中断更危险的场景。假设集群从 v1.25 升级到 v1.26 后发现问题,决定回滚到 v1.25:

  1. v1.26 的 API Server 在运行期间可能已将某些资源(如 HorizontalPodAutoscaler)的存储版本从 autoscaling/v2beta2 升级为 autoscaling/v2;
  2. 回滚后,v1.25 的 API Server 不认识 autoscaling/v2 的序列化格式;
  3. etcd 中所有以 autoscaling/v2 存储的对象在读取时直接报错,导致 HPA 控制器完全失效。
⚠️ 关键风险: 回滚操作本身不会自动将 etcd 中的对象版本“降级”。存储版本是单向演进的,回滚前必须执行存储版本迁移或数据备份恢复,否则集群将处于不可用状态。

1.5 原因三:API 版本过期

Kubernetes 社区和 CRD 开发者会定期废弃(deprecate)并最终移除(remove)旧 API 版本。例如:

  • 内置资源 extensions/v1beta1 的 Ingress 在 v1.22 中被彻底移除;
  • CRD 的 spec.versions 中标记 served: false 的版本不再被 API Server 服务。

如果 CRD 的 spec.versions[].storage 指向一个已被废弃的版本,或者 etcd 中仍残留旧版本对象但该版本已从 CRD 定义中删除,API Server 将无法解码这些对象。此时必须通过 kubectl convert 或自定义迁移工具将对象显式转换为当前存储版本。

1.6 存储版本漂移全景图

下图从集群生命周期视角展示了三种漂移原因的发生位置与传播路径:

存储版本漂移全景图 正常集群 存储版本一致 升级中断 混合存储状态 回滚不一致 版本降级失败 API版本过期 废弃版本残留 存储版本漂移 etcd 对象版本 ≠ API Server 存储版本 解码失败 / 控制器异常 三种原因均可独立或叠加触发漂移 校准需在发布窗口内完成

1.7 漂移的检测与初步判断

在实际运维中,可以通过以下命令快速判断集群是否存在存储版本漂移:

# 检查 API Server 日志中的解码错误
kubectl logs -n kube-system kube-apiserver-<node> | grep -i "unable to decode"

# 检查 CRD 的存储版本配置
kubectl get crd <crd-name> -o jsonpath='{.spec.versions[?(@.storage==true)].name}'

# 直接读取 etcd 中对象的原始存储版本(需 etcdctl)
ETCDCTL_API=3 etcdctl get /registry/<resource>/<namespace>/<name> --prefix --print-value-only | \
  jq '.apiVersion'
💡 最佳实践: 在每次集群升级或回滚操作完成后,立即执行上述检测命令,将存储版本漂移的发现时间从“故障爆发后”提前到“变更验证阶段”,这是 SRE 在发布窗口内进行回归检查的关键步骤。