专业书籍精读 · SRE · 第 8 章
Site Reliability Engineering · Ch 8 · Dinah McNutt · Google · 2016
你手机里那个 App,昨天还是 3.2.1,今天变成了 3.2.2。从工程师敲完最后一行代码,到新版本真的跑在你手机上,中间隔着一整套动作:编译、打包、测试、分批推送。Google 这本书的第 8 章讲的就是这段路——而它的立场很鲜明:这不该是「谁有空谁来」的收尾杂活,它是一门有专职工程师、有原则、有工具的学科,叫「发布工程」。
把发布想成药厂生产。写代码像研发出配方,发布像把配方变成一板一板的药片。药厂最看重的从来不是「今天产量多高」,而是三件事:同一张配方,换哪条产线、隔多久再做,做出来的药片必须一模一样;每一批都能追溯是谁、用哪批原料、哪天做的;真出了问题,能精确召回那一批,而不是全厂停产。软件发布要的,恰好是同样这三件事。
老办法是:到了发布日,某个最熟的人登上服务器,按一份写在脑子里的步骤敲一遍。麻烦有三层:换台机器就做不出同样的东西(他电脑上恰好装了个别人没有的库,于是「在我这儿明明能跑」);半年发一次、一次夹带几千处改动,出事根本不知道是哪一处干的;想退回上一版只能从头再造一遍,几十分钟起步,造出来的还未必和上次那个一样。
① 自助——中央团队只造工具、不当守门人,各团队按自己的节奏自己发;不然那个中央团队就是全公司的排队瓶颈。
② 小步快跑——发得越勤,每次夹带的改动越少;真出事时嫌疑犯就那么几个,一眼揪出来。
③ 自带全套原料的密闭厨房——编译时不许用「这台机器上现成的」任何东西,用到的工具、材料统统写死版本、跟着源码一起存。于是同一份源码,今天在这台、三个月后在那台,烤出来的是同一炉面包。好处很实在:线上出急事,你能回到三个月前那一版、只补上那一处修复,而不必把这三个月里其他人的改动全带上去。
④ 门禁与台账——谁能改代码、谁能批准上线、谁能推到生产,全写进工具里管着,每一步自动留痕。
真正把线上搞挂的常常不是程序本身,而是配置——那些开关、参数、地址。这一章的忠告是:把配置也当成货来管,一样打包、一样编版本号、一样能单独退回。把配置和程序捆在一起发最省事,但改一个开关就得重发整个程序;让程序运行时去外面读最灵活,可那样「此刻生效的是哪一份」就没人说得清了。
发布工程要的不是「发得快」,而是发得可复刻、可追溯、可单独退回:同一份源码在任何机器上都造出同一个程序,配置和程序各有各的版本号,谁批准了什么全有台账——快,是这些做到之后自然来的结果。诚实说一句代价:这套东西前期要专门投人投工具,小团队照搬 Google 的规格并不划算,能搬的是原则而不是编制。
想进到具体机制、分支模型和示意图? → 切到精读版
SRE 第 8 章把「发布」从项目末尾的一次性操作,抬成一门有专职工种、有明确原则、有专用工具链的工程学科。四条哲学——自助、高速度、密闭构建、策略强制——加起来指向同一个目标:让「从源码到生产」这条路径变得可复现、可审计、可独立回滚。最反直觉的一课是:发布的地基不是「发得多快」,而是「同一份源码能不能在任何机器、任何时候构建出同一个二进制」;而事故里最被低估的一环,是配置该怎么发。
dev / canary / production。作者 Dinah McNutt 是 Google 的发布工程师;本章位于 SRE 书 Part II「原则」,上承第 7 章「Google 自动化的演进」,下启第 9 章「简单性」。它与第 3 章「拥抱风险」、第 4 章 SLO 直接咬合——错误预算决定你敢不敢发,发布工程决定你能不能安全地发。对应现实里的 CI/CD 流水线、Bazel、容器镜像仓库、Spinnaker / Argo,以及后来的软件供应链安全。
多数公司的「发布」长这样:项目末尾,某个最熟悉环境的人按一份半成文的步骤,把包传上去、改几个配置、重启服务。团队小的时候这能跑,规模一上来就必然崩。Google 的量级给了个参照:2016 年公开的数字是单一仓库超过 20 亿行代码、每个工作日约 4 万次提交(Potvin & Levenberg, CACM 2016)——在这个量级上,手工发布连「今天该发哪些提交」都数不清。
崩在四个地方,每一个都对应本章的一条对策:
不解决的下场,这本书在引论里给了数字:SRE 发现约 70% 的线上故障源自对运行中系统的变更——发布路径本身就是可靠性的头号风险面。
本章开门见山:发布工程是一门独立的工程学科,不是谁的兼职杂活。Google 的发布工程师要同时懂源码管理、编译器与构建配置语言、自动化构建工具、包管理器与安装器,并和开发工程师、SRE 一起在项目开始时就定下「这个服务怎么构建、怎么发、怎么回滚、配置怎么管」。反直觉正在这里:多数团队把发布当成开发后的收尾动作,本章的立场却是发布方式属于架构决策——发布工程不是事后才想的事,开头就拨资源,比等系统长大了再回头改造便宜得多。
这四条是本章骨架,彼此咬合,缺一条另外三条就漏气。
① 自助(self-service)。中央发布团队负责做工具、定最佳实践,不负责替所有人按发布按钮。理由很朴素:任何需要人工介入的中心环节,在几千个服务的规模下都会变成排队瓶颈。
② 高速度(high velocity)。面向用户的软件要频繁发,理想形态是 Push on Green——只要全部测试通过,就把这一版推上去;Google 有团队每小时构建一次,再从中挑一版部署。频繁发布的真正收益不是「新功能来得快」,而是相邻两版差异小:出问题时嫌疑提交从几千个缩到几十个,定位与回滚都变便宜。
③ 密闭构建(hermetic build)。构建对「构建机上装了什么」不敏感——编译器、库、工具链统统锁到具体版本、与源码一起纳入版本控制,构建过程自给自足。收益兑现在两个场景:复刻(拿一个月前的 revision,构建出与当时线上完全一致的二进制)与热修(回到那个 revision,只 cherry-pick 一个补丁,不把这一个月里别人的改动一起带上生产)。
④ 策略与流程的强制执行(enforcement)。把「谁能做什么」从口头约定变成工具里的门控操作(gated operation):谁能批准代码变更、谁能创建发布分支、谁能批准一次 cherry-pick、谁能推到生产、谁能改构建配置,各自挂一份 ACL。副产品同样重要——自动生成的审计轨迹,让每次发布都能回答「这个二进制含哪些提交、谁批的、何时上的」。
分支模型只有两条规则:所有代码都提交到主干最新处(即主干开发,trunk-based development);发布不从主干直接发,而是从某个 revision 拉一条发布分支,分支上的改动永不合并回主干——要修 bug,先修在主干上,再把那个提交 cherry-pick 到分支。
为什么这么设计?「从主干最新处直接发」意味着发布内容随时在变——你原本要发的那一版,等构建那一刻主干上又多了几十个别人的提交。拉分支等于把发布内容冻结在一个确切 revision 上;单向 cherry-pick 则保证修一个 bug 就只带进这一个修复。这也正是密闭构建兑现价值之处:在分支上重新构建,出来的二进制与当初那个逐字节相同。
测试是两道:持续集成在每次提交后对主干跑单测,快速发现被改坏的构建;发布时在分支那个 revision 上重跑全部单测——这一遍不只为找 bug,更为生成一条与本次发布绑定的审计记录。
构建产物用 Google 的包管理器 MPM(Midas Package Manager) 打成包:二进制、数据与必要配置装进同一个带版本的单元,由名字 + 版本 + 构建 ID 唯一标识,并签名以证明来源可信。关键设计是标签:可以往某个包版本上贴 dev、canary、production 这样的名牌,而且标签可以移动。于是「生产现在跑的是哪一版」不再靠人记,而是一次查询;发布与回滚也简化成把标签挪到另一个包版本上,不必重新构建任何东西。今天你在 Kubernetes 里坚持用镜像 digest 而不是 latest,就是同一套思想的现代版本。
Rapid 是 Google 的自动化发布系统:用蓝图(blueprint)描述项目的构建目标、测试目标与部署方式,自动完成创建发布分支、密闭构建、并行跑测试、生成并签名 MPM 包、贴标签这一整串动作,作业跑在 Borg 上。Sisyphus 则是 SRE 开发的通用滚动发布框架:把一次 rollout 定义成一串任务,从「一把推全部」到「跨集群分批、每批之间烘焙观察、先金丝雀」都能表达。
分工很清楚——Rapid 造出可信制品,Sisyphus 决定它以什么节奏铺到生产。这个分离本身就是可搬的经验:制品的正确性与铺开过程的风险控制是两个问题,别揉进同一个脚本。
本章直白地说:配置管理是出了名的、不易察觉的不稳定来源。难点在于配置与二进制的生命周期不一致——二进制按发布分支冻结,配置却常想随时改。书里列了四种方案,本质是在耦合度这条轴上取点。
四种放法的具体代价见下一节的表 1。这里只点最要紧的一条:本章推荐的是把配置单独打成配置包——即把密闭与版本化那套原则同样施加给配置,让二进制与配置各有版本、各自发布、各自独立回滚。今天的 Helm chart、Kustomize 与 GitOps,思想原型都在这儿。
表 1 · 发布配置的四种放法(本章原文分类)
| 方案 | 改配置要重发二进制吗 | 能否独立回滚 | 主要代价 | 适合 |
|---|---|---|---|---|
| 配置放主干 head | 不用 | 可以,但版本对应关系模糊 | 配置漂移:分支二进制配主干配置 | 早期做法;配置与二进制耦合弱的项目 |
| 与二进制同包 | 要,走完整流程 | 与二进制一起回滚,不能单独退 | 灵活性差,改一个开关成本高 | 配置文件少、极少变动 |
| 独立配置包 | 不用 | 能,且版本明确 | 要多维护一条发布流程 | 本章推荐的默认形态;对应今天的 Helm / GitOps |
| 外部存储运行时读 | 不用,且立即生效 | 取决于外部系统自己的版本机制 | 脱离发布流程的版本与审计 | 限流阈值、功能开关等必须动态生效的配置 |
表 2 · 发布节奏:三种档位的真实代价
| Push on Green(全绿即发) | 发布火车(固定周期) | 大版本(季度 / 半年) | |
|---|---|---|---|
| 每版夹带变更 | 几个到几十个提交 | 数百个 | 数千个 |
| 出事时定位 | 嫌疑范围极小 | 要二分查找,几小时 | 大海捞针,可能几天 |
| 回滚粒度 | 退一版 ≈ 退几个提交 | 退一版 = 退掉一整周功能 | 基本退不动,只能往前修 |
| 前置条件 | 测试强到敢当唯一的门 + 金丝雀 + 秒级回滚 | 较完整的回归测试 | 人工验收 + 发布窗口 |
| 适合 | 服务端、可灰度可回滚的系统 | 移动端、需商店审核 | 装机版软件、嵌入式固件 |
表 3 · 分支策略:为什么 Google 选「主干 + 单向发布分支」
| 直接从主干 head 发 | 主干 + 单向发布分支(Google) | 长期分支互相合并(GitFlow 式) | |
|---|---|---|---|
| 发布内容 | 随时在变,构建那刻才定 | 冻结在一个确切 revision | 取决于合并了什么,难说清 |
| 夹带无关变更 | 高 | 低:只 cherry-pick 单个修复 | 高:一次合并带进整段历史 |
| 热修成本 | 低,但会顺带发布别人的改动 | 低:回到分支 rev + 一个补丁 | 高:先解决合并冲突 |
| 分支维护成本 | 无 | 低(分支短命、单向) | 高,且随分支存活时间陡增 |
| 前置条件 | 极强的 CI 与灰度 | 密闭构建(否则重建不出同一个二进制) | 无特殊要求 |
小团队怎么裁剪?密闭构建与配置独立版本化投入产出比最高,几个人的团队也该做(固定基础镜像 + 依赖锁文件就能拿到大半);自助在人少时天然满足;策略强制可从「生产部署要求一次代码评审 + 留下谁批准的记录」这条最小规则起步。真正不该照搬的是编制——专职发布工程团队是几千个服务规模上的答案。
这一章的价值,在于把今天所有 CI/CD 实践背后的判据说清楚了。拿它当一张体检表逐条问自己的流水线:能不能拿三个月前的 commit 复刻出当时那个二进制(密闭构建)?能不能一句话说出生产在跑哪个制品、含哪些提交、谁批准的(标签 + 审计)?改一个超时参数要不要重发整个程序(配置包)?发布按钮握在中央团队还是各团队手里(自助)?
本章那四个内部工具今天都有公开的对应物:Blaze → Bazel、MPM 包 + 标签 → 容器镜像 + digest + 镜像仓库、Rapid → GitHub Actions / Cloud Build 之类的构建流水线、Sisyphus → Spinnaker、Argo Rollouts 这类渐进式交付工具。面试里被问「你们怎么发布」,能答到「构建可复现、配置独立版本化、发布与铺开分离、每一步有 ACL 与审计」,就已经是这一章的水平。
20 亿 行代码、每个工作日约 4 万 次提交——在这个量级上,主干开发 + 自助发布不是偏好,是唯一撑得住的形态。R. Potvin & J. Levenberg, CACM, 2016 ↗① 一句话:发布工程是一门独立学科,目标是让「从源码到生产」可复现、可审计、可独立回滚——发得快是结果,不是目的。
② 四条原则:自助(中央做工具不做守门人)、高速度(Push on Green,缩小每版差异)、密闭构建(不依赖构建机环境)、策略强制(门控操作 + 自动审计轨迹)。
③ 分支模型:主干开发 + 从某个 revision 拉发布分支,分支永不合并回主干,修复靠单向 cherry-pick——冻结发布内容,且不夹带无关变更。
④ 测试两道:提交后对主干跑 CI;发布时在分支 revision 上重跑全部单测,顺带生成审计记录。
⑤ 打包:包由名字 + 版本 + 构建 ID 唯一标识并签名;可移动的标签让「生产在跑哪一版」成为一次查询,回滚 = 挪标签。
⑥ Rapid 造制品,Sisyphus 铺开——制品的正确性与铺开的风险控制是两个问题。
⑦ 配置是不易察觉的头号不稳定源;四种放法在耦合度上取点,独立配置包是本章推荐的默认形态。
⑧ 发布工程不是事后才想的事:开头就拨资源比长大后改造便宜;今天的对应物是 Bazel、镜像 digest、GitHub Actions、Spinnaker / Argo,以及 SLSA 那层供应链安全。