跳到主要内容
08:44:02
🔅🔆

存储版本

Kubernetes API 服务器在存储对象时,依赖于一个与 etcd 兼容的后端存储(通常就是 etcd 本身)。每个对象都使用该 API 类型的某个特定版本进行序列化,例如 ConfigMap 的 v1 表示形式。Kubernetes 使用术语存储版本(Storage Version) 来描述对象在你的集群中是如何存储的。

官方文档参考​

文档语言链接
存储版本(Storage Versions)概念页中文https://kubernetes.io/zh-cn/docs/concepts/overview/working-with-objects/storage-version
Storage Versions 概念页(英文,用于补全迁移与静态加密细节)英文https://kubernetes.io/docs/concepts/overview/working-with-objects/storage-version/
关于链接路径的说明

存储版本中文页的实际路径为 /zh-cn/docs/concepts/overview/working-with-objects/storage-version(位于 working-with-objects 层级下,资源名为单数 storage-version);上表两个链接均为本次抓取验证可用的官方链接。

核心概念​

  • 自动转换:API 服务器负责转换每个 API 调用。例如你有一个 HorizontalPodAutoscaler,可以用其 API 的 v1 和 v2 版本的任意组合与它交互,客户端看不到实际被序列化的版本。
  • 与 API 版本无关:对象的(API)版本与存储版本完全独立。例如同一资源的 v1alpha1 和 v1beta1 API 对象,只要两个对象之间存储版本没有被更新过,它们在存储中的编码就是相同的。
  • 多存储版本:同一个 API 可能具有多个存储版本,API 服务器可以在所有已存储版本之间动态转换;但单个对象在任何时刻只能有一个存储版本。
  • 为什么重要:存储版本把对象的 API 表示与存储后端中的实际二进制编码关联起来。当底层二进制编码变得重要时(例如静态加密、API 弃用),这个概念就非常关键。

存储版本与资源的映射​

操作存储版本的行为
写入(创建/更新)以当前活跃的存储版本存储对象
读取把已存储的数据转换为对象的 API 表示
更新已有旧版本对象写入时将对象转换为新的存储表示
一直不更新的旧对象旧存储版本编码可以无限期存在
  • 每个资源在任意时刻都只有一个活跃的存储版本,对对象的任何写入都会以该版本存储。
  • 存储版本可以被更新,不同对象因而可以以不同版本存储;但单个对象在任何时刻只会以一种存储版本存储。
  • 读操作不会改变对象的存储编码,只有写操作才会把对象迁移到新存储版本。

自定义资源的存储版本​

自定义资源(CR)是动态定义的,其存储版本机制与内置类型不同:

  • 内置对象的存储编码通常与 API 类型分开定义,已存储的对象充当"枢纽",具体资源版本只是对象模式中的一个字段;
  • 对于自定义资源,必须将资源的某个版本设置为存储版本,该版本定义的模式(schema)会被用作资源在存储层的编码。

示例 CRD(crontabs):

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: crontabs.example.com
spec:
group: example.com
# 该 CustomResourceDefinition 支持的版本列表
versions:
- name: v1beta1
served: true # served 表示该版本是否对外提供 API 服务
storage: true # 有且仅有一个版本必须标记为存储版本
schema:
openAPIV3Schema:
type: object
properties:
host:
type: string
port:
type: string
- name: v1
served: true
storage: false # 非存储版本,仅提供 API 服务
schema:
openAPIV3Schema:
type: object
properties:
host:
type: string
port:
type: string
time: # 注意:该字段不在存储版本 v1beta1 的 schema 中
type: string
conversion:
strategy: None
scope: Namespaced
names:
plural: crontabs
singular: crontab
kind: CronTab
shortNames:
- ct

上例中 v1beta1 被用作存储版本:任何 crontabs 的创建或更新都会以 v1beta1 的对象模式存储。这意味着通过 v1 API 对象写入的 time 字段永远不会被存储,因为它不在存储版本的定义中。

不允许同时存在两个存储版本

把两个版本同时标记为存储版本是非法的——那意味着同时承认两种数据模式都是有效的存储方式。修改存储版本后,新创建或被更新的 CR 会立即使用新的存储版本;而读取/Watch 只是临时转换,不会改变已存储的编码。

存储版本与静态加密的关系​

集群可以对静态存储(encryption at rest)进行加密,尤其是集群的 Secret。存储数据被加密后,即使数据被窃取也多了一层保护;API 服务器从存储读取数据时会执行解密。

  • API 服务器必须持有该存储版本对应的密钥才能正确解码对象。
  • 此时存储版本就不仅仅是对象的二进制编码:只要存储的内容能够以某种方式转换成 API 对象,它就可以作为存储版本。

迁移到不同的存储版本​

单个资源存在多个存储版本会给管理员带来问题:

  • 在确认所有对象都不再使用旧存储版本之前,不能删除 CRD 的旧版本 API(可能是已不受支持的版本)。对象数量庞大且难以看清哪些对象还依赖旧存储版本时,很难判断何时可以安全移除某个版本。过早删除版本可能导致对象完全无法读取。
  • 加密密钥轮换问题:由于对象必须被主动写入才能更新存储版本,轮换密钥时,旧密钥和新密钥必须同时保留,直到管理员确认所有对象都至少被写入过一次——这会带来安全风险和可用性问题。
  • 官方提供了存储版本迁移(storage version migration)方案,可以确保所有对象都使用较新的存储版本,无需人工逐个干预。
迁移的朴素原理

存储版本迁移的本质是:把集群里所有旧存储版本的对象批量读写一遍(读出来再写回去),让写入操作把编码刷新为新存储版本,之后就可以安全移除旧版本和旧加密密钥。

我的总结与避坑清单​

  1. API 版本和存储版本是两回事:客户端用 v2 请求不代表 etcd 里存的是 v2,排查"改了 schema 不生效"类问题时要想到底层编码可能还是旧版本。
  2. 定义 CRD 时想清楚哪个版本做 storage:只有存储版本里的字段才真正落盘,只改 served 版本的 schema 而不动 storage 版本,新字段存不进去。
  3. 升级 CRD 存储版本后,旧对象不会自动迁移——要么做一次全量读写(storage version migration),要么等待自然更新;删除旧版本 API 前务必确认迁移完成。
  4. 轮换加密密钥(KMS provider)时,必须保留所有仍在用的旧存储版本的密钥,否则那些从未被更新过的对象将无法解密读取。
  5. 集群长期不动的"僵尸对象"是存储版本迁移的盲区:迁移后建议核对旧版本对象计数是否归零,再执行旧版本/旧密钥的清理。
🛡️本站已安全运行2334天23时18分58秒
Bottom GIF
Top GIF