透明思考


Transparent Thoughts


持续集成将死

在思考“云时代的研发环境长什么样”这个问题的时候,我逐渐意识到一件很重要的事。2000年首次被提出、在过去十几年中我们习以为常的敏捷核心实践持续集成,很可能正在走到它生命周期的尾声。

让我们来回顾一下Martin Fowler在他那篇著名的文章里如何描述持续集成这个过程:

一旦完成了修改,我就会在自己的计算机上启动一个自动化build。……当我build成功后,我就可以考虑将改动提交到源码仓库。……然而,提交完代码不表示就完事大吉了。我们还要做一遍集成build,这次在集成计算机上并要基于mainline的代码。只有这次build成功了,我的修改才算告一段落。……在持续集成环境里,你必须尽可能快地修复每一个集成 build。好的团队应该每天都有多个成功的 build。错误的 build 可以出现,但必须尽快得到修复。

从上面加粗的文字就能看出,过去的十多年里,在谈及持续集成这个实践时,我们已经预设了这个场景:有一个集中式的持续集成服务器在监听代码库的变化,每当有人提交代码时,持续集成服务器会自动取出最新的代码,执行整个构建和测试流程。围绕着这个场景,我们发展出了一整套的纪律来保障持续集成少失败、失败的时候能尽快修复。围绕着这个场景,我们发明了CruiseControl、GoCD、SnapCI等一代又一代的持续集成服务器,并在我们所有的项目中使用它们。这个场景在我们的脑海中如此根深蒂固以至于我们不再去询问:为什么需要这样做?

实际上,我们需要一个集中式的持续集成服务器,这是有历史原因的。2000年代初期的技术时代背景,尤其是以下两个非常具体的约束条件,造成了今天我们看到的持续集成的形态:

  1. 计算资源短缺。这个约束条件决定了完整的、与生产环境相似的、能执行端到端验证的环境必定是稀缺品。典型的交付团队没有能力给每个成员配备整套环境,只能在他们各自的计算机上模拟一套尽可能接近于生产环境的开发环境。于是开发环境的验证结果不足为信,必须在一个标准的、更接近于生产环境的集成环境上通过验证,才能说软件达到了质量要求。
  2. 版本控制工具的局限性。Subversion(以及其他更早的版本控制工具)在pre-commit阶段通过服务器端回调钩子很难——如果不是完全不可能的话——得到完整的“提交后版本”,因此svn的pre-commit钩子基本只能用于检查提交信息是否符合规范,完整的验证则必须在代码已经合入代码库之后才能——在一台独立的“持续集成服务器”上——进行。

云计算彻底改变了第一个约束条件。计算资源仍然不能说极大丰富,但企业应用开发所需的x86架构计算资源在云环境下已经不再短缺,结合各种基础设施自动化和配置自动化的技术,随时、按需提供整套环境已经不是难题。而且使用docker等容器技术开通出来的环境是抛弃型的、不可变更的,因此也就不存在环境不一致、验证结果不可信的问题:每个开发人员都可以从云上拿出一套环境,执行build,其过程与效果都与持续集成服务器的build完全一致。

在过去的十多年里,持续集成之所以必须是一种“技能”、一门“手艺”,而不仅仅是一套工具的定制与实施,很大程度上正是由于计算资源短缺这个约束条件造成的。因为计算资源几乎总是短缺,所以每个团队、每个项目拥有的计算资源几乎总是有些这里那里的不同——这个项目可能有两套完整的测试环境,那个项目可能只有一套。这种资源的局限,逼迫每个项目的技术领导者们不得不根据手上能得到的环境,来微调持续集成的流水线乃至软件交付的流程。简言之,流程是依据环境来调整的。

当计算资源短缺的约束条件不再存在,在考虑构建流水线时就可以有一个根本的观念转变:可以制订一套标准的构建流水线,并要求计算环境向这套流水线对齐。这时,持续集成就可以不必是每个团队的技术领导者都掌握的“技能”和“手艺”,它完全可以在一个组织范围内定制和大规模实施。因为环境可以弹性地适配流程,我们就能够为相同类型的项目定义统一的最佳流程。

而git对svn的全面取代则带来一个细微而深远的影响:由于可以在pre-commit阶段直接获得完整的待提交快照、并在这个版本基础上执行测试,不能通过build的代码将直接被拒绝提交。换句话说,整个“持续集成纪律”尝试解决的问题——有缺陷的代码进入团队的代码仓库从而妨碍其他人不断提交没有缺陷的代码——将不复存在,有缺陷的代码将根本无法进入团队的代码仓库。

综上所述,这两个要素的结合:

  1. 每个开发人员(以及自动构建)都可以在PaaS云上获得完整的技术栈运行时环境;以及,
  2. pre-commit阶段可以对待提交的代码进行完整的构建

带来的是一个非常重要的影响:持续集成服务器这个东西,我们不再需要了。持续集成的“集成”这个动作,将在代码进入团队代码库之前发生。我们有办法(git的pre-commit钩子)确保这次集成发生,也有办法(云化、容器化的环境)确保这次集成是可信的。因此我们不再需要一个持续集成服务器来扮演团队的守门人。集中式的持续集成服务器将退化为团队研发行为的可视化仪表盘:它不再负责管理环境和构建软件,只负责采集所有构建中产生的数据、并以适当的形式展示,作为团队研发过程的可视化呈现。

当持续集成服务器消亡,一个开发者的典型工作流程可能会是这样:

  • 从git仓库clone出代码,在自己的电脑上做修改;
  • 修改完成,从研发PaaS上获得一个运行环境,把刚写好的代码运行起来,用浏览器查看一下效果;
  • 执行构建,构建脚本自动从研发PaaS上获得一个运行环境,在其中执行编译、打包、代码检查和测试;
  • 构建通过,提交代码并push,git仓库的pre-commit钩子自动触发一次构建,过程与效果都与刚才手动执行的完全一致;
  • 如果没有手工执行构建就尝试提交,自动构建会失败,代码无法push到团队的代码仓库中,开发者自己去修复;
  • 如果自动构建成功,代码提交完成,最新版本的代码被构建成容器镜像;
  • 测试人员从研发PaaS上获得一个运行环境,把待测版本的容器镜像装载上去,执行测试,如果测试通过就将该版本标记为“发布候选”;
  • 运维人员从生产PaaS上获得一个运行环境,把发布候选版本的容器镜像装载上去,即完成上线。

这个流程直接地实现了《持续交付》中描述的“两道门”结构。虽然每个项目运行的环境不同,但这个持续交付的结构可以是完全一致的,因为环境可以弹性地适配研发流程。

与持续集成服务器同时消亡的,还有持续集成这个概念本身。由于对响应力(responsiveness)的要求是如此之高,现代的IT团队已经不能容忍有缺陷的代码先进入代码库、阻塞整个团队的工作、然后再来修复(甚至不修复、或者还需要说服某些团队成员去及时修复)。持续集成是如此重要,以致于它会变成团队的“空气和水”。它会被嵌入到日常的研发工具当中,成为程序员感知不到、而又不可妥协的质量要求——正如IntelliJ之类现代IDE把“通过编译”这项要求变成了程序员感知不到、而又不可妥协的质量要求。

持续集成对于软件开发是如此重要,以至于不应该把它交给软件开发者自己去做。

这就是为什么我认为持续集成工具、以及这些工具背后的持续集成概念在云计算深入研发之后将会消亡。取代持续集成的,将是更紧密地内嵌质量要求、更充分地利用云计算优势的云原生(Cloud Native)开发方法及支撑工具。

(本文作者熊节是ThoughtWorks的总监咨询师)