第 11 章 · 收尾:一张可复用的「分布式系统设计清单」

一句话结论:把前面十章的所有设计决策收拢成一张表,就是你「用 Go 造自己的系统」时的检查清单。每一个决策点都不是「唯一正确」,而是「你要基于自己的问题形状,主动选一个并承担它的代价」。


1. 一张贯穿全文的决策地图

#决策点可选方向K8s 的选型选它的核心理由章节
1编排模型命令式脚本 / 声明式平台声明式只有「持久化的期望状态」才能支持自愈1、2
2状态驱动方式edge-driven / level-drivenlevel-driven对「终态」而非「事件」,天然抗丢失、抗重复3
3状态存哪关系库 / 分布式 KVetcd匹配「强一致+小对象+watch」的问题形状4
4并发控制悲观锁 / 乐观并发乐观并发(resourceVersion)读多写少冲突罕见,乐观并发代价极低4
5读的时效每次强一致 / 缓存+最终一致缓存+最终一致读容忍最终一致,写才强一致4
6抽象粒度调度容器 / 调度 PodPod反映「共享组」的真实协作形态5
7对象关联硬编码地址 / 标签选择器label/selector用「特征」而非「地址」,应对对象易变5
8调度策略全局最优 / 启发式过滤+打分用「够好」替代「不可解的最优」6
9决策与执行合一 / 分离中心决策+边缘执行中心要全局视角,边缘要本地可靠6
10子系统实现焊死内核 / 插件接口CRI/CNI/CSI隔离「变化点」,核心精简生态自由9
11扩展方式改核心 / CRD+OperatorCRD+Operator复用「控制循环」范式,扩展在用户空间8
12接口边界并集 / 最小公约数最小公约数接口只收「共同契约」,私有特长走扩展点9
13特性演进一步到位 / 分级门禁alpha→beta→GA用分级+开关,把「快」「稳」给不同用户10

2. 三条「第一性原理」——比任何具体选型都重要

  1. 声明式 + 控制循环,是「自愈系统」的通用范式。 把「期望状态」和「实际状态」分开,用一个永不停止的循环去对齐它们。这个范式不只能管容器,还能管数据库、管网络、管任何「需要持续保持某种状态」的领域。 你造任何系统,先问自己:它的「期望状态」是什么?「调谐循环」在哪?

  2. 用「够好 + 持续纠偏」替代「一次算准」。 调度是 NP-hard、系统永远在变、决策永远可能错。不求一次做对,但求持续逼近——快决策 + 慢纠偏,比「试图一次算准」可靠得多。

  3. 抽象要「跟着真实需求长出来」,接口要划在「真正的变化点」上。 过早抽象和拒不抽象都是坑,中间的平衡叫「等第二个用例出现」。抽象的价值不是「能力更强」,而是「边界更清晰、把不可能关在门外」。


3. 用 Go 造一个「迷你 K8s」的最小骨架(思路级)

不写完整代码,只给你「K8s 的核心概念在 Go 里落到哪些原语」的映射,帮你打破「这是别人家的系统」的隔层:

K8s 概念Go 的落点
资源对象(Pod/Deployment)struct + JSON/YAML tag,存 etcd 的 key
声明式 APIHTTP server + Apply(obj) 只做「持久化期望状态」
控制器/调谐循环for { key := <-queue; reconcile(key) } 的 goroutine
level-drivenreconcile 里每次都「读期望、读实际、算差值」,幂等
乐观并发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 的设计者做出相似的思考。

你现在脑子里已经有了一张完整的图:从「为什么声明式」到「控制器怎么循环」到「状态怎么一致」到「怎么调度」到「怎么扩展」到「怎么演进」。剩下的,就是动手造一个你自己的——哪怕先造一个能跑通「声明式 + 调谐循环 + 简单调度」的最小版本,你对「系统设计」的认识,也会从此上一个台阶。

祝造轮子愉快。