首先,我们需要这个流程尽量频繁。如果我们能够把产品和功能尽快地发布到市场上,就能够更快地服务客户,以更快的试错速度寻找到用户,提供真正对客户有价值的功能。
即使你的产品由于自身特性不会太频繁地部署给用户,但这种能够频繁生产出可以马上部署的产品的能力,也能让你在需要部署时,快速完成任务。
你应该已经注意到了,整条流水线中,持续部署只是持续交付的最后一步,也就是自动化上线的那一步,前面的各种检查,都属于持续交付流水线。所以,**我在后面的内容中再提到流水线时,CI/CD指的就是“持续集成/持续交付”。**
CI/CD流水线,能够大大提高代码入库的速度和质量,是这几年硅谷互联网公司做到高效研发的必要条件。接下来,我就与你介绍CI/CD流水线的具体原则以及最佳实践,然后以Facebook的具体实践为例帮助你加深理解。
需要注意的是,在这篇文章中,我会重点与你分享CI/CD流水线的搭建原则。而关于具体的搭建方式,通常是持续集成工具+代码仓管理系统+检查工具+测试工具,比如Jenkins+GitLab+SonarQube+Linter+UnitTest的组合。你可以参考这个[链接](https://www.jianshu.com/p/e111eb15da90)提供的方式去搭建。
## CI/CD流水线的具体原则以及最佳实践
根据上面提到的3个“持续”的本质,要做到高效,有3条基本原则:
- 流水线的测试要尽量完整;
- 流水线的运行速度一定要快;
- 流水线使用的环境,尽量和生产环境一致。
### 基本原则1:流水线的测试要尽量完整
CI/CD流水线的测试只有尽量完整,代码和产品的质量才能有保证。所以,最主要的工程实践,就是在流水线中运行大量高质量的测试和检查。
Facebook就有大量的单元测试和集成测试用例、安全扫描,以及性能专项测试用例。如果某个验证在流水线中失败,开发人员会考虑是否要添加测试用例来防止再出现类似的问题。
另外,Facebook持续在测试用例的开发上投入。在内部工具团队,有一个专门的测试工具团队来建设测试框架和测试流程,方便开发人员自己开发测试用例。比如,我在Facebook那几年,他们就一直在改进JavaScript的Mock框架,对开发人员写测试用例来说非常方便。
### 基本原则2:流水线的运行速度一定要快
因为每一个变更都要通过CI/CD流水线的检验,所以流水线的速度关乎研发速度。而要提高这条流水线的速度,我们可以从以下两个方面考虑。
首先,从技术角度考虑。比如:
- 使用并行方式运行各种测试来提速;
- 投入硬件资源,使用水平扩展的方式来提速;
- 使用增量测试的方式进行精准验证。也就是说,只运行跟当前改动最相关的测试,以减少测试用例的运行数量。
其次,权衡流水线的运行速度、流水线资源和测试完整性的关系。不难理解,运行速度快、占用资源少、测试完整难以兼顾,因此我们必须做出权衡。这里我推荐几个方法:
- 如果通过增加硬件资源来提升运行速度需要的成本太高的话,可以对测试用例按优先级进行分类,每天运行流水线的时候,不用每次都运行所有测试用例,只选择其中几次进行全量测试。
- 提供支持,让开发人员在本地也能运行这些测试,从而使用本地资源尽早发现问题,这就避免了一些有问题的提交占用流水线的资源,进而提高整条流水线的运行速度。
- 运行测试的时候,按照一定的顺序运行测试用例。比如可以先运行速度快的用例,以及历史上容易发现问题的用例。这样可以尽早发现问题,避免耗费不必要的资源。
### 基本原则3:流水线使用的环境,尽量和生产环境一致
这里的环境,包括机器环境、数据、软件包、网络环境等。环境不一致可能导致问题暴露在用户面前,损失严重;另外,在非生产环境上难以复现生产环境的问题,调试困难。
保证流水线环境与生产环境一致,具体方法包括:
- 软件包最好只构建一次,保证各种不同环境都用同一个包。如果不同的运行环境需要不同的参数,可以采用环境变量的方式把这些参数传递给软件包。
- 使用Docker镜像的方式,把发布的产品以及环境都打包进去,实现环境的一致性。在我看来,这正是Docker的最大好处。
- 尽量使用干净的环境。比如,测试时,使用刚从镜像产生的系统;又比如,使用蓝绿部署,每次产生新的部署时,直接丢弃旧的环境。
以上就是CI/CD流水线的3个基本原则和最佳实践。通过提高验证的完整性、速度,以及保证环境的一致性,我们可以降低成本,提高产品质量和验证产品价值假设的速度。
接下来,为了帮助你理解并正确运用这些原则和最佳实践,我们一起来看看Facebook是怎么做的。
## 具体案例:Facebook是如何实施CI/CD来提高效能的?
Facebook一直就非常注重CI/CD,早在2009年就建设了顺畅的CI/CD流水线,而且一直在持续改进。
**在CI方面**,加强建设持续开发,让开发人员能在开发环境上进行大量的验证。本地的所有验证,与CI流水线上的验证方式保持一致,这就大大提高了开发人员在本地发现问题的能力,从而大量避免了有问题的代码提交到CI流水线,浪费资源。
在**代码入库的步骤**,采用Phabricator作为CI的驱动,并作为质量检查中枢,尽量提高入库前代码审查的流畅性。在这个过程中,Facebook做到了以下几点:
- 测试的完整性。代码提交到Phabricator进行代码审查的同时,进行各种静态检查、单元测试、集成测试、安全测试,以及性能测试等。
- 工具的集成。Phabricator提供的插件机制,可以跟其他系统和工具集成,以支持运行各种检查。
- 沙盒环境。代码在提交到Phabricator进行审查时,Phabricator会自动产生一个沙盒环境。沙盒环境有两个好处:一是,可以让开发者之间进行联调;二是,可以让开发者并行地进行其他开发工作,因为在进行代码审查时,开发者的开发机器并没有被占用。
- 高效的代码审查。比如,代码审查不通过时,代码作者可以方便地在原来的提交之上进行修改,并在下一轮审查时只进行增量代码的审查。这就大大降低了每次代码审查的交易成本,从而保证了CI的顺畅性。
**代码入库之后,进入持续交付步骤**。Facebook使用大仓,同一个仓中每天有几千个代码提交,所以持续交付的挑战很大。他们有一个专门的发布工具团队,自研了一套发布工具来实现自动化流水线,通过以下两点比较好地实现了流水线资源和测试完整性的平衡。
不针对每一个提交进行CD验证,而是按照一定时间间隔进行验证。因为提交太多,如果每个提交都进行构建打包,资源消耗实在太大,所以Facebook采用了按照一定时间间隔,比如,每10分钟进行一次构建打包。这就大大降低了资源的消耗,不过这里有个问题,在验证步骤发现Bug时,因为验证的是最近10分钟的所有提交,所以不能精准定位造成问题的提交。
针对这个问题,Facebook使用单主干开发分支方式,并强制在代码合并时,只能使用git rebase不能产生合并提交,所以提交历史是线性的,从而可以使用git bisect命令来自动化定位问题。这部分内容我会在下一篇文章中详细介绍。
## 思考题
1. 在几千名开发人员共同使用一个大代码仓的工作方式下,做好CI有很大的挑战性。你觉得挑战在哪里,容易出现什么样的问题,又应该怎么解决呢?
1. 今天我提到了持续开发在CI中的作用,请你结合上一篇文章,思考一下持续开发和CI/CD是怎样互相促进的。
感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!