持续集成 (CI) 和持续交付 (CD) 是在谈论软件生产时使用的极其常见的术语。但它们真正的含义是什么?在本文中,我将解释这些术语以及相关术语(例如持续测试和持续部署)背后的含义和重要性。
快速总结
工厂中的装配线以快速、自动化、可重现的方式从原材料生产消费品。 同样,软件交付管道以快速、自动化和可重现的方式从源代码生产版本。 完成此操作的总体设计称为“持续交付”。 启动装配线的流程称为“持续集成”。 确保质量的流程称为“持续测试”,使最终产品可供用户使用的流程称为“持续部署”。 让一切顺利运行并为每个人简化的总体效率专家被称为“DevOps”实践者。
“持续”是什么意思?
“持续”用于描述遵循我在此处描述的实践的许多不同流程。它并不意味着“始终运行”。 它确实意味着“始终准备好运行”。 在创建软件的上下文中,它还包括几个核心概念/最佳实践。 这些是
-
频繁发布: 持续实践背后的目标是以频繁的间隔交付高质量的软件。 这里的频率是可变的,可以由团队或公司定义。 对于某些产品,每季度、每月、每周或每天一次可能就足够频繁了。 对于其他产品,可能需要并且可以每天多次。 “持续”也可以采用“偶尔、按需”的方式。 最终目标是相同的:通过可重复、可靠的流程向最终用户交付高质量的软件更新。 通常,这可以在用户几乎没有互动甚至不知道的情况下完成(想想设备更新)。
-
自动化流程: 实现此频率的关键部分是拥有自动化流程来处理软件生产的几乎所有方面。 这包括构建、测试、分析、版本控制,以及在某些情况下,部署。
-
可重复: 如果我们使用自动化流程,给定相同的输入,这些流程始终具有相同的行为,那么处理应该是可重复的。 也就是说,如果我们返回并输入相同版本的代码作为输入,我们应该获得相同的可交付成果集。 这也假设我们具有相同版本的外部依赖项(即,我们不创建但我们的代码使用的其他可交付成果)。 理想情况下,这也意味着我们管道中的流程可以进行版本控制和重新创建(请参阅稍后关于 DevOps 的讨论)。
-
快速处理: “快速”在这里是一个相对的术语,但无论软件更新/发布的频率如何,都期望持续流程以高效的方式处理从源代码到可交付成果的更改。 自动化可以处理大部分这些,但自动化流程仍然可能很慢。 例如,对产品各个方面进行集成测试需要一整天的大部分时间,对于每天有多个新候选版本的產品更新来说可能太慢了。
什么是“持续交付管道”?
处理将源代码转换为可发布产品的不同任务和作业通常连接到软件“管道”中,其中一个自动流程的成功完成会启动序列中的下一个流程。 这样的管道有许多不同的名称,例如持续交付管道、部署管道和软件开发管道。 一个整体的监控应用程序管理管道不同部分的定义、运行、监控和报告,因为它们正在执行。
持续交付管道如何工作?
软件交付管道的实际实施方式可能差异很大。 有大量的应用程序可以在管道中使用,用于源代码跟踪、构建、测试、收集指标、管理版本等各个方面。 但总体工作流程通常是相同的。 单个编排/工作流应用程序管理整个管道,每个流程都作为单独的作业运行,或者由该应用程序进行 阶段管理。 通常,单个“作业”以编排应用程序理解并可以作为工作流管理的语法和结构定义。
创建作业是为了执行一项或多项功能(构建、测试、部署等)。 每个作业可能使用不同的技术或多种技术。 关键是作业是自动化的、高效的且可重复的。 如果作业成功,工作流管理器应用程序将触发管道中的下一个作业。 如果作业失败,工作流管理器会提醒开发人员、测试人员和其他人员,以便他们可以尽快纠正问题。 由于自动化,可以比运行一组手动流程更快地发现错误。 这种快速识别错误被称为“快速失败”,并且在到达管道的终点时也同样有价值。
“快速失败”是什么意思?
管道的其中一项工作是快速处理更改。 另一项工作是监视创建版本的不同任务/作业。 由于无法编译或测试失败的代码可能会阻止管道,因此重要的是尽快通知用户这种情况。 快速失败是指管道处理尽快发现问题并快速通知用户,以便可以纠正问题并重新提交代码以再次通过管道运行的想法。 通常,管道流程可以查看历史记录以确定谁进行了该更改,并通知该人和他们的团队。
持续交付管道的所有部分都必须自动化吗?
管道的几乎所有部分都应该自动化。 对于某些部分,具有人工干预/交互的场所可能是有意义的。 一个例子可能是用户验收测试(让最终用户试用该软件并确保它能满足他们的需求/期望)。 另一种情况可能是部署到希望拥有更多人工控制的生产环境。 当然,如果代码不正确并中断,则需要人工干预。
有了持续含义的背景知识,让我们看一下不同类型的持续处理以及每种处理在软件管道中的含义。
什么是持续集成?
持续集成 (CI) 是指在产品源代码发生更改时,自动检测、拉取、构建以及(在大多数情况下)进行单元测试的过程。 CI 是启动管道的活动(尽管某些预验证(通常称为“预检”)有时会在 CI 之前纳入)。
CI 的目标是快速确保开发人员的新更改是“好的”并且适合在代码库中进一步使用。
持续集成如何工作?
基本思想是拥有一个自动化流程“监视”一个或多个源代码存储库中的更改。 当更改被推送到存储库时,监视流程会检测到该更改,下载副本、构建它并运行任何相关的单元测试。
持续集成如何检测更改?
如今,监视流程通常是一个像 Jenkins 这样的应用程序,它还可以协调管道中运行的所有(或大部分)流程,并监视更改作为其功能之一。 监视应用程序可以通过几种不同的方式监视更改。 这些包括
-
轮询: 监视程序会重复询问源代码管理系统,“在我感兴趣的存储库中是否有任何新内容?” 当源代码管理系统有新的更改时,监视程序会“唤醒”并执行其工作以拉取新代码并构建/测试它。
-
定期: 监视程序配置为定期启动构建,无论是否有更改。 理想情况下,如果没有更改,则不会构建任何新内容,因此这不会增加太多额外成本。
-
推送: 这是监视应用程序与源代码管理系统检查的相反。 在这种情况下,源代码管理系统配置为在将更改提交到存储库时向监视应用程序“推送”通知。 最常见的是,这可以通过“webhook”的形式完成 - 一种在推送新代码时“挂钩”以运行的程序,并通过 Internet 将通知发送到监视程序。 为了使这起作用,监视程序必须具有一个开放端口,该端口可以通过 Internet 接收 webhook 信息。
什么是“预检”?
在将代码引入源代码存储库并触发持续集成之前,可能会进行其他验证。 这些遵循最佳实践,例如测试构建和代码审查。 它们通常在代码引入管道之前构建到开发过程中。 但是,某些管道也可能将它们作为其监控流程或工作流程的一部分。
例如,名为 Gerrit 的工具允许在开发人员推送代码之后但在允许进入(Git 远程)存储库之前进行正式的代码审查、验证和测试构建。 Gerrit 位于开发人员的工作区和 Git 远程存储库之间。 它“捕获”来自开发人员的推送,并且可以进行通过/失败验证以确保它们在被允许进入存储库之前通过。 这可以包括检测提议的更改并启动测试构建(一种 CI 形式)。 它还允许组在该点进行正式的代码审查。 这样,就有额外的信心,即在将更改合并到代码库中时,该更改不会破坏任何内容。
什么是“单元测试”?
单元测试(也称为“提交测试”)是开发人员编写的小型、有针对性的测试,以确保新代码独立工作。 这里的“独立”是指不依赖于或调用不可直接访问的其他代码,也不依赖于外部数据源或其他模块。 如果代码运行需要这样的依赖项,则这些资源可以用模拟来表示。 模拟是指使用看起来像资源并且可以返回值但不实现任何功能的代码存根。
在大多数组织中,开发者负责创建单元测试来证明他们的代码能够正常工作。事实上,有一种模型(被称为测试驱动开发 [TDD])要求首先设计单元测试,作为清晰地识别代码应该做什么的基础。由于这样的代码更改可能快速且频繁,因此它们的执行也必须很快。
当单元测试与持续集成工作流程相关时,开发者会在其本地工作环境中创建或更新源代码,并使用单元测试来确保新开发的功能或方法有效。通常,这些测试采用断言的形式,即断言给定的一组函数或方法的输入会产生给定的一组输出。它们通常会测试以确保错误条件被正确地标记和处理。有各种单元测试框架可供使用,例如用于 Java 开发的 JUnit。
什么是持续测试?
持续测试是指随着代码通过 CD 管道,运行范围不断扩大的自动化测试的实践。单元测试通常与构建过程集成在一起,作为 CI 阶段的一部分,并且专注于测试与其他代码隔离的代码。
除此之外,还有各种可以/应该进行的测试形式。这些可能包括:
-
集成测试验证组件和服务组是否都能协同工作。
-
功能测试验证产品中执行功能的結果是否符合预期。
-
验收测试根据可接受的标准衡量系统的某些特征。示例包括性能、可伸缩性、压力和容量。
所有这些测试可能不会都出现在自动化管道中,并且某些不同类型之间的界限可能会模糊。但是,交付管道中持续测试的目标始终相同:通过连续的测试级别来证明代码具有可以在正在进行的版本中使用的高质量。基于快速的持续原则,第二个目标是快速发现问题并提醒开发团队。这通常被称为快速失败。
除了测试之外,还可以对管道中的代码进行哪些其他类型的验证?
除了测试的通过/失败方面,还存在一些应用程序可以告诉我们测试用例执行(覆盖)了多少行源代码。这是一个可以跨源代码计算的指标示例。此指标称为代码覆盖率,可以通过工具(例如用于 Java 源代码的 JaCoCo)来测量。
还存在许多其他类型的指标,例如计算代码行数、测量复杂性以及将编码结构与已知模式进行比较。诸如 SonarQube 之类的工具可以检查源代码并计算这些指标。除此之外,用户还可以设置阈值,以确定他们愿意接受哪些范围作为这些指标的“通过”。然后,可以设置管道中的处理以检查计算值是否符合阈值,如果值不在可接受的范围内,则可以停止处理。诸如 SonarQube 之类的应用程序具有高度可配置性,可以进行调整以仅检查团队感兴趣的内容。
什么是持续交付?
持续交付 (CD) 通常是指整体的流程链(管道),该流程链自动获取源代码更改,并通过构建、测试、打包和相关操作来运行它们,以生成可部署的版本,而无需任何人工干预。
CD 在生成软件版本方面的目标是自动化、效率、可靠性、可重现性和质量验证(通过持续测试)。
CD 包含 CI(自动检测源代码更改、为更改执行构建过程以及运行单元测试以进行验证)、持续测试(在代码上运行各种类型的测试以在代码质量方面获得连续级别的信心)和(可选的)持续部署(自动将来自管道的版本提供给用户)。
如何在管道中识别/跟踪多个版本?
版本控制是使用 CD 和管道的关键概念。持续意味着能够频繁地集成新代码并提供更新的版本。但这并不意味着每个人总是想要“最新和最好的”。对于希望针对已知的稳定版本进行开发或测试的内部团队而言,尤其如此。因此,重要的是管道可以对其创建的对象进行版本控制,并且可以轻松地存储和访问这些版本控制的对象。
从源代码的管道处理中创建的对象通常可以称为制品。制品在构建时应应用版本。建议的制品版本号分配策略称为语义版本控制。(这也适用于从外部来源引入的依赖制品版本。)
语义版本号包含三个部分:主版本号、次版本号和修订版本号。(例如,1.4.3 反映了主版本号 1、次版本号 4 和修订版本号 3。)这个想法是,其中一部分的更改表示制品中的更新级别。只有在不兼容的 API 更改时才递增主版本号。当以向后兼容的方式添加功能时,递增次版本号。并且在进行向后兼容的错误修复时,递增修订版本号。这些是推荐的准则,但是团队可以自由地更改此方法,只要他们在整个组织中以一致且易于理解的方式进行操作即可。例如,可以将每次为版本构建时递增的数字放入修订版本号字段中。
如何“晋升”制品?
团队可以将晋升“级别”分配给制品,以指示其是否适合测试、生产等。有多种方法。可以启用诸如 Jenkins 或 Artifactory 之类的应用程序以进行晋升。或者,一个简单的方案是在版本字符串的末尾添加标签。例如,-snapshot 可以指示用于构建制品的代码的最新版本(快照)。可以使用各种晋升策略或工具将制品“晋升”到其他级别,例如 -milestone 或 -production,以指示制品的稳定性和发布准备就绪程度。
如何存储和访问制品的多个版本?
可以通过管理“制品仓库”的应用程序来存储从源代码构建的版本控制的制品。制品仓库类似于为构建的制品进行源代码管理。应用程序(例如 Artifactory 或 Nexus)可以接受版本控制的制品,存储和跟踪它们,并提供检索它们的方法。
管道用户可以指定他们想要使用的版本,并使管道提取这些版本。
什么是持续部署?
持续部署 (CD) 是指能够自动获取从 CD 管道出来的代码版本并将其提供给最终用户的想法。根据用户“安装”代码的方式,这可能意味着自动在云中部署某些东西、提供更新(例如对于手机上的应用程序)、更新网站或仅更新可用版本的列表。
这里的一个重要观点是,仅仅因为可以进行持续部署并不意味着从管道中出来的每组可交付成果都始终会进行部署。这确实意味着,通过管道,每组可交付成果都被证明是“可部署的”。这主要通过连续测试的连续级别来实现(请参阅本文中有关持续测试的部分)。
是否部署来自管道运行的版本可能取决于人为决定以及用于在完全部署之前“尝试”版本的各种方法。
在完全部署到所有用户之前,有哪些方法可以测试部署?
由于不得不回滚/撤消对所有用户的部署可能是一种代价高昂的情况(无论是在技术上还是在用户的认知上),因此已经开发了许多技术来允许“尝试”新功能的部署,并在发现问题时轻松“撤消”它们。这些包括:
蓝/绿测试/部署
在这种部署软件的方法中,维护着两个相同的托管环境——一个蓝色环境和一个绿色环境。(颜色并不重要,仅用作标识符。)在任何给定的时间点,其中一个是生产部署,另一个是候选部署。
在这些实例的前面是一个路由器或其他系统,该系统充当客户访问产品或应用程序的“网关”。通过将路由器指向所需的蓝色或绿色实例,可以将客户流量定向到所需的部署。这样,交换指向哪个部署实例(蓝色或绿色)是快速、简便且对用户透明的。
当准备好测试新版本时,可以将其部署到非生产环境。经过测试和批准后,可以更改路由器以将传入的生产流量指向它(因此它将成为新的生产站点)。现在,曾为生产的托管环境可用于下一个候选版本。
同样,如果在最新的部署中发现问题,并且先前的生产实例仍部署在另一个环境中,则一个简单的更改可以将客户流量重新指向先前的生产实例——有效地使出现问题的实例“脱机”,并回滚到先前的版本。然后可以在另一个区域中修复出现问题的新部署。
金丝雀测试/部署
在某些情况下,通过蓝/绿环境交换整个部署可能不可行或不希望这样做。另一种方法称为金丝雀测试/部署。在此模型中,一部分客户流量将重定向到产品的新部分。例如,产品的搜索服务的新版本可以与该服务的当前生产版本一起部署。然后,可以将 10% 的搜索查询路由到新版本以在生产环境中对其进行测试。
如果新服务可以毫无问题地处理有限的流量,那么随着时间的推移,可能会将更多的流量路由到该服务。如果没有出现问题,那么随着时间的推移,可以增加路由到新服务的流量,直到 100% 的流量都流向它。这有效地“淘汰”了该服务的先前版本,并将新版本投入使用,供所有客户使用。
功能开关
对于可能需要轻松退出的新功能(以防发现问题),开发人员可以添加功能开关。这是代码中的一个软件if-then开关,只有在设置数据值时才会激活该代码。此数据值可以是已部署的应用程序检查其是否应执行新代码的全局可访问位置。如果设置了数据值,它将执行代码;如果未设置,则不会执行。
这使开发人员拥有一个远程“终止开关”,可以在部署到生产环境后关闭新功能(如果发现问题)。
暗启动
在这种实践中,代码会逐步测试/部署到生产环境中,但是更改对用户不可见(因此称为“暗启动”)。例如,在生产版本中,一部分 Web 查询可能会重定向到一个查询新数据源的服务。开发部门可以收集此信息以进行分析,而不会将有关界面、事务或结果的任何信息公开给用户。
这里的想法是在不影响用户或改变用户体验的情况下,获取有关候选变更在生产负载下表现的真实信息。 随着时间的推移,可以重定向更多的负载,直到发现问题或新功能被认为可以供所有人使用。 功能标志实际上可用于处理暗启动的机制。
什么是 DevOps?
DevOps 是一套关于如何让开发和运维团队更容易地协同工作,开发和发布软件的想法和推荐实践。 从历史上看,开发团队创建产品,但不会像客户那样以常规、可重复的方式安装/部署它们。 这组安装/部署任务(以及其他支持任务)留给运维团队在周期的后期来解决。 这通常会导致大量的困惑和问题,因为运维团队在周期的后期才被引入,并且必须在短时间内让他们获得的东西工作。 同样,开发团队也经常处于不利的境地,因为他们没有充分测试产品的安装/部署功能,他们可能会对该过程中出现的问题感到惊讶。
这通常导致开发和运维团队之间严重的脱节和缺乏合作。 DevOps 理想提倡从周期开始到结束都涉及开发和运维人员的方式,例如 CD。
CD 如何与 DevOps 相交?
CD 管道是几个 DevOps 理想的实现。 产品的后期阶段,例如打包和部署,始终可以在管道的每次运行中完成,而不是等待产品开发周期中的特定点。 同样,从开发到部署,开发和运维人员都可以清楚地看到何时工作以及何时不工作。 为了使 CD 管道的周期成功,它不仅必须通过与开发相关的流程,还必须通过与运维相关的流程。
更进一步,DevOps 建议甚至将实现管道的基础设施也像代码一样对待。 也就是说,它应该是自动配置的、可跟踪的、易于更改的,并且如果发生更改,则会产生新的管道运行。 这可以通过将管道实现为代码来完成。
什么是“管道即代码”?
管道即代码 是通过编程代码创建管道作业/任务的通用术语,就像开发人员使用产品的源代码一样。 目标是将管道实现表示为代码,以便它可以与代码一起存储、审查、随时间跟踪,并且如果出现问题并且必须停止管道,则可以轻松地再次启动它。 许多工具允许这样做,包括 Jenkins 2。
DevOps 如何影响用于生产软件的基础设施?
传统上,管道中使用的各个硬件系统都是一次配置一个软件(操作系统、应用程序、开发工具等)。 在极端情况下,每个系统都是一个定制的、手工制作的设置。 这意味着,当系统出现问题或需要更新时,这通常也是一项自定义任务。 这种方法违背了 CD 理想的可轻松重现和可跟踪环境的根本理念。
多年来,已经开发了用于标准化配置(安装和配置)系统的应用程序。 此外,虚拟机被开发为模拟在其他计算机之上运行的计算机的程序。 这些虚拟机需要一个管理程序才能在底层主机系统上运行它们。 并且它们需要自己的操作系统副本才能运行。
接下来是容器。 容器在概念上与虚拟机相似,但工作方式不同。 它们不是需要单独的程序和操作系统的副本才能运行,而是简单地使用一些现有的操作系统结构来在操作系统中开辟隔离空间。 因此,它们的行为类似于 VM 以提供隔离,但不需要开销。
由于虚拟机和容器是从存储的定义创建的,因此可以轻松地销毁和重新创建它们,而不会对运行它们的主机系统产生影响。 这允许一个可重新创建的系统在上面运行管道。 此外,对于容器,我们可以跟踪对其构建的定义文件的更改,就像我们对源代码一样。
因此,如果我们在虚拟机或容器中遇到问题,可能更容易和更快地直接销毁并重新创建它,而不是尝试调试和修复现有的虚拟机或容器。
这也意味着,对管道代码的任何更改都可以触发管道的新运行(通过 CI),就像代码更改一样。 这是 DevOps 关于基础设施的核心理念之一。
1 条评论