介绍
开发和发布软件可能是一个复杂的过程,特别是当应用程序、团队和部署基础设施变得越来越复杂时。通常,随着项目的增长,挑战会变得更加突出。为了以快速和一致的方式开发、测试和发布软件,开发人员和组织已经创建了三种相关但不同的策略来管理和自动化这些过程。
持续集成侧重于将个别开发人员的工作多次集成到主存储库中,以便及时捕获集成错误并加速协作开发。持续交付关注的是减少部署或发布过程中的摩擦,自动化部署构建所需的步骤,以便随时安全地发布代码。持续部署进一步推动了这一点,每次进行代码更改时都会自动部署。
在本指南中,我们将讨论这些策略的各个方面,它们之间的关系,以及如何将它们纳入应用程序生命周期中可以改变您的软件开发和发布实践。要更好地了解各种开源 CI/CD 项目之间的差异,请查看我们的 CI/CD 工具比较。
什么是持续集成,为什么它有用?
持续集成 是一种鼓励开发人员早期和频繁地将其代码集成到共享存储库的实践。与在开发周期结束时在隔离环境中构建功能并进行集成不同,开发人员通过一天中的多次集成将代码与共享存储库集成。
其想法是通过将集成作为早期考虑的一部分来最小化集成成本。开发人员可以在新代码和现有代码之间的边界上早期发现冲突,而在冲突仍然相对容易协调时。一旦冲突解决,工作就可以继续,确保新代码符合现有代码库的要求。
频繁集成的代码本身并不提供有关新代码或功能质量的任何保证。在许多组织中,集成是昂贵的,因为使用手动流程来确保代码符合标准,不会引入错误,并且不会破坏现有功能。频繁集成可能会在自动化方法与现有的质量保证措施不一致时产生摩擦。
为了解决集成过程中的这种摩擦,实际上,持续集成依赖于强大的测试套件和自动化系统来运行这些测试。当开发人员将代码合并到主存储库时,自动化流程会启动新代码的构建。然后,测试套件将针对新构建运行,以检查是否引入了任何集成问题。如果构建或测试阶段失败,团队将收到警报,以便他们可以解决构建问题。
持续集成的最终目标是使集成成为日常开发工作流程的一部分,从而降低集成成本并及早响应缺陷。努力确保系统健壮、自动化和快速,同时培养鼓励频繁迭代和对构建问题的响应的团队文化对于持续集成的成功至关重要。
什么是持续交付,为什么它有用?
持续交付 是持续集成的延伸。它侧重于自动化软件交付过程,以便团队可以轻松自信地随时将其代码部署到生产环境。通过确保代码库始终处于可部署状态,发布软件变得不再是一个复杂的事件,没有任何复杂的仪式。团队可以确信他们可以随时发布,而无需复杂的协调或后期测试。与持续集成一样,持续交付是一种需要技术和组织改进相结合才能发挥作用的实践。
在技术方面,持续交付在很大程度上依赖于部署管道来自动化测试和部署过程。部署管道 是一个自动化系统,按顺序运行越来越严格的测试套件,以对构建进行测试。这是持续集成的延续,因此可靠的持续集成设置是实施持续交付的先决条件。
在每个阶段,构建要么未通过测试,这会向团队发出警报,要么通过测试,这将自动推进到下一个阶段。随着构建通过管道移动,后续阶段将构建部署到尽可能接近生产环境的环境中。这样,构建、部署过程和环境可以同时进行测试。管道以一步到位的方式结束,可以随时将构建部署到生产环境。
持续交付的组织方面鼓励将“可部署性”作为首要关注点。这对特性的构建方式以及将其连接到代码库的方式产生了影响。必须对代码的设计进行思考,以便即使在不完整的情况下也可以安全地将特性随时部署到生产环境。在这一领域出现了许多技术。
持续交付是令人信服的,因为它自动化了从将代码检入存储库到决定是否将经过充分测试的功能构建发布到生产基础设施之间的步骤。有关代码质量和正确性的步骤是自动化的,但关于发布什么的最终决定留在组织手中,以实现最大的灵活性。
什么是持续部署以及它为什么有帮助?
持续部署 是持续交付的延伸,它会自动部署通过完整测试周期的每个构建。与等待人为守门员决定何时以及何物部署到生产环境不同,持续部署系统会部署一切成功通过部署流程的内容。需要记住的是,当新代码被自动部署时,新功能仍然可以在以后的某个时间条件下或者针对一部分用户进行激活。自动部署可以快速将功能和修复推送给客户,鼓励范围有限的小改动,并有助于避免对当前部署到生产环境的内容产生混淆。
这种完全自动化的部署周期可能会让一些组织感到焦虑,他们担心将控制权交给自动化系统来决定发布什么内容。自动部署所提供的权衡有时被认为对于它们提供的回报来说太过危险。
其他团队利用这种方法来确保始终遵循最佳实践。在部署代码之前没有最终的手动验证,开发人员必须负责确保他们的代码设计良好,并且测试套件是最新的。这将决定何时提交到主代码库以及何时发布到生产环境的所有决策都集中到开发团队的一个决策点上。
持续部署还允许组织从一致的早期反馈中受益。功能可以立即提供给用户,缺陷或无效的实现可以在团队投入大量精力进入无效方向之前及时发现。快速获得一个功能不是有用的反馈可以让团队转移注意力,而不是将更多精力投入到对影响最小的领域中。
持续过程的关键概念和实践
虽然持续集成、交付和部署在其涉及范围上有所不同,但有一些概念和实践对于每个过程的成功都是至关重要的。
小而迭代的改变
在采用持续集成时,最重要的实践之一是鼓励小的改变。开发人员应该将较大的工作分解为小块,并尽早提交这些小块。特殊的技术,比如分支抽象和功能标志(见下文),有助于保护主分支的功能免受正在进行的代码更改的影响。
小的改变最小化了集成问题的可能性和影响。通过在尽可能早的阶段提交到共享分支,然后在整个开发过程中持续提交,集成的成本降低了,不相关的工作也定期同步了。
基于主干的开发
在基于主干的开发中,工作是在代码库的主分支(“主干”)中进行的,或者经常性地合并回共享代码库。短暂的功能分支是允许的,只要它们代表小的改变,并且尽快合并回去。
基于主干的开发的理念是避免违反上面讨论的小而迭代的改变概念的大型提交。代码早早地对同事可用,这样冲突就可以在其范围较小的时候解决。
发布是从主分支或者从专门为此目的从主干创建的发布分支进行的。在发布分支上不进行开发,以便将焦点保持在主分支作为唯一的真相来源。
保持构建和测试阶段的快速
每个过程都依赖于自动化构建和测试来验证正确性。因为构建和测试步骤必须频繁进行,所以必须将这些过程流程化以最小化在这些步骤上花费的时间。
构建时间的增加应该被视为一个重大问题,因为每次提交都会触发一次构建,这种影响是成倍增加的。因为持续过程迫使开发人员每天都参与这些活动,所以在这些领域减少摩擦是非常值得的。
在可能的情况下,同时运行测试套件的不同部分可以帮助构建更快地通过流水线。还应该注意确保每种类型的测试比例合理。单元测试通常非常快速,并且维护开销很小。相比之下,验收测试通常复杂且容易出错。为了解决这个问题,通常是依赖大量的单元测试,进行相当数量的集成测试,并尽量减少更复杂的测试数量。
部署流程的一致性
因为持续交付或部署的实现应该测试发布的可行性,所以在流程的每个步骤中保持一致性是至关重要的——包括构建本身、部署环境和部署过程本身:
- 代码应该在流水线开始时构建一次:生成的软件应该被存储并且可以在后续过程中访问而无需重新构建。通过在每个阶段使用完全相同的构件,您可以确保您不会因为不同的构建工具而引入不一致性。
- 部署环境应该是一致的:配置管理系统可以控制各种环境,并且环境变更可以通过部署流程本身来确保正确性和一致性。每个测试周期应该为干净的部署环境进行配置,以防止旧条件损害测试的完整性。暂存环境应该尽可能与生产环境匹配,以减少在推广构建时存在的未知因素。
- 应该在每个环境中使用一致的流程来部署构建:每个部署都应该是自动化的,并且每个部署都应该使用相同的集中工具和程序。应该消除临时部署,而只使用流水线工具进行部署。
解耦部署和发布
将代码的部署与向用户发布分离是持续交付和部署的一个非常强大的部分。代码可以被部署到生产环境,而不会立即激活或向用户开放。然后,组织可以独立于部署决定何时发布新功能或特性。
这样做使组织在业务决策和技术流程之间获得了很大的灵活性。如果代码已经部署到服务器上,那么部署就不再是发布过程中的一个棘手部分,这最小化了参与者的数量和发布时所涉及的工作量。
有许多技术可以帮助团队部署负责某个功能的代码而不发布它。特性标志设置了基于环境变量值的条件逻辑,以检查是否运行代码。通过抽象分支允许开发人员通过在处理输入和输出之间创建一个抽象层来逐步重写流程。仔细规划并结合这些技术,使您能够解耦这两个流程。
测试类型
持续集成、交付和部署都严重依赖自动化测试来确定每个代码更改的有效性和正确性。在这些流程中需要不同类型的测试来确保对给定解决方案的信心。
虽然以下分类方式并不代表一个详尽的列表,而且对于每种类型的确切定义存在争议,但这些广泛的测试类型代表了在不同上下文中评估代码的各种方式。
冒烟测试
冒烟测试是一种特殊的初始检查,旨在确保核心功能以及一些基本的实现和环境假设。冒烟测试通常在每个测试周期的开始阶段作为一个理智检查,在运行更完整的测试套件之前进行。
这种测试的理念是帮助捕捉实现中的重大问题,并引起对可能表明进一步测试要么不可能要么不值得的问题的关注。冒烟测试并不是非常广泛,但应该非常快速。如果一个更改未通过冒烟测试,这是一个早期信号,表明核心断言被打破,您不应该在问题解决之前再花费时间进行测试。
特定上下文的冒烟测试可以在任何新阶段测试开始时使用,以确保基本假设和要求得到满足。例如,冒烟测试可以在集成测试或部署到暂存服务器之前使用,但在每种情况下要测试的条件会有所不同。
单元测试
单元测试负责以孤立和高度针对的方式测试代码的各个元素。对单个函数和类的功能进行独立测试。任何外部依赖关系都被替换为存根或模拟实现,以完全专注于所讨论的代码。
在将代码放入更复杂的上下文之前,单元测试对于测试内部一致性和正确性至关重要。测试的有限范围和去除依赖关系使得更容易找出任何缺陷的原因。这也是测试各种输入和代码分支的最佳时机,这些可能在以后难以复现。通常,在任何冒烟测试之后,单元测试是进行任何更改时首先运行的测试。
单元测试通常由个别开发人员在他们自己的工作站上运行,然后在开始集成测试之前,持续集成服务器几乎总是再次运行这些测试作为保障。
集成测试
在单元测试之后,通过将组件分组并将它们作为一个整体进行测试,进行集成测试。虽然单元测试验证了代码在隔离状态下的功能,但集成测试确保组件在相互接口时合作。这种类型的测试有机会捕捉到通过组件之间的交互暴露出来的完全不同类别的错误。
通常,当代码被检入共享存储库时,集成测试会自动执行。持续集成服务器检出代码,执行任何必要的构建步骤(通常执行快速冒烟测试以确保构建成功),然后运行单元和集成测试。模块以不同的组合方式连接在一起并进行测试。
集成测试对于共享工作非常重要,因为它们保护项目的健康。更改必须证明它们不会破坏现有功能,并且它们与其他代码的交互符合预期。集成测试的次要目的是验证更改是否可以部署到一个干净的环境中。这通常是在开发人员自己的机器上不执行的第一个测试周期,因此也可以在此过程中发现未知的软件和环境依赖关系。这通常也是新代码第一次针对真实的外部库、服务和数据进行测试的时候。
系统测试
一旦进行了集成测试,就可以开始另一级别的测试,称为系统测试。在许多方面,系统测试充当了集成测试的延伸。系统测试的重点是确保组件组以一个统一的整体正确地运行。
与关注组件之间的接口不同,系统测试通常评估完整软件的外部功能。这一系列测试忽略了组成部分,以便评估组合软件作为一个统一实体。由于这种区别,系统测试通常关注用户或外部可访问接口。
验收测试
验收测试是软件交付前执行的最后一种测试类型之一。验收测试用于确定软件是否满足业务或用户需求的所有要求。这些测试有时根据原始规范构建,并经常测试某些预期功能和可用性的接口。
验收测试通常是一个更为复杂的阶段,可能会延伸到软件发布之后。自动验收测试可用于确保满足设计的技术要求,但手动验证通常也起到一定作用。
通常,验收测试开始时会将构建部署到模拟生产系统的暂存环境。在此之后,自动化测试套件可以运行,内部用户可以访问系统,以检查其是否按照他们的需求进行功能。在发布软件或向用户提供测试版访问之后,通过评估软件在实际使用中的功能以及收集额外反馈来执行进一步的验收测试。
附加术语
虽然我们已经讨论了一些上述更广泛的想法,但在学习持续集成、交付和部署时,你可能会遇到许多相关概念。让我们定义一些你可能会遇到的其他术语:
- 蓝绿部署:蓝绿部署是一种在类似生产环境中测试代码并在最小停机时间内部署代码的策略。维护两组可用于生产的环境,并将代码部署到非活动集中,以进行测试。准备发布时,将生产流量路由到具有新代码的服务器,立即使更改生效。
- 抽象分支:抽象分支是在源代码存储库中执行主要重构操作的一种方法,而持续集成实践则不鼓励长期开发分支。在现有实现中构建并部署一个抽象层,以便可以在抽象层后面并行构建新实现。
- 构建(名词):构建是从源代码创建的特定软件版本。根据语言,这可能是编译代码或一致的解释代码捆绑。
- 金丝雀发布:金丝雀发布是一种向有限用户子集发布更改的策略。其想法是确保一切在生产工作负载下正确运行,同时最小化如果出现问题的影响。
- 暗部署:暗部署是将代码部署到生产环境,接收生产流量但不影响用户体验的实践。新更改与现有实现一起部署,通常相同的流量被路由到两个位置进行测试。旧实现仍然连接到用户界面,但在幕后,可以使用生产环境中的真实用户请求评估新代码的正确性。
- 部署管道:部署管道是一组组件,通过越来越严格的测试和部署场景来移动软件,以评估其发布准备情况。管道通常最终通过自动部署到生产环境或提供手动部署选项来完成。
- 功能标志或功能切换:功能标志是一种通过条件逻辑部署新功能的技术,该逻辑根据环境变量的值来确定是否运行。通过适当设置此标志,可以将新代码部署到生产环境而不激活。功能标志通常包含允许用户子集访问新功能的逻辑,从而创建逐渐推出新代码的机制。
- 推广:在持续过程的上下文中,推广意味着将软件构建移动到下一个测试阶段。
- 浸泡测试:浸泡测试涉及在显著的生产或类似生产负载下对软件进行长时间的测试。
结论
在本指南中,我们介绍了持续集成、持续交付和持续部署,并讨论了它们如何用于安全快速地构建和发布经过充分测试的软件。这些过程利用广泛的自动化,并鼓励不断的代码共享以便早期修复缺陷。虽然实施这些解决方案所需的技术、流程和工具代表了巨大的投资,但设计良好且正确使用的系统的好处是巨大的。
要了解哪种持续集成/交付解决方案可能适合您的项目,请查看我们的持续集成/交付工具比较指南以获取更多信息。