【译文】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 步骤。是的,那里的故障也需要修复。但是,由于它们不会阻止任何人做他们的事情,因此您可以在完成工作后将这些修补程序作为“下一项任务”。只需在工作时关闭通知并不时检查即可。保持上下文切换到最小。
持续交付和部署是工程问题
让我们来解决一下定义,以解决这个问题。
持续交付是指能够随时部署任何版本的代码。实际上,它是指代码的最新版本。您不会自动部署,通常是因为您不必或不受项目生命周期的限制。但是只要有人愿意,就可以在最短的时间内完成部署。有人可以成为想要在暂存或预生产环境中进行测试的 test/QA 团队。或者实际上可能是时候将代码推向生产了。
持续交付的思想是准备与您要在环境中运行的制品尽可能接近。如果使用 Java,则可以是 jar 或 war 文件,如果使用 .NET,则可以是可执行文件。它们也可以是已转译 JS 代码的文件夹,甚至是 Docker 容器,或者其他使部署变得更短(即,您已尽可能预先构建)。
通过准备制品,我不是要把代码变成制品。这通常是一些脚本和执行时间。准备意味着:
运行所有测试,以确保代码一旦部署便可以正常工作。如果可以自动执行单元测试,集成测试,端到端测试,甚至性能测试。
这样,您可以过滤主分支的哪些版本实际上已准备好生产,哪些尚未准备就绪。理想的测试套件:
- 确保应用程序关键功能正常工作。理想情况下,所有功能正常
- 确保没有引入性能破坏因素,因此当您的新版本受到众多用户的欢迎时,它就有机会发生
- 空运行您的代码需要的任何数据库更新,以免出现意外
它不需要非常快。30分钟或1小时是可以接受的。
持续部署是下一步。您将代码的最新版本和生产就绪版本部署到某些环境。如果您足够信任 CD 测试套件,则是理想的生产方式。
请注意,根据上下文,这并非总是可能或值得付出。持续交付通常足以提高生产力。特别是如果您在封闭的网络中工作并且环境有限,则可以部署到该环境。也可能是软件的发布周期阻止了计划外的部署。
持续交付和持续部署(从现在起将其称为 CD)不是团队问题。他们的目的是在执行时间,维护工作和测试套件的相关性之间找到适当的平衡,以便能够说“此版本应能正常工作”。这是一个平衡。如果您的测试持续 30 个小时,那就有问题了。有关 Oracle 数据库测试套件的例子,请参见这篇史诗般的帖子。如果您花费大量时间使测试与最新代码保持最新,从而阻碍了团队的进步,那也不是一件好事。而且,如果您的测试套件几乎没有任何保证……那基本上是没有用的。
在理想的世界中,我们每次向主分支提交都需要 1 组可部署的制品。您可以看到我们有一个垂直的可扩展性问题:我们从代码转移到制品的速度越快,我们就越准备好部署最新版本的代码。
最大的不同是什么?
持续集成是一个水平可伸缩性问题。您希望开发人员经常合并其代码,因此检查必须快速。理想情况下,几分钟之内就可以避免开发人员始终通过 CI 版本的高度异步反馈来切换上下文。
您拥有的开发人员越多,则在所有活动分支上运行简单检查(构建和测试)所需的计算能力就越高。
良好的 CI 构建:
- 确保没有将破坏基本内容并阻止其他团队成员工作的代码引入主分支
- 足够快,可以在几分钟内向开发人员提供反馈,以防止任务之间进行上下文切换
持续交付和部署是垂直可伸缩性问题。您需要执行一个相当复杂的操作。
良好的 CD 构建:
- 确保尽可能多的功能正常运行
- 速度越快越好,但这不是速度问题。30 至 60 分钟的构建就可以了
一个常见的误解是将 CD 视为诸如 CI 之类的水平可扩展性问题:从代码移至制品的速度越快,实际处理的提交越多,并且越接近理想的情况。但是我们不需要。尽可能快地为每次提交生产制品通常是过度的。您可以尽最大的努力很好地使用 CD:拥有一个 CD 构建,它将在给定构建完成后立即选择最新提交进行验证。
毫无疑问 CD 真的很难。获得足够的测试信心才能说您的软件已准备好自动部署,通常可以在诸如 API 或简单 UI 之类的底层应用程序上使用。在复杂的 UI 或大型整体系统上很难实现。
结论
用于执行 CI 和 CD 的工具和原理通常非常相似。但是目标是非常不同的。
持续集成是在给开发人员的反馈速度与执行的检查(构建和测试)的相关性之间做出的折衷。没有任何妨碍团队进步的代码可以进入主分支。
持续交付部署是要进行彻底检查,以发现代码问题。检查的完整性是最重要的因素。通常以测试的代码覆盖率或功能覆盖率来衡量。尽早发现错误可以防止将坏代码部署到任何环境,并节省测试团队的宝贵时间。
精心设计 CI 和 CD 构建以实现这些目标并保持团队的生产力。没有工作流是完美的。问题会时不时地发生。每次使用它们时,都可以将其作为学习的经验教训来加强您的工作流程。