专业书籍精读 · 持续交付 · 第 11 章
Continuous Delivery · Ch 11 · Jez Humble & David Farley · 2010
你打开的每个 App,背后都跑在成千上万台机器上。前面几章讲的是「怎么把新版本推上这些机器」,这一章往下挖一层,讲一件更基础的事:这些机器本身,是怎么来的、由谁改、坏了怎么办。
把这些机器想成一家连锁餐厅的后厨。理想状态是:总部有一张后厨图纸,每家分店严格照图纸建——灶台在哪、抽油烟机什么型号、消防器材放哪一格,全都写死。新开一家店,照图纸建就行;哪家店的灶台坏了,照图纸再建一间,比修快。
老办法是另一个样子:每家店的后厨由当地师傅凭经验搭,看着都差不多。第一年没事。第三年总部要统一换灶具,才发现每家店的后厨其实都不一样——有的插座位置不对,有的当年为图方便挪过一堵墙,而挪墙这件事谁都没记下来。
手工搭起来的服务器是被一点一点「养大」的:三年里有人临时装过一个软件包、有人为救急改过一行配置、有人开过一个端口忘了关。每一次都很小、都没记录。三年后它成了没人敢碰的活化石:还能跑,但谁也说不清它凭什么能跑,更没人能再造一台一模一样的。
于是有了那句著名的甩锅:「在我这儿是好的呀。」——多半不是谁撒谎,而是测试用的机器和线上跑的机器本来就不是一回事。
第一,把机器的样子写成一张清单:装哪些东西、每个配置项是什么值、哪些端口开着。清单和代码放进同一个仓库,谁改了、为什么改,全都留痕。
第二,只让机器人照清单动手,人不许自己上去改。这是全章最硬的规矩:想改线上的一个设置,就去改清单、让自动化推下去,不能自己登上服务器敲两下。
第三,机器人定期回来对账:清单说该有的没了就补回去,有人偷偷加了什么就抹掉。机器于是不会随时间跑偏。
后来还长出更狠的一招:与其修一台跑歪的机器,不如照清单新建一台、把旧的扔掉——像一次性餐具,脏了不洗,直接换。
最后是盯梢:给这一整片机器装上仪表盘、摆在团队看得见的大屏上,哪里红了,路过的人一眼就知道。
把一切写成清单,前期投入不小;而且总有些老古董系统压根写不进清单(当年怎么装的已经没人知道了),只能继续当活化石供着。
别再用手养机器。把每台机器该长什么样写成清单、存进版本库,只让自动化照着清单去建、去改、去纠偏;人一旦手工登上去动一下,这台机器就再也无法被复制。
想看清单具体怎么写、期望状态怎么收敛、云和虚拟化在这里改变了什么? → 切到精读版
你以为环境是「运维的事」,其实环境是软件的一部分。这一章把 Ch2「一切纳入版本控制」从代码推到机器上:基础设施的每一处配置——操作系统、中间件、网络设备、监控——都必须被建模、被版本化,并且只能由自动化过程创建和修改。判据只有一条:随时能从版本库把整套环境重建出来。任何人手工登上生产机改一行,都是缺陷而不是运维手段。
本章是《持续交付》Part III「交付生态」的开篇。它把 Ch2「配置管理」的主张从应用代码推到机器与环境上,同时接住 Ch10「部署与发布」留下的问题:蓝绿也好、金丝雀也好,你到底部署到哪儿去?下启 Ch12(数据)与 Ch13(组件与依赖)。现实对应物是今天人人在用的 Terraform、Ansible、容器镜像、Kubernetes、GitOps。
先摆 2010 年前后一家典型企业的实况:申请一套新环境要走工单,从提交到拿到机器动辄数周;机器由运维手工装好,交付前照一份 Word 文档逐条勾;此后三年里,为救急改过的配置、临时装过的包、临时开过的端口,没有一处留在版本库里。三个后果互相加固:
价签是公开的:2012 年 8 月 1 日,Knight Capital 的技术人员把新版本手工部署到 8 台服务器、漏了其中 1 台,且无人复核——公司在 45 分钟内亏掉逾 4.6 亿美元(详见 §6)。一次手工操作、一台不一致的机器,就是全部原因。
本章开篇不谈技术、先谈人——因为持续交付最容易被读成「开发绕开运维、自己往生产上推」,那是走反了。书里摆出运维团队的四项真实诉求,后面所有技术手段都要同时满足它们:
RTO 内恢复、丢失数据不超过 RPO。关键是恢复流程必须被真正演练过——从没跑过的恢复方案等于没有。本章最硬的一条规矩:对测试和生产环境的任何改动,都必须通过自动化过程进行,没有第二条路。落地是两件事——访问控制:把环境锁起来,谁(包括运维自己)都不能登上去手工改;变更通道:想改就改版本库里那份配置,让自动化应用到所有环境。
为什么这么极端?因为「能不能复制」是非黑即白的:只要有一处改动没进版本库,整台机器就不可复制了——不存在「95% 可复制」。附赠品也实在:变更自动留下审计记录、环境可随时重建、测试与生产的差异被压到只剩配置文件里的几个值。
本章把机器的一生拆成两半。前半是置备(provisioning)——从空白到可用,三条路:手工装(不可重复,只在没办法时用);自动化远程安装(网络启动、按应答文件无人值守装完,即 PXE + Kickstart 那一套);虚拟镜像(从做好的模板克隆,最快)。
后半是持续管理——机器活着这几年怎么保持正确,这是技术上最有价值的一段。用 Puppet、CfEngine、Chef 这类工具,你不写「怎么做」的步骤,只写「该是什么样」的期望状态(这个包要装上、这个配置文件内容是这样、这个服务要在跑),工具比对实际与期望、只补差的那部分。两个性质使它成立:幂等——同一份定义反复执行结果一样,所以能放心每 30 分钟跑一次;收敛——每跑一次就更靠近期望状态一步。配置漂移于是被持续纠回去。
这里还有一条被反复引用的判据:重建一台机器,应当比修好它更便宜。一旦成立,运维的心态就变了——不必在凌晨三点对着一台跑歪的机器做取证式排查,扔掉、按定义重建即可。这也是后来「不可变基础设施」的思想源头。
这是最容易被跳过、却在真实项目里最常出事的一节。中间件(Web 服务器、应用服务器、消息队列)同样要被管理起来,书里的三个追问在选型和接手遗留系统时都能直接用:配置怎么给?能否从版本库里的文本文件驱动——只能靠图形界面点、点完导不出文本的,就进不了自动化,这是硬性选型判据。状态放在哪?它往本地磁盘写了什么(消息、会话、缓存、日志),这决定你能不能随手销毁重建这台机器。怎么装、怎么升级、怎么把应用部署进去?这三件事能否全部脚本化、无人值守完成。
再往外一层是基础设施服务:DNS、防火墙规则、路由与交换机配置、负载均衡器、SMTP。它们同样是「配置」,同样该进版本库、由自动化推送——现实里这些常年纯手工,于是成了流水线上最后一块黑箱。书里还提醒多宿主(multihomed)系统:机器接在多个网络上时,环境定义必须把网络拓扑本身写进去,否则「同样的配置」在不同网段上行为不同。
前面所有主张在 2010 年都撞上同一个物理障碍:机器很贵、很慢。虚拟化把它拆了——环境变成可以从基线镜像(baseline image)克隆出来的东西,几分钟拿到一套,用完销毁。书里由此推出两个用法:
2 小时的套件切到 10 台上并行,大致压到十几分钟(不会正好是十分之一,切分不均与环境启动都有开销)——这直接决定了验收测试能不能留在流水线里。再往前一步是云。书里分成 IaaS(租虚拟机和网络,机器还是你管)和 PaaS(只交代码,平台管其余),强调它最大的价值是按需弹性:需要一百台跑一小时测试,就开一百台、跑完关掉。同时——这是本章很值得称道的诚实——书里把当时对云的批评原样列出:数据放在别人手里的安全与合规风险(尤其数据落在哪个司法辖区)、供应商依赖与锁定、以及并非所有负载上云都更便宜。结论是「一种尺寸不必适合所有人」:弹性需求强的放上去,其余留下,混合是常态。
最后一节讲监控,逻辑上是闭环——你既然声称环境处于某个状态,就得有办法证明它确实处于那个状态。书里给了四层:
表 1 · 三种置备方式:造一台机器的成本、可重复性与适用场景
| 手工安装 | 自动化远程安装 (PXE + Kickstart 类) | 虚拟镜像 / 模板 (后来的容器镜像) | |
|---|---|---|---|
| 首次投入 | 几乎为零 | 中——搭安装服务器、写应答文件 | 中——建镜像制作与分发流程 |
| 造一台机器 | 数小时到数天,每次都不太一样 | 几十分钟,无人值守 | 分钟级甚至秒级 |
| 可重复性 | 无——取决于当天是谁在装 | 高,由应答文件与配置定义保证 | 最高——字节级一致 |
| 审计 | 靠人写文档,等于没有 | 定义在版本库里,天然留痕 | 镜像有版本号,可追到构建输入 |
| 主要代价 | 不可复制、不可审计 | 装完仍会漂移,需持续纠偏 | 镜像本身要被版本管理,否则镜像库就是新的雪花堆 |
| 适用 | 只用于无法自动化的遗留设备 | 物理机、从裸机起步的场景 | 默认选择;虚拟化 / 云 / 容器 |
表 2 · 两种「保持机器正确」的范式:收敛式(本章的方案)vs 不可变(书成之后的主流)
| 收敛式配置管理 Puppet / CfEngine / Chef / Ansible | 不可变基础设施 镜像 + 替换(容器 / AMI) | |
|---|---|---|
| 怎么改 | 改期望状态定义,工具把现有机器拉回去 | 构建新镜像,换掉整台机器,旧的销毁 |
| 漂移 | 会发生,靠定期重跑纠偏(如每 30 分钟) | 根上不可能发生——机器从不被修改 |
| 回滚 | 改回定义、等下一次收敛 | 换回上一个镜像,秒级 |
| 硬前提 | 定义必须幂等;工具本身要可靠 | 状态必须全部挪到机器之外(数据库、对象存储、外部日志) |
| 不适合 | 机器极多、变更极频繁时纠偏窗口成风险 | 有状态的遗留系统、无法容器化的重型中间件 |
| 今天怎么选 | 物理机、遗留系统、既有机器上的增量治理 | 云原生的默认答案;也常两者混用(用配置管理构建镜像) |
表 3 · 环境放哪儿:自建 vs IaaS vs PaaS(判据与批评均来自本章)
| 自建机房 | IaaS | PaaS | |
|---|---|---|---|
| 你管到哪一层 | 机房、硬件、系统、中间件、应用 | 系统、中间件、应用 | 只管应用 |
| 拿到一套新环境 | 走工单,数周量级 | 分钟级,按需开关 | 分钟级 |
| 弹性 | 按峰值买机器,平时闲置 | 按需伸缩:一百台跑一小时,跑完关掉 | 平台自动伸缩 |
| 主要代价 | 资本开支高、扩容慢 | 数据出境与合规、供应商依赖;并非所有负载上云都更便宜 | 锁定最深——应用要按平台规矩写,迁走成本高 |
| 适用 | 合规约束硬、负载平稳、已有重资产 | 多数场景,尤其测试环境的弹性需求 | 标准 Web 应用、团队不想碰基础设施 |
三张表之外,本章真正的总判据只有一条,可以当场自查:把任意一台生产服务器销毁,你能不能只用版本库里的东西、在可接受时间内把它一模一样造回来?能,说明前面这些你做到了;不能,那么无论买了什么工具、上了什么云,你养的都还是雪花。
今天几乎整个云原生工具链都是这一章的兑现:Terraform / CloudFormation 把网络、负载均衡、DNS 这些「不是服务器」的东西也写成了代码(正是第四节的诉求);Ansible / Puppet / Chef 是「期望状态 + 幂等 + 收敛」的直接后代;容器镜像把「虚拟镜像置备」推到极致;而 Kubernetes 的调谐循环(reconciliation loop)——你声明期望状态、控制器不断把实际状态往它上面拉——几乎就是图 2 那条收敛曲线的工业化版本。GitOps 更把「版本库是唯一事实源」写成了产品形态。
面试与架构评审里这也是高频考点。被问到「你们的基础设施怎么管」,能拿分的答法不是报工具名,而是几个事实:生产机上一次被人手工登录修改是什么时候?一台生产机销毁后能否只凭版本库重建、要多久?中间件和防火墙规则在不在版本库里?备份恢复上一次真正演练是什么时候?
① 一句话:环境是软件的一部分——基础设施的每一处配置都要被建模、版本化,且只能由自动化过程创建与修改。
② 唯一的硬判据:能不能只凭版本库把任意一台机器一模一样地重建出来。做不到,你养的就是雪花服务器。
③ 最硬的规矩:禁止手工登机改配置。一次手工改动就让机器与版本库永久分叉,且下次自动化部署会把它悄悄覆盖——「幽灵故障」的常见来源。
④ 起手是运维的四项诉求(文档与审计、异常告警、服务连续性 RTO/RPO、用他们熟悉的技术):自动化恰恰是审计最省事的实现方式,合规与自动化不对立。
⑤ 置备三选一:手工 / 自动化远程安装(PXE + Kickstart)/ 虚拟镜像;持续管理靠声明式期望状态 + 幂等 + 定期收敛纠正漂移。而重建一台机器应当比修好它更便宜——后来「不可变基础设施」的思想源头。
⑥ 别漏掉「不是服务器」的部分:中间件、DNS、防火墙、负载均衡器同样要进版本库;中间件选型先问「配置能否从文本驱动、状态放在哪」。
⑦ 虚拟化与云让「重建比修复便宜」第一次成立,并解锁一次性环境上的并行测试;书里也诚实列出云的代价:合规、锁定、并非总更便宜。
⑧ 监控闭环:采集 → 汇聚 → 信息辐射器 → 行为驱动的监控,让「环境是否正确」有自动化的答案。落到今天:Terraform / Ansible / 容器镜像 / Kubernetes 调谐循环 / GitOps,都是这一章的直系后代。