fedorthinks

速成课 · No. 20

有两个想法改变了软件从开发者笔记本走到真实用户手中的方式。第一,把从代码到生产环境的整条路自动化,让发布变得无聊而不是吓人——这就是 CI/CD。第二,把应用连同它需要的一切一起打包,让它在任何地方都跑得一模一样——这就是容器。两者结合,把「在我机器上能跑」从借口变成了正经的计划。

21 个概念 · 每个都有图 · 结尾有速查表

§ 01

写代码只是工作的一半;把它安全地送到用户面前是另一半。在很长一段时间里,后一半都缓慢、靠手工、令人胆战心惊——而理解它为什么如此,正是理解一切修补手段的关键。

课程地图:七组共 21 个概念

一张地铁线路图:先看整条线路,再一站一站地坐过去。

这门课跟着一个改动,从笔记本一路走到用户手里。先讲难题:为什么手工 deploy 吓人。然后是 CI(检查每个改动)、CD(把它送到用户那里)、pipeline(完成这一切的机器)、容器(到处都是同一个环境)、编排(很多容器跑在很多服务器上),以及安全 deploy(怎样发布才能随时撤销)。

The problemDeployWorks on my machineBoring releasesCIContinuous integrationShift leftGreen main branchCDContinuous deliveryDelivery vs deploymentArtifactPipelinePipeline stagesFail fast · red/greenContainersContainerDockerImage vs containerOrchestrationKubernetesDesired stateRolling updateSafe deploysRollbackCanaryBlue-greenInfrastructure as code
七组概念——各章就按它们来。

每个概念怎么读

一本护照:每个人的栏目都一样,所以很容易比较。

下面每个概念的结构都相同:它的英文名称(你在文档和工作中会遇到的术语)、一幅生活中的画面、一张图,以及一张有若干栏目的卡片——问题或「是什么」、思路、例子、代价或陷阱,以及相关概念。整门课只有一个大想法:交付软件应该是无聊的、自动化的、处处一致的。

Deploy——为什么手工做很吓人

一位外科医生在午夜里凭记忆徒手做一台精细的手术,又累又困——手一抖,病人就垮了。这就是手工的生产环境 deploy。

所谓 deploy,不过就是「让新版本上线」。过去的做法,是一个人按部就班地手工操作:把文件复制到服务器,敲几条命令,然后祈祷。这又慢又容易出错,风险大到团队都很少 deploy——于是每次 release 都变得庞大,从而风险更高。靠手工去做,它就成了一周里最让人紧张的时刻。整个 CI/CD 领域存在的意义,就是把那个时刻变得无聊。

Deploy by handcopy · run · prayDeploy rarelyit's too scaryHuge releaseweeks of changesriskyso each one is bigeven riskierthe vicious circle of manual deploys
恶性循环:手工 deploy 吓人 → 团队很少 deploy → release 越来越庞大 → 更吓人。
TermDeploy· release, rollout, 发布、上线
是什么
让应用的新版本对用户上线。
例子
手工 deploy:SSH 登进服务器,复制文件,重启进程——然后祈祷。
陷阱
靠手工又慢又有风险——所以很少做,每次 release 都变得庞大。
关联

由 Continuous Delivery 和 Pipeline 自动化;由 Rollback、Canary、Blue-green 变得安全。

Works on my machine——经典陷阱

一份食谱在你自家厨房做出来完美无缺,到了别人厨房却失败了——烤箱不同、面粉不同、海拔不同。食谱并不是故事的全部。

在开发者笔记本上跑得好好的代码,到了生产环境常常崩掉,因为两个环境不一样——版本不同、设置不同、库不同、操作系统不同。**「在我机器上能跑」**正是这一整类 bug 的笑点,它们都源于这些差异。笔记本和服务器不是同一个地方,所以「在我构建它的地方能跑」对它实际运行的地方什么都保证不了。

Developer laptopProduction serverApp codeApp codeNode 20Node 16≠libssl 3libssl 1.1≠macOSLinux≠same codesame code, different environment → a bug only production has
同样的代码,不同的环境:另一个运行时版本、另一些库、另一个操作系统——于是出现一个只有生产环境才有的 bug。
ConceptWorks on my machine· environment drift, 环境漂移
是什么
一个只在另一个环境里出现的 bug,因为两个环境不一样。
例子

笔记本上是 Node 20,服务器上是 Node 16——用了较新语言特性的代码,只在生产环境里报错。

陷阱
代码不是故事的全部:版本、设置、库,以及它周围的操作系统,也都有影响。
关联

由 Container 根治——应用自带环境——也靠 Artifact——你测试的就是你发布的。

Boring releases——发布应该是无聊的

一部电梯,你一天进出上百次都不会多想一下——因为它是自动化的、经过测试的、安全的。你绝不会去坐一部每趟都得手工小心操作的电梯。

这门课里所有内容的目标,就是让交付软件变得无聊:自动化、经过测试、可重复到这种地步——你一天里多次 release 小改动,却毫无波澜。这有点反直觉:deploy 得 越 勤,反而 越 安全——每次 release 都很小(small batches),所以一旦出问题,原因一目了然,修起来也很轻。罕见、庞大、手工的 release 才是危险的那种。「无聊」是一次 deploy 所能赢得的最高赞美。

Rare and bigRelease 1200 changes · scaryRelease 2200 changes · scarySmall and oftenv1v2v3v4v5v6v7v8v9v10tiny diff → obvious cause → small fix
上:两次庞大又吓人的 release。下:十次小的——哪一次出问题,一眼就知道。
PracticeBoring releases· small batches, frequent small releases, 小步频繁发布
问题
一次大 release 里有几百个改动,出问题时没人知道是哪一个干的。
思路
经常发布小改动——自动化地、毫无波澜地。
例子
不再一个月发一次——而是一天发好几次很小的 release,每次只有几行。
代价
需要自动化:没人能靠手工 deploy 得这么勤。
关联
只有靠 CI、Continuous Delivery 和 Rollback 才可能做到。

手工 deploy 又慢、又危险、又罕见——这反而让它更危险。目标恰恰相反:让交付软件自动化到、细碎到无聊的地步,一天里做很多次。

§ 02

CI/CD 的前一半,讲的是在问题一冒头的瞬间就抓住它——做法是在每个改动落地时自动 build 并 test 它——这样那份共享的代码库就始终能正常工作。

Continuous Integration(CI)——每个改动都被 build 和 test

一个拼写检查器,你每打一句话它就检查一句,当场标出错误——而不是一个校对员,一个月后等一切纠缠在一起时,才找出上百个错误。

Continuous integration(CI) 的意思是:每当有人提出一个改动,一个自动化系统就会 build 这个项目并对它运行 test——在几分钟之内完成,全程不经人手。如果这个改动让 build 坏掉或没通过某个 test,你立刻就会知道,而此时它还很小、还鲜活地留在你脑子里。CI 是一个常开的哨兵,在每一份贡献能惹麻烦之前就先检查它。

Commitpush / pull requestCI serverbuild + run tests✓ Passsafe to merge✗ Failauthor told in minutesevery change · automatically · within minutes
每个 commit 都进 CI 服务器:build 和 test。通过——可以安全合并;失败——作者几分钟内就知道。
PracticeContinuous Integration· CI, 持续集成
问题
一个有问题的改动,几周后才被发现,那时它已经和其他一切纠缠在一起了。
思路
每个改动一到,就自动 build 并 test。
例子

每个 pull request 上,一个 CI 服务(比如 GitHub Actions)运行 build 和 test,并在上面打一个绿色或红色的标记。

代价
你需要好的自动化 test——CI 只检查你的 test 检查的东西。
关联

尽早抓住问题(Shift left),保持 Green main branch;是任何 Pipeline 的第一个阶段。

Shift left——趁问题还小就抓住它

发现一块砖砌歪了,趁早改很容易;等在上面又盖了三层楼才发现,那就是一场灾难。早发现很便宜;晚发现很昂贵。

CI 的价值在于时机。一个 bug 在被引入的当下就被抓住,修起来不费吹灰之力——你清清楚楚知道改了什么。同一个 bug 几周后才被发现,埋在上面盖起来的一切之下,可能要花上几天才能理清。这就是 shift left 的想法——把发现问题的时间点在时间线上往左移,更靠近改动发生的那一刻。CI 把发现故障的时机从「很久以后,跟一切混在一起」挪到了「就是现在,孤立地」。

cost to fix a bugat commitnext dayweeks laterin productioncheapexpensiveCI moves discovery to the left — to the moment of the change
bug 发现得越晚,修复的代价越高。CI 把发现的时间点左移——移到 commit 本身。
ConceptShift left· catch it early, 尽早发现
是什么
尽可能早地发现问题——靠近改动发生的那一刻。
例子

commit 3 分钟后某个 test 失败了——你知道是哪一行干的。同一个 bug 一个月后在生产环境里出现——要查好几天。

陷阱
检查必须快:如果 CI 要跑一个小时,人们就不再等它了。
关联
CI 的核心价值;与 Fail fast 是同一种精神。

Green main branch——让 main 始终可用

一间共用的厨房,每个人走之前都必须收拾干净——这样下一个人随时都能用,而不是一堆要别人先去收拾的烂摊子。

CI 更深一层的目的,是让 main 分支——那个共享的、可信赖的版本——始终处于可用状态。一个改动只有在能 build 并通过 test 时才会并入 main,所以 main 永远不会变成一团堵住整个团队的烂摊子。这正是频繁、有把握地交付得以成立的原因:当 main 始终可用,你随时都可以从它发布。

main · always worksfeature ACI ✓feature BCI ✗blocked — fix firstmerged
分支 A 通过了 CI,合并进 main。分支 B 没通过 CI——修好之前一直留在外面。
PracticeGreen main branch· main always green, main 始终是绿的
问题
一个坏掉的 main 会堵住整个团队,让任何 release 都无法进行。
思路
只有 build 成功并通过 test 的东西才能进入 main。
例子
仓库里的一条规则:在 CI 检查全绿之前,「Merge」按钮一直是禁用的。
代价
有时你得等 CI,并在合并前先修好 test。
关联
由 CI 强制执行;让 Continuous Delivery 可以随时从 main 发布。

Continuous integration 在每个改动落地的瞬间就 build 并 test 它,趁问题还小就抓住它,让 main 分支始终处于可用状态。

§ 03

如果说 CI 确保每个改动都是好的,那么 CD 就负责把这个好改动自动送到用户面前——把 release 从一场手工仪式,变成一个按钮,甚至连按钮都不用。

Continuous Delivery(CD)——自动化的是发布,不只是测试

一条工厂流水线,并不止于质检,而是把合格的产品一路送到装货码头、装上卡车——没人用手搬箱子。

Continuous delivery(CD) 是 CI 的延伸:一个改动通过所有自动检查之后,系统还会自动为 release 做好准备,并且可以把它推到生产环境。那些手工的、容易出错的 deploy 步骤,变成了每次都以同样方式运行的代码。CI 证明改动是好的,CD 把这个好改动送到用户手里——不需要有人在半夜里小心翼翼地往服务器上敲命令。

CI: prove it's goodCD: get it to usersCommita changeBuild + testproved goodPackageartifactReleaseautomatedProductionusersthe deploy steps become code that runs the same way every time
CI 负责「改动是好的」,CD 负责「已经送到用户面前」。整条路都是自动化的。
PracticeContinuous Delivery· CD, 持续交付
问题
改动已经证明是好的,但发布仍靠手工——又慢又出错。
思路
发布步骤就是代码;每个好改动都自动处于随时可发布的状态。
例子
CI 跑绿之后,一个脚本构建软件包、存起来,再用一个按钮把它推到生产环境。
代价
你要把整条发布路径一次性写成代码,而且得信任你的自动化 test。
关联
建立在 CI 之上;发布的是 Artifact;运行在 Pipeline 里。

Continuous Delivery vs Continuous Deployment——有按钮还是没按钮

一个封好、写好地址、放在门边等着的包裹,对比一个已经交给快递员的包裹——都已准备好出发,但前一个要等你发话。

这里有个微妙的区别。Continuous delivery 指每个好改动都被自动准备到随时 可 deploy 的状态,但要由人按下按钮来发布。Continuous deployment 则更进一步:每个通过检查的改动都会自动发布到生产环境,完全没有按钮。两者都叫「CD」;区别在于最后一步是否由人批准。

Continuous deliveryChecks passReady to shipApprovea human clicksProductionContinuous deploymentChecks passReady to shipProductionautomatic, no button
上——delivery:由人批准最后一步。下——deployment:通过检查,直接上生产环境。
ConceptContinuous Delivery vs Continuous Deployment· CD 里的两个 D
是什么
Delivery——准备好发布,由人按按钮。Deployment——自己发布,没有按钮。
例子

银行在签字批准后按一下按钮发布(delivery);一个网络服务每个绿色 commit 都自己上线(deployment)。

陷阱
怎么选,取决于你有多信任自己的自动检查。
关联

两者都属于 Continuous Delivery;没有按钮时,Canary 和 Rollback 就更重要了。

Artifact——你发布的那个东西

一个封好、贴了标签的箱子,只打包一次,然后作为一个整体被运输、存放、送达——而不是每到一站都用散件重新组装。

pipeline 会产出一个 artifact——你的应用的一个构建好的、带版本号的、可直接运行的软件包。你只构建一次,这同一个 artifact 会依次经过 testing、staging 和生产环境,所以你测试的就恰恰是你发布的。如果每个阶段都重新构建,细微的差异就可能悄悄混进来。Build once, deploy everywhere——一个 artifact,构建一次,到处部署——消除了一整类「可是在测试里是通过的」这种意外。

Buildonceapp v1.4.2the artifactTestruns v1.4.2Stagingruns v1.4.2Productionruns v1.4.2what you tested is exactly what you ship — no rebuilds
只构建一次——v1.4.2 版本去 test,去 staging,去生产环境。沿途不重新构建。
TermArtifact· build once, deploy everywhere, 一次构建、处处部署
是什么
应用的一个构建好的、带版本号的软件包,可直接运行。
例子
app v1.4.2——同一个软件包依次经过 test、staging 和生产环境。
陷阱
每个阶段都重新构建,到达生产环境的就不再是你测试过的那个东西。
关联

Pipeline 的产出;对容器来说,artifact 就是 Image;让 Rollback 成为可能。

Continuous delivery 把每个好改动的发布,作为一个构建好的 artifact 自动化——无论是由人按下最后的按钮(delivery),还是它自己上线(deployment)。

§ 04

CI 和 CD 由一条 pipeline 来运行:这是一条自动化的装配线,一个改动从 commit 到生产环境都沿着它走,路上只要出问题就会停下。

Pipeline——自动化的装配线

一条汽车工厂的流水线:底盘依次经过焊接、喷漆、检验,每个工位做完自己的活才往下传——没有跳过的步骤,也没有人用手搬运。

pipeline 是一个改动在通往生产环境的路上所经过的一系列自动化阶段——通常是 build,然后 test,然后 deploy。一个 commit 从一头进去,如果一切顺利,另一头出来的就是一次上线的 release,中间没有任何人工步骤。pipeline 就是让 CI/CD 变成现实的那台具体机器:「把整条路自动化」真正落在这里。

pipeline (automated)CommitBuildcompile, packTestunit + e2eDeployto productionLivereleaseno human steps between the commit and the release
Commit → build → test → deploy → 上线的 release。框里的一切都由机器完成,而不是人。
ToolPipeline· CI/CD pipeline, 流水线
是什么
从 commit 到生产环境之间,一串定义好的自动化阶段。
例子

仓库里的一个配置文件描述了三个阶段:build → test → deploy;CI 服务在每个 commit 上运行它们。

陷阱
pipeline 也是代码:它需要维护,而且一条很慢的 pipeline 会拖慢整个团队。
关联

CI 和 Continuous Delivery 的家;由 Fail fast 守护;产出 Artifact。

Fail fast——红灯停,绿灯行

一条装配线上有一根拉绳,谁都可以拉,一出现缺陷就能让一切立刻停下——线上还有一盏红绿灯:绿灯,工作继续往下流;红灯,一切等待,直到问题被清除。

一条好的 pipeline 就是为 fail fast 而建的:如果在任何一个阶段 build 坏了或 test 失败了,pipeline 就停下,这个改动不再往前走。pipeline 用绿色(通过)和红色(失败)来说话。这个简单的信号,用一道毫不含糊的关卡取代了焦虑的人工判断。团队的规矩很简单:红灯时不在上面继续构建,红灯时不发布。

BuildgreenTestred: 2 failedDeploynever runsProductionuntouched✗ stop the linegreen = go onred = stop and fixnever build on red, never ship on red
test 变红了——流水线停了:deploy 根本不会运行,生产环境毫发无损。绿灯继续,红灯停止。
PracticeFail fast· red / green, 红 / 绿
问题
如果流水线不停下,一个有问题的改动可能一路滚到生产环境。
思路
任何一个阶段变红,pipeline 立刻停下;只有绿色才往下走。
例子
两个 test 失败了——deploy 阶段连启动都不会;团队先去修。
代价
不稳定的 test 会无缘无故让流水线停下——必须把它们修好。
关联
Pipeline 的安全属性;与 Shift left 是同一种精神。

pipeline 是从 commit 到生产环境的自动化装配线——build、test、deploy——它 fail fast,遇红就停,所以没有哪个有问题的改动能溜过去。

§ 05

故事的另一半直接解决「在我机器上能跑」:把应用连同它需要的一切打包成一个单元,让它在任何地方都跑得一模一样。这个单元就是容器。

Container——应用连同它的环境

宇航员的航天服自带空气、温度和气压——所以无论在空间站里还是在太空中,人都能一样地工作,因为他把自己的环境带在身上。

container 把你的应用和它运行所需的一切——代码、库、设置、正确的版本——打包成一个自成一体的单元。因为它自带环境,所以无论你把它放在哪里,都跑得一样:你的笔记本、测试服务器、生产环境。容器不依赖宿主机上装了什么,所以造成「在我机器上能跑」的那些差异就这样被填上了。

ContainerApp codeLibrariesConfigRuntime · Node 20Laptopsame container runsTest serversame container runsProductionsame container runs
容器里面:代码、库、设置和运行时。这就是它在笔记本上和生产环境里跑得一样的原因。
ConceptContainer
是什么
应用加上它的整个环境,打包成一个自成一体的单元。
例子

里面装着你的 API 和 Node 20 的容器,在 macOS 笔记本和 Linux 服务器上跑得一样。

陷阱
容器修好的是环境,不是你代码里的 bug——你仍然需要 test。
关联

根治 Works on my machine;从 Image 启动;大规模由 Kubernetes 管理。

Docker——给它命名的那个集装箱

钢制的海运集装箱:任何港口、轮船、卡车处理它的方式都一样,因为这个箱子是标准的,不管里面装的是什么。全世界的货物就靠这一个想法在流动。

这个名字本身就是比喻。在有标准集装箱之前,货物是一件一件笨拙地装上去的;标准箱让任何起重机、任何轮船都能以同样的方式处理任何货物。软件的容器对应用做的是同一件事:一个标准单元,任何兼容的系统都能运行它,而不必关心里面是什么。Docker 就是让这一切成为主流的工具。把箱子标准化,才让整条物流链——也就是你的 pipeline——顺畅运转。

CargoStandard boxany cargo insideCranehandles itShiphandles itTruckhandles itSoftwareDocker imageany app insideLaptopruns itCI runnerruns itCloudruns it
就像任何起重机、轮船和卡车都能处理标准箱,任何笔记本、CI runner 和云都能运行 Docker image。
ToolDocker
是什么
让容器成为主流的工具:它构建并运行容器。
例子

docker build 根据 Dockerfile 构建一个 image;docker run 从它启动一个容器。

陷阱
标准化的是箱子,不是里面的东西:里面装的仍然得是一个能正常工作的应用。
关联
制造 Container 和 Image;由 Kubernetes 运行它们。

Image vs Container——食谱和菜

一份食谱,对比照它做出来的那顿饭:一个是固定的一套说明,另一个是一个具体的实例,你可以做出很多份,份份一样。

有两个词常被混淆。image 是打包好的、冻结的蓝图——包含你的应用及其环境的构建产物,存放在仓库里(一个 registry)。container 是从这个 image 启动的、正在运行的实例。一个 image 可以启动许多一模一样的容器,就像一份食谱能做出许多一模一样的菜。你在 pipeline 里只构建一次 image,然后想跑多少容器就从它跑多少——每一个都是一样的。

Dockerfilethe recipe textImagefrozen blueprintRegistryimages stored hereContainer 1running instanceContainer 2running instanceContainer 3running instancebuildrun
Dockerfile → build → image(存在 registry 里)→ run → 你需要多少,就有多少个一模一样的运行中的容器。
TermImage vs Container· 食谱和菜
是什么
Image——冻结的蓝图(食谱)。Container——它正在运行的实例(菜)。
例子
一个 image api:1.4.2——五个一模一样的运行中的容器。
陷阱
在运行中的容器里改东西没有意义——应该改 image,然后重新构建。
关联
对容器来说,image 就是 Artifact;Kubernetes 让这些副本持续运行。

容器把应用连同它需要的一切打包,所以它在哪里都跑得一样。你只构建一次 image(食谱),然后从它运行一模一样的容器(菜)。

§ 06

运行一个容器很容易。要在许多机器上运行几百个——让它们保持健康、随需求伸缩、不停机地更新——就需要一位指挥。这就是编排。

Orchestration / Kubernetes——容器的指挥

一位空中交通管制员在调度满天的飞机——决定哪架降落在哪里,遇到问题就改道,让几百个移动的部件保持安全、协调。

当你有许多容器分散在一大批服务器上时,必须有个东西来决定每个容器跑在哪里、重启挂掉的那些、并均衡负载。这就是 orchestration,占主导地位的工具是 Kubernetes。你告诉它想要的状态——「把这个服务跑五份」——它就持续不断地努力让现实与之相符:安放容器、盯着它们、修正偏差。

Desired staterun 5 copiesKubernetescontrol planeplaces · watchesrestarts · balancesNode 1appappserverNode 2appappserverNode 3appserver
你说「5 份」——Kubernetes 自己把它们分散到各台服务器(节点)上,盯着它们,并在挂掉时重启。
ToolKubernetes· K8s, container orchestrator, 容器编排器
问题
没人能靠手工盯住许多服务器上的几百个容器。
思路
编排器决定每个容器跑在哪里,重启挂掉的,并均衡负载。
例子

你在 YAML 里用 replicas: 5 描述一个服务——Kubernetes 就在不同服务器上维持五份副本。

代价
一个有自己一套语言的复杂系统;对一个小服务来说往往大材小用。
关联

从 Image 运行 Container;保存 Desired state;执行 Rolling update。

Desired state——自愈与自动伸缩

一个你设定了目标温度的恒温器:它不停地感知房间,自己调节,你不用再碰它。你声明目标,它来维持。

编排是声明式的:你描述目标,而不是步骤。说一句「始终保持五份健康的副本在运行」,如果有一份崩了,系统会自动启动一个替补——这就是 self-healing(自愈)。如果流量激增,它可以增加副本,需求回落时再撤掉——这就是 auto-scaling(自动伸缩)。你不再手工照看服务器,而是声明你想要什么。从「做」到「声明」的这个转变,正是它的核心。

Desired: 5you declare itActual: 4one copy crashedComparedrift = 1Fix the driftstart 1 new copyloop foreverself-healing: crash → replace · auto-scaling: traffic up → more copies
一个无尽的循环:把期望的状态和实际的状态对比,修正偏差。一份副本崩了——就启动一个新的。
ConceptDesired state· declarative, self-healing, auto-scaling, 声明式、自愈、自动伸缩
是什么
你声明目标(「5 份健康副本」);系统不断把现实拉回到目标上。
例子
夜里一份副本崩了——几秒钟内就有新的顶上,没人被叫醒。
陷阱
系统会精确地维持你所描述的东西:目标写错了,它也会同样固执地维持错的。
关联

Kubernetes 的核心;与 Infrastructure as Code 是同一种「描述它,而不是手工去做」的想法。

Rolling update——不停机地发布

在行驶的卡车上一个一个地换轮胎,让它永远不用停下——车一直在往前开,每个部件却在底下被换掉。

编排让你可以通过 rolling update 不停机地更新:它每次替换几个旧容器为新容器,并在继续之前检查每个新容器是否健康(一次 health check),所以服务自始至终都在线。如果新版本表现不对,它可以停下来,并自动 roll back 到旧版本。现代服务就是这样在没有维护窗口的情况下发布更新的——在线、逐步、可逆。

startv1v1v1v1service uphalfwayv2v2v1v1service updonev2v2v2v2service upswap a few at a time, health-check each; bad → stop and roll back
副本一次几个地从 v1 切到 v2,每个都经过检查——服务一路都在线。
PatternRolling update
问题
靠停掉服务来更新,意味着用户要经历停机。
思路
一次替换几份副本,并对每个新副本做 health check。
例子
四份 v1 副本中,先有两份变成 v2,再轮到另外两份——服务一秒钟都没停过。
代价
有一段时间两个版本同时在运行——它们必须能够并存。
关联

由 Kubernetes 完成;出问题时——Rollback;近亲——Canary 和 Blue-green。

编排(Kubernetes)在许多机器上运行许多容器:你声明期望状态,它就自愈、自动伸缩,并且不停机地推出更新。

§ 07

这一整套机制,都是为了让交付既安全又频繁。最后这些习惯,是为了缩小任何一次 release 的爆炸半径——因为最安全的 deploy,是你能撤销的那一种。

Rollback——永远留一条退路

一份带「撤销」的文档:你放心大胆地做一个大改动,因为知道按一下键就能恢复上一个版本,所以可以自由尝试,而不是害怕每一次编辑。

最重要的安全属性是 rollback——当一次 release 出了问题时,能立刻回到上一个已知良好的版本。因为 pipeline 发布的是带版本号的 artifact,所以退回去就只是把前一个重新 deploy 一遍。知道自己能在几秒钟内撤销一次 deploy,正是让频繁发布变得安全的原因:一个坏版本只是一个小小的、可逆的波折,而不是一场危机。永远不要发布你收不回来的东西。

last known goodv41oldv42workedv43broken releaserollback: redeploy v42 in secondsbad deploy?minor blip,not a crisis
v43 坏了——重新 deploy 上一个好的 artifact,v42。几秒钟,而不是通宵救火。
PracticeRollback
问题
一个坏的 release 已经在生产环境里了,而修复要花好几个小时。
思路
立刻把上一个好版本放回去,之后再从容地修。
例子
v43 在报错——一条命令就把 artifact v42 放回生产环境。
代价
需要带版本号的 artifact;数据库的改动比代码更难回滚。
关联

靠 Artifact 才行得通;内置于 Rolling update、Canary 和 Blue-green 之中。

Canary release——先给少数用户

在把新菜端给整场宴席之前,先让几位愿意尝鲜的客人试一口——如果味道不对,只有几个人注意到,你什么也没损失。

canary deploy 会先把新版本发给一小部分用户。如果他们那边的指标保持健康,你就把它推给所有人;如果不健康,你把它撤回,几乎没影响到任何人。它把一次 release 从「全赢或全输」的赌博,变成一个受控的、可逆的步骤,并让一次失误的爆炸半径保持很小。

UsersRoutersplits trafficv1 · stable95% of trafficv2 · canary5% of trafficmosta fewwatch its metrics: healthy → roll out to all · unhealthy → pull back
路由器把 5% 的流量发给新版本。指标正常——推给所有人;不正常——撤回。
PatternCanary release
问题
新版本里的一个 bug 会一下子打到所有用户。
思路
先把新版本交给一小部分流量,并观察指标。
例子
5% 的用户用上 v2;错误并不比 v1 多——比例逐步升到 100%。
代价
需要能按百分比拆分流量,并且要有好的指标来判断。
关联
不好就 Rollback;邻居——Blue-green 和 Rolling update。

Blue-green deployment——两套环境和一个开关

剧院里两个一模一样的舞台:演出在一个上面进行,同时另一个上面布置新的一场。到了合适的时机,观众只需转向第二个——而第一个仍然备好,万一要退回去。

Blue-green 保持两套生产环境,并把流量一次性地从旧的(blue)切到新的(green),需要时还能瞬间切回来。新版本在旧版本仍然服务用户的时候就部署好并检查完毕;然后一个开关——所有人就都在新版本上了。和 canary 一样,它把一次 release 变成一个受控的、可逆的步骤。

UsersRouterone switchBlue · v1old, kept readyGreen · v2new, gets 100%switch back if neededall traffictwo full environments; traffic flips all at once
路由器一次开关就把全部流量从 blue(v1)挪到 green(v2);blue 保持待命,以备 rollback。
PatternBlue-green deployment
问题
在线切换到新版本有风险,而且难以撤销。
思路
两套完整的环境;流量一次性从旧的翻到新的。
例子

v2 在 green 里启动并检查完毕;路由器把 100% 的流量发给它;出问题——切回 blue。

代价
你要维持两套完整的环境——release 期间资源翻倍。
关联
瞬间 Rollback;与 Canary 不同,它一次性切换所有人。

Infrastructure as Code(IaC)——用文件描述你的服务器

一张蓝图,你能照着它把整栋房子原样重建出来;对比一栋凭记忆盖的房子,一旦烧毁,没人能把它复原。

同样的纪律也延伸到服务器本身。Infrastructure as code(IaC) 的意思是:用文件来定义你的服务器、网络和配置,而不是在控制台里手工点来点去。这样,你的整个环境就是带版本的、可审查的、可复现的——你能把它原样重建,改动也和代码一样走同一条 pipeline。它堵上了最后一个缺口:免得「有人手工改了个设置然后忘了」,把别处所有的严谨都一笔勾销。

Infra filesservers · networkGit + reviewversionedPipelineapplies itStagingrebuilt identicallyProductionrebuilt identically✗ click in the console by handnot like thisthe same path as code
基础设施文件 → 在 git 里评审 → pipeline 把它们应用到 staging 和生产环境。不在控制台里手工点击。
PracticeInfrastructure as Code· IaC, 基础设施即代码
问题
有人手工改了服务器的设置然后忘了——环境没法复现。
思路
用文件描述服务器、网络和设置,并通过同一条 pipeline 来改动它们。
例子

Terraform 文件描述一台服务器和一个数据库;改动要经过评审,再由 pipeline 应用。

代价
多学一门语言,再加一点纪律:不在控制台里手工做「快速修复」。
关联
经由 Pipeline 流转;与 Desired state 是同一种声明式的想法。
在你依赖一套 deploy 流程之前
  • 是不是自动化的——从 commit 到生产环境全程走 pipeline,而不是靠手工?
  • CI 是不是 build 并 test 每个改动,让 main 始终可用?
  • 你发布的是不是同一个 artifact 或容器,从 test 到生产环境完全一致?
  • 你能不能立刻 rollback 到上一个好版本?
  • 有风险的 release 是不是逐步推出——canary 或 blue-green,而不是一下子全上?
  • 基础设施本身是不是代码——带版本、可复现?

安全的交付就是可逆的交付:把 pipeline 自动化,发布一个经过测试的 artifact,留一个一键 rollback,把有风险的改动逐步推出,并把基础设施定义成代码。

§ 08

二十一个概念其实是一条线。一个改动通过 CI,变成一个 artifact,由 CD 交付,由编排器运行,而安全发布的各种技巧则在出问题时接住你。在这一切之下,是被写成代码的基础设施。

一张图:从 commit 到用户

一个包裹的路线:只打包一次,在仓库里检查,开车送出,交到手上——而且每一步都可以退回给寄件人。

从左到右读这张图:Commit → CI(build 和 test;红灯就停)→ Image(artifact,只构建一次)→ CD(release)→ Kubernetes(运行它并维持 desired state)。怎样安全地发布,由 Rolling update、Canary、Blue-green 和 Rollback 来决定。而 Infrastructure as Code 则是把同样的规则用到服务器本身:它们也要走 pipeline。

the pipelineCommitCIbuild + testImageartifact, onceCDreleaseKubernetesruns itred → stopsafe rolloutRolling updateCanaryBlue-greenRollbackInfrastructure as codethe servers themselves, versionedsame path
上面是 pipeline,中间是安全发布的各种技巧,基础设施即代码是走同一条路的地基。
你交付得好的迹象
  • 发布是无聊的——小、自动化、经常做,而不是一件罕见又吓人的事。
  • 任何东西合并之前,CI 都是绿的,而且 main 始终可用。
  • 你一路发布到生产环境的,是你测试过的同一个 artifact/容器。
  • 一个坏的 release 只是一次快速的 rollback,而不是一场危机。
  • 有风险的改动通过 canary 或 blue-green 推出,而你的基础设施是代码。

速查表:全部 21 个概念

deploy 这个难题

Deployrelease

让新版本上线。靠手工——又慢、又吓人、又罕见。

Works on my machineenvironment drift

由环境不同引起的 bug:同样的代码,不同的版本和设置。

Boring releasessmall batches

小而频繁的 release,比罕见而庞大的更安全。

Continuous Integration

Continuous IntegrationCI

每个改动在几分钟内被自动 build 并 test。

Shift leftcatch it early

bug 发现得越早,修复得越便宜。

Green main branchmain always works

只有通过 CI 的东西才能进入 main。

Continuous Delivery

Continuous DeliveryCD

好改动的发布被自动化:deploy 的步骤就是代码。

Delivery vs Deployment有按钮还是没按钮

Delivery——由人按按钮;deployment——直接上生产环境。

Artifactbuild once, deploy everywhere

一个构建好的软件包流经每个环境——你测试的就是你发布的。

Pipeline

PipelineCI/CD pipeline

自动化的流水线:build → test → deploy。

Fail fastred / green

一个阶段变红,流水线就停;只有绿色才往下走。

容器

Container应用 + 环境

应用连同它的环境——在哪里都跑得一样。

Docker工具

让容器成为标准「箱子」的那个工具。

Image vs Container食谱和菜

image 是冻结的蓝图;container 是它正在运行的实例。

Orchestration

Kubernetes编排器

把容器分散到各台服务器上,盯着它们,重启它们。

Desired stateself-healing, auto-scaling

你声明目标——系统让现实一直停在那里。

Rolling updatezero downtime

一次替换几份副本——不停机。

安全的 deploy

Rollback撤销

立刻放回上一个好版本。

Canary release先给少数用户

新版本先发给一小部分用户。

Blue-green deployment两套环境

流量一次性从旧环境翻到新环境——也能翻回来。

Infrastructure as CodeIaC

服务器和网络用文件描述,并通过 pipeline 改动。

交付应该是无聊的:把路径自动化,发布同一个经过测试的 artifact,并且永远留一条退路。

速成课结束 · 8 章 · 21 个概念

接下来是实践:找一个小项目,写一个 Dockerfile 让它在容器里跑起来,再搭一条 pipeline,在每次改动时 build 并 test 它。然后 deploy 它,弄坏它,再 rollback。当一次 release 不再让你害怕,因为你知道流水线会把问题拦住、你也能把它撤销时,整套哲学就豁然开朗了。但要把一个想法放在其他所有想法之上:交付应该是无聊的。把路径自动化,让每个改动都以同样的方式被 build、被 test、可被 deploy,再把应用打包,让它在任何地方都跑得一模一样——于是「在我机器上能跑」终于变成了「在哪里都能跑」。