第 3 章:持续集成的构建和预部署测试步骤
简而言之,我们现代的软件交付实践提供了一种结构,帮助我们规划、编写、构建、测试和部署软件。在第二章中,我们探讨了 SCM 系统如何帮助跟踪和管理代码编写过程中的变更。
在本章中,我们将关注持续集成。图 3-1 展示了一个 CI/CD 流水线,我们将简要介绍,并在第四章和第八章中再次回顾。

我们将深入探讨持续集成流水线,重点关注构建过程和预部署测试(静态扫描、单元测试和集成测试)。我们将演示 AI 原生方法如何通过生成式 AI (GenAI)、智能体 AI (Agentic AI) 和开放标准(如 MCP 实现)来加速 CI。这些技术在构建、缓存和测试阶段中实现了自动化流程、预测性优化、标准化上下文管理和智能测试策略。
除了关键的持续集成步骤外,我们还将回顾持续集成工具,并讨论选择工具时需要考虑的因素。您将深入了解如何提高构建流水线的效率、质量和安全性。
软件构建与测试简史
这是一个耳熟能详的故事。1947 年,在哈佛 Mark II 计算机上工作的一个工程师团队发现一只飞蛾卡在继电器中,导致机器出现故障。他们移走了飞蛾,并将其用胶带贴在日志本上,附注“首次发现实际错误案例”,从而巩固了“Bug”与软件错误的关联。早期软件开发中的测试,准确地描述了在机器中寻找 Bug。开发者独立编写代码并进行集成。测试通常是手动且临时的。当发现错误时,团队的重点是找出 Bug,清除机器中的“飞蛾”。Bug 通常在生产环境中发现,导致延误和不可靠的软件。
随着软件开发的演进,测试变得更加正式和严谨,重点放在尝试“破坏”软件以发现缺陷。正式的测试方法和标准也开始出现,例如 IEEE 829 软件和系统测试文档标准(1983 年)。
结构化软件开发与瀑布式方法
瀑布式方法引入了一种结构化的软件开发方法,其中测试成为一个独立的阶段。在需求收集阶段定义的验收标准,概述了软件必须满足的条件。然后,在开发结束时开发并执行测试用例以验证这些标准。缺陷会被记录并解决,直到软件满足所有要求。然而,这种正式的方法通常导致编码和测试之间存在相当大的延迟,使得早期问题检测和解决具有挑战性,最终导致新产品和新功能的上市时间变慢。
敏捷与测试驱动开发
在第一章中,我们讨论了敏捷方法在软件开发中的兴起,其动机是瀑布式开发的低效率和局限性。敏捷方法更为灵活和响应迅速的开发模型强调频繁反馈和迭代开发,这需要新的测试方法来跟上快速的开发周期。这导致了新的测试方法。
极限编程 (XP) 是由 Kent Beck、Ward Cunningham 和 Ron Jeffries 开发的一种特定敏捷方法,由一系列最佳实践定义。其中一个基本的 XP 实践是测试驱动开发 (TDD)。在 TDD 中,您在编写相关代码之前先编写测试。Beck 颇具影响力的著作《解析极限编程》(Addison-Wesley 出版),于 1999 年首次出版,将 TDD 推广给了广大受众,而 JUnit(用于 Java)和 NUnit(用于 .NET)等早期工具为开发者提供了框架,以便在编写相应代码之前轻松编写这些类型的测试。
在编写代码之前编写测试,鼓励开发者深入思考预期的代码行为,从而带来更好的设计和更少的缺陷。虽然这个概念以前就存在,但 TDD 先编写失败测试,然后编写代码使其通过的特定方法,与敏捷方法关注短周期和频繁交付可用软件的理念非常契合。这种实践重新定义了“完成”的概念:当代码可用时,功能并不算完成,而是当自动化测试完成并通过时才算完成。
在 TDD 期间创建的自动化测试提供了一个安全网,让开发者能够放心地重构代码,因为他们知道任何回归问题都会被测试迅速捕获。这使得更快的迭代和更频繁的发布成为可能,进而允许从客户和利益相关者那里获得更快的反馈。测试本身也作为一种文档形式,清晰地阐明了系统的预期行为。
持续集成登场
正如我们在第一章中介绍的,CI 是将来自多个贡献者的代码变更集成到共享代码库中,并频繁触发自动化构建和测试,以确保软件保持可用状态的实践。这与 TDD 相辅相成。
CI 的根源可追溯到 1990 年代。Grady Booch 于 1991 年首次提出了“持续集成”这个术语,但真正将其付诸实践的是 Kent Beck 和 Ron Jeffries,他们在 1997 年的一个项目中合作时。他们的目标是解决代码合并不频繁导致的“集成地狱”问题,即冲突和错误会堆积如山,变得越来越难以解决。
早期的 CI 系统通常是定制化的,并针对特定项目进行调整。一个值得注意的例子是 CruiseControl,由 ThoughtWorks 于 2001 年创建。它是首批开源 CI 服务器之一,允许团队在每次代码提交时自动构建和测试软件。然而,它缺乏用户友好的界面和灵活的任务调度,导致 Kohsuke Kawaguchi 于 2005 年开发了 Hudson。Hudson 因其易用性和强大功能而迅速普及。
2011 年,与 Oracle 的一次争议导致 Hudson 分叉为 Jenkins,自那时起,Jenkins 已成为不仅用于持续集成,还用于持续交付和部署的最广泛使用的工具之一。Jenkins 的受欢迎程度可归因于其灵活性、可扩展性以及庞大的插件生态系统,使其能够与各种工具集成并适应不同的工作流程。
今日持续集成
持续集成已演变为现代软件开发中的一项基础实践,CI/CD 系统是任何交付流水线的支柱。通过代码变更的持续集成,团队已开始依赖以下优势:
减少集成问题
CI 通过确保开发者频繁合并代码变更来消除令人恐惧的“集成地狱”,最大程度地减少冲突,并使其更容易解决。
更快的反馈
CI 的自动化构建和测试过程为开发者提供了对其代码变更的快速反馈,使他们能够迅速捕获和修复错误,从而维护稳定且可部署的代码库。
提高效率和可靠性
通过自动化构建和测试过程,CI 消除了人工错误和不一致性,从而实现更可靠和可预测的构建。
提高透明度
CI 仪表板和通知提供构建和测试状态的实时可见性,让团队中的每个人都能跟踪进度、识别潜在问题并更有效地协作。
加速发布
通过简化和自动化构建、测试和集成过程,CI 能够实现更快、更频繁的发布,使企业能够更迅速地响应客户反馈和市场变化。
在“CI/CD 流水线中的持续集成”中,我们将探讨 CI 在交付流水线中的功能,并探索 CI 工具的图景。
CI/CD 流水线中的持续集成
在第二章中,我们介绍了 CI/CD 流水线,重点关注代码库和代码集成之间的关系。让我们回到这个流水线,并关注持续集成,即构建步骤以及执行预部署测试类型(包括静态分析、单元测试和集成测试)的步骤。
图 3-2 中的流水线显示了一个典型的 CI 过程。

此示例在开发者打开拉取请求时触发。此流水线的目的是在变更合并到主分支之前验证 PR 中提出的变更。让我们来看一下这些步骤:
1. 代码触发
开发者或 AI 智能体在托管代码库(例如 GitHub、GitLab、Bitbucket)上打开拉取请求,从而触发流水线。
2. 检出
流水线从 PR 中指定的分支检出源代码。
3. 构建
代码被编译(如果需要)并构建成可执行或可部署的制品。
4. 静态分析
Linter 和代码分析器等工具扫描代码,查找风格违规、潜在 Bug 和安全问题。
5. 单元测试
执行自动化测试,验证单个代码单元的功能。
6. 集成测试
可以运行相对快速的测试,以验证代码不同组件之间的交互。
7. 反馈
流水线向开发者提供 PR 状态(成功/失败)和发现的任何问题的反馈。此反馈直接显示在托管代码库上的 PR 中。
这个流水线检测并通知开发者其代码中的任何问题。构建步骤确定代码变更是否破坏了构建。测试步骤回答以下问题:此代码是否实现了预期功能?此代码是否包含安全漏洞、不安全操作、潜在 Bug、不良实践、已弃用功能,甚至是不一致的格式?
代码流水线通过在拉取请求打开或更新时检测问题和运行快速测试,为开发者提供了近乎实时的反馈。它回答了关于代码功能、安全性和质量的关键问题。然后,开发者可以迅速解决问题,完善 PR,或者在所有检查通过时放心地合并它,从而加速开发并确保代码库的健壮性。
(在第四章中,我们将探讨当 PR 合并时触发的补充性 CI 流水线。此流水线将新代码部署到测试环境并执行运行时间更长的测试套件。)
请注意,虽然我们的示例流水线使用代码变更触发器,但 CI/CD 系统通常提供其他触发选项,例如计划触发和手动触发,以提供更大的灵活性。
关键的构建步骤
构建步骤涉及将代码打包成可部署制品。可部署制品的示例包括容器镜像(用于在 Kubernetes/无服务器环境中部署)、特定语言包(例如 JAR、npm、NuGet 等)以及移动应用程序包(例如 APK 或 IPA)等。例如,用编译型语言(如 C++)编写的代码首先被编译,然后链接以创建机器码。解释型语言通常需要一个构建步骤来将代码打包成中间格式,例如 Java 归档 (JAR) 文件,以便在运行时编译。其他解释型语言,包括 JavaScript,可以被转译或压缩以优化执行。
根据代码类型,此步骤或系列步骤依赖于构建自动化工具、任务运行器或构建脚本。
构建自动化工具协调整个构建过程。流行的自动化工具示例包括:
Make 和 CMake
Make 是最古老、最基本的构建工具之一。它使用 Makefile 来定义文件之间的依赖关系以及构建它们所需的命令。CMake 是一种较新的跨平台构建系统生成器,可以生成 Makefile、Visual Studio 项目和其他构建脚本。它广泛用于 C 和 C++ 项目。
Ant
一种早期的基于 Java 的构建工具,使用 XML 描述构建过程。它以其灵活性和跨平台兼容性而闻名。
Maven
另一种流行的 Java 构建工具,它不仅限于编译。它管理依赖、构建、测试和打包项目。
Gradle
一种较新的构建工具,结合了 Ant 和 Maven 的优点。它使用基于 Groovy 的 DSL 来定义构建,并提供更灵活、更简洁的语法。
Bazel
由 Google 开发,Bazel 是一种功能强大的构建系统,专为大型项目设计。它以其速度、可伸缩性和对多种语言的支持而闻名。
MSBuild
一个构建自动化平台,通常与 .NET 框架和 C#、Visual Basic .NET 和 F# 等语言一起使用。
Cargo
Cargo 是 Rust 编程语言的包管理器,用于构建、编译和管理 Rust 项目。
任务运行器自动化开发工作流中的重复性任务,例如压缩、合并和转译。JavaScript 广泛使用的任务运行器包括:
npm 脚本
作为 Node 包管理器 (npm) 的一部分,npm 脚本是定义在 package.json 文件中的简单脚本,可以自动化常见任务,例如启动开发服务器、运行测试和进行生产构建。
Gulp
一种流式构建系统,使用 JavaScript 代码定义任务。它以其文件处理速度和效率而闻名。
Grunt
另一种用于 JavaScript 项目的任务运行器,Grunt 使用配置文件定义任务。它以其庞大的插件生态系统而闻名。
Webpack
一个主要用于 JavaScript 应用程序的模块打包器。它可以将 JavaScript、CSS 和其他资产打包成优化的文件以供生产使用。
Rollup
另一个模块打包器,以其专注于生成比 Webpack 更小、更高效的捆绑包而闻名。
最后,构建脚本是自定义脚本(通常用 Bash、Python 或其他脚本语言编写),它们定义构建项目所需的具体步骤和命令。它们可以与构建自动化工具或任务运行器结合使用。
通过静态分析优先保障质量和安全
在代码构建完成后,我们立即运行静态分析工具,其中可能包括 Linter。Linter 是一种特定类型的静态分析工具,用于检查编码风格(例如,确保一致的格式和命名模式);对于 JavaScript 等解释型语言,Linter 检查拼写错误、缺少分号或不正确的语言用法。这些工具在不执行源代码的情况下检查源代码,类似于发布前校对文档。它们有助于在开发过程的早期识别潜在问题。静态代码分析涵盖了一系列技术,用于评估代码的:
潜在 Bug
识别常见的编程错误,例如空指针解引用、资源泄露或逻辑缺陷。
安全漏洞
检测可能导致 SQL 注入、跨站脚本 (XSS) 或其他攻击的不安全编码实践。
代码异味
标记可维护性问题,例如重复代码、过度复杂或未使用的变量,并提出重构建议。
遵循标准
强制执行编码指南,有时还包括特定于语言或项目的最佳实践,以确保一致性和可读性。
通过将这些静态分析工具集成到开发过程的早期阶段,我们不仅能确保代码质量,还能实施一种称为“左移安全”的最佳实践。“左移安全”是指在开发的最早期阶段实施安全实践的策略。我们将在第五章深入探讨左移安全,并探索 AI 如何帮助快速修复安全问题。
自动化测试:尽早测试,频繁测试
自动化测试是 CI/CD 流水线的基础。在我们的示例流水线运行静态分析检查后,它会针对新代码执行单元测试和集成测试。让我们看看这些测试类型:
单元测试
这些测试验证最小的独立代码块(单元),例如函数或方法,以验证它们在隔离环境中按预期运行。想象一个简单的天气应用程序,它从外部 API 获取天气数据,对其进行处理,然后显示给用户。单元测试可能会测试处理原始天气数据的函数,验证它们是否正确地将数据转换为所需的格式。这些测试仅验证转换逻辑。
集成测试
这些测试侧重于验证软件模块之间的交互,确保正确的通信和数据交换。集成测试相对快速,通常在单元测试之后进行,并且像单元测试一样,有助于及早发现问题。同一个天气应用程序的集成测试可能会关注数据获取和处理模块如何交互。这些测试可以验证应用程序是否正确地从 API 检索和处理天气数据,包括错误场景,使用部分模拟来模拟真实世界的 API 响应。与隔离组件的单元测试不同,集成测试评估多个组件如何协同工作。在流水线早期使用的集成测试(例如在我们的示例流水线中)应避免慢速操作,例如访问数据库、文件系统或其他外部系统。
单元测试和集成测试框架数量众多,因语言而异,例如:
Java
JUnit 5 和 TestNG 是用于单元测试的框架。Mockito 和 Spring 用于 Java 集成测试。
JavaScript
Jest 和 Mocha 广泛用于 JavaScript 单元测试。Jest 也支持集成测试。
Python
pyTest 和 pyUnit (UnitTest) 既可用于单元测试,也可用于集成测试。
.NET
NUnit 和 xUnit 用于 .NET 单元测试,而 Moq 和 NSubstitute 通常用于集成测试。
Ruby
RSpec 支持 Ruby 的单元测试和集成测试。
iOS 的 XCTest 和 Android 的 Espresso 是移动单元测试和集成测试的标准。
单元测试和集成测试是第一道防线,它会提醒开发者代码中潜在的 Bug 或回归问题。这些快速的自动化检查只是我们测试策略的开始。在第四章中,我们将探讨当 PR 关闭并合并时触发的后续流水线。
彻底测试每个代码单元,包括所有可能的场景,会产生大量但至关重要的测试套件——即使对于看似简单的代码也是如此。然而,由于单元测试是隔离的,不依赖外部资源,因此它们执行迅速。
我们的流水线优先考虑这些快速的单元测试作为基础,然后是验证不同组件如何协同工作的集成测试,最后是数量较少但全面的端到端测试,它们模拟真实世界的使用情况。
在“测试金字塔”中,我们将探讨测试金字塔框架,它说明了如何平衡不同测试类型以实现最佳软件质量。
测试金字塔
测试金字塔提供了一个模型,用于战略性地构建我们的测试,根据它们的范围和速度来优先排序不同类型。虽然测试金字塔有时会以特定测试类型来描绘每个层,但我们更倾向于将层概念化为包含广泛测试类别,如图 3-3 所示。

在金字塔的底部是预部署测试,包括单元测试、集成测试和静态扫描等类型。这些测试量小且执行迅速。
集成测试可以指一系列测试策略。不与数据库和网络服务等外部系统交互的集成测试速度快,并包含在此级别。宽阔的金字塔底部反映了这些类型的测试套件应该庞大,并且理想情况下应覆盖整个代码库。测试应旨在为开发者提供快速反馈。
向上移动金字塔,我们将中间层描绘为包括我们在预生产测试环境中对已部署代码执行的任何类型的测试。通常,这些测试比上面提到的测试慢,但可以提供关于系统整体如何运作的宝贵洞察。
在金字塔的顶端,我们发现手动测试。这些测试速度慢且劳动密集,并且在代码经过多层自动化测试验证后进行。
采用金字塔方法使团队能够在测试工作中平衡速度、成本和有效性。通过专注于小而快的测试的坚实基础,并辅以针对已部署代码的战略性测试,我们可以在最大程度地减少所需时间和资源的同时实现全面的测试覆盖。
强大的测试策略是精简流水线的关键,可加速高质量发布的交付。在“持续集成工具”中,我们将考虑 CI 工具的选择如何优先考虑这一因素。
持续集成工具
有效的 CI 流程对于现代开发团队至关重要。在本节中,我们将探讨传统的 CI 工具以及现代工具的特点。
一家大型全国性零售商——我们的客户——在预计数字需求激增的情况下,发现自己正处于十字路口。其传统 CI/CD 工具,包括 Jenkins,分散在客户端网页、移动端和后端服务团队中,导致构建时间过长,每年给公司造成高达 50 万美元的开发者空闲时间损失。这些工具不仅抑制了创新,还带来了重大的安全风险,每年花费 80 万美元用于维护和自定义脚本更是加剧了这一问题。这项巨额投资使资源从增强客户体验方面转移。面对日益增长的挑战和不断攀升的成本,该零售商寻求一个统一的 CI/CD 平台,以简化操作、加速创新并强化安全性。
该公司日益增加的挑战揭示了 Jenkins 固有的局限性,尤其是在组织规模化和数字需求加剧时。让我们来看看其中一些局限性。
Jenkins 考量
Jenkins 将持续集成引入主流,功不可没。作为一个开源自动化服务器,Jenkins 利用庞大的插件生态系统来扩展其功能和特性,并赋予用户无限定制流水线的能力。Jenkins 插件市场是一个集中式存储库,用户可以在其中查找和安装数千个社区开发的插件。Jenkins 社区庞大,其文档丰富。它是一个适用于各种开发环境的适应性解决方案。
虽然 Jenkins 因其专用插件(例如,大型机)对于传统系统仍然很有价值,但现代 CI 流水线需要更多功能。如今的开发环境需要能够提供速度、安全性、协作工作流以及与多个云提供商技术、Kubernetes 编排和容器化应用程序原生集成的 CI 工具。以下各节探讨了使 Jenkins 不太适合这些现代要求的具体挑战。
插件复杂性
Jenkins 的灵活性和庞大的插件生态系统常常导致复杂且碎片化的架构,从而阻碍可维护性并增加开发者的负担。对 Groovy 脚本进行流水线定制的依赖可能会使故障排除和更新变得繁琐,特别是随着流水线数量和复杂性的增加。
此外,现代 CI/CD 解决方案通常采用“流水线即代码”范式,使用 YAML 等声明式语言来定义流水线。这种方法通常被认为比 Jenkins 的脚本化方法更直接且更易于维护。基于 YAML 的流水线通常比 Groovy 脚本更具可读性且更易于维护(可能存在例外),Groovy 脚本随着流水线规模和复杂性的增长会变得复杂且更难调试。将流水线定义为代码,可以将其与应用程序代码一起存储在版本控制系统 (VCS) 中。这确保流水线变更被跟踪、审查和审计,从而促进团队成员之间更好的协作。因此,“流水线即代码”方法可以实现更好的版本控制、协作和更简单的故障排除。
最后,管理众多插件(每个插件都有自己的配置)会引入维护开销。团队成员发现自己将宝贵的时间花在解决插件冲突、更新依赖项和破译神秘错误消息等繁琐任务上。这会分散对创新和核心开发的关注,从而减缓创新和功能交付。
可伸缩性挑战
Jenkins 的架构主要为单服务器设置设计,随着作业、流水线和用户数量的增加,其扩展效率常常会遇到困难。这可能导致性能瓶颈、构建时间变慢和整体系统不稳定。虽然 Jenkins 提供分布式构建和集群选项,但设置和维护这些解决方案可能很复杂且资源密集,需要专业知识和大量开销。因此,水平扩展 Jenkins 以满足大型组织或高吞吐量 CI/CD 工作流的需求通常成为一项重大挑战。
安全隐患
虽然 Jenkins 插件提供了可扩展性,但它们也引入了潜在的漏洞。每个插件都有自己的代码库和依赖项,从而扩大了 Jenkins 实例的攻击面。监控这些插件是否存在漏洞并确保及时更新成为管理员持续的开销。此外,配置 Jenkins 安全性,包括用户权限、访问控制和网络配置,可能非常复杂。错误配置可能会使系统暴露于未经授权的访问或恶意活动。插件生态系统的动态性质以及错误配置的可能性意味着您必须警惕监控风险并积极缓解 Jenkins 环境中的风险。
资源使用和效率问题
Jenkins 的资源消耗可能是一个显著的缺点,特别是当作业和插件的数量增加时。基于 Java 的架构(JVM 的运行时要求、垃圾收集行为和框架抽象)通常会导致高内存使用,管理大量并发构建可能会对 CPU 和磁盘资源造成压力。这可能导致构建时间变慢、基础设施成本增加和潜在的性能问题。在更大的环境中,水平扩展 Jenkins 可能会变得复杂且资源密集,需要额外的硬件和仔细的配置。
此外,在 CI 流水线中构建 Docker 镜像可能会迅速变得资源密集且成本高昂,尤其是在处理大型代码库或频繁提交触发大量并行构建时。每个镜像都需要计算资源、存储空间和网络带宽——这些成本在不同环境和分支中都会成倍增加。同样,虽然全面的可观测性提供了宝贵的系统洞察,但实施过度日志记录可能会产生其自身的问题:存储成本飙升,信噪比降低,以及处理开销增加。在全面覆盖和资源效率之间找到正确的平衡仍然是一个关键挑战。
超越 Jenkins
由于 Jenkins 的局限性,像我们国家零售商这样的公司常常会超越它,并寻求提供以下功能的现代全托管解决方案:
内置、完全支持的构建块
现代 CI/CD 工具提供丰富的内置、完全支持的构建块库,可简化流水线设置。这消除了对社区维护插件的依赖,确保了可靠性和稳定性。然而,鉴于定制需求,大多数解决方案仍支持通过自定义插件进行扩展。这使团队能够自动化独特的工作流,并根据其特定需求定制 CI/CD 环境。
声明式流水线定义
现代 CI/CD 工具使用 YAML 等声明式代码简化流水线定义,使其比 Jenkins 的 Groovy 脚本更易于访问和维护。这加速了设置并最大程度地减少了与手动脚本编写相关的错误。
原生支持容器化和编排
Jenkins 早于 Docker 和 Kubernetes 的广泛采用,虽然 Jenkins 流水线可以使用插件来处理和编排容器,但缺乏原生支持通常会导致繁琐的配置。相比之下,新工具无缝地集成了容器化和编排功能,简化了应用程序在容器化环境中的部署和管理。
在接下来的部分中,我们将探讨 Jenkins 之后的新工具提供的其他现代特性。在我们关注这些特性之前,让我们思考一个在考虑 CI/CD 工具时最基本的问题:是自行托管和管理工具,还是选择全托管解决方案。这个决定将影响从开发速度和成本效益到维护要求的一切。鉴于移动端的重要性,选择能够处理移动应用程序构建和部署复杂性的 CI/CD 设置至关重要,我们将探讨移动应用程序开发特有的考量因素。
托管选项
组织有三种主要的 CI/CD 系统构建基础设施选择:本地自托管、云端自托管和供应商托管(云)。每种选择都有其独特的优缺点,应仔细考虑:
本地自托管解决方案
在本地自托管 CI/CD 系统,您可以完全控制和拥有其基础设施和数据。这种方法允许最大程度的定制,能够根据特定的安全协议和组织需求进行调整。此外,一些组织可能更喜欢与本地解决方案相关的一次性付费模式。然而,这种方法也有几个缺点。它需要对硬件和软件进行大量前期投资,以及时间和精力进行维护和更新。对持续维护的需求和潜在的可伸缩性挑战可能会耗费资源,特别是对于小型组织而言。
云端自托管解决方案
云端自管理模型在控制和可伸缩性之间取得了平衡。组织可以控制其 CI/CD 软件,同时利用云的灵活性和可伸缩性。这种方法减少了对物理硬件的需求,并且与本地解决方案相比简化了扩展。
云托管应用程序在称为 Hypervisor 的虚拟化环境中运行,在考虑云托管时,您选择的 Hypervisor 类型将影响简单性和性能。需要了解的两种 Hypervisor 类型是:
类型 1 裸机 Hypervisor
这些 Hypervisor 直接在硬件上运行,提供卓越的性能和隔离性,但需要专用硬件。
类型 2 嵌入式 Hypervisor
这些 Hypervisor 在操作系统之上运行,提供更简单的设置和灵活性,但性能可能较低。
裸机可能更适合高要求、高安全性的设置,而嵌入式则适用于需求不那么密集且预算有限的情况。
任何云托管工具集都将需要持续维护和更新,您的组织仍将负责管理云基础设施。这可能导致与本地解决方案类似的挑战,尽管前期成本可能会降低。
全托管、供应商托管解决方案
供应商托管的 CI/CD 解决方案提供完全托管的服务,其中供应商处理基础设施、维护和更新。您的组织专注于开发而不是基础设施管理。这些解决方案高度可伸缩、易于使用,并且通常遵循按需付费模型,使其具有成本效益。然而,它们可能比自托管选项提供更少的定制,并可能限制您的组织根据特定需求定制系统的能力。此外,这种方法可能会出现数据安全和潜在的供应商锁定问题。
移动应用开发特有挑战
拥有健壮高效的 CI/CD 解决方案对于跟上移动用户期望的快速发布周期和高质量应用程序至关重要。移动端开发带来了独特的挑战:您的流程和 CI/CD 工具必须能够管理设备碎片化和频繁的移动操作系统更新。
在选择自托管和全托管 CI/CD 解决方案时,请考虑自托管解决方案虽然提供控制和定制,但可能导致物理硬件限制等挑战。此外,您的团队将负责构建环境的持续维护和更新。这些复杂性可能导致意想不到的成本。Xcode 等 iOS 开发工具的频繁发布周期需要定期更新硬件,这对于任何团队来说都可能是大量的时间和资源消耗。
另一方面,全托管 CI/CD 解决方案通过提供构建环境的自动更新和可预测的成本来缓解这些痛点。这使您的团队能够专注于构建功能和改进应用程序,而不是管理基础设施。此外,专门为移动开发优化的全托管 CI/CD 解决方案提供移动端专用集成和功能,可简化开发过程。其中许多平台为您全面管理移动开发中的挑战,例如设备碎片化和操作系统更新。
加速软件构建的现代特性
回到我们的零售商:它研究了更新的选项,并决定放弃 Jenkins 以及与其配合使用的零散插件和工具。该公司选择了一个统一的平台,简化了其工具集,同时提供了所需的伸缩性和成本节约。它能够将服务、客户端网页和移动团队的 CI/CD 流程整合到这一个平台上。新平台消除了对大量脚本编写的需求,节省了开发人员的时间,并使他们能够专注于创新。它还利用 AI/ML 进行测试,进一步节省了成本并大大加快了构建速度。此外,统一平台通过在流水线早期支持安全测试来提高安全性,从而能够更快地检测和修复漏洞。新平台的效率、安全性和可靠性使该零售商能够轻松应对其数字增长。
在接下来的部分中,我们将探讨现代系统中能够实现更快、更经济高效且更安全的流水线的特性。
通过缓存加速构建
现代构建环境是短暂的,通过提供隔离、经济高效且可伸缩的设置来增强敏捷性,从而加速开发周期,同时保持 CI/CD 流水线各个阶段的一致性。然而,短暂环境每次都需要从头开始设置整个构建过程,包括下载依赖项、编译代码和生成制品。这非常耗时。
缓存是 CI/CD 中用于存储和重用构建制品、依赖项、Docker 层和中间结果的技术。这通过避免冗余操作并仅专注于构建已更改的部分来显著缩短构建时间,这不仅加速了开发周期,还节省了计算资源和能源。现代 CI/CD 系统智能地管理此缓存过程,无需人工干预即可优化构建。缓存可以在不同阶段进行——缓存软件依赖项、缓存 Docker 层以及缓存 Bazel、Gradle 和 Maven 等工具的构建输出。
利用 AI 简化构建、缓存和测试
一个 AI 原生 CI 解决方案将无缝集成生成式 AI (GenAI)、智能体 AI (Agentic AI) 和 MCP,以增强软件构建、所需组件缓存以及每次构建的测试。让我们更详细地了解这些增强功能。
构建阶段增强。 生成式 AI 可以自动化重复任务的样板代码创建(例如,Dockerfile 模板、CI 配置文件),减少人工工作量。它还可以分析历史构建数据,预测依赖冲突并建议最佳版本,从而最大程度地减少构建失败。生成式 AI 的另一个有趣的用例是根据项目结构生成优化的 CI 流水线 YAML 配置,减少试错式设置。
智能体 AI 可以检测构建失败(例如,缺少依赖项),然后自动使用纠正后的配置重试并记录根本原因。它还可以根据工作负载需求动态扩展构建资源(例如,云实例),平衡速度和成本,并且可以动态地将单体构建拆分为可并行任务,从而缩短执行时间。
MCP 可以标准化分布式团队的环境变量、构建标志和工具链版本,确保一致性并通过 MCP 的集中式缓存,在相关项目之间共享预构建制品(例如,编译的库),从而避免冗余构建。
缓存阶段增强。 生成式 AI 可以使缓存技术变得更智能。它可以根据代码变更预测需要哪些依赖项(例如,node_modules、.m2 制品),并在构建开始前对其进行预缓存。机器学习模型可以通过分析代码差异模式来识别过期缓存,确保只保留相关制品。智能体 AI 可以实时标记并清除损坏的缓存(例如,损坏的制品),从而防止构建失败。
在可伸缩基础设施中使用 MCP 具有许多优势,包括通过标准化 API 实现跨 CI 流水线的安全、低延迟缓存共享,以及通过在 CI 运行之间缓存中间构建输出(例如,Docker 层)来减少冗余数据传输。MCP 可以通过标准化 API 实现并行 CI 作业之间的安全缓存共享,从而消除单体仓库架构中的冗余构建。
测试阶段增强。 考虑这样一种情况:开发人员在一个大型应用程序中修改了一个很少使用的组件中的一行代码。我们拥有大量健壮的单元测试套件,代码覆盖率很高;这些是我们测试策略的基础,是测试金字塔的底部。然而,当代码变化很小时,执行整个测试套件会导致漫长、资源密集且效率极低的测试周期。
现代工具可以通过 AI 工具来缓解这些问题,这些工具智能地选择并执行仅与修改代码直接相关的测试。这种方法显著减少了测试所需的时间和资源,从而带来了更快的反馈循环和更高效的开发过程。
Harness 测试智能 (TI) 是这种方法的一个示例。让我们看看 TI 的底层工作原理。三个组件协同工作以实现 Harness TI:
TI 服务
此服务使用 AI 并理解您的代码库、Git 提交和单元测试,并利用这些数据动态构建一个图,映射代码方法与其对应单元测试之间的关系。此图会持续更新以反映代码库中的变更。
一个测试运行器代理
此组件与服务通信并执行测试。
一个测试步骤
这是您添加到 CI 流水线中以将 TI 集成到您的工作流中的步骤。
TI 工作流始于开发者发起拉取请求并触发流水线。TI 服务分析代码变更并将其与图进行比较,以识别需要执行的测试。它不仅考虑代码修改,还考虑测试本身的任何变更或新增。这确保了代码库的所有相关方面都得到彻底测试,同时避免了冗余的测试运行。
因此,通过专注于受影响的测试,智能测试方法可以显著减少测试时间,尤其是在具有大量测试套件的大型项目中。这转化为更快的构建和更快的开发者反馈,使他们能够更快地识别和解决问题。
AI 驱动的构建和测试洞察
现代 CI/CD 工具还利用生成式 AI 来自动化繁琐的任务,并在出现问题时提供洞察。例如,工具可以自动生成您的流水线,分析代码中潜在的问题,并实时排查构建和部署失败。如果 CI 构建失败,生成式 AI 可以分析日志文件,查明错误,甚至建议潜在的修复方案。这节省了您的时间,减少了停机时间,并加速了软件交付过程。
智能体 AI 也可以用于根据组织的黄金标准来提出优化现有流水线的建议。由于组织往往是优化现有流水线而非创建新流水线,因此此功能将极其有价值。
生成式 AI 的另一个绝佳用例是编写意图驱动测试。测试,尤其是 UI 测试,如果 UI 发生变化,可能会非常手动且不稳定。通过使用生成式 AI,开发人员和质量保证工程师只需说明测试的意图,让生成式 AI 自动生成步骤。我们将在第四章详细讨论意图驱动测试。
最后,AI 还可以用于以道德和负责任的方式生成测试数据。一些示例包括:在使用生产数据进行模型训练时确保符合 GDPR 和其他法规,在整个数据生成过程中维护数据隐私和安全,以及使用适当的算法生成合成数据。
通过企业可观测性统一 CI/CD 指标
一个现代的 CI/CD 解决方案应该是一个团队合作者,与您企业生态系统中的其他关键平台协同工作,特别是您组织赖以理解系统行为、识别性能瓶颈以及主动检测和解决问题(在它们影响用户或业务运营之前)的可观测性平台。可观测性平台包括包含 Logstash 和 Kibana 的流行开源平台 Elastic,以及知名的商业选项 Datadog 和 Splunk。
现代持续集成工具通过实现 OpenTelemetry(一个开源框架)向这些平台提供遥测数据。这带来了 CI/CD 指标,从而实现可观测性和仪表板,帮助您了解正在发生的事情并提高构建性能和可靠性。
现代 CI/CD 对单体仓库的支持
当管理跨多个代码库的复杂代码时,版本控制和依赖管理变得非常具有挑战性。单体仓库是包含项目或组织所有代码的单个代码库,提供了一种集中管理复杂代码的方法。单个代码库通过保留任何共享库或组件的单个副本来简化依赖管理,并简化了跨不同项目的代码共享和重用。虽然单体仓库增加了合并冲突的风险,并且需要仔细设计以避免紧密耦合的代码,但许多大型公司已成功将其应用于海量代码库,这表明有效管理的单体仓库可以提供一种非常可伸缩的方法。
在采用单体仓库策略时,了解单体仓库对代码库和 CI 工具的独特要求非常重要。在数百名开发者可能贡献到大型单体仓库的情况下,高效管理变更和拉取请求变得至关重要。团队必须能够按子目录定义适当的访问权限,部分是为了确保只有相关的审阅者才能收到每个变更的通知。代码库应支持子目录特定所有权。
单体仓库需要 CI 系统能够对已更改的组件进行选择性构建和测试,并且支持高级依赖管理、缓存和并行执行。Harness CI 等工具通过以下功能支持这些需求:基于路径的触发器(仅当代码库中特定目录发生更改时才运行流水线,例如,针对 serviceA/ 的更改触发服务 A 的流水线),以及稀疏检出(克隆子目录而不是整个代码库)。这优化了资源使用并加速了反馈循环,同时保持了依赖完整性。
总结
持续集成已成为一项不可或缺的实践,它减少了集成问题,提供了更快的反馈,并提高了整体效率。在本章中,我们探讨了现代全托管 CI/CD 工具的特性,并将其与自托管的成本和挑战所带来的权衡进行了对比。我们探讨了优先进行更快、更小的单元测试以获得快速反馈的重要性,然后是较慢的测试类型以实现全面覆盖。我们所探讨的持续集成流水线就例证了这种实践:在打开 PR 的背景下,我们进行构建、完成静态扫描,然后运行快速测试,以确保我们的代码按预期运行并且不引入回归问题。我们还探讨了 AI 原生 CI 工具如何利用生成式 AI、智能体 AI 和 MCP 来增强 CI 的构建、缓存和测试阶段的各种方式。
在第四章中,我们将继续探讨 CI/CD,重点关注部署到测试环境以及执行评估系统性能、弹性以及端到端行为的慢速测试。