第 11 章 · 收尾:一张可复用的「分布式系统设计清单」
一句话结论:把前面十章的所有设计决策收拢成一张表,就是你「用 Go 造自己的系统」时的检查清单。每一个决策点都不是「唯一正确」,而是「你要基于自己的问题形状,主动选一个并承担它的代价」。
1. 一张贯穿全文的决策地图
| # | 决策点 | 可选方向 | K8s 的选型 | 选它的核心理由 | 章节 |
|---|---|---|---|---|---|
| 1 | 编排模型 | 命令式脚本 / 声明式平台 | 声明式 | 只有「持久化的期望状态」才能支持自愈 | 1、2 |
| 2 | 状态驱动方式 | edge-driven / level-driven | level-driven | 对「终态」而非「事件」,天然抗丢失、抗重复 | 3 |
| 3 | 状态存哪 | 关系库 / 分布式 KV | etcd | 匹配「强一致+小对象+watch」的问题形状 | 4 |
| 4 | 并发控制 | 悲观锁 / 乐观并发 | 乐观并发(resourceVersion) | 读多写少冲突罕见,乐观并发代价极低 | 4 |
| 5 | 读的时效 | 每次强一致 / 缓存+最终一致 | 缓存+最终一致 | 读容忍最终一致,写才强一致 | 4 |
| 6 | 抽象粒度 | 调度容器 / 调度 Pod | Pod | 反映「共享组」的真实协作形态 | 5 |
| 7 | 对象关联 | 硬编码地址 / 标签选择器 | label/selector | 用「特征」而非「地址」,应对对象易变 | 5 |
| 8 | 调度策略 | 全局最优 / 启发式 | 过滤+打分 | 用「够好」替代「不可解的最优」 | 6 |
| 9 | 决策与执行 | 合一 / 分离 | 中心决策+边缘执行 | 中心要全局视角,边缘要本地可靠 | 6 |
| 10 | 子系统实现 | 焊死内核 / 插件接口 | CRI/CNI/CSI | 隔离「变化点」,核心精简生态自由 | 9 |
| 11 | 扩展方式 | 改核心 / CRD+Operator | CRD+Operator | 复用「控制循环」范式,扩展在用户空间 | 8 |
| 12 | 接口边界 | 并集 / 最小公约数 | 最小公约数 | 接口只收「共同契约」,私有特长走扩展点 | 9 |
| 13 | 特性演进 | 一步到位 / 分级门禁 | alpha→beta→GA | 用分级+开关,把「快」「稳」给不同用户 | 10 |
2. 三条「第一性原理」——比任何具体选型都重要
-
声明式 + 控制循环,是「自愈系统」的通用范式。 把「期望状态」和「实际状态」分开,用一个永不停止的循环去对齐它们。这个范式不只能管容器,还能管数据库、管网络、管任何「需要持续保持某种状态」的领域。 你造任何系统,先问自己:它的「期望状态」是什么?「调谐循环」在哪?
-
用「够好 + 持续纠偏」替代「一次算准」。 调度是 NP-hard、系统永远在变、决策永远可能错。不求一次做对,但求持续逼近——快决策 + 慢纠偏,比「试图一次算准」可靠得多。
-
抽象要「跟着真实需求长出来」,接口要划在「真正的变化点」上。 过早抽象和拒不抽象都是坑,中间的平衡叫「等第二个用例出现」。抽象的价值不是「能力更强」,而是「边界更清晰、把不可能关在门外」。
3. 用 Go 造一个「迷你 K8s」的最小骨架(思路级)
不写完整代码,只给你「K8s 的核心概念在 Go 里落到哪些原语」的映射,帮你打破「这是别人家的系统」的隔层:
| K8s 概念 | Go 的落点 |
|---|---|
| 资源对象(Pod/Deployment) | struct + JSON/YAML tag,存 etcd 的 key |
| 声明式 API | HTTP server + Apply(obj) 只做「持久化期望状态」 |
| 控制器/调谐循环 | for { key := <-queue; reconcile(key) } 的 goroutine |
| level-driven | reconcile 里每次都「读期望、读实际、算差值」,幂等 |
| 乐观并发 | resourceVersion 字段 + CompareAndSwap(etcd 的 txn) |
| watch/事件 | etcd Watch + channel 推送,workqueue 去重 |
| informer 缓存 | 内存 map + 读写锁,watch 保持同步 |
| 调度过滤/打分 | []Filter func(pod, node) bool + []Score func(pod, node) int |
| 插件接口 | type RuntimeService interface {...} + 多实现注册 |
| CRD/扩展 | 定义新 struct 存 etcd + 一个独立的 reconcile goroutine |
核心提示:别一开始就想着复刻 K8s 的规模。 先用 Go 把「声明式状态 + etcd 存状态 + 一个调谐循环 + 一个简单的调度器」这四件套跑通,你就已经摸到了「分布式控制面」的门。K8s 的庞然大物,本质就是这四件套在无数细节上的放大和加固。
4. 如果你还想继续深挖
- 读源码:
kubernetes/kubernetes仓库,从pkg/controller/(控制器)、pkg/scheduler/(调度器)开始,先看一个最简控制器(比如 ReplicaSet 控制器)的完整reconcile。 - 读 client-go:
k8s.io/client-go的 informer、workqueue、reflector,这是「事件驱动 + 对账」的 Go 教科书。 - 追 KEP:
kubernetes/enhancements仓库的 KEP 目录,是「怎么把一个问题写成一份设计提案、怎么论证取舍」的最佳范文。 - 横向对比:把 K8s 和 HashiCorp Nomad、Apache Mesos 放一起看「调度模型」的分歧,你会更清楚「中心化调度」这个选择的重量。
结语
K8s 从 0 到 1 的故事,本质上是一个「如何用工程手段,驯服『规模』和『变化』这两个恶魔」的故事:
- 规模逼出了「声明式 + 控制器」——因为手动管不过来,所以要让系统自己盯着目标。
- 变化逼出了「插件化 + 分级演进」——因为需求和实现都在变,所以要用接口和门禁把变化关进笼子。
而这套思想,不专属于 K8s,也不专属于容器。你以后造任何「要长期运行、要持续对账、要被人依赖」的系统——不管是一个搜索引擎、一个消息队列、还是一个业务中台——都会在这些决策点上,和 K8s 的设计者做出相似的思考。
你现在脑子里已经有了一张完整的图:从「为什么声明式」到「控制器怎么循环」到「状态怎么一致」到「怎么调度」到「怎么扩展」到「怎么演进」。剩下的,就是动手造一个你自己的——哪怕先造一个能跑通「声明式 + 调谐循环 + 简单调度」的最小版本,你对「系统设计」的认识,也会从此上一个台阶。
祝造轮子愉快。