第 12 章 · 演进总览:从 1.0 到 1.37 的十年时间线
一句话结论:K8s 十年演进的主旋律,可以浓缩成一句话——「把当年为了『快』而内嵌、写死、粗放的东西,在规模和技术成熟后,一样一样地『外置、抽象、精细化』」。看懂这条主线,再看任何一个组件的演进,都能秒懂「它为什么要改」。
1. 一张「十年演进」的地图
先给你一个俯瞰视角,把十年里「有改方案故事」的关键节点串起来:
| 版本 | 年份 | 里程碑 | 背后是「什么被改变了」 |
|---|---|---|---|
| 1.0 | 2015 | 正式 GA | 从 Google Borg 经验开源化 |
| 1.4 | 2016 | StorageClass 动态供给 | 存储从「管理员手动建」→「声明式按需建」 |
| 1.5 | 2016 | CRI 引入 | 运行时从「绑死 Docker」→「接口化」 |
| 1.6 | 2017 | RBAC GA、etcd v3 | 权限从「全有全无」→「细粒度角色」 |
| 1.9 | 2017 | apps/v1 稳定、CSI alpha | API 从「临时堆」→「分组稳定化」 |
| 1.13 | 2018 | CSI GA、CoreDNS 默认 | 存储插件开始「外置」 |
| 1.14 | 2019 | CSI 迁移 alpha | in-tree 存储插件开始「抽出去」 |
| 1.15 | 2019 | 调度框架 alpha | 调度器从「写死的两阶段」→「插件框架」 |
| 1.19 | 2020 | 调度框架 GA、Ingress v1 | 调度器、网络 API 双双稳定化 |
| 1.20 | 2020 | 宣布弃用 dockershim | 运行时「外置」进入倒计时 |
| 1.24 | 2022 | 移除 dockershim | 彻底解绑 Docker |
| 1.25 | 2022 | CSI 迁移核心 GA | 存储「外置」基本完成 |
| 1.35 | 2025 | in-place resize 稳定 | 资源管理开始「精细化」 |
| 1.36 | 2026 | user namespaces 稳定 | 安全开始「内建」 |
| 1.37 | 2026 | Pod 级资源、HPA scale-to-zero | 精细化 + 服务 AI 负载 |
2. 把十年读成一条主线:三次「搬家」
如果你只记一条线,记这个:K8s 十年,本质是做了三次大规模的「搬家」——
搬家一:把「绑死的东西」搬成「接口」(1.5 ~ 1.24)
早期为了快,运行时绑死 Docker、存储插件内嵌在核心、云厂商代码混在核心里。后来发现——这些「实现细节」会拖死核心的演进。于是:
- 运行时 → CRI(第 9、17 章)
- 存储 → CSI(第 9、14 章)
- 云厂商代码 → cloud-provider 抽取(第 17 章)
共同动机:把「会变、且众口难调」的部分,用接口包起来,让核心和生态各自演进。
搬家二:把「临时堆」搬成「稳定的家」(1.9 ~ 1.22)
早期 API 是「边开发边堆」,都临时塞在 extensions/v1beta1 里。后来发现——用户需要一个稳定的 API 契约,不能天天跟着 alpha/beta 变。于是:
extensions/v1beta1里的 Deployment、Ingress、NetworkPolicy…逐个搬到各自的分组(apps/v1、networking.k8s.io/v1)并稳定化(第 15 章)。
共同动机:把「实验性的、会变的」和「稳定的、可依赖的」分开,给用户一个可预期的契约。
搬家三:把「粗放的」搬成「精细的」(1.14 ~ 至今)
规模和技术都成熟后,早期「图省事的粗放设计」开始变成束缚:
- 调度器从「写死两阶段」→「插件框架」(第 13 章)
- 资源从「按容器最坏预留」→「Pod 级共享预算 + 原地扩容」(第 10 章)
- 安全从「外挂补丁」→「内建」(user namespaces)
共同动机:把「当初合理的简化」,在需求成熟时,变成「可配置、可细化」。
3. 一个贯穿十年的「方法论」:先跑起来,再在正确的时间抽象
回看这三次搬家,你会发现一个反复出现的节奏:
- 早期:为了尽快跑起来,选择「最简单直接」的实现(绑死 Docker、API 塞一个组、调度逻辑写死)。
- 中期:规模起来后,发现「简单实现」开始卡脖子(发布被拖慢、API 不稳定、调度难扩展)。
- 成熟期:把当初的「权宜之计」,用「接口 + 稳定化 + 插件化」一步步替换掉。
这不是「设计失误」,而是「刻意为之的演进策略」——因为:
- 如果早期就过度设计(一开始就搞一堆接口和抽象),很可能根本「活不到」验证需求的那一天。
- 而「先跑起来、再在真实痛点出现时抽象」,既保证了「能落地」,又保证了「抽象是长在真实需求上的」(呼应第 7、9 章的「等第二个用例出现」)。
类比:K8s 的十年,像一个人「先租了个离公司近的小房子将就(快速跑起来),住进去发现各种不便,再一步步搬家、装修(外置、稳定化、精细化)」。它不是一开始就住进了豪宅,而是一路根据真实的生活需求,把房子越住越顺。
4. 接下来几章怎么读
演进篇的每一章,都聚焦一个组件,讲同一个故事结构:
它当年是什么样(早期实现)
→ 踩了什么坑(真实痛点)
→ 为什么必须改(不改成不成)
→ 新方案是什么 + 怎么平滑过渡(兼容性)
- 第 13 章:调度器(两阶段 → 插件框架)
- 第 14 章:存储(手动建卷 → CSI)
- 第 15 章:API 版本(extensions → 稳定分组)
- 第 16 章:网络(Service → Gateway API)
- 第 17 章:运行时与云提供商(内嵌 → 外置)
思考题
Q:这三次「搬家」里,有一个贯穿的代价——每一次外置/稳定化/精细化,都会带来「迁移成本」和「一段时间的双重维护」(比如 CSI 迁移期间,in-tree 和 CSI 两套代码并存了好几年)。那 K8s 为什么还要坚持做这些「费力不讨好」的迁移?直接「维持现状」不行吗?
参考答案:
因为「维持现状」的代价是隐性但持续累积的,而「迁移」的代价是显性但一次性付清的:
- 维持 in-tree 存储插件:每个云厂商的存储代码都堆在 K8s 核心里,每个 K8s release 都要等所有厂商一起测,一个厂商的 bug 会拖累全世界的 K8s 发布。这个成本看不见,但每天都在付。
- 迁移到 CSI:要花几年做「翻译层」、做双轨维护,但这个成本是一次性的,付完之后,厂商的存储驱动可以自己发版,K8s 核心瘦身,发布节奏从此解耦。
核心认知:技术债有两种——「显性的一次性成本」和「隐性的持续成本」。 成熟系统的维护者,往往愿意「付一笔大的显性成本」,去消掉「一笔看似小、但永远在扣的隐性成本」。判断要不要做一次重构/迁移,本质是算清「隐性成本的累积曲线」和「显性成本的回收周期」。 如果隐性成本随时间线性甚至超线性增长,那越早付显性成本越划算——这正是 K8s 在「还来得及」的时候,坚持做这些大迁移的原因。