CI 和 CD 之间的真正区别

2025-06-07

CI 和 CD 之间的真正区别

有很多内容描述了持续集成、持续交付和持续部署的含义。但这些流程到底是用来做什么的呢?

理解 CI 和 CD 解决的问题对于正确使用它们至关重要。这将帮助你的团队改进流程。避免将精力投入到那些对你的流程没有任何价值的花哨指标上。

持续集成是一个团队问题

如果您在一个团队中工作,那么几位开发人员会在同一个代码库上工作。代码库中有一个主分支,其中包含最新版本的代码。开发人员在不同的分支上处理不同的工作。一旦有人完成了他的更改,他就会将其推送或合并到主分支。最终,整个团队会拉取此更改。

我们要避免的情况是,错误的提交最终被提交到主分支。错误意味着代码无法编译,或者应用程序无法启动或不可用。
为什么呢?不是因为应用程序有问题,也不是因为所有测试都必须通过。这完全没问题,你完全可以永远不部署该版本,等待修复。

问题是,你的整个团队都陷入了困境。所有拉取了错误提交的开发人员都会花 5 分钟时间思考为什么它不起作用。一些人可能会尝试找到错误的提交。有些人会尝试在错误代码作者的同时自行修复问题。

这浪费了你的团队的时间。最糟糕的是,反复发生的事件会加剧对主分支的不信任,并导致开发人员分头工作。

持续集成的核心在于防止主分支崩溃,避免团队陷入困境。仅此而已。它并非要求所有测试始终保持绿色,也并非要求每次提交都能将主分支部署到生产环境。

持续集成的过程独立于任何工具。您可以手动验证分支与主分支的合并是否在本地有效,然后才将合并内容推送到代码库。这样做效率极低。因此,我们采用自动化检查来实现持续集成。

检查应确保最低限度:

  • 应用程序应该构建并启动
  • 大多数关键功能应始终保持功能正常(用户注册/登录流程和关键业务功能)
  • 所有开发人员所依赖的应用程序的公共层应该是稳定的。这意味着需要对这些部分进行单元测试。

实际上,这意味着你需要引入任何适合你的单元测试框架,并确保应用程序的通用层安全。有时代码量不大,可以很快完成。此外,你还需要添加一个“冒烟测试”,验证代码是否编译通过以及应用程序是否启动。这在 Java Spring 或 .NET Core 等依赖注入技术中尤为重要。在大型项目中,依赖关系很容易出现错误,因此必须验证应用程序是否至少始终能够启动。

如果您有数百或数千个测试,则无需在每次合并时都运行它们。这会花费大量时间,而且大多数测试可能只是验证“非团队阻碍”功能。

我们将在下一节中看到持续交付流程如何充分利用这些测试。

这与工具无关

工具和自动化检查固然好,但如果你的开发人员只合并他们工作了数周的大型分支,那对你来说就没什么帮助了。团队会花费大量时间合并分支,并修复最终会出现的代码不兼容问题。这就像被错误的提交阻碍一样浪费时间。

持续集成与工具无关。它指的是以小块的方式工作,并将新代码集成到主分支并频繁拉取。

频繁意味着至少每天一次。将你正在处理的任务拆分成更小的任务。经常合并代码并拉取代码。这样,没有人会分开工作超过一两天,问题也不会有时间越滚越大。

大型任务无需全部集中在一个分支中,绝对不应该。将正在进行的工作合并到主分支的技术被称为“抽象分支”和“功能切换”。更多详情,请参阅博客文章《如何开始持续集成》 。

良好 CI 构建的关键点

很简单。尽量简短。最多 3-7 分钟。这与 CPU 和资源无关,而是关乎开发人员的生产力。
生产力的第一法则是专注。做一件事,完成它,然后再去做下一件事。

上下文切换的代价很高。研究表明,当你被打扰时,需要大约 23 分钟才能重新深入地专注于某件事。想象一下,你推送了分支并进行合并。
你启动了另一个任务。你花了 15-20 分钟才进入状态。进入状态后不久,你收到了“构建失败”的通知,通知来自你为上一个任务构建的 20 分钟 CI 构建。你返回去修复它,然后再次推送。你很容易就因为来回切换而浪费了超过 20 分钟的时间。

将每天一次或两次的 20 分钟乘以团队中的开发人员数量...这就会浪费大量宝贵的时间。

现在想象一下,反馈会在3分钟内到来。而且你知道它会到来。你可能根本不会开始新的任务。你会在等待期间再校对一遍代码,或者审查一个PR。失败的通知会到来。你会修复它。然后继续下一个任务。这才是你的流程应该实现的专注力。

保持 CI 构建周期短是需要权衡的。那些运行时间较长或在 CI 环境中价值不大的测试应该移到 CD 步骤。当然,那里的故障也需要修复。但由于这些故障不会妨碍任何人完成他们的工作,你可以在完成手头的工作后,将修复工作作为“下一个任务”。只需在工作时关闭通知,并时不时地查看即可。尽量减少上下文切换。

持续交付和部署是工程问题

让我们先确定定义来解决这个问题。

持续交付意味着能够随时部署任何版本的代码。实际上,它指的是代码的最新版本或前最新版本。您不会自动部署,通常是因为您不需要这样做,或者受项目生命周期的限制。但只要有人愿意,部署就可以在最短的时间内完成。这个人可以是测试/QA团队,他们希望在预生产环境或预生产环境中进行测试。或者,实际上,是时候将代码部署到生产环境了。

持续交付的理念是准备尽可能接近您想要在环境中运行的工件。如果您使用 Java,这些工件可以是 jar 或 war 文件;如果您使用 .NET,这些工件可以是可执行文件。它们也可以是编译好的 JS 代码文件夹,甚至是 Docker 容器,只要能缩短部署时间即可(也就是说,您尽可能提前构建好所有文件)。

我所说的准备工件,并非指将代码转化为工件。这通常需要几个脚本和几分钟的执行时间。准备工作意味着:

运行所有可以运行的测试,确保代码部署后能够正常工作。运行单元测试、集成测试、端到端测试,如果可以自动化,甚至性能测试也应如此。

这样,你就可以筛选出主分支中哪些版本实际上已经可以投入生产,哪些版本还没有。理想的测试套件:

  • 确保应用程序关键功能正常运行。理想情况下,所有功能
  • 确保没有引入任何性能破坏因素,这样当您的新版本面向众多用户时,它就有机会持续
  • 试运行代码所需的任何数据库更新,以避免意外

不需要很快,30分钟或者1小时都可以。

下一步是持续部署。你需要将最新的、可用于生产的版本代码部署到某个环境中。理想情况下,如果你足够信任你的持续部署测试套件,那么部署到生产环境中。

请注意,根据具体情况,这并不总是可行或值得付出努力。持续交付通常足以提高效率。尤其是在封闭式网络环境中工作,且可部署环境有限的情况下。也可能是因为软件的发布周期会阻止计划外的部署。

持续交付和持续部署(我们以后简称为CD)并非团队问题。它们的关键在于在执行时间、维护工作量和测试套件的相关性之间找到合适的平衡点,以便能够说“这个版本运行正常”。这确实是一个平衡点。如果你的测试持续了30个小时,那就有问题了。可以看看这篇关于Oracle数据库测试套件的精彩文章。如果你花费大量时间让测试保持最新代码,以至于阻碍了团队的进度,那同样不好。如果你的测试套件几乎什么都做不了……那它基本上就没用了。

理想情况下,我们希望每次提交到主分支时,都能生成一组可部署的成果。您可以看到,我们面临着一个垂直扩展性问题:从代码迁移到成果的速度越快,我们就越能准备好部署最新版本的代码。

有什么大的区别?

持续集成是一个水平扩展性问题。你希望开发人员经常合并代码,因此检查必须快速完成。理想情况下,最好在几分钟内完成,以避免开发人员在 CI 构建过程中频繁切换上下文,并获取高度异步的反馈。

开发人员越多,您需要在所有活动分支上运行简单检查(构建和测试)的计算能力就越强。

良好的 CI 构建:

  • 确保不会将破坏基本功能并妨碍其他团队成员工作的代码引入主分支
  • 速度足够快,可以在几分钟内向开发人员提供反馈,以防止任务之间的上下文切换

持续交付和部署是垂直扩展性问题。你需要执行一项相当复杂的操作。

良好的 CD 构建:

  • 确保尽可能多的功能正常运行
  • 越快越好,但这不是速度的问题。30-60分钟就可以了。

一个常见的误解是将持续交付 (CD) 视为类似持续交付 (CI) 的水平可扩展性问题:从代码到成品的迁移速度越快,实际能处理的提交就越多,就越接近理想情况。但我们不需要这样。尽可能快地为每一次提交生成成品通常有些矫枉过正。您可以尽最大努力来处理持续交付 (CD):使用单个 CD 构建,在给定构建完成后,只选择最新的提交进行验证。

不要误会持续交付 (CD) 的重要性。它真的很难。获得足够的测试置信度,以表明你的软件已经准备好自动部署,通常适用于 API 或简单 UI 等低表面应用。但在复杂的 UI 或大型单体系统上,实现这一点非常困难。

结论

执行 CI 和 CD 的工具和原则通常非常相似,但目标却截然不同。

持续集成是在开发人员反馈速度和执行检查(构建和测试)的相关性之间进行权衡。任何妨碍团队进度的代码都不应进入主分支。

持续交付部署是指尽可能全面地检查代码,以发现问题。检查的完整性是最重要的因素。它通常以测试的代码覆盖率或功能覆盖率来衡量。尽早发现错误可以防止有问题的代码部署到任何环境中,并节省测试团队的宝贵时间。

精心设计您的 CI 和 CD 构建,以实现这些目标并保持团队高效。没有完美的工作流程。问题时有发生。每次遇到问题时,请吸取经验教训,改进您的工作流程。

Jean-Paul Delimat 于 2019 年 11 月 27 日在Fire CI 博客上发布

文章来源:https://dev.to/jpdelimat/the-real-difference- Between-ci-and-cd-3k
PREV
Flexbox 的 80/20
NEXT
无需单元测试的持续集成