深入剖析:持续集成 (理论篇)

December 24, 2017 - 2 minute read -
devops ci/cd

软件只有交付到客户手上才会有价值,在这之前做的所有工作和假设都是高成本的持续累积

随着近几年 DevOps 理念和持续集成/持续交付实践在国内的盛行,相信我们对持续集成,都已有自己的认识和理解。

但是我们所了解的,甚至在做的持续集成实践,从原则上来讲是否是合理的?从实践上来讲是否是最佳(最适合)的?是否还有优化和提升的空间?

这篇文章我们将尝试对持续集成做一个全面的梳理和认识,希望对你能有所帮助。

认证测试

continuous integration certification

如图上所示,在开始之前,有三个问题,我们可以尝试回答一下:

  • 每一位开发人员是否每天都会往开发主线上提交代码?
  • 每一次的提交动作是否都会触发自动化的构建和测试?
  • 在构建或测试失败后,是否在十分钟内修复了这些失败?

如果你之前对持续集成了解不多,从这三个问题上,可以对它有一个直观的认识。

如果你已经在做持续集成的实践了,并且你的答案都是正面的,那么恭喜你,你的实践是非常不错的。

这三个问题,是对持续集成的 “认证测试”,一定程度上可以作为我们持续集成实践的参考。

那么到底什么是持续集成呢?

持续集成 Integration Testing,简称 CI,Martin Fowler 给予了实践持续集成的原则。

现 wiki 百科,对持续集成最佳实践的描述,也是引用他的文章。

本文就是参考他的博文理论和其他多篇相关文章以及结合自身的过往的实际项目实践,对持续集成理论做的一个总结和剖析。

什么是持续集成?

每一位开发人员,每天至少向主线合并一次代码

从严格意义上来讲,持续集成是一项软件开发实践,在实践过程中,团队的成员们频繁地集成他们的工作,通常每人至少每天一次集成,直到每天多次集成。

每次的集成动作都会通过触发自动化的构建和测试检验,来尽可能快的检查集成引发的错误和失败。

这样的持续集成实践,会在很大程度上减少集成问题,可以快速的合作开发高内聚的应用软件。

为什么需要持续集成?

持续集成不能防止缺陷的产生,但它能明显地让寻找和修改缺陷的工作变非常简单

在过去,我们按照瀑布式开发流程,项目开发流程从一个阶段完成后,才会进入下一个阶段,直到最后交付我们的产品。

其中会有一个集成阶段,就是在每个团队把其所负责的功能模块开发完成后,把这些模块都集成到一起的过程。

然而在这个集成的过程中,往往会遇到很多难以预料的问题,这样集成过程就成了一个不可预料的流程,有时甚至会超过项目开发的时长。

集成的问题在于时间难以估计,因为无法获得当前集成的进展,这常常导致的结果是,我们在项目最紧张的阶段时候把自己置入了一个盲区,这也导致了产品延期交付的风险,即使最后没有延期交付(很少见)这也轻松不了多少。

这里的风险包括有:

  • 集成的难度: 不可预料的集成过程,导致项目/产品被延期交付的风险
  • 失败的产品: 用户只有在产品完整交付后才能接触到产品,存在着交付了不具备真正有价值的项目/产品的风险

持续集成非常巧妙地解决了这个问题。长时间的集成阶段不再存在,盲区被彻底消除了。

因为在持续集成的实践中需要与主线的进行频繁的集成,这样即使集成失败,因为每次集成都是较小的变更,所以产生的集成问题难度也会很小,修复该问题所需要的成本也就越低。 这样一来,集成的风险自然也很小,由集成问题导致的延期交付的风险也就减少了。

同时持续集成也为其他持续化实践提供了必要的条件,例如:持续测试/持续部署/持续交付等。 后面会单独写一篇文章介绍这几个持续化实践之间的关系和区别。

总的来说,持续集成的方法和实践,解决了过去的 “集成问题”,降低了交付风险,这其中也衍生出了一些其他的益处,都围绕着三个点:

  • 频繁的集成
  • 自动化的构建和测试
  • 高效的构建和测试

这其中包括有:

  1. 频繁的向主线集成,减少了功能分支和主线的差异,确保开发中的代码不会与主线偏离太多。
  2. 较小的代码变更和透明度,加强了团队之间沟通和协作的效率,特别是在修复集成、构建和测试失败问题以及缺陷上
  3. 自动触发的高效构建和测试任务,提高了失败反馈效率 (快速失败)。
  4. 降低了修复缺陷的难度和成本,使我们可以更多的关注在业务功能开发上。
  5. 自动化的测试,帮助我们减少了人工测试的成本。
  6. 高频率的可重复性的测试帮助我们提高应用程序的健壮性。
  7. 集成的频率越快、软件通过集成、测试和发布的速度就会越快,获取客户对产品的反馈越快。

在实际的实践中,其实还有很多我们无法一一列出的好处。

如何实践持续集成 ?

代码放入版本控制, 实现自动化构建,然后尝试加快构建速度

下面部分内容翻译和总结自 Martin Fowler 的文章,这 10 条建议,现已经成为我们实践持续集成的事实标准和指导原则。

也有很多文章将其称之为最佳实践。

只维护一个源码仓库

把所有跟该应用软件相关的文件都放到一个仓库中,不仅仅是源代码,也包括构建和测试部署需要的依赖库、脚本、测试用例、配置文件,甚至是数据库结构脚本。

原则上,使用单个仓库可以实现在一个完全干净的机器上完成从无到有的构建和运行应用软件 (当然不包括应用依赖的第三方服务和系统)。

自动化构建

不同类型的技术栈和业务类型会有不同的构建步骤和流程,一般来说,构建一般包括:编译、打包、测试。

自动化可以帮助我们提高构建效率和避免人工操作带来的失误,提高构建的可靠性。

原则上,我们要自动化一切可以被自动化的流程。

自测我们的构建

在过去,我们说的构建,不包含对编译完成的应用软件包的测试行为,这样只是确保应用软件是可以运行的,但是却无法保证它是正确地运行。

自动化测试不可能完全的代替人工测试,不过它可以帮助我们快速的定位和检查一些大部分的缺陷。

当然我们不能指望通过测试来发现所有的问题,就像我们经常说的:测试通过并不意味着没有缺陷。

没有测试是完美的,但是对于发现大多数的缺陷,已经足够了。

“完美” 也不是我们使用自测试构建想要到达的唯一目的,经常执行不完美的测试比完全不执行的完美测试要好得多。

每人每天都要向主线提交代码

集成的主要工作其实是沟通。集成可以让开发人员告诉别人他都更改了哪些东西。频繁的沟通可以让人们更快地了解变化。

允许开发人员提交代码到主线的一个先决条件是,这些即将提交的更改,必须在开发人员本地,使用与构建平台相同或类似的构建脚本,能被成功地构建并且通过测试。

具体来说是: 在每次提交之前,开发人员首先要 check-out 主线代码到本地,保证与主线的一直,并解决任何出现的冲突,然后在自己的机器上做 build。在 build 成功并且通过其他一些简单测试后,就可以随便向主线提交了。

越频繁的向主线提交代码,越能减少特性分支和主线之间的差异,减少集成的失败率,同时也会在出现问题时,降低我们调试和解决问题的难度,因为每次的提交都是少量的。

commit strategy

Big change -> Big risk –> Big effort;

Small change –> Small risk –> Small effort

频繁提交客观上也会鼓励开发人员将工作分解成以小时为单位的工作块。这有助于跟踪进度和让大家感受到持续的进展。

备注: 上面提到的提交流程,在实际的实践中,会根据我们基于源代码仓库的工作流程不同而不同。

每次提交都重新构建主线

持续集成服务器软件就像一个监视着源码仓库的监视器。

每次源码仓库中有新的提交,服务器就会自动 check out 出源代码并启动一次 build,并且把 build 的结果通知提交者。这种情况下,提交者的工作直到收到通知才算集成阶段的工作结束。

通知需要是透明公开的,包括的信息有:谁触发了构建,该构建的(代码)变更是什么,构建的结果是否成功,以及构建花费了多长时间。

做好持续集成的一个关键因素是一旦主线上的构建失败了,它必须被马上修复。

在持续集成环境中工作最大的好处是,你总能在一个稳定的基础上做开发。

保持高效的构建

快速高效的构建可以帮助我们获得快速的反馈,失败暴露的越早,越快,我们修复的时间越早,集成的成本越小,主线也就越健壮。

持续集成的重点就是快速反馈,XP 思想中理想的构建时间是 10 分钟。

在我们刚开始启用持续集成流程和系统的时候,一次构建可能花费较长的时间,甚至 1 个小时,这都没有关系,在我们的持续集成流程走通后,结合我们的实践,就可以花时间来优化和改善流程中各个瓶颈。

一般保持快速构建的实践是,将整个流程拆分成多个不同的阶段,以最大化的提高构建速度为目标,根据各阶段的特性把它们串联起来(部分子阶段可以并联),这里就是我们通常所说的持续集成流水线作业。

在类生产环境中进行测试

测试的关键在于在受限的条件下找出系统内可能在实际生产环境中会出现的任何问题。

不可避免的是,环境的差异带来的不可预料的问题,即使是在集成环境下,我们应该尽可能在类生产(预生产)环境下做最后的测试和检验,避免环境差异带来的问题。

实际的实践中,我们可能会有很多限制导致我们不可能复制出跟生产环境一样的测试环境,即使有这些限制,我们的目标仍然是尽可能地复制生产环境,并且要理解并接受,因测试环境和生产环境不同带来的风险。

这里具体的实践涉及有:基础架构即代码,保证环境的一致性,容器化,部署策略(灰度、金丝雀、蓝绿)等。

每个人都易获取的发布包

原则上,要部署的发布包,就是我们前面编译完成和测试通过的可执行文件/软件包/镜像。

其实不仅是最新的发布包,各个版本的发布包都是有必要的,这样做的好处有:

  1. 可以用来做 demo (特别是移动端的应用类型)
  2. 可以做探索性测试和调试(不用访问线上环境)

每个人都能看到集成进度

持续集成中最重要的是沟通,我们需要保证每个人都能非常容易地看到系统的状态和最新的修改。

在应用软件的发布流程中,透明的、可视化的构建进展和详细信息,可以帮助流程中涉及到的每一个人关注并获取到当前的进度。

即使是在做人工的持续集成(没有自动化构建和测试),可视化程度依然很重要。

在最早期的,人工的持续集成实践中,有这样实现的:

构建主机的显示器用来显示主线当前的构建进度和状态,把一个构建令牌放在正在做构建的那人的桌子上 (橡皮鸡这种容易引人注意的东西最好)。这样,在构建成功或失败时弄出一点噪音来,比如摇铃的声音,可以引起大家的注意。

在现在的实践中,我们一般实现一个构建仪表盘,使用状态灯和消息强通知的方式引人注意。

自动化部署

不是持续部署,这里的自动化部署仅是区别于人工的部署操作。

避免了繁琐的手工操作,通过脚本自动化的部署,也提高了部署的效率和成功性。

如果我们是部署到生产环境,那需要多考虑一件事情:自动化回滚。失败随时可能发生,如果情况不妙,最好的办法是尽快回到上一个已知的正常状态。

能够自动回滚也会减轻部署的压力,从而鼓励人们更频繁地部署,使得新功能更快发布给用户。

总结一下

总的来说,持续集成的实践中有以下几个方面:

  • 开发人员频繁的 check out 仓库主线的代码到个人的工作环境,来确保与主线不存在冲突
  • 开发人员每次都是将小量的更改 check in 到仓库主线中
  • 持续集成主机监控代码仓库主线,当有人往仓库中提交变更后,自动触发构建
  • 持续集成主机,构建应用并且运行单元测试和集成测试
  • 持续集成主机为每次构建的代码添加版本标签
  • 持续集成主机在构建成功后通知团队,在构建失败后也要告警通知
  • 在项目的整个生命周期中持续地做持续集成和测试

另外在持续集成的实践中,从团队的职责角度来说有:

  • 频繁地 check in 代码到仓库主线
  • 不要 check in 没有自测的代码
  • 不要 check in 没有自测成功的代码
  • 在主线的构建已经失败时,不要 check in 代码
  • 在主线的构建失败时,第一优先级是修复该失败问题
  • 不要在刚提交完代码后,就直接下班回家,要直到系统构建成功

参考: