第 15 章 · API 版本的演进:从 extensions/v1beta1 到「稳定分组」

一句话结论:K8s 的 API 演进,解决的是一个「既要快速迭代、又要给用户稳定契约」的矛盾。它的答案是**「分组 + 分级 + 严格的弃用政策」**——把资源按领域分组(apps、networking…),每个资源按 alpha/beta/stable 分级,再配上一套「beta 至少保留 9 个月」的铁律,让「快速演进」和「稳定承诺」可以共存。


1. 当年是什么样:一堆资源「临时堆」在 extensions/v1beta1

K8s 最早,很多还在开发中的资源,都被临时塞进一个叫 extensions/v1beta1 的 API 组里。这是一个「开发期的临时仓库」:

  • Deployment(最早的控制器之一)在 extensions/v1beta1
  • DaemonSet、ReplicaSet 在 extensions/v1beta1
  • Ingress、NetworkPolicy、PodSecurityPolicy 都在 extensions/v1beta1

为什么都挤在这:因为早期这些资源「还没稳定」,不能承诺它们的 API 不再变,于是统一放在一个「beta」组里,暗示「这些还在变,别太依赖」。


2. 踩了什么坑:用户需要一个「可依赖的契约」

但问题随之而来:

  1. beta 的 API 字段随时可能变。你今天写的 Deployment YAML,下个版本可能就失效了。对生产用户来说,这是灾难——他们的部署文件、CI 脚本、控制器,全都建立在「API 不变」的假设上。

  2. extensions/v1beta1 这个组名毫无语义。「extensions」是个什么领域?什么都往里塞,等于「没有分类」。这导致 API 结构混乱,用户摸不清「这个资源到底属于哪、稳定性如何」。

  3. 没有明确的「什么时候会变、会变什么」的承诺。用户升级集群,最怕的就是「API 悄悄没了」。

核心矛盾:K8s 要快速演进(API 就得能变),但用户要稳定(API 就不能变)。怎么在「能变」和「不变」之间,给出一套清晰的规则?


3. 解决方案:分组 + 分级 + 弃用政策(三件套)

① 分组(API Groups):把「临时堆」拆成「按领域的家」

extensions/v1beta1(临时堆,已废弃)
    ├── Deployment / DaemonSet / ReplicaSet / StatefulSet  →  apps/v1
    ├── Ingress                                            →  networking.k8s.io/v1
    ├── NetworkPolicy                                      →  networking.k8s.io/v1
    └── PodSecurityPolicy                                  →  policy/v1beta1(后废弃)

每个资源搬到语义清晰的独立分组里,apps 组管「应用负载」,networking 组管「网络」。分组让 API 结构可理解、可独立演进(apps 组发 v2 不影响 networking 组)。

② 分级(alpha/beta/stable):给每个资源标一个「稳定性承诺」

级别含义承诺
alpha实验性随时可能变、可能被删除,默认关闭
beta接近稳定默认开启,但字段和语义可能变
stable(v1)正式稳定向后兼容,永不移除

这一层的关键:用户通过「资源在哪个级别」,就能预判它的风险和承诺。生产环境只敢用 stable,尝鲜才用 alpha/beta。

③ 弃用政策(Deprecation Policy):一条铁律,给足迁移时间

这是「稳定」承诺的法律保障:

  • stable(GA)API:在 v1 大版本内永不移除。
  • beta API:弃用后至少保留 9 个月,或 3 个 release(取更长)。
  • alpha API:可以随时移除,无需通知。

这条铁律的价值:用户升级集群时,知道「就算有 API 被弃用,我也至少有 9 个月 + 3 个版本的时间来迁移」。「变化」被加上了一个「最低缓冲期」,从「措手不及」变成了「可规划」。


4. 一个精妙的机制:多版本共存 + 存储版本分离

这里有个 K8s 特别聪明的设计,值得专门讲——一个资源可以「同时以多个版本被服务」,但 etcd 里只存「一个版本」:

用户 A 用 apps/v1 读 Deployment
用户 B 用 apps/v1beta1 读同一个 Deployment(如果还在服务)
        ↓
API Server 在读取时「实时转换」版本
        ↓
etcd 里,这个 Deployment 只以「一个 storage version」存储

这个设计解决了「迁移期间」最大的痛点:新老客户端可以共存。

  • 老客户端继续用旧 API 读,新客户端用新 API 读,API Server 在中间做转换,两边都无感知。
  • etcd 只存一份(storage version),不产生「双份数据不同步」的问题。

再配合 kubectl convert 工具,用户可以自动把旧的 YAML 转成新版本。这套「多版本服务 + 实时转换 + 转换工具」,就是 API 迁移的「黄金三件套」。


5. 这条演进线教给我们什么

  1. 「稳定」不是一个口号,是一套「分级 + 铁律 + 缓冲期」的制度。 只嘴上说「我们的 API 稳定」没用,得用「stable 永不移除、beta 保留 9 个月」这种可执行的规则来兜底。你设计对外 API 时,也该有一套明确的「弃用政策」。

  2. 「快速迭代」和「稳定契约」可以共存,靠的是「分级」——把「还在变的东西」和「承诺不变的东西」分开管理,而不是「要么全变、要么全不变」。

  3. 「平滑迁移」的通用手法:多版本并存 + 转换层。 这是第 14 章「翻译层」思想的又一应用——要换 API,最稳的是「让新旧并存、中间加转换」,而不是「一刀切」。

类比:API 分级像「软件版本号」——alpha 是「内测版,别拿来干活」,beta 是「公测版,可能有坑」,stable 是「正式版,出了问题我负责」。而弃用政策像「软件的生命周期承诺」——「停服前 9 个月公告」。这些都是为了把「变化」变得可预期,把「信任」变成制度。


思考题

Q:stable API「永不移除」这个承诺,意味着 K8s 一旦把某个 API 稳定化,就背上了一个「永久兼容」的包袱。那如果一个 stable API 的早期设计「后来发现是错的」,K8s 怎么办?是硬扛着不修,还是有别的办法?

参考答案:

这正是「stable 承诺」的代价,而 K8s 的答案是**「用新版本,而不是改旧版本」**:

  • 如果发现 apps/v1 里的某个设计有问题,K8s 不会改 apps/v1(因为承诺了向后兼容),而是新开一个 apps/v1beta1 或 apps/v2 来承载新设计。
  • 旧版本继续存在、继续兼容,新版本逐步引导用户迁移,成熟后再把新版本也稳定化。

这套做法的本质是**「版本是不可变的历史,演进通过『新版本』而不是『改旧版本』来实现」**——和很多后端系统「API 加 /v2 而不是改 /v1」是同一个道理。

但它也揭示了「stable 承诺」的真实成本:一旦稳定化,你就不能反悔,只能「叠加新版本」。所以 K8s 对「什么能转 stable」极其慎重——宁可让一个 API 在 beta 待很久(反复验证),也不轻易给 stable 的终身承诺。这和第 10 章「alpha→beta→GA 的漫长门禁」是同一个逻辑的又一体现:「承诺」是昂贵的,所以「做出承诺」这件事本身,必须极其谨慎。