本文通过对持续集成相关概念和工具的全面介绍,帮助您在保证软件质量的前提下,实现交付和部署的各项目标。
近年来,软件开发界流行的一些热门词汇莫过于:持续集成(Continuous Integration,CI)、持续交付(Continuous Delivery)、和持续部署(Continuous Deployment,CD)了,有时也被连起来称为 CI/CD。
无论是单个人的开发作坊,还是大型的跨国公司,大家都在为他们的软件产品实践着 CI 和 CD。
在本文中,我们将和您探究 CI,并简要介绍 CD,以及如何有效地使用它们。
同时,我们也会对那些能够帮助您加速开发流程的热门工具和系统进行评估。
持续集成:自动化开发流程和最佳实践
为了阐明持续集成是如何能融入现代软件环境中的,首先让我们来简单了解一下典型的软件开发流程。
如今,无论是网站、智能手机应用、还是传统的桌面应用程序,一般都会遵循如下精简的开发流程:
- 开发人员编写一段被称为变更集或补丁的代码,这些一般是针对某个项目的变更代码库(例如,增添新的功能、或是修复某个 Bug)。
- 他们将变更代码整合(或合并)到为项目设立的集中式权威代码存储库中(例如,GitHub 库)。
- 如果与现有编程语言或应用相关,这些项目的源代码会被编译,然后被构建为可部署的版本,它们通常被称为神器(artifacts 或工件)或包。
上述步骤是各种开发流程的简化步骤,它们省略了一些对于分批部署的策略考虑。
在具体考虑该流程每个阶段的责任时,我们很自然地会想到如下两个关键问题:
- 在第一步中,我们如何确保开发人员的变更集能够与现有的项目相集成?任何变更不应破坏现有代码库,如:不可引入新的问题。
如何界定变更具有良好的代码质量,我们在应用环境该如何判定呢?特别是那些与人身安全相关的医疗应用。
- 谁(或是什么工具)对于上述第二、三步进行管控和保障?
在 CI 的开发模式中,我们需要通过自动化来回答上述问题。例如:针对第二、三步,我们需要验证开发人员的变更,是否能被主代码库所接受和集成,以及整个团队能否成功完成项目的构建,并运行相关的测试。
因此,在一个理想的 CI 环境中,每一段代码都是在开发的过程中被集成的。对于此类企业而言,每一天(更有甚者是每次提交)都会发生好几次的代码集成。
什么是持续交付和持续部署?
持续交付和持续部署将自动化带到了一个新的高度,它们能够将您最近一次提交的内容进行自动分发,并成为整个软件的新版本。
持续交付:是指构建神器,并做好部署准备的过程。一般需要人工来判定是否的确需要部署。
持续部署:意味着所有流程都是自动的。即:在无人为干预的情况下,通过单次提交来触发自动管道,并最终将您的应用生产环境更新为最新版本的过程。
虽然有许多公司已经实现了持续交付,但是很少做到持续部署的。而且,持续部署是有风险的,因为任何人都有可能通过一个简单的提交,就将 Bug 引入了生产环境。因此,我们需要通过流程来降低这种风险。
持续集成的好处
以往在非 CI 环境中,人们针对软件项目,主要采用的是主干-分支(trunk-branch)式的版本控制。
开发人员长期在分支上开发各种功能。随着时间的推移,他们在将自己的变更与其他开发人员集成时,往往会不知不觉地让分支偏离了主干。
因此,为了确保所有的变更都能兼容生产系统,开发人员往往在整合分支功能的过程中苦不堪言,他们甚至创造了短语--“整合地狱(integration hell)”。
如今,CI 的工作流程正好能够通过轻松、例行的集成方式,解决这个问题。
持续集成不但能够节省开发人员的时间,避免他们手动整合的各种变更,还能提高软件的可靠性。
开发团队能够籍此更有信心地通过编写代码(和相关的测试),来增加新的功能,并向用户自动推送发布。
持续集成实践的前提
我们在采用持续集成的工作流程中,会涉及到如下必备的条件:
- 版本控制系统工具
- 构建工具
- 神器库管理器(Artifacts Repository Manager)
持续集成依赖于版本控制系统
持续集成最重要的前提条件是对其代码库的版本控制,即:对于代码库的每一项变更,都必须被安全地存放到专有的版本控制系统(Concurrent Versions System,VCS)中。
一旦实现了代码版本控制,我们就能用 CI 工具来进行访问。在市场上盛行的 VCS 工具中,Git 是最流行的一款,我们下面来简单介绍一下。
Git 最初是由 Linus Torvalds 为 Linux 内核所创建的,其主要特征包括:
- 支持非线性开发:Git 的分支(与合并)是在开发者的电脑上进行的。
- 分布式开发:每个开发人员都拥有整个存储库历史记录的本地拷贝。
- 对大型项目的有效处置:在处置大型代码库时,Git 能够快速地执行性能测试。
- 用户身份认证:通过加密和签名,来实现对提交人的验明正身。
- 对代码历史的加密认证:正如在区块链中,某个特定提交的 ID 取决于其前面的内容那样,在 Git 中改变历史代码后,其提交 ID 也会跟着变化。
- 垃圾收集:无用的对象会被自动进行垃圾收集。如果空间不足,您也能显式地调用垃圾收集来清理某个 Git 存储库。
用构建工具实现持续集成
构建工具能够通过处理应用的源代码,自动生成所需的软件。一般而言,软件工具的构建步骤取决于所选用的技术栈。
下面是一个 Java 应用程序构建步骤的示例:
- 如有需要,根据现有配置生成 .java 的文件。
- 将源代码(.java 文件)编译成字节码(.class 文件)。
- 将测试代码编译成字节码。
- 执行各种单元测试。
- 按需进行集成测试。
- 将多个 .class 文件打包成一个 JAR 文档。
- 按需将 JAR 文件存入神器库管理器。
- 按需在控制系统版本中,对代码做相应的标记。
对于上述例子,我们一般可用到如下的构建工具:
- Ant,针对 Java 技术的、且基于 XML 的跨平台生成工具。
- Maven,是一种基于 XML 的广泛声明,它偏向于将约定优于配置。
上述两种典型的构建工具和流程,都具有重复构建的能力。这意味着只要是一组相同的源代码,就应该能产生相同的一组神器输出。
例如:对于相同代码库,无论是开发人员用笔记本电脑进行构建、还是 CI 系统构建,它们都应当产生相同的结果。
这样会带来如下的好处:
- 首先,通过消除在开发人员笔记本和数据中心服务器上运行代码的差异性,进而降低在开发环境和生产环境中运行可能产生的异常问题。您也不会再听到“它在我的电脑上运行的时候是好好的啊!”之类的言论。
- 其次,如果在 CI 系统中通过了测试,那么由于运行的代码是相同的,它就能最大限度地减少生产环境的中断。
- 最后,如果它们能够以一种一致的、且可重复的方式构建的话,必定能提高神器的缓存效率,并能在不同阶段共享二进制文件。
用神器库管理器存储各种持续集成过程的结果
正如源代码需要被存储在 VCS 中那样,那些能够产生构建过程的神器同样需要被存储在某个远程文件系统中。
因此,我们可以使用专用的软件,如二进制库管理器(Binary Repository Manager)来管理神器。
维基百科是这样定义二进制库管理器的:它是一种软件工具,旨在优化下载和存储那些在软件开发的过程中所产生和使用到的二进制文件。它被组织用来集中式管理二进制的神器,从而克服多种二进制神器类型的复杂性,进而减少整个工作流之间的依赖关系。
可见,神器库管理器具有如下主要功能:
- 缓存:由于库管理器会被安装在公司系统内,因此开发人员访问它的速度比远程访问更快。
- 通过被设为代理服务器,它能够缓存那些已下载的第三方神器,并加快对其访问。
- 保存策略:库管理器能够自动清空闲置的神器,以收回宝贵的空间。
- 高可用性:由于库管理器的掉线势必会影响到企业项目构建的平稳运行,因此我们可以把库管理器组建成为群集,以便开发人员和 CI 工具随时访问。
用户限制:最后库管理器能够通过限制访问权限,来为神器制定对应的用户和组。
从开发到真实构建的简单 CI 工作流
由于不同企业所使用的软件、堆栈和用例有所不同,他们的 CI 工作流也可能各式各样。
下面让我们以一个简单工作流为例,探讨一下从开始研发到真正构建的自动化过程。
分支
我们一般有两种从代码库获取最新拷贝的可能:
- 如果是首次访问的话,您需要进行“下载”。即:使用 git clone 命令,拷贝 Git 的远程代码库到本地。
- 如果代码库已经存在本地,您可以使用类似 git pull 的命令,将其与远程存储库同步。
在版本控制系统中,有一个专有的分支可以指向软件的最新、且稳定的版本(通常被称为 master 版本),这个版本正是我们需要发布到生产环境中的。
为了保留这个黄金标准(gold standard)版本,并避免出现各种的 Bug,我们不应该对它进行直接的写操作。因此,每一次开发都应该从 master 版本里创建一个专有的分支开始。
同时,为了保持代码的井井有条,我们还应当尽可能地对分支采取有命名规则的方案,其中常用到的前缀包括:develop、feature、release 和 hotfix 等。
测试
根据具体情况的不同,测试方案既可以被事先编写,即:测试驱动型设计(Test-Driven Design);也可以在代码完成后再编写。无论是之前还是之后,我们都需要通过回归测试来保证代码的稳定运行。
测试代码的覆盖率也应当视具体情况而定:对于涉及到人类生命的软件,如飞机导航或手术辅助,其每一行代码都需要被检查(甚至要二重、或三重检查)。而在其他情况下,测试的覆盖率就不一定那么高了。
拉式请求(Pull Request)
记住,不要对 master 进行直接变更,而应该在某个专门的分支上进行。一旦开发完成,团队成员就应该考虑是否要将变更合并到主分支上。
因此,拉式请求的目标就是:您去询问自己的团队是否将变更接纳成为黄金标准,并且开放您的补丁包供他人评审。
而一旦拉式请求被开启,分支就会自动使用项目的构建工具进行构建,并确保变更不会破坏我们的主分支。
质量保证
一般情况下,我们还会伴随进行其他步骤,包括:对提交的代码进行安全性、代码质量、文档标准等方面的自动审核。
说到代码质量,就不得不提及该领域的知名开源平台 SonarQube(https://www.sonarqube.org/)。
SonarQube 能够与各大主流 CI 工具相集成,对某个代码库执行配置检查。
下面是对于持续检查(Continuous Inspection)的诠释:SonarQube 不仅能够展示某个应用程序的健康状态,还能突显各种新引入的问题。通过质量门(Quality Gate),您可以修复漏洞,进而系统地提高代码的质量。
构建
拉式请求一旦启动,自动构建就会通过各种 CI 工具,自动进行编译、测试、打包等步骤。
当然,如果一个(或多个)自动构建步骤出现失败,我们则认为整个构建失败。
在大多数 CI 工具中,失败的构建会以红色显示,而绿色则表示已经顺利通过的构建。
因此,您可能会听到有人将已通过的构建称为“绿色构建(green build)”。相反,如果构建失败,则无论是什么原因,都会返回给处于拉式请求之初的开发人员予以修复。
代码审查
虽然诸如 SonarQube 之类的工具可以检测一些简单的错误模式,如双重检查锁定(Double Checked Locking,https://fr.wikipedia.org/wiki/Double-checked_locking),但是更复杂的 Bug 还是需人工进行代码审查的。
因此,将代码变更合并到 master 之前的最后一步便是:团队成员的手动代码审查(这正是拉式请求的意义所在)。
不同的开发人员会有不同的代码审查方式。但是我建议您从基础方面做起,例如,您可以参考:代码审查到底要查什么?(https://leanpub.com/whattolookforinacodereview)。
标记、版本控制和存储构建神器
代码变更终于在这个阶段以 hot-fix 的形式被合入 master 并发布了。而 CI 工具也会再进行一次构建的重放,以确保万无一失。
首先,VCS 需要对版本打上相应的版本标签。虽然在企业实际开发过程中,大家常会以某个山名、湖名、甚至是蛋糕名字,来增加创意性,例如:Ubuntu 的“Placid Pangolin”和安卓的“Oreo”。
但是从原理上说,软件开发者应该采用一套标准的版本控制方案和命名规则(如使用数字编号),通过遵循规范的语义版本,来区分和定义 major、minor 和 bugfix 版本。如你想了解更多信息,请参见 semver.org。
其次,通过构建所产生的神器应当被存储到神器库管理器之中。籍此,当出现异常情况需要执行“回滚”操作时,您可以直接使用以前的工作版本,而无需对源代码进行重新构建。
持续集成工具概述
随着持续集成的广泛应用,与之相关工具的生态系统也日趋成熟。下面我们来讨论一些目前最为流行和常用的 CI/CD 系统。
它们的用户既有完全依赖云服务运营的初创企业,也有在自己内部使用复杂 CI 平台的大型组织。
Jenkins
Jenkins 是持续集成领域最老牌、且最被广泛应用的开源项目之一。该工具利弊共存:多年来,其核心架构已经历了从小型部署到大公司生产环境的多种“实战”检验。
另外,Jenkins 拥有一个“充满活力”的在线用户社区,能够帮助您为各种问题寻求解决方案。
当然,它的内部抽象机制可能对于一些具有大型遗留代码库、和向后兼容性的需求有些过时,并可能会经常出现跨不同用户场景的泄漏现象。
此外,尽管 Jenkins 拥有着广泛的插件生态系统,并提供了许多时髦的功能,但是由于这些插件通常是由社区所开发,因此质量和可靠性参差不齐。
近年来,Jenkins 常被描述为持续集成的工作流,即:管道(pipelines,请参见:https://jenkins.io/doc/book/pipeline/)。
开发人员能够用它来声明和描述与构建和部署有关的过程。而且,Jenkins 还允许您创建一些能够在不同项目中,通过重用来规范和简化通用流程的不同模块。
总之,Jenkins 拥有悠久的开发和应用历史,以及庞大且活跃的社区支持,同时它能够被高度定制。可以说,开发人员一旦掌握了它,就远离了失业的风险。
Travis CI
Travis CI 与 Jenkins 几乎处于同样的地位。虽然它有着许多开源的组件,但是如果缺少了企业账号,您将无法采用自托管的模式。不过,使用任何开源项目来运行 Travis 都是免费的。
Travis 运行的每个任务都必须被包含在 travis.yml 中,而该文件也需要被载入到您的代码段中。这就意味着您可以从一个存储库的不同分支上运行不同的任务。
Travis 的宗旨是简单化。您可以从 GitHub 托管库中直接工作,并在许多应用中维护一个通用的服务库。
当然,如果您没有用到 GitHub、或是需要有更多的控制,那么 Travis 可能就不适合您了。
Travis 的一个实用功能是能够在多种操作系统上运行,即:您可以在不同的目标系统上测试自己的代码,而无需维护各种虚拟机或镜像。
GitLab 持续集成/持续交付
GitLab 源于一项源码托管的服务。它与 GitHub 有类似之处,同时也提供一个开源的版本。
不过,与 GitHub 的不同之处在于:GitLab 包括一个内置于平台之中的,被称为 AutoDevOps 的高级 CI/CD 实现。
对于已经使用 GitLab 来存储源代码的用户来说,这种紧密集成是 GitLab 在 CI/CD 上能够提供的最实用的功能之一。
您可以通过在源代码库的根目录上添加 .gitlab-ci.yml 配置文件,来启用它。当然,您也可以将 GitLab 的 CI/CD 与 GitHub 的存储库相集成。
Bamboo
Bamboo 是 Atlassian 公司的持续集成/持续交付产品。该公司的另一个知名软件产品是 JIRA —一种 Bug 跟踪软件。
Bamboo 的主要优势在于:它能与那些已经在自己的系统中使用的其他 Atlassian 产品(如 JIRA 和 Bitbucket)紧密集成。
不过,相比其拥有大量插件市场的优势,Bamboo 的用户社区规模不算大,因此用户需要更加依赖于 Atlassian 本身的支持。
CircleCI
CircleCI 是一种在线服务,它提供了强大的 CI 平台,当然它也提供主机托管版本。
CircleCI 平台主要服务于容器,并能为测试提供快速的扩展(spin-up)。它的工作流功能,允许用户为复杂的项目自定义 CI 和 CD 的作业序列。
CircleCI 的主要优点是:它是一个全托管式的 CI 解决方案,因此减少了最终用户花费在系统维护上的时间。
结论
仅靠选对了持续集成的最佳实践和工具,我们是无法保证能够将自己的组织带入 CI 正轨的。
对于许多传统的软件企业而言,他们还需要整个软件开发团队在协作方式上的转变。
为了能够成功地将一整套变更集整合到代码库中,团队成员应当事先约定好各种工作模式和规范,并能够坚持执行。
要想实现可靠的构建步骤,团队成员有时候要对生产环境中的各种问题进行严格的重构和持续的部署,进而拓宽整个团队在设计上的视野。
综上所述,持续集成能够给软件组织带来的好处是不言而喻的。如今它已经成为了新的软件规范,企业必将通过不断的 CI 实践,以加速项目的交付和质量的提升。