原文:https://opensource.com/article/18/8/what-cicdgit
在谈论软件开发时,常常会提到持续集成Continuous Integration(CI)和持续交付Continuous Delivery(CD)这几个术语。但它们真正的意思是什么呢?在本文中,我将解释这些和相关术语背后的含义和意义,例如持续测试Continuous Testing和持续部署Continuous Deployment。web
工厂里的组装线以快速、自动化、可重复的方式从原材料生产出消费品。一样,软件交付管道以快速、自动化和可重复的方式从源代码生成发布版本。如何完成这项工做的整体设计称为“持续交付”(CD)。启动组装线的过程称为“持续集成”(CI)。确保质量的过程称为“持续测试”,将最终产品提供给用户的过程称为“持续部署”。一些专家让这一切简单、顺畅、高效地运行,这些人被称为运维开发DevOps践行者。数组
“持续”用于描述遵循我在此提到的许多不一样流程实践。这并不意味着“一直在运行”,而是“随时可运行”。在软件开发领域,它还包括几个核心概念/最佳实践。这些是:网络
将源代码转换为可发布产品的多个不一样的任务task和做业job一般串联成一个软件“管道”,一个自动流程成功完成后会启动管道中的下一个流程。这些管道有许多不一样的叫法,例如持续交付管道、部署管道和软件开发管道。大致上讲,程序管理者在管道执行时管理管道各部分的定义、运行、监控和报告。架构
软件交付管道的实际实现能够有很大不一样。有许多程序可用在管道中,用于源代码跟踪、构建、测试、指标采集,版本管理等各个方面。但总体工做流程一般是相同的。单个业务流程/工做流应用程序管理整个管道,每一个流程做为独立的做业运行或由该应用程序进行阶段管理。一般,在业务流程中,这些独立做业是以应用程序可理解并可做为工做流程管理的语法和结构定义的。app
这些做业被用于一个或多个功能(构建、测试、部署等)。每一个做业可能使用不一样的技术或多种技术。关键是做业是自动化的、高效的,而且可重复的。若是做业成功,则工做流管理器将触发管道中的下一个做业。若是做业失败,工做流管理器会向开发人员、测试人员和其余人发出警报,以便他们尽快纠正问题。这个过程是自动化的,因此比手动运行一组过程可更快地找到错误。这种快速排错称为快速失败fail fast,而且在抵达管道端点方面一样有价值。框架
管道的工做之一就是快速处理变动。另外一个是监视建立发布的不一样任务/做业。因为编译失败或测试未经过的代码能够阻止管道继续运行,所以快速通知用户此类状况很是重要。快速失败指的是在管道流程中尽快发现问题并快速通知用户的方式,这样能够及时修正问题并从新提交代码以便使管道再次运行。一般在管道流程中可经过查看历史记录来肯定是谁作了那次修改并通知此人及其团队。运维
管道的几乎全部部分都是应该自动化的。对于某些部分,有一些人为干预/互动的地方多是有意义的。一个例子多是用户验收测试user-acceptance testing(让最终用户试用软件并确保它能达到他们想要/指望的水平)。另外一种状况多是部署到生产环境时用户但愿拥有更多的人为控制。固然,若是代码不正确或不能运行,则须要人工干预。函数
有了对“持续”含义理解的背景,让咱们看看不一样类型的持续流程以及它们在软件管道上下文中的含义。工具
持续集成(CI)是在源代码变动后自动检测、拉取、构建和(在大多数状况下)进行单元测试的过程。持续集成是启动管道的环节(尽管某些预验证 —— 一般称为上线前检查pre-flight checks —— 有时会被归在持续集成以前)。
持续集成的目标是快速确保开发人员新提交的变动是好的,而且适合在代码库中进一步使用。
持续集成的基本思想是让一个自动化过程监测一个或多个源代码仓库是否有变动。当变动被推送到仓库时,它会监测到更改、下载副本、构建并运行任何相关的单元测试。
目前,监测程序一般是像 Jenkins 这样的应用程序,它还协调管道中运行的全部(或大多数)进程,监视变动是其功能之一。监测程序能够以几种不一样方式监测变动。这些包括:
在将代码引入仓库并触发持续集成以前,能够进行其它验证。这遵循了最佳实践,例如测试构建test build和代码审查code review。它们一般在代码引入管道以前构建到开发过程当中。可是一些管道也可能将它们做为其监控流程或工做流的一部分。
例如,一个名为 Gerrit 的工具容许在开发人员推送代码以后但在容许进入(Git 远程)仓库以前进行正式的代码审查、验证和测试构建。Gerrit 位于开发人员的工做区和 Git 远程仓库之间。它会“接收”来自开发人员的推送,而且能够执行经过/失败验证以确保它们在被容许进入仓库以前的检查是经过的。这能够包括检测新变动并启动构建测试(CI 的一种形式)。它还容许开发者在那时进行正式的代码审查。这种方式有一种额外的可信度评估机制,即当变动的代码被合并到代码库中时不会破坏任何内容。
单元测试(也称为“提交测试”),是由开发人员编写的小型的专项测试,以确保新代码独立工做。“独立”这里意味着不依赖或调用其它不可直接访问的代码,也不依赖外部数据源或其它模块。若是运行代码须要这样的依赖关系,那么这些资源能够用模拟mock来表示。模拟是指使用看起来像资源的代码存根code stub,能够返回值,但不实现任何功能。
在大多数组织中,开发人员负责建立单元测试以证实其代码正确。事实上,一种称为测试驱动开发test-driven develop(TDD)的模型要求将首先设计单元测试做为清楚地验证代码功能的基础。由于这样的代码能够更改速度快且改动量大,因此它们也必须执行很快。
因为这与持续集成工做流有关,所以开发人员在本地工做环境中编写或更新代码,并通单元测试来确保新开发的功能或方法正确。一般,这些测试采用断言形式,即函数或方法的给定输入集产生给定的输出集。它们一般进行测试以确保正确标记和处理出错条件。有不少单元测试框架都颇有用,例如用于 Java 开发的 JUnit。
持续测试是指在代码经过持续交付管道时运行扩展范围的自动化测试的实践。单元测试一般与构建过程集成,做为持续集成阶段的一部分,并专一于和其它与之交互的代码隔离的测试。
除此以外,能够有或者应该有各类形式的测试。这些可包括:
全部这些可能不存在于自动化的管道中,而且一些不一样类型的测试分类界限也不是很清晰。可是,在交付管道中持续测试的目标始终是相同的:经过持续的测试级别证实代码的质量能够在正在进行的发布中使用。在持续集成快速的原则基础上,第二个目标是快速发现问题并提醒开发团队。这一般被称为快速失败。
除了测试是否经过以外,还有一些应用程序能够告诉咱们测试用例执行(覆盖)的源代码行数。这是一个能够衡量代码量指标的例子。这个指标称为代码覆盖率code-coverage,能够经过工具(例如用于 Java 的 JaCoCo)进行统计。
还有不少其它类型的指标统计,例如代码行数、复杂度以及代码结构对比分析等。诸如 SonarQube 之类的工具能够检查源代码并计算这些指标。此外,用户还能够为他们可接受的“合格”范围的指标设置阈值。而后能够在管道中针对这些阈值设置一个检查,若是结果不在可接受范围内,则流程终端上。SonarQube 等应用程序具备很高的可配置性,能够设置仅检查团队感兴趣的内容。
持续交付(CD)一般是指整个流程链(管道),它自动监测源代码变动并经过构建、测试、打包和相关操做运行它们以生成可部署的版本,基本上没有任何人为干预。
持续交付在软件开发过程当中的目标是自动化、效率、可靠性、可重复性和质量保障(经过持续测试)。
持续交付包含持续集成(自动检测源代码变动、执行构建过程、运行单元测试以验证变动),持续测试(对代码运行各类测试以保障代码质量),和(可选)持续部署(经过管道发布版本自动提供给用户)。
版本控制是持续交付和管道的关键概念。持续意味着可以常常集成新代码并提供更新版本。但这并不意味着每一个人都想要“最新、最好的”。对于想要开发或测试已知的稳定版本的内部团队来讲尤为如此。所以,管道建立并轻松存储和访问的这些版本化对象很是重要。
在管道中从源代码建立的对象一般能够称为工件artifact。工件在构建时应该有应用于它们的版本。将版本号分配给工件的推荐策略称为语义化版本控制semantic versioning。(这也适用于从外部源引入的依赖工件的版本。)
语义版本号有三个部分:主要版本major、次要版本minor 和 补丁版本patch。(例如,1.4.3 反映了主要版本 1,次要版本 4 和补丁版本 3。)这个想法是,其中一个部分的更改表示工件中的更新级别。主要版本仅针对不兼容的 API 更改而递增。当以向后兼容backward-compatible的方式添加功能时,次要版本会增长。当进行向后兼容的版本 bug 修复时,补丁版本会增长。这些是建议的指导方针,但只要团队在整个组织内以一致且易于理解的方式这样作,团队就能够自由地改变这种方法。例如,每次为发布完成构建时增长的数字能够放在补丁字段中。
团队能够为工件分配分销promotion级别以指示适用于测试、生产等环境或用途。有不少方法。能够用 Jenkins 或 Artifactory 等应用程序进行分销。或者一个简单的方案能够在版本号字符串的末尾添加标签。例如,-snapshot
能够指示用于构建工件的代码的最新版本(快照)。可使用各类分销策略或工具将工件“提高”到其它级别,例如 -milestone
或 -production
,做为工件稳定性和完备性版本的标记。
从源代码构建的版本化工件能够经过管理工件仓库artifact repository的应用程序进行存储。工件仓库就像构建工件的版本控制工具同样。像 Artifactory 或 Nexus 这类应用能够接受版本化工件,存储和跟踪它们,并提供检索的方法。
管道用户能够指定他们想要使用的版本,并在这些版本中使用管道。
持续部署(CD)是指可以自动提供持续交付管道中发布版本给最终用户使用的想法。根据用户的安装方式,多是在云环境中自动部署、app 升级(如手机上的应用程序)、更新网站或只更新可用版本列表。
这里的一个重点是,仅仅由于能够进行持续部署并不意味着始终部署来自管道的每组可交付成果。它实际上指,经过管道每套可交付成果都被证实是“可部署的”。这在很大程度上是由持续测试的连续级别完成的(参见本文中的持续测试部分)。
管道构建的发布成果是否被部署能够经过人工决策,或利用在彻底部署以前“试用”发布的各类方法来进行控制。
因为必须回滚/撤消对全部用户的部署多是一种代价高昂的状况(不管是技术上仍是用户的感知),已经有许多技术容许“尝试”部署新功能并在发现问题时轻松“撤消”它们。这些包括:
在这种部署软件的方法中,维护了两个相同的主机环境 —— 一个“蓝色” 和一个“绿色”。(颜色并不重要,仅做为标识。)对应来讲,其中一个是“生产环境”,另外一个是“预发布环境”。
在这些实例的前面是调度系统,它们充当产品或应用程序的客户“网关”。经过将调度系统指向蓝色或绿色实例,能够将客户流量引流到指望的部署环境。经过这种方式,切换指向哪一个部署实例(蓝色或绿色)对用户来讲是快速,简单和透明的。
当新版本准备好进行测试时,能够将其部署到非生产环境中。在通过测试和批准后,能够更改调度系统设置以将传入的线上流量指向它(所以它将成为新的生产站点)。如今,曾做为生产环境实例可供下一次候选发布使用。
同理,若是在最新部署中发现问题而且以前的生产实例仍然可用,则简单的更改能够将客户流量引流回到以前的生产实例 —— 有效地将问题实例“下线”而且回滚到之前的版本。而后有问题的新实例能够在其它区域中修复。
在某些状况下,经过蓝/绿发布切换整个部署可能不可行或不是指望的那样。另外一种方法是为金丝雀canary测试/部署。在这种模型中,一部分客户流量被从新引流到新的版本部署中。例如,新版本的搜索服务能够与当前服务的生产版本一块儿部署。而后,能够将 10% 的搜索查询引流到新版本,以在生产环境中对其进行测试。
若是服务那些流量的新版本没问题,那么可能会有更多的流量会被逐渐引流过去。若是仍然没有问题出现,那么随着时间的推移,能够对新版本增量部署,直到 100% 的流量都调度到新版本。这有效地“更替”了之前版本的服务,并让新版本对全部客户生效。
对于可能须要轻松关掉的新功能(若是发现问题),开发人员能够添加功能开关feature toggles。这是代码中的 if-then
软件功能开关,仅在设置数据值时才激活新代码。此数据值能够是全局可访问的位置,部署的应用程序将检查该位置是否应执行新代码。若是设置了数据值,则执行代码;若是没有,则不执行。
这为开发人员提供了一个远程“终止开关”,以便在部署到生产环境后发现问题时关闭新功能。
在暗箱发布dark launch中,代码被逐步测试/部署到生产环境中,可是用户不会看到更改(所以名称中有暗箱dark一词)。例如,在生产版本中,网页查询的某些部分可能会重定向到查询新数据源的服务。开发人员可收集此信息进行分析,而不会将有关接口,事务或结果的任何信息暴露给用户。
这个想法是想获取候选版本在生产环境负载下如何执行的真实信息,而不会影响用户或改变他们的经验。随着时间的推移,能够调度更多负载,直到遇到问题或认为新功能已准备好供全部人使用。实际上功能开关标志可用于这种暗箱发布机制。
运维开发DevOps 是关于如何使开发和运维团队更容易合做开发和发布软件的一系列想法和推荐的实践。从历史上看,开发团队研发了产品,但没有像客户那样以常规、可重复的方式安装/部署它们。在整个周期中,这组安装/部署任务(以及其它支持任务)留给运维团队负责。这常常致使不少混乱和问题,由于运维团队在后期才开始介入,而且必须在短期内完成他们的工做。一样,开发团队常常处于不利地位 —— 由于他们没有充分测试产品的安装/部署功能,他们可能会对该过程当中出现的问题感到惊讶。
这每每致使开发和运维团队之间严重脱节和缺少合做。DevOps 理念主张是贯穿整个开发周期的开发和运维综合协做的工做方式,就像持续交付那样。
持续交付管道是几个 DevOps 理念的实现。产品开发的后期阶段(如打包和部署)始终能够在管道的每次运行中完成,而不是等待产品开发周期中的特定时间。一样,从开发到部署过程当中,开发和运维均可以清楚地看到事情什么时候起做用,什么时候不起做用。要使持续交付管道循环成功,不只要经过与开发相关的流程,还要经过与运维相关的流程。
说得更远一些,DevOps 建议实现管道的基础架构也会被视为代码。也就是说,它应该自动配置、可跟踪、易于修改,并在管道发生变化时触发新一轮运行。这能够经过将管道实现为代码来完成。
管道即代码pipeline-as-code是经过编写代码建立管道做业/任务的通用术语,就像开发人员编写代码同样。它的目标是将管道实现表示为代码,以便它能够与代码一块儿存储、评审、跟踪,若是出现问题而且必须终止管道,则能够轻松地重建。有几个工具容许这样作,如 Jenkins 2。
传统意义上,管道中使用的各个硬件系统都有配套的软件(操做系统、应用程序、开发工具等)。在极端状况下,每一个系统都是手工设置来定制的。这意味着当系统出现问题或须要更新时,这一般也是一项自定义任务。这种方法违背了持续交付的基本理念,即具备易于重现和可跟踪的环境。
多年来,不少应用被开发用于标准化交付(安装和配置)系统。一样,虚拟机virtual machine被开发为模拟在其它计算机之上运行的计算机程序。这些 VM 要有管理程序才能在底层主机系统上运行,而且它们须要本身的操做系统副本才能运行。
后来有了容器container。容器虽然在概念上与 VM 相似,但工做方式不一样。它们只需使用一些现有的操做系统结构来划分隔离空间,而不须要运行单独的程序和操做系统的副本。所以,它们的行为相似于 VM 以提供隔离但不须要过多的开销。
VM 和容器是根据配置定义建立的,所以能够轻易地销毁和重建,而不会影响运行它们的主机系统。这容许运行管道的系统也可重建。此外,对于容器,咱们能够跟踪其构建定义文件的更改 —— 就像对源代码同样。
所以,若是遇到 VM 或容器中的问题,咱们能够更容易、更快速地销毁和重建它们,而不是在当前环境尝试调试和修复。
这也意味着对管道代码的任何更改均可以触发管道新一轮运行(经过 CI),就像对代码的更改同样。这是 DevOps 关于基础架构的核心理念之一。