You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

16 KiB

12 | 持续集成你说的CI和我说的CI是一回事吗

你好我是石雪峰。今天我来跟你聊聊CI。

之前我曾应邀参加某公司的DevOps交流活动他们质量团队的负责人分享了DevOps平台建设方面的经验其中有一大半时间都在讲CI。刚开始还挺好的可是后来我越听越觉得奇怪以至于在交流环节我只想提一个问题“你觉得CI是个啥意思”后来为了不被主办方鄙视话到嘴边我又努力憋回去了。

回来的路上我就一直在思考这个问题。很多时候人们嘴上总是挂着CI但是他们说的CI和我理解的CI好像并不是一回事。比如有时候CI被用来指代负责内部工具平台建设的团队有时候CI类似一种技术实践间接等同于软件的编译和打包有时候CI又成了一种职能和角色指代负责版本的集成和发布的人。可见CI的定义跟DevOps一样每个人的理解都千差万别。

可问题是如果不能理解CI原本的含义怎么发挥CI真正的价值呢以CI的名义打造的平台又怎么能不跑偏并且解决真正的问题呢

所以,今天,就让我们一起重新认识下这个“最熟悉的陌生人”。

CI是Continuous Integration的缩写也就是我们熟悉的持续集成顾名思义这里面有两个关键的问题集成什么东西为什么要持续要回答这两个问题就得从CI诞生的历史说起了。

在20世纪90年代软件开发还是瀑布模式的天下人们发现在很长一段时间里软件是根本无法运行的。因为按照项目计划软件的功能被拆分成各个模块由不同的团队分别开发只有到了开发完成之后的集成阶段软件才会被真正地组装到一起。可是往往几个月开发下来到了集成的时候大量分支合并带来的冲突和功能问题集中爆发团队疲于奔命各种救火甚至有时候发现压根集成不起来。

我最初工作的时候做的就是类似这样的项目。我们负责客户端程序的开发到了集成的时候才发现客户的数据库使用的是Oracle而我们为了省事使用的是微软Office套件中的Access估计现在很多刚工作的年轻工程师都没听说过这个数据库这就导致客户下发的数据没法导入到本地数据库中。结果整整一个元旦假期我们都在加班加点好不容易赶工了一个数据中间层这才把两端集成起来。

所以,软件集成是一件高风险的、不确定的事情,国外甚至有个专门的说法,叫作“集成地狱”。也正因为如此,人们就更倾向于不做集成,这就导致开发末端的集成环节变得更加困难,从而形成了一个恶性循环。

为了解决这个问题CI的思想应运而生。CI本身源于肯特·贝克Kent Beck在1996年提出的极限编程方法ExtremeProgramming简称XP。顾名思义极限编程是一种软件开发方法作为敏捷开发的方法之一目的在于通过缩短开发周期提高发布频率来提升软件质量改善用户需求响应速度。

不知道为什么,每次听到极限编程,我心中都热血沸腾。不管在任何时代,总有那么一群程序员走在时代前沿,代表和传承着极客精神,就像咱们平台的名字极客时间,就代表了不甘于平庸、追求极致的精神,特别好。

扯远了让我们回归正题。极限编程方法中提出的实践现在看来依然相当前沿比如结对编程、软件重构、测试驱动开发、编程规范等这些词我们都耳熟能详但是真正能做到的却是凤毛麟角。其中还有一个特别有意思的实践规范叫作每周40小时工作制也就是一周工作5天每天工作8小时。联想到前些日子在网络上引发激烈争论的“996”就可以看出极限编程方法在国内的发展还是任重而道远啊。

当然,在这么多实践中,持续集成可以说是第一个被广泛接受和认可的。

关于CI的定义我在这里引用一下马丁·福勒Martin Fowler的一篇博客中的内容这也是当前最为业界公认的定义之一

CI是一种软件开发实践团队成员频繁地将他们的工作成果集成到一起通常每人每天至少提交一次这样每天就会有多次集成并且在每次提交后自动触发运行一次包含自动化验证集的构建任务以便尽早地发现集成问题。

CI采用了一种反常规的思路来解决软件集成的困境其核心理念就是越是痛苦的事情就要越频繁地做。很多人不理解为什么举个例子你就明白了。我小时候身体非常不好经常要喝中药第一次喝的时候每喝一口都想吐可是连续喝了一个星期之后我发现中药跟水的味道也没什么区别。这其实是因为人的适应力很强慢慢就习惯了中药的味道。对于软件开发来说也是这个道理。

如果开发周期末端的一次性集成有这么大的风险和不确定性,那不如把集成的频率提高,让每次集成的内容减少,这样即便失败,影响的也仅仅是一次小的集成内容,问题定位和修复都可以更加快速地完成。这样一来,不仅提高了软件的质量,也大大降低了最后阶段的返工所带来的浪费,还提升了软件交付效率。

你可能会说,这个道理我也懂啊,我们的持续集成就是这样的。别急,我们一起来测试一下。

假如你认为自己所在的项目和团队在践行CI那么你可以思考3个问题看看你们是否做到了。

  1. 每一次代码提交,是否都会触发一次完整的流水线?
  1. 每次流水线是否会触发自动化的测试环节?
  1. 如果流水线出现了问题是否能够在10分钟之内修复

我曾在现场做过很多次这个测试如果参与者认为做到了就会举手表示如果没有做到就会把手放下。每次面对一群自信满满的CI“信徒们”三连问的结果总会让人“暗爽”因为最开始几乎所有人都会举手他们坚信自己在实践持续集成。但接下来我每问一个问题就会有一半的人把手放下坚持到最后的人寥寥无几这几个人面对周边人的目光内心也开始怀疑起来如果我再适时地追问两下基本就都放下了。

这么看来CI听起来简单易懂但实施起来并没有那么容易。可以说CI涵盖了三个阶段每个阶段都蕴含了一组思想和实践只有把这些都做到了那才是真正地在实施CI。接下来让我们逐一看下这三个阶段。

第一阶段:每次提交触发完整的流水线

第一个阶段的关键词是:快速集成。这是对CI核心理念的最好诠释也就是集成速度做到极致每次变更都会触发CI。

当然,这里的变更有可能是代码变更,也有可能是配置、环境、数据变更。我之前强调过,要将一切都纳入版本控制这样所有的元数据变更都会被版本管理系统捕获并通过事件或者Webhook的方式通知持续集成平台。

对于现代的持续集成平台比如大家常用的Jenkins默认支持多种触发方式比如定时触发、轮询触发或者Webhook触发。那么如果想做到每次提交都触发持续集成的话,首先就需要打通版本控制系统和持续集成系统比如GitLab和Jenkins的集成网上已经有很多现成的材料大家照着操作一般都不会有太多问题。但是只要打通两个系统就足够了吗显然没有这么简单。实施提交触发流水线还需要一些前置条件。

1.统一的分支策略

既然CI的目的是集成那么首先就需要有一条以集成为目的的分支。这条分支可以是研发主线也可以是专门的集成分支一旦这条分支上发生任何变更就会触发相应的CI过程。那么可能有人会问很多时候开发都是在特性分支或者版本分支上进行的难道这些分支上的提交就不要经过CI环节了吗这就引出了第2个前置条件。

2.清晰的集成规则

对于一个大中型团队来说,每天的提交量是非常惊人的,这就要求持续集成具备足够的吞吐率,能够及时处理这些请求。而对于不同分支来说,持续集成的步骤和要求也不尽相同。不同分支的集成目的不同,相应的环节自然也不相同。

比如,对于研发特性分支而言,目的主要是快速验证和反馈,那么速度就是不可忽视的因素,所以这个层面的持续集成,主要以验证打包和代码质量为主;而对于系统集成分支而言,它的目的不仅是验证打包和代码质量,还要关注接口和业务层面的正确性,所以集成的步骤会更加复杂,成本也会随之上升。所以,根据分支策略选择合适的集成规则对于CI的有效运转来说非常重要

3.标准化的资源池

资源池作为CI的基础设施重要性不言而喻。

首先,资源池需要实现环境标准化也就是任何任务在任何节点都具备可运行的能力这个能力就包括了工具、配置等一系列要素。如果CI任务在一个节点可以运行跑到另外一个节点就运行失败那么CI的公信力就会受到影响。

另外,资源池的并发吞吐量应该可以满足集中提交的场景,可以动态按需初始化的资源池就成了最佳选择。当然,同时还要兼顾成本因素,因为大量资源的投入如果没有被有效利用,那么造成的浪费是巨大的。

4.足够快的反馈周期

越是初级CI对速度的敏感性就越强。一般来讲如果CI环节超过1015分钟还没有反馈结果那么研发人员就会失去耐心所以CI的运行速度是一个需要纳入监控的重要指标。对于不同的系统而言要约定能够容忍的CI最大时长如果超过这个时长同样会导致CI失败。所以这就需要环境、平台、开发团队共同维护。

你看一套基本可用的CI所依赖的条件远不止这些核心还是为了能够在最短的时间内完成集成动作并给出反馈。如果你们公司已经实现了代码提交的CI并且不会有大量失败和排队的情况发生那么恭喜你第一阶段就算通过了。

第二阶段:每次流水线触发自动化测试

第二个阶段的关键词是:质量内建。关于质量内建我会在专栏后面的内容中详细介绍。实际上CI的目的是尽早发现问题这些问题既包括构建失败也包括质量不达标比如测试不通过或者代码规约静态扫描等不符合标准。

我见过的很多CI都是“瘸腿”CI因为缺失了自动化测试的能力注入或者自动化测试的能力很差基本无法发现有效问题。这里面有几个重要的关注点我们来看一下。

1.匹配合适的测试活动

对于不同层级的CI而言同样需要根据集成规则来确定需要注入的质量活动。比如最初级的提交集成就不适合那些运行过于复杂、时间太长的测试活动快速的代码检查和冒烟测试就足以证明这个版本已经达到了最基本的要求。而对于系统层的集成来说质量要求会更高这样一来一些接口测试、UI测试等就可以纳入到CI里面来。

2.树立测试结果的公信度

自动化测试的目标是帮助研发提前发现问题但是如果因为自动化测试能力自身的缺陷或者环境不稳定等因素造成了CI的大量失败那么这个CI对于研发来说就可有可无了。所以我们要对CI失败进行分类分级重点关注那些异常和误报的情况并进行相应的持续优化和改善

3.提升测试活动的有效性

考虑到CI对于速度的敏感性那么如何在最短的时间内运行最有效的测试任务就成了一个关键问题。显然大而全的测试套件是不合时宜的只有在基础功能验证的基础上结合与本次CI的变更点相关的测试任务发现问题的概率才会大大提升。所以根据CI变更自动识别匹配对应的测试任务也是一个挑战。

当你的CI已经集成了自动化验证集并且该验证集可以有效地发现问题那么恭喜你第二阶段也成功了。但这并不是“一锤子买卖”毕竟由于业务需求的不断变化自动化测试要持续更新才能保证始终有效。

第三阶段:出了问题可以在第一时间修复

到现在为止我们已经做到了快速集成和质量内建说实话利用现有的开源工具和框架快速搭建一套CI平台并不困难真正让CI发挥价值的关键还是在于团队面对持续集成的态度以及团队内是否建立了持续集成的文化

硅谷的很多公司都有一种不成文的规定,那就是员工每天下班前要先确认持续集成是正常的,然后再离开公司,同时,公司也不建议在深夜或者周末上线代码,因为一旦出了问题,很难在第一时间修复,造成的影响难以估计。

其实很多企业并不知道他们花费大量人力、物力建设CI的平均修复时长是多少也缺乏这方面的数据统计。就现状而言有些时候他们可以做到在10分钟内修复而有些时候就需要几个小时原因可能是负责人出去开会了或者是赶上了午休的时间。

当然也有一些企业质疑10分钟这个时间长度因为软件项目的特殊性很有可能每次集成周期就远大于10分钟。如果你也是这样想的那你可能就误解CI的理念和初衷了毕竟我也不相信马丁·福勒能够保证在10分钟内修复问题。在这么短的时间里人为因素其实并不可控所以人不是关键,建立机制才是关键

什么是机制呢?机制就是一种约定,人们愿意遵守这样的行为,并且做了会得到好处。对于CI而言保证集成主线的可用性其实就是团队成员间的一种约定。这不在于谁出的问题谁去修复而在于我们是否能够保证CI的稳定性足够清楚问题的降级路径并且主动关注、分析和推动问题解决。

另外团队要建立清晰的规则比如10分钟内没有修复则自动回滚代码比如当CI“亮红灯”的时候团队不再提交新的代码因为在错误的基础上没有办法验证新的提交这时需要集体放下手中的工作共同恢复CI的状态。

只有团队成员深信CI带给团队的长期好处远大于短期投入并且愿意身体力行地践行CI这个“10分钟”规则才有可能得到保障并落在实处。

总结

在这一讲中我们回顾了CI诞生的历史和CI试图解决的根本问题。同时我们也介绍了CI落地建设的三个阶段和其中的核心理念即快速集成、质量内建和文化建立。

最后我特别想再提一点很多人经常会把工具和实践混为一谈一旦结果没有达到预期就会质疑实践是否靠谱工具是否好用很容易陷入工具决定论的怪圈。实际上CI的核心理念从未有过什么改变但工具却一直在升级换代。工具是实践的载体实践是工具的根基单纯的工具建设仅仅是千里之行的一小步这一点我们必须要明白。

思考题

可以说一个良好的CI体现了整个研发团队方方面面的能力那么你对企业内部实践CI都有哪些问题和心得呢

欢迎在留言区写下你的思考和答案,我们一起讨论,共同学习进步。如果你觉得这篇文章对你有所帮助,欢迎你把文章分享给你的朋友。