Azure-流水线-CICD-实现指南-全-
Azure 流水线 CICD 实现指南(全)
原文:
annas-archive.org/md5/3178513c377c59339d02797425ed7c78译者:飞龙
前言
持续集成(CI)是利用自动化工具,根据开发人员对源代码所做的更改,自动持续地编译、打包和测试,从而确保它们的稳定性并能够提供预期的功能。
持续交付(CD)通过 CI 生成的工件,将这些应用程序自动部署到最终用户,无需人工干预,确保这些应用程序始终保持最新版本,并且在大多数情况下,在到达最终用户之前会在多个环境中进行额外验证。
所有这些都可以通过利用 Azure Pipelines 来实现,Azure Pipelines 是支持软件开发生命周期(SDLC)各个方面的领先平台之一;然而,本书将重点关注 CI/CD 方面,以及如何利用可用的功能来实现 DevOps 框架下的各种任务自动化选项,甚至触及 DevSecOps 的相关内容。
本书为您提供了从基础概念开始学习的工具,并从这里构建更复杂的场景。这将引导您实现端到端的场景,使您的软件开发团队能够在整个应用程序交付过程中(从源代码到运行平台),使用自动化工具来完成每个步骤。
本书适合谁阅读
从初学者到最先进的用户,任何希望更好理解如何利用 Azure Pipelines 的人,都能从本书中受益。
该内容的主要受众有三类人物,分别如下:
-
软件开发人员:他们将学习如何在非常早期的阶段,自动构建和部署他们的软件产品,无论目标平台如何,从而加速软件开发生命周期(SDLC)。
-
DevOps 工程师:他们将学习如何让 Azure Pipelines 支持任何自动化需求,无论流程的哪个阶段,都能在每一步注入质量和检查。
-
安全工程师:他们将学习如何将他们的工具集成到 CI/CD 流程中,在构建和部署过程的初期强制执行安全性和质量。
本书内容概述
第一章,了解 Azure Pipelines,提供了 CI/CD、Azure DevOps、Azure Pipelines 及其组件的介绍。它解释了为什么 Azure 管道是某些场景的正确选择,介绍了 Azure DevOps 下的其他服务(如 Azure Repos),并指导您如何设置新项目、配置自托管代理、准备管道环境以及配置代理池和部署组。
第二章,创建构建管道,教您如何在 Azure DevOps 中创建和管理管道、阶段、作业、任务、触发器和工件,以及在将代码推送到 Azure Repos 后如何运行管道。
第三章,设置变量、环境、审批和检查,涵盖了在 Azure DevOps 中创建服务连接、变量组、机密文件和发布管道的内容。它还解释了如何为 Azure Repos 和 GitHub 连接设置服务帐户。此外,你将学习如何安全地存储机密密钥并使用环境、审批和检查来控制阶段进展。
第四章,使用 YAML 扩展高级 Azure Pipelines,帮助你理解如何使用 YAML 创建构建和发布管道。它详细讨论了如何使用 YAML 语法创建阶段、作业和任务来进行 Web 应用程序部署。
第五章,使用部署任务实现构建管道,探讨了如何为构建过程创建和重用构建任务。本章介绍了使用 YAML 语法的流行 Node.js、NPM、.NET、Docker 和 SQL Server 部署任务。
第六章,集成测试、安全任务和其他工具,帮助你理解 Azure Pipelines 如何与其他工具扩展。本章涵盖了流行的代码分析工具 SonarQube 和用于制品的 Jenkins。
第七章,监控 Azure Pipelines,教你如何监控 Azure Pipelines 及相关任务,如构建任务、部署任务和管道代理。你还将学习如何在管道中构建监控,以确定部署是否改善或恶化了系统的质量。
第八章,使用基础设施即代码(IaC)配置基础设施,考察了如何为基础设施即代码(IaC)过程创建和重用部署任务。本章涵盖了流行的 IaC 工具 Terraform、Azure Bicep 和 ARM 模板,均使用 YAML 语法。
第九章,为 Azure 服务实现 CI/CD,展示了如何为 Azure 服务部署创建 YAML 和管道。你将学习如何在Azure 应用服务、Azure Kubernetes 服务(AKS)、Azure 容器应用和Azure 容器实例(ACI)上设置和部署应用程序。
第十章,为 AWS 实现 CI/CD,探讨了如何创建 YAML 和管道来在不同的服务上部署容器化应用程序,如AWS Lightsail、Elastic Kubernetes 服务(EKS)和Elastic 容器服务(ECS)。
第十一章,使用 Flutter 自动化 CI/CD 跨移动应用程序,介绍了如何使用 YAML 创建流水线来自动化移动应用程序的构建和发布流程。您还将学习如何实现 YAML 流水线以在 Apple TestFlight 和 Google Play 控制台上部署 Flutter,设置端到端流程的环境。
第十二章,在 Azure Pipelines 中导航常见陷阱和未来趋势,教您如何避免常见错误,并探讨 Azure Pipelines 的潜在未来趋势。
要充分利用本书
您需要基本了解如何使用自动化构建和部署应用程序;但是,本书将指导您如何在 Azure Pipelines 中执行这些操作。每章都有具体的技术要求。
| 书中涉及的软件/硬件 | 操作系统要求 |
|---|---|
| Docker | Windows、Linux 或 macOS |
| Visual Studio Code | Windows、Linux 或 macOS |
如果您正在使用本书的数字版本,建议您自己输入代码或从书籍的 GitHub 存储库中获取代码(下一节中提供链接)。这样做将有助于避免与复制粘贴代码相关的潜在错误。
下载示例代码文件
您可以从 GitHub 下载本书的示例代码文件,网址为 github.com/PacktPublishing/Implementing-CI-CD-Using-Azure-Pipelines。如果代码有更新,将在 GitHub 存储库中更新。
我们还有来自丰富书籍和视频目录的其他代码包,可以在 github.com/PacktPublishing/ 查看!
使用的约定
本书使用了许多文本约定。
文本中的代码:指示文本中的代码字词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。例如:“在 Linux 上添加以下基本脚本 - echo "Hello Second Task"。”
代码块设置如下:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "",
"apiProfile": "",
"parameters": { },
"variables": { },
"functions": [ ],
"resources": [ ],
"outputs": { }
}
命令行输入或输出如下所示:
$id=az ad sp list –display-name azure-pipelines –query "[].id" -o tsv
粗体:表示新术语、重要单词或屏幕上看到的词语。例如,菜单或对话框中的单词以粗体显示。例如:“首先点击主菜单下的管道中的环境选项。”
提示或重要注意事项
如此显示。
联系我们
我们非常欢迎读者的反馈。
总体反馈:如果您对本书的任何方面有疑问,请发送电子邮件至 customercare@packtpub.com,并在消息主题中提及书名。
勘误:尽管我们已尽全力确保内容的准确性,但难免会出现错误。如果您在本书中发现错误,我们将非常感谢您向我们报告。请访问 www.packtpub.com/support/errata 并填写表单。
盗版:如果您在互联网上发现我们的作品有任何非法复制版本,我们将感激您提供相关的网址或网站名称。请通过 copyright@packtpub.com 联系我们,并提供该材料的链接。
如果您有兴趣成为作者:如果您在某个领域有专业知识,且有兴趣写书或为书籍贡献内容,请访问 authors.packtpub.com。
分享您的想法
阅读完 使用 Azure Pipelines 实现 CI/CD 后,我们希望听到您的反馈!请 点击这里直接进入亚马逊评论页面 分享您的想法。
您的评价对我们和技术社区非常重要,将帮助我们确保提供优质的内容。
下载本书的免费 PDF 版本
感谢购买本书!
您喜欢随时随地阅读,但又无法将纸质书带在身边吗?
您的电子书购买与所选设备不兼容吗?
不用担心,现在每本 Packt 出版的书籍,您都能免费获得该书的无 DRM PDF 版本。
任何地方、任何设备上都能阅读。您可以直接将最喜爱的技术书籍中的代码搜索、复制并粘贴到您的应用程序中。
福利不仅如此,您还可以获得独家折扣、新闻通讯以及每天通过邮箱收到的精彩免费内容
按照以下简单步骤即可享受福利:
- 扫描二维码或访问以下链接:

packt.link/free-ebook/978-1-80461-249-1
-
提交您的购买证明
-
就是这样!我们将直接通过电子邮件将您的免费 PDF 和其他福利发送给您
第一部分:开始使用 Azure Pipelines
本部分将带你了解 Azure Pipelines 的基础知识,帮助你理解其概念,并展示如何快速入门,实现自动化构建和部署任务。
本部分包含以下章节:
-
第一章,理解 Azure Pipelines
-
第二章,创建构建管道
-
第三章,设置变量、环境、审批和检查
-
第四章,使用 YAML 扩展高级 Azure Pipelines
第一章:1
理解 Azure Pipelines
本书将成为你在 Microsoft DevOps 世界中的最爱之一,因为它提供了全面的指南,帮助你学习关于 Azure Pipelines 的所有知识,并使你成为一名经验丰富的 Azure DevOps 工程师。Azure DevOps 工程师负责设计和实施使用 Azure Pipelines 服务的 持续集成和持续部署(CI/CD)管道,Azure Pipelines 是 Azure DevOps 的一个组件。Azure DevOps 是一组 Microsoft 服务,旨在帮助项目团队实现项目目标。
在本章中,你将更详细地了解 CI/CD 和 Azure DevOps 的概念,并学习使用 Azure Pipelines 创建 CI/CD 管道以支持应用程序部署过程的优势。更具体地说,本章将介绍以下主题:
-
什么是 CI/CD?
-
介绍 Azure DevOps
-
介绍 Azure Pipelines 及其组件
-
将 Azure Pipelines 与其他 CI/CD 工具进行比较
-
设置代理池
-
创建 个人访问 令牌(PAT)
-
设置和更新自托管代理
-
设置部署组
技术要求
你可以在 github.com/PacktPublishing/Implementing-CI-CD-Using-Azure-Pipelines/tree/main/ch01 找到本章的代码。
什么是 CI/CD?
CI/CD 是开发人员应了解的自动化开发和部署的工作流过程,旨在提升技能。
CI 是自动化构建和测试代码的工作流过程,每当团队成员提交更改到 Git 时,就会触发该过程。Git 是一种版本控制工具,运行在源代码控制平台上,如 Azure Repos、GitHub、GitLab 等。CI 为所有开发人员创造了一种现代化文化,鼓励大家分享代码,包括单元测试,通过将所有更改合并到共享版本控制库中,完成一个小任务后就提交。CI 基于提交代码触发器运行,从共享版本控制库中获取最新代码来构建、测试并验证任何他们提交的分支。使用 CI 可以迅速发现错误代码问题并加以修正,确保所有开发人员的代码质量良好。
CD 涉及将 CI 工作流过程自动化,构建、测试、配置并部署到特定环境,如 QA、预生产和生产环境。
以下图示说明了此工作流:

图 1.1 – CI/CD 图示
CI/CD 减少了人为错误,并自动化了手动构建、测试和部署阶段,帮助开发人员专注于应用程序开发。
本书将重点介绍 CI/CD 工具Azure Pipelines,这是一个综合性的 DevOps 服务,也是Azure DevOps生态系统中的一部分。在深入了解这一工具之前,我们先来介绍一下 Azure DevOps。
介绍 Azure DevOps
许多 CI/CD 工具用于支持现代软件开发,如 Azure Pipelines、GitLab CI/CD、GitHub Actions 和 Bitbucket Pipelines。最广泛使用的工具之一是 Azure Pipelines,它是Azure DevOps的一部分,包含以下五个服务:
-
Azure Boards是 Azure DevOps 的一个子服务,便于在一个地方跟踪与项目相关的所有任务。它适合团队协作。它支持 Kanban 看板、待办事项、团队仪表板和自定义报告,能够在任务和源代码版本库(如 GitHub 或 Azure Repos)之间建立联系,从而促进协作。
-
https://dev.azure.com/{your-organization})。本书将重点关注此服务,因为它具备这样的优势。 -
Azure Repos是 Azure DevOps 的一个子服务,用于控制源代码的版本。它使得在一个地方管理代码变得简单。便捷的维护还可以帮助你定义规则,从而安全地将代码部署到所需的环境中,例如团队创建拉取请求后进行合并检查或静态代码分析。本书中的示例将使用 Azure Repos。
-
Azure Test Plans是 Azure DevOps 的一个子服务,帮助测试或质量保证团队编写用例场景,轻松将测试结果交付给客户。测试人员或质量保证团队可以在 Azure Test Plans 上创建系统集成测试(SIT)和用户验收测试(UAT)。它可以将测试结果显示为仪表板报告,并包含评论或反馈。Azure Test Plans 还帮助团队在同一页面上了解项目的测试过程。
-
Azure Artifacts是 Azure DevOps 的一个子服务,允许开发者在一个地方共享和管理所有由构建代码生成的包。开发者可以将包发布到他们的源,并在同一团队、组织内甚至公开分享。开发者还可以从不同的公共仓库加载包,如
www.nuget.org/或www.npmjs.com/。Azure Artifacts 还支持多种包类型,如 NuGet、npm、Python、Maven 和 Universal Packages。
所有这些服务都属于 Azure DevOps 的范畴,涵盖了项目所需的开发流程。你无需使用其他额外的服务来进行开发。
介绍 Azure Pipelines 及其组件
Azure Pipelines 是一个用于构建、测试和将代码部署到实时应用程序的 CI/CD 平台。首先,我们来看看它的关键组件。
探索关键组件
在创建 Azure 管道时,你需要理解一些关键概念:
-
一个代理是运行作业的服务器上的软件。它可以是 Microsoft 托管的代理,也可以是自托管的代理。
-
一个流水线是您应用程序开发的 CI/CD 工作流过程。它可以定义如何构建、测试、集成和部署您的项目。
-
一个触发器是调用流水线运行的操作。
-
一个阶段是流水线中定义的作业流,每个阶段可以包含一个或多个作业。使用阶段的好处是,您可以重新运行该阶段下的作业,而无需重新运行整个流水线。例如,假设开发人员创建了一个包含两个阶段的流水线:构建阶段和部署阶段。如果部署阶段失败,则可以只重新运行部署阶段下失败的作业。
-
一个作业是一组在一个阶段中设置的一个或多个步骤。当您需要在不同操作系统环境中运行一组步骤时,它很有用。
-
一个步骤可以是一个任务或脚本,是流水线中的最小单元:
-
一个任务是一个预定义的脚本,您可以通过它来定义您的想法。
-
一个脚本是一个使用命令行界面(CLI)、PowerShell 或 Bash 的操作。它取决于您为工作选择的操作系统代理。例如,如果您选择在 Linux 代理上使用命令行运行,它将使用 Bash 脚本。PowerShell 在 macOS 代理上运行,并将使用 PowerShell 核心来进行跨平台脚本。
-
-
一个目标是流水线的目的地。它可以是 Azure Artifacts,一个 Azure 资源服务(例如 Azure App Services、Azure Functions、Azure Container Apps、Azure Kubernetes Services 等),或者调用一个 REST API,例如 Microsoft Teams 上的 Webhook。
现在,让我们来看一下这些组件是如何相互作用的:

图 1.2 – 关键组件
本节描述了关键对象的含义及其关系。在我们更深入地了解平台的不同方面之前,先来学习如何开始使用它。
注册 Azure Pipelines
可以使用两种方法注册:
-
https://dev.azure.com/{your-organization}。 -
{your-organization}。
注册 Azure Pipelines 账户后,您就可以创建一个新项目,用于构建您的代码并将构建后的代码发布到生产应用程序。
创建新项目
创建新项目是注册后的第一步,在创建任何 CI/CD 流水线之前。创建项目后,您可以设置项目可见性:

图 1.3 – 创建新项目
您可以输入项目名称并选择可见性,然后点击创建项目。
邀请团队成员
当您需要与团队合作时,必须通过邀请一个或多个团队成员来添加新成员。请按照以下步骤邀请团队成员:
- 在 Web 门户中点击您的项目名称,然后点击项目设置:

图 1.4 – 项目设置
- 选择团队 | 添加:

图 1.5 – 添加新团队成员
- 输入团队成员的电子邮件地址并点击保存:

图 1.6 – 邀请团队成员
现在您已经邀请了合作者加入项目,我们来深入了解如何开始使用此服务。
创建 Azure 管道
创建 Azure 管道有两种方法:
-
使用经典界面(从 Web Azure DevOps 门户创建 Azure 管道),同时按照以下基本步骤进行操作:
-
配置 Azure Pipelines 以使用您的 Azure Repos Git 仓库。
-
使用 Azure Pipelines 在 Azure DevOps 门户中通过拖放创建并配置您的构建和发布管道。
-
将代码推送到您的版本控制库。管道将通过默认触发器自动启动,定义的任务将会执行。
-
-
通过定义自定义构建,使用
azure-pipelines.yml文件。 -
将代码推送到您的版本控制库。此操作会运行默认触发器。
为了便于理解,我们来说明 Azure Pipelines YAML 方法:

图 1.7 – Azure Pipelines YAML 步骤
这两种方法都有不同的管道功能可用,其中有些适用于两者,而其他一些仅适用于其中一种。我们将在下一部分详细介绍这些功能。
功能可用性
某些管道功能仅在使用经典界面或 YAML 时可用。下表显示了哪些功能适用于这些方法中的哪一种:
| 功能 | YAML | 经典 | 描述 |
|---|---|---|---|
| 代理 | 是 | 是 | 用于定义管道可以运行的资源。 |
| 审批 | 是 | 是 | 用于定义额外验证步骤,在完成部署阶段之前进行检查。 |
| 工件 | 是 | 是 | 用于定义发布或消费不同类型包的库包。 |
| 缓存 | 是 | 是 | 用于定义额外任务,通过允许在代理上存储输出或下载的依赖项并再次重用它们,来减少构建时间。 |
| 条件 | 是 | 是 | 用于定义在运行作业之前的特定条件。 |
| 容器作业 | 是 | 否 | 用于定义在容器中运行的特定作业。 |
| 需求 | 是 | 是 | 用于定义特定管道,以确保在管道阶段运行之前满足要求。 |
| 依赖关系 | 是 | 是 | 用于定义在运行下一个作业或阶段之前的特定验证要求。 |
| 部署组 | 否 | 是 | 用于定义将部署到目标机器的代码的逻辑组。 |
| 部署组作业 | 是 | 是 | 用于定义发布到部署组的作业。 |
| 部署作业 | 是 | 否 | 用于定义部署步骤。 |
| 环境 | 是 | 否 | 定义一组面向部署的资源。 |
| 网关 | 是 | 是 | 支持在完成发布阶段之前自动收集和评估外部健康信号。仅适用于经典发布。 |
| 作业 | 是 | 是 | 定义一组步骤的执行顺序。 |
| 服务连接 | 是 | 是 | 定义连接到执行作业中任务所需的远程服务。 |
| 服务容器 | 是 | 否 | 定义一个服务,你可以用它来管理容器化服务的生命周期。 |
| 阶段 | 是 | 是 | 定义流水线中的作业流。 |
| 任务组 | 否 | 是 | 定义一组顺序任务,作为一个可重用的任务。 |
| 任务 | 是 | 是 | 定义构建流水线的基本单元。 |
| 模板 | 是 | 否 | 定义可重用的内容、逻辑和参数。 |
| 触发器 | 是 | 是 | 定义一个特定事件,触发流水线的运行。 |
| 变量 | 是 | 是 | 定义数据替换的值,并将其传递给流水线。 |
| 变量组 | 是 | 是 | 定义你希望控制并在多个流水线之间共享的值存储。 |
表 1.1 – 流水线功能
除了这些功能外,Azure Pipelines 还可以连接到源版本控制仓库。我们将在下一节中详细讨论这些仓库。
源版本控制仓库的可用性
YAML 流水线仅支持某些版本控制仓库。下表展示了哪些版本控制仓库支持哪种方法:
| 仓库 | YAML | 经典界面 |
|---|---|---|
| Azure Repos | 是 | 是 |
| GitHub | 是 | 是 |
| GitHub Enterprise Server | 是 | 是 |
| Bitbucket Cloud | 是 | 是 |
| Bitbucket Server | 否 | 是 |
| Subversion | 否 | 是 |
表 1.2 – 比较仓库
在本节中,我们讨论了 Azure Pipelines 的所有可用功能。在下一节中,我们将把 Azure Pipelines 的关键组件转换成 YAML 结构,以便更好地管理。
理解 Azure Pipelines 的 YAML 结构
通常,创建一个名为 azure-pipelines.yml 的文件可以帮助你记住哪个 YAML 文件用于源代码仓库中的 azure-pipelines。基本的 Azure Pipelines YAML 结构如下:

图 1.8 – azure-pipelines.yml 文件
本示例中的 azure-pipelines.yml 文件包含一个典型的结构:
-
有两个阶段,
stage1和stage2,每个阶段包含一个job步骤。 -
第 1-2 行 显示开发人员在主 (
master) 分支推送更改时,流水线会运行。 -
第 8-9 行 和 第 21-22 行 显示流水线使用了一个 Microsoft 托管的代理,操作系统镜像为
windows-latest。 -
第 11 行 是一个预创建的脚本,用于使用 NuGet 库。你可以在本书 GitHub 仓库中的
ch1文件夹找到这个脚本。 -
第 12 行是一个用于使用 NuGet 命令行的预创建脚本。
-
第 15 行是运行
echo命令的命令行。 -
第 24 行是一个跨平台的 PowerShell Core 脚本。
正如您所看到的,基本的 YAML 结构相当简单易懂。准备好 YAML 文件后,您可以查看运行状态。我们将在下一节讨论这一点。
查看 Azure 管道的状态
Azure 管道的状态显示在 Azure DevOps Web 门户中的运行管道下:

图 1.9 – Azure 管道的状态
单击当前管道状态行将带您查看管道的历史状态。使用两种颜色来指示状态:绿色和红色。分别表示成功和失败的管道。
本节描述了所有组件及其关系。在下一节中,您将了解 Azure Pipelines 与其他常用 CI/CD 工具之间的关键区别。
比较 Azure Pipelines 与其他 CI/CD 工具
与目前市场上的其他 CI/CD 服务相比,Azure Pipelines 具有不同的特点。让我们仔细看一下:
| 功能 | Azure Pipelines | GitHub Actions | GitLab CI | Bitbucket Pipelines |
|---|---|---|---|---|
if 表达式 |
X | X | - | - |
| 循环语句 | X | - | - | - |
| 在线服务 – 每月免费 CI/CD 分钟数 | 1,800 | 2,000 | 400 | 50 |
| 在线服务 – 免费包存储 (GB) | 2 | 0.5 | 5 | 1 |
| 自托管代理 | X | X | X | X |
| 免费用户 | 5 | 无限 | 无限 | 5 |
表 1.3 – CI/CD 工具比较
这将帮助您了解在选择适合您的 CI/CD 平台的工具时需要考虑的重要因素。
在开始为应用程序部署构建管道之前,我们必须准备好必要的代理池,如下一节所示。
设置代理池
在使用 Azure Pipelines 构建代码和部署代码之前,您需要至少一个构建代理。有两种构建代理类型:微软托管构建代理,默认包含,和自托管构建代理。每种代理类型将位于一个代理池下,代理池是构建和发布代理的集合。
一个由微软托管的构建代理将位于名为Azure Pipelines的代理池下。您可以为自托管构建代理创建一个新的代理池,并将其分配到该池下。
创建代理池,请按照以下步骤操作:
- 在 Web 门户中单击您的项目名称,然后单击项目设置 | 代理池 | 添加代理池:

图 1.10 – 添加代理池
- 输入以下截图所示的信息,然后点击创建:

图 1.11 – 创建代理池
- 最后,您将看到新的代理池:

图 1.12 – 显示新的代理池
完成新代理池的创建后,你可以在新的代理池下开始创建和设置自托管代理。接下来的部分将指导你如何创建个人访问 令牌(PAT)。
创建 PAT
在你的服务器或机器上创建自托管代理之前,必须先创建一个 PAT。请按照以下说明操作:
- 转到个人图标下的设置菜单,然后点击个人 访问令牌:

图 1.13 – 创建 PAT
- 点击新建令牌:

图 1.14 – 新建令牌
-
输入所需的相关信息:
-
名称:输入你需要的名称
-
组织:选择你将要关联的组织
-
过期时间(UTC):有四个选项——30 天、60 天、90 天,以及自定义定义但不超过 2 年
-
作用域:选择自定义定义 | 代理池,并选中读取和管理 | 审计,并勾选读取 审计日志:
-

图 1.15 – 输入所需信息
- 在点击关闭按钮之前,请复制 PAT,因为你将无法再次查看它:

图 1.16 – 复制 PAT
现在,你已经准备好设置自托管代理。
设置自托管代理
创建 PAT 后,你可以在新的代理池下创建一个新的自托管代理。按照以下步骤操作:
- 点击PacktAzureDevOps | 代理池 | 常规:

图 1.17 – 输入新的代理池
- 点击新建代理:

图 1.18 – 输入新的代理
-
你可以根据操作系统下载自托管代理。以下三种操作系统选项将指导你如何下载并设置它们:
- Windows 用户可以从Windows标签页下载构建代理软件。以下截图展示了两个选项:Windows 64 位(x64)和 Windows 32 位(x86):

图 1.19 – 设置文件的 Windows 代理
-
要设置 Windows 代理,你需要以管理员身份在 PowerShell 中运行。
-
Mac 用户可以从macOS标签页下载构建代理软件:

图 1.20 – macOS 代理
-
设置 macOS 代理时,管理员角色下不需要使用
bash命令。 -
Linux 用户可以从Linux标签页下载构建代理软件。计算机架构有四个选项:x64、ARM、ARM64和RHEL6:

图 1.21 – Linux 代理
- 安装 Linux 代理时,你不需要使用
root用户。
-
在配置每个操作系统中的代理之后,你必须输入以下信息:
Enter (Y/N) Accept the Team Explorer Everywhere license agreement now? (press enter for N) > Y Enter server URL > https://dev.azure.com/yourOrganization Enter authentication type (press enter for PAT) > [ENTER] Enter personal access token > [Personal Access Token] Enter agent pool (press enter for default) > General Enter agent name (press enter for [computer name]) > agent01 agent01 is active:

图 1.22 – 代理状态仪表板
- 你可以看到已经创建的构建代理的在线状态:

图 1.23 – 代理的操作菜单
- 你可以通过点击带有省略号或三个点的按钮来删除代理并更新到代理的新版本。
现在,你已经准备好在 agent01 构建代理上创建构建和部署。然而,你需要设置部署组,以便在本地 web 服务器上部署应用程序,如 Microsoft 互联网信息服务(IIS)。我们将在下一节中进行设置。
设置部署组
部署组是基于环境对目标机器进行应用程序部署的逻辑代理分组 —— 它们通常根据项目需求和推广级别命名,在应用程序进入生产之前进行部署。每个环境中都安装有代理。部署组下的每个代理仅支持 Windows 和 Linux。
这些部署组可以根据环境进行划分,例如开发(Dev)、质量保证(QA)、用户验收测试(UAT)和生产(Prod),如下图所示:

图 1.24 – 部署组概念
要创建部署组,请按照以下说明进行操作:
- 导航到 Pipelines | 部署组 | 组 | 添加一个 部署组:

图 1.25 – 添加部署组
- 输入所需信息并点击创建:

图 1.26 – 输入部署组信息
-
创建部署组后,你必须在你的部署组下设置部署代理。部署代理支持两种操作系统:
- Windows 用户可以通过复制 PowerShell 脚本并以管理员命令提示符运行它,来安装部署组的代理:

图 1.27 – 在 Windows 上部署代理的脚本
- Linux 用户可以通过复制 bash shell 脚本并以管理员命令提示符运行它,来安装部署组的代理:

图 1.28 – 在 Linux 上部署代理的脚本
- 在为部署组设置好代理后,你将看到该部署组的构建代理已经在线:

图 1.29 – 部署代理已在线
在自托管计算机上设置构建和部署代理后,你已准备好创建你的第一个 Azure 流水线。
总结
在本章中,你学习了与 CI/CD 相关的关键概念,这是微软 Azure DevOps 服务的一部分。你了解了 Azure Pipelines YAML 的基本结构,以及 Azure Pipelines 与市场上其他服务的区别。你还学习了 Azure Pipelines 的基本原理,这将帮助你为本书后续章节中讨论的所有实际场景准备 CI/CD 流水线。最后,你学习了如何设置代理池、部署组和自托管代理,为接下来的实操项目做好准备。
在下一章中,你将把本章学到的知识应用到基本概念和功能上,学习如何创建流水线。
第二章:2
创建构建管道
在上一章中,我们通过准备构建和部署代理、池以及部署组,并解释如何使用它们,为你的 Azure DevOps 之旅打下了基础。本章将迈出下一步,帮助你了解如何创建第一个管道。我们将探讨如何使用任务、触发器、阶段以及如何基于这些概念创建构建管道的基本原理。这将帮助你简化工作流程,减少错误,提高生产力。它还将有助于提高协作效率,让你更好地为实现 CI/CD 等实践做好准备。
本章将涵盖以下主题:
-
创建一个带有单一任务的构建管道
-
创建任务
-
创建多个任务
-
创建触发器
-
创建阶段
创建一个带有单一任务的构建管道
在上一章节准备好构建和部署代理之后,本节将介绍如何在 Azure DevOps 门户上创建第一个 构建管道。在创建第一个构建管道之前,必须创建一个 Azure 仓库作为源代码库。有两种创建构建管道的方式:经典编辑器,即通过图形界面编辑器拖放组件来构建管道,和 另一种标记语言 (YAML),它通过标记语言自定义 Azure 的高级管道。在本章中,我们将重点使用经典编辑器。
让我们创建一个空白任务来看看它是如何工作的:
- 点击项目名称,然后点击 管道 | 创建管道:

图 2.1 – 创建新管道
- 点击 使用经典编辑器:

图 2.2 – 使用经典编辑器
- 选择 Azure Repos Git,这是 Azure DevOps 下的一个现有服务,作为源代码库。它与其他 Azure DevOps 服务(如 Azure Pipelines)高度兼容,这些服务将在本演示中使用。选择下图所示的选项,用于 团队项目、代码库 和 手动及定时构建的默认分支 来启动 Azure 管道,然后点击 继续:

图 2.3 – 选择默认分支
- 点击 空白任务。该模板提供了一个单一任务,包含用于管道的任务。我们将从空白任务开始,以便你可以先从菜单中学习基本设置,然后再选择适合每个项目的所有模板。

图 2.4 – 选择构建模板
屏幕截图中显示的其他选项包括以下内容:
-
.NET 桌面:用于构建和测试 .NET 桌面解决方案的管道模板
-
Android:用于构建管道的模板,用于构建和测试 Android APK 文件以用于 Android 应用程序
-
ASP.NET:用于构建管道的模板,用于构建和测试 ASP.NET Web 应用程序
-
Azure Web App for ASP.Net:用于构建管道的模板,用于构建、测试和部署 ASP.NET 到 Azure Web App 服务
- 点击 保存 & 队列 的下拉菜单:

图 2.5 – 保存构建管道
- 在接下来的屏幕上,你可以选择一个文件夹保存并添加评论。点击 保存:

图 2.6 – 确认构建管道
- 点击 保存 后,将显示所有构建管道的列表,如下所示:

图 2.7 – 管道仪表板
- 创建简单构建管道后,你可以通过点击你想要的管道并点击 运行管道 来测试它:

图 2.8 – 运行管道
-
在运行构建管道之前,你可以设置特定的选项:
-
代理池,在此处你可以选择 Microsoft 托管的代理或自托管代理
-
代理规格,在此处你可以选择一个代理操作系统来运行构建管道
-
分支/标签,在此处你可以选择要运行的构建管道
-

图 2.9 – 运行管道选项
- 最后,你可以详细查看构建结果和一些摘要信息。

图 2.10 – 详细的构建结果
你还可以查看构建管道行上的最新状态:

图 2.11 – 带有构建结果的管道
在这一部分,你学会了如何使用经典编辑器来轻松创建一个新的构建管道。在下一部分,我们将描述如何在这个构建管道中创建任务。
创建任务
本节将教你如何在作业下创建任务。构建管道将包含一个或多个作业,每个作业将包含一个或多个任务。在前一部分中,你创建了一个仅包含一个作业的构建管道。按照以下步骤创建任务:
- 点击三个点符号来编辑构建管道,然后点击 编辑。

图 2.12 – 编辑构建管道
- 编辑构建管道后,点击搜索框中的
Command line,然后点击 添加:

图 2.13 – 添加命令行任务
-
点击
First CommandLine Task -
echo "HelloFirst Task":

图 2.14 – 输入命令行任务的详细信息
然后,点击 保存 & 队列。
- 点击保存并排队后,你将看到确认页面。然后,点击保存 并运行:

图 2.15 – 保存并运行构建管道
- 然而,在保存之前,你还可以选择高级选项。在这里,你可以在构建管道时输入变量,而不是提前准备好它们。你可以在下面的截图中看到可用的选项:

图 2.16 – 高级配置
让我们更仔细地看看这些选项:
-
高级:
-
工作目录:你可以输入任何路径来运行命令行任务。如果你没有输入路径,它将在根路径下运行。
-
标准错误失败:当你需要在任务中出现任何错误时停止任务时,启用此功能。例如,如果你运行一个命令从互联网读取邮件,而互联网断开连接,这个任务将显示错误。
-
-
控制选项:
-
启用:当你需要启用控制选项时,开启此功能:
-
继续出错:当你需要在发现错误后继续运行下一个任务时,启用此选项。
-
任务失败后的重试次数:输入任务失败后需要重新运行的次数。
-
超时:输入此任务在被取消之前将运行的分钟数。
-
-
运行 此任务:
-
仅当所有前置任务 成功时
-
即使前置任务失败,除非构建 被取消
-
即使前置任务失败,即使构建 被取消
-
仅当前置任务 失败时
-
自定义条件:如果上述选项不符合你的需求,你可以在此处创建规则。
-
-
环境变量:你可以在运行命令时添加额外的变量。例如,你可以在运行此命令时添加一个 URL 来下载文件。
-
输出变量:你可以将此任务的输出赋值并传递到下一个任务。例如,你可以在此任务的输出变量中传递 token login,以便在下一个任务中成功登录。
- 一旦你保存了这些选项,你就可以看到构建管道的新历史记录:

图 2.17 – 显示构建管道的历史
- 点击你想查看任务详情的行。在这里,你将看到每个任务运行命令的输出:

图 2.18 – 作业状态
- 点击代理作业 1以查看作业详情:

图 2.19 – 带有任务的作业详情
在本节中,你学会了如何在构建管道中创建任务并查看作业状态和任务详情。这可以帮助你确保所有任务按预期进行。下一节将教你如何在构建管道中创建多个作业。
创建多个作业
有时,当你为需要同时在不同操作系统上构建代码的应用创建构建管道时,你需要为此目的创建另一个任务。任务有两种类型 —— 代理任务和无代理任务。代理任务是需要在代理或目标计算机上运行的任务,而无代理任务将在 Azure DevOps 应用服务器上直接运行。以下步骤描述了如何在构建管道下创建其他代理任务:
- 通过点击…符号,再点击编辑来编辑构建管道:

图 2.20 – 编辑构建管道
- 点击...按钮并选择添加代理任务:

图 2.21 – 添加代理任务
-
点击
Agentjob 2 -
代理池:Azure Pipelines
-
代理 规格:ubuntu-latest

图 2.22 – 添加另一个任务
- 在搜索框中点击
命令行,然后点击添加按钮:

图 2.23 – 添加新任务
-
点击包含
2的行。 -
第二个命令行任务 -
echo "Hello Second Task on Linux"。运行完管道后,这将把这段文本打印到结果页面:

图 2.24 – 更新第二个命令行任务
- 点击保存并排队后,你将看到以下结果:

图 2.25 – 两个代理任务的结果
专业提示
对于错误处理,当你有多个代理任务时,可以在高级配置中设置参数,确保代理任务 2会在代理任务 1成功完成后运行。
让我们来看一下在代理任务属性中可用的一些高级选项,如下图所示:

图 2.26 – 代理任务的高级选项
让我们更详细地了解这些选项:
-
代理选择:
-
代理池:选择自定义或默认代理来构建任务。
-
需求:添加条件参数,仅允许符合条件的代理运行此代理任务。例如,只有 Linux 代理才能运行此任务。
-
-
执行计划:
-
并行性:这里提供的选项如下:
-
无:没有任务将并行运行。
-
多配置:当你有不同的配置需要为每个代理任务运行时,启用此选项。例如,你可能需要在三个浏览器上运行测试任务,因此你将需要 Chrome、Edge 和 Firefox 浏览器。
-
多代理:当你有多个代理并需要使用它们来运行代理任务时,启用此选项。
-
-
超时:输入分钟数;代理任务允许在代理上执行的时间,超过该时间将被取消。
-
任务取消超时:输入在接收到取消请求后,代理任务取消前的分钟数。
-
-
依赖关系:当你需要在运行另一个代理任务之前先完成某个任务时,可以选择之前的代理任务。
-
附加选项:
- 允许脚本访问 OAuth 令牌:当你需要使用 OAuth 令牌通过 REST API 传递给另一个代理任务时,打开此选项。
-
运行 此任务:
-
仅当所有前置任务 成功完成时
-
即使前一个任务 失败时
-
仅当前一个任务 失败时
-
使用变量表达式的自定义条件:例如,succeeded() 表示如果前一个任务完全成功,代理任务将会运行。
-
在这一部分,你将学到如何创建第二个任务来分离 Linux 操作系统。两个任务可以同时运行,这种用例适用于在不同操作系统上运行任务,而不互相依赖,比如你在创建一个构建管道时,需要同时将应用程序部署到 Google Play 和 Apple Store,而不是按顺序运行。
以下部分将教你如何创建一个触发器,使构建管道能够自动运行。
创建触发器
本部分将教你如何创建一个 触发器 —— 也就是在你将代码推送到特定分支时使构建管道自动运行:
- 点击三点菜单编辑构建管道,查看构建管道的详细信息,然后点击 编辑:

图 2.27 – 编辑构建管道
-
点击 触发器 并更新以下信息:
-
启用持续集成:开启此选项
-
分支过滤器 | 类型:包括 | 分支 规范:main
如果你在主分支上推送代码,构建管道将自动运行;这就是启用持续集成:
-

图 2.28 – 启用持续集成
在这一部分,你将学到如何在推送代码后启用自动运行构建管道。这样可以减少每次推送代码到源代码库时手动构建代码的工作量。
以下部分将教你如何创建一个阶段,用于部署环境的任务组。
创建阶段
本节将教你如何使用经典编辑器创建一个 阶段 —— 也就是说,创建一个用于环境目的的任务组,如开发、非功能性测试和生产。每个组都包含为每个环境运行的工作和任务。当我们需要将应用程序部署到多个环境时,使用多个阶段是有优势的。因为如果我们只为开发和生产环境使用一个默认阶段,若开发管道失败,它将继续运行生产管道并导致失败。让我们看看如何创建一个阶段:
- 点击 发布 | 新建管道:

图 2.29 – 创建新管道
- 输入阶段名称并点击 保存:

图 2.30 – 编辑阶段属性
- 选择相关的仓库文件夹。在我们的案例中,它是一个 Azure Repos 文件夹,包含你的代码和 Azure 管道文件的根文件夹:

图 2.31 – 确认管道
现在你已经学会了如何创建一个包含空工作任务的阶段,让我们总结一下这一章的内容。
总结
在这一章中,你学习了创建管道所需的核心功能。你学会了如何创建一个包括工作和任务的构建管道。你还学会了如何设置触发器,以便过滤任何分支来运行构建代理。这些触发器与工作和任务结合,构成了任何有效 CI/CD 管道的基石,使得软件交付能够自动化、高效且可靠。
在下一章,你将学习如何使用 YAML 来增强构建管道,并在代理上运行它。
第三章:3
设置变量、环境、审批和检查
在前几章中,我们通过创建任务和工作来构建了第一个构建流水线,并设置了触发器来自动运行构建流水线。本章将介绍创建发布流水线的下一步。到本章结束时,你将学会如何创建发布流水线,从而在 Azure 上部署应用。这包括设置变量组库,并学习为特定用例(例如移动应用部署)添加和使用机密文件。
本章将涵盖以下内容:
-
为 Azure 资源创建服务连接
-
创建变量组库
-
上传和管理机密文件
-
创建发布流水线。
为 Azure 资源创建服务连接
本节将教你如何创建服务连接,以便你可以在 Azure 资源上发布应用。在创建服务连接之前,你需要提供一个 Azure 凭证,可以从 Azure 门户获取。
探索 Azure 应用注册
应用注册是你可以获取 Azure 凭证的门户部分,允许 Azure Pipelines 将应用部署到 Azure 资源。你可以通过执行以下步骤来创建 Azure 应用注册:
-
访问
portal.azure.com| Microsoft Entra ID。 -
点击应用注册,然后点击+ 新建注册:

图 3.1 – 应用注册页面
- 点击注册应用选项。此时,你需要提供注册应用的名称,之后可以选择四个关于帐户类型的选项。第一个选项是单租户,意味着在一个 Azure 帐户中只有一个身份。第二个选项是多个身份,适用于一个 Azure 帐户中的多个身份。第三个选项是如果有多个身份,包括一个 Azure 帐户中的个人 Microsoft 帐户。最后一个选项仅适用于一个 Azure 帐户中的个人 Microsoft 帐户。选择相关选项后,点击注册:

图 3.2 – 注册应用页面
- 注册应用 ID 后,导航至证书和机密以创建机密。点击+ 新建客户端机密 | 添加客户端机密。提供描述和到期日期后,点击添加:

图 3.3 – 证书和机密
- 不要忘记复制密钥值——它会消失,并且在关闭此页面后无法恢复:

图 3.4 – 客户端机密
- 复制高亮显示的信息,为创建服务连接做准备:

图 3.5 – 应用注册概览
在接下来的部分中,我们将使用这些信息来创建服务连接。
创建服务连接
要在 Azure 上部署应用程序,您需要创建一个服务连接,这是一个服务帐户,允许您访问 Azure 资源。为此,请按照以下说明操作:
- 在 Azure 门户中创建应用注册后,导航到 Azure DevOps 页面
dev.azure.com/并点击 登录:

图 3.6 – 登录页面
- 点击 项目设置 | 服务连接 > 创建 服务连接:

图 3.7 – 创建服务连接
- 选择 Azure 资源管理器,然后点击 下一步:

图 3.8 – 选择服务连接类型
-
对于 身份验证方法,有四个选项可供选择:
-
第一个选项涉及自动查找所有 Azure 资源中的服务主体。
-
第二个选项是手动方法,您可以输入有关应用程序 ID 的所有信息。此选项使您能够轻松连接到 Azure 资源。
-
第三个选项用于一个已存在的身份,该身份用于另一个系统。
-
最后一个选项涉及从 Azure 门户页面导出公共配置文件并使用它。
在本示例中,选择第二个选项,服务主体(手动),然后点击 下一步:
-

图 3.9 – 选择身份验证方法
-
填写以下所需字段,然后点击
Azure 云 -
<在订阅菜单上检查> -
<在订阅菜单上检查> -
<应用注册的客户端 ID> -
<应用注册的秘密> -
<租户 ID 的应用注册> -
sp-for-devops -
安全性:开启 授予所有 管道 访问权限。

图 3.10 – 输入 Azure 服务连接详细信息
- 现在,您可以查看新的服务连接:

图 3.11 – 所有服务连接的列表
在本节中,您学习了如何创建一个用于 Azure 资源连接的服务连接。您将在创建发布管道以部署 Azure 应用程序时使用它。在下一部分中,您将学习如何为项目中所有 Azure 管道创建全局变量和秘密文件。
管理全局变量和秘密文件
大多数项目在创建发布管道时会使用相同的值,例如 Azure 服务连接的名称。本节将教你如何创建变量组和秘密文件。这些资源对于在多个管道之间共享公共值至关重要,其中包括例如用于将应用程序部署到 Microsoft Azure 的用户名和密码。
创建变量组库
如果某个流水线需要使用 Azure 服务连接,您需要创建一个全局变量并将其链接到所有流水线。使用变量组可以减少错误的发生和在多个流水线中重复赋值的情况。当您需要更新值时,可以在一个集中位置进行更新,而不必在所有流水线中逐一修改。
当您需要创建在所有流水线中共享的变量时,可以按照这些步骤操作:
- 导航到您的项目并点击流水线 | 库 | 变量组:

图 3.12 – 创建变量组
- 填写所有必填字段;指定变量组名称和名称,并输入您希望在所有流水线中共享的值,然后点击保存:

图 3.13 – 输入变量组详细信息
- 编辑需要链接到变量组的现有流水线。点击现有流水线的编辑选项,导航到变量 | 变量组 | 链接 变量组:

图 3.14 – 选择变量组
- 选择您刚刚创建的变量组并点击链接:

图 3.15 – 链接变量组
- 审查已分配给变量组的所有值,然后点击保存 & 排队:

图 3.16 – 审查并保存变量组到流水线
在本节中,您学习了如何创建一个变量组,以便在所有流水线中共享。您还学习了如何将变量组链接到现有的流水线。在下一节中,您将学习如何上传密钥文件并将其连接到您的流水线。
上传并选择密钥文件
密钥文件通常包含敏感信息,如签名证书、SSH 密钥、许可证文件或移动配置文件。在某些情况下,您需要生成这些文件,而在其他情况下,这些文件是在其他平台上生成的。然后,您必须从该平台下载文件,并使其可供您的 CI/CD 流水线使用。
例如,要部署一个移动应用程序,如 iOS 应用程序,您必须首先生成一个配置文件,该配置文件包含关于谁在开发该 iOS 应用的信息,下载该文件,然后将其链接到流水线。
要执行此操作,请按照以下步骤操作:
- 转到流水线 | 库 | + 安全文件:

图 3.17 – 安全文件
- 点击您需要保留为密钥文件的
*.mobileprovision文件,然后点击确定:

图 3.18 – 上传文件
- 查看您上传的安全文件:

图 3.19 – 查看安全文件
- 点击
安全,然后点击添加在下载安全 文件任务上:

图 3.20 – 下载安全文件任务
- 在前面的步骤中,我们在全局部分添加了秘密文件。现在,我们需要在使用时下载它。为此,输入相关的详细信息:

图 3.21 – 输入相关的详细信息
- 点击
命令行,然后点击添加在命令行任务上:

图 3.22 – 添加命令行任务
- 输入相关的
显示安全文件路径并输入echo $(demo.secureFilePath)。点击保存并 排队:

图 3.23 – 输入命令行任务的详细信息
- 查看作业以查看显示安全 文件日志:

图 3.24 – 查看安全文件路径
为什么使用安全文件?
这些文件将被安全存储并加密在 Azure DevOps 中,以供你在管道中使用,最大限度减少它们被团队误放或误用的可能性。
本节教会你如何创建安全文件并将其连接到管道。你还学会了如何将安全文件下载到管道并在命令行上显示它们。在下一节中,你将学习如何创建一个包含变量组和安全文件的发布管道。
创建发布管道
本节将教你如何创建一个发布管道,以部署从构建管道接收到的工件。
发布管道还包括来自库的变量组中的安全文件和变量。
要创建这样的管道,请按照以下步骤操作:
- 导航到管道 | 发布 | 新管道:

图 3.25 – 新管道
- 点击相关的管道,在此情况下是release-app-dev。导航到管道 | 工件 | + 添加:

图 3.26 – 添加工件
- 点击构建并选择PacktAzureDevOps-CI,然后点击添加:

图 3.27 – 输入工件数据
- 转到变量 | 变量组 | global-var-group | 链接:

图 3.28 – 链接变量组
- 展开global-var-group (1)以查看所有关联的变量:

图 3.29 – 审查变量
- 点击
demo下的引用名称:

图 3.30 – 添加下载安全文件任务
-
点击
显示安全文件 &变量 -
脚本:
echo $(demo.secureFilePath) echo "==============" echo $(Global.ServerName)
这可以在以下截图中看到:

图 3.31 – 添加命令行任务
-
点击代理作业,然后选择以下选项:
-
代理池: Azure Pipelines
-
代理 规格: ubuntu-latest
然后点击保存:
-

图 3.32 – 代理作业属性。
- 要创建发布流水线,请导航至发布 | PacktAzureDevOps-CD | 发布 | 创建 发布:

图 3.33 – 创建发布
- 你可以保留默认值。你也可以在发布描述框中输入信息,描述发布流水线的目的,然后点击创建:

图 3.34 – 确认创建发布流水线
前面截图中的默认值有不同的含义;让我们来一一解析:
-
启用了自动触发器的阶段将在发布创建后立即开始。通过此选项,你可以停止阶段在发布创建时自动启动,然后从门户手动启动它。
-
对于工件,创建发布时可用的最新版本将是默认选中的选项。这个选项让你有机会选择任何其他可用版本,比如在你想通过部署先前版本来执行回滚的场景中。
- 现在,你可以看到发布流水线运行的进度:

图 3.35 – 构建发布
- 你还可以通过点击显示安全文件 & 变量来查看任务列表:

图 3.36 – 查看发布结果
任务结果将显示如下:

图 3.37 – 命令行任务结果
至此,你已经学会了如何创建一个发布流水线,包括一个安全文件和一个变量组库。你可以在创建任何流水线时应用这一模式。
摘要
本章介绍了如何创建一个包含变量组库的发布流水线,并上传和管理一个机密文件。这些组件不仅增强了流水线的组织性和安全性,还为高效、一致的部署提供了基础。
理解如何创建这些组件将帮助你协调 Azure DevOps 流水线与 Azure 生态系统之间高效、简洁的交互,这是任何 Azure 开发项目中宝贵的技能。
在下一章中,你将学习如何使用 YAML 自定义构建管道并在代理上运行它。这个 YAML 将创建一个高级构建管道,而不是在 Azure 门户上创建,并且是你可以为构建管道创建参数的地方。
第四章:4
使用 YAML 扩展高级 Azure Pipelines
在上一章中,我们通过创建作业、任务和触发器创建了构建管道。本章将教您如何使用 YAML 自定义 Azure 管道,例如,在设置复杂条件时通过变量组创建条件语句。这也有助于创建一个灵活的管道,而不是经典的在线版本。例如,当您需要同时将移动应用程序部署到 Google Play 控制台和 App Store Connect 时,YAML 可以做到这一点。
在本章结束时,您将学会如何使用 YAML 创建构建和发布管道。您还将学会如何从 Azure DevOps 门户的经典编辑器中克隆、导出和导入 YAML。
我们将涵盖以下主题:
-
使用 YAML 创建构建管道
-
使用 YAML 创建发布管道
-
克隆、导出和导入 YAML 管道
-
复杂的 YAML 配置
-
基于 YAML 的管道的优点和局限性
我们从使用 YAML 语法创建一个管道开始。
使用 YAML 创建构建管道
在本节中,您将学习如何使用 YAML 构建一个管道。您还将学习如何在 Azure DevOps 门户中查看 YAML,并将 YAML 文件保存到 Azure Repos。要使用 YAML 创建构建管道,请按照以下步骤操作:
- 在通过 Azure DevOps 门户登录后,请选择您的组织,然后导航到 管道 页面。点击 新建管道:

图 4.1 – 新建管道
- 点击 Azure Repos Git,这是演示的源代码仓库:

图 4.2 – Azure Repos Git
- 点击 PacktAzureDevOps 仓库:

图 4.3 – 选择一个仓库
- 如果您已经有现有的 YAML 文件,您需要选择 现有的 Azure Pipelines YAML 文件。但是,由于我们在这里创建一个新的文件,我们将点击 启动管道:

图 4.4 – 选择一个启动管道
- 点击 保存,然后您可以查看以 YAML 格式的新管道:

图 4.5 – 保存管道 YAML
- 输入一个提交信息,以帮助您记住在文件中所做的更改,并选择 直接提交到主分支 选项。此选项将把您的文件保存在主分支中:

图 4.6 – 提交 YAML 到 Azure Repos Git
如果您希望将文件保存在新分支中,请选择 为此提交创建一个新分支。点击 保存。
- 点击 保存 后,您将返回到构建管道的主仪表板结果:

图 4.7 – 构建管道仪表板
- 点击 运行管道:

图 4.8 – 运行构建管道
此后,你可以看到构建管道结果的摘要:

图 4.9 – 构建管道结果
- 你可以通过点击以下截图中 Run new 按钮旁边的省略号 (…),然后选择 Edit pipeline 来编辑管道:

图 4.10 – 编辑管道
-
现在,你可以查看 YAML 文件结构的初始部分。让我们来看看示例 YAML 的结构,并描述每个部分:
-
Azure Repos 的 main 分支保存 YAML 文件。
-
这是一个仓库名称。
-
这是一个 YAML 文件名。
-
构建管道将在主分支的任何更改上运行。
-
构建管道将在 Ubuntu 操作系统上运行。
-
脚本任务包含一行代码。
-
脚本任务包含多行代码。
以下截图展示了这些组件:
-

图 4.11 – YAML 文件结构
- 你可以通过点击作业来查看运行管道后的结果,该作业基于之前截图中的 YAML 文件:

图 4.12 – 显示作业结果详情
- 点击 Run a one-line script,这将显示 Hello, world! 文本。这个示例展示了当你希望在 Azure 管道任务中显示消息时:

图 4.13 – 作业步骤及其结果
本节中你学会了如何使用 YAML 文件创建一个简单的构建管道。Azure Repos 保存了 YAML 文件,并且你可以通过 Git 查看其历史记录。在下一节中,你将学习如何使用包含阶段、作业和任务的 YAML 文件创建发布管道。
使用 YAML 创建发布管道
本节将教你如何使用 YAML 创建发布管道。你还将学习如何在 YAML 格式中创建阶段、作业和任务。为此,请按照以下步骤进行:
- 通过点击省略号 (…) 并选择 Edit pipeline,可以编辑现有管道:

图 4.14 – 编辑现有管道
- 如下截图所示,替换现有
azure-pipelines.yml文件的所有内容:

图 4.15 – 带有两个阶段的高级管道
如前面截图所示,有两个阶段:
-
第一个阶段将显示
Buildstage job -
第二阶段将显示
Release stage
该场景展示了适用于构建应用程序(Build 阶段)和部署应用程序(Release 阶段)的构建和发布阶段。如果构建阶段失败,发布阶段将不会继续运行,因此很容易找到错误或问题。
- 你可以通过点击 … 在 保存 旁边并点击 验证 来验证 YAML 文件的语法:

图 4.16 – 验证 YAML 文件
- 如果 YAML 文件有效,你将看到以下消息:

图 4.17 – 有效的 YAML 文件
如果 YAML 文件无效,你将看到以下错误消息,说明哪个行出现了问题:

图 4.18 – 无效的 YAML 文件
- 点击保存 | 运行管道。你可以看到一个包含两个阶段的管道结果:

图 4.19 – 阶段详情
- 你可以通过展开某个阶段并点击 重新运行阶段 来重新运行特定的阶段:

图 4.20 – 重新运行阶段
你已经学会了如何创建一个包含两个阶段的发布管道,并且包含一个作业。你还了解了使用阶段的好处,因为你可以重新运行有问题的阶段,而不需要重新运行整个管道。接下来的章节将教你如何克隆、导出和导入 YAML 来创建一个新管道。
克隆、导出和导入 YAML 管道
本节将教你如何从 Azure DevOps 门户中克隆、导出和导入 YAML 管道。这些操作将帮助你节省时间,当你需要复制相同的模板并进行调整时。如果你需要创建新的 Azure 管道,可以通过克隆现有的管道来完成。让我们看看执行这些任务所需遵循的步骤:
-
克隆:你可以通过复制和粘贴克隆管道,这是克隆管道的简单快捷方式。
-
导出和导入:以下步骤展示了如何从管道中导出整个 YAML 文件:
- 你可以通过点击 编辑 来导出 YAML 管道:

图 4.21 – 编辑管道
- 点击 … | 下载完整 YAML 下载文件:

图 4.22 – 下载完整的 YAML 文件
- 打开已下载的文件,复制并粘贴到你创建的新管道中。
在本节中,你了解了如何轻松下载、复制并粘贴 YAML 文件到新的管道中。现在,让我们来看一下使用基于 YAML 的管道的一些优点和缺点。
复杂的 YAML 配置
YAML 语法支持几种复杂的配置,允许 YAML 文件的模块化和重用。比如模板重用和模板表达式的实现。接下来的章节将探讨这些功能是如何工作的。
YAML 模板重用
在处理大型项目以及由同一团队成员开发的多个应用程序时,定义通用模板以便重用,而不是为每个应用程序的 CI/CD 需求从头开始编写所有内容,这非常有帮助。为此,Azure Pipelines 支持引用模板来重用步骤、作业和阶段。这在减少 YAML 重复性方面尤其有用,因为项目中的所有应用程序或部署过程都是相同的。还可以在模板中包含参数,以传递可用于被引用模板中的值,从而自定义行为。
让我们来看以下场景,其中一个 Azure 管道使用两种不同的构建配置两次构建相同的应用程序,采用 .NET 语言。以下截图显示的文件定义了一个参数 buildConfiguration,以及执行 NuGet 工具安装(NuGetToolInstaller@1)、NuGet 依赖项恢复(NuGetCommand@2)和使用 VS 构建工具(VSBuild@1)构建解决方案的三个步骤:

图 4.23 – 带参数的 dotnet-build-steps 模板
在以下示例中,你可以看到相同的 dotnet-build-steps.yml 文件现在可以在两个不同的代理 linux-latest 和 windows-latest 上使用相同的步骤构建相同的应用程序,并且可以在前后添加其他任务:

图 4.24 – 带有模板引用的 Azure 管道
该功能提供了极大的灵活性,减少了 YAML 管道中的重复代码,并实现了管道的标准化,这有助于减少出错的几率。
更复杂的配置还可以包括将所有模板放在一个单独的存储库中,由另一个团队负责将这些构建块整合在一起,帮助负责 CI/CD 管道的团队,正如以下截图所示:

图 4.25 – 来自另一个存储库的模板引用
现在让我们来看一下如何使用模板表达式。
YAML 模板表达式
表达式 是 Azure Pipelines 中的一种自定义语法功能,允许你在运行时动态解析值。可以把它看作是模板执行中的控制逻辑。本章无法涵盖所有表达式类型,但了解以下几点是很重要的:
-
评估字面量和变量
-
使用内置函数,如
coalesce、contains、eq、format等,来评估逻辑条件或转换值 -
使用内置函数来评估作业状态
-
使用条件来有条件地插入变量值或任务
-
使用
each关键字遍历参数 -
评估对前置作业或阶段的依赖关系,如状态或输出变量。
以下截图展示了如何使用两种不同工具集(msbuild 或 dotnet CLI 工具)来支持执行应用程序的构建和测试,并根据参数选择工具的示例:

图 4.26 – 模板表达式示例
使用表达式时,您可以完全控制如何定义管道,并根据进度动态执行步骤,而不仅仅是简单地定义一组静态步骤。
现在您已经理解了这些复杂的配置,让我们来讨论基于 YAML 的管道的优缺点。
基于 YAML 的管道的优缺点
首先,让我们看看使用基于 YAML 的管道的好处:
-
YAML 管道作为代码存储在您的版本控制系统中,如 Azure Repos。这意味着它们可以像其他代码一样进行版本控制、分支和审查,从而提供更好的协作和可追溯性。
-
YAML 管道允许在实施分支策略时以受控方式引入变更,分支策略将不同团队成员的工作隔离开来。这确保了变更在完成之前不会影响其他团队成员,从而进行充分测试。
-
YAML 管道使得在不同环境中一致地重现构建和发布过程变得容易。这有助于减少配置漂移,并确保一致的结果。
-
您可以控制构建和发布过程。您可以定义步骤、依赖关系和条件,使您能够根据特定需求定制管道。
尽管有这些好处,使用这些管道可能会有以下缺点:
-
基于 YAML 的管道需要掌握 YAML 语言和 Azure Pipelines 语法,对于不熟悉 Azure Pipelines 的人来说,这可能是一个学习曲线。
-
基于 YAML 的管道语法仅适用于 Azure Pipelines,无法直接迁移到其他 CI/CD 工具。
-
基于 YAML 的管道对扩展的 YAML 文件大小有 4 MB 的限制,这可能使得极其复杂的 CI/CD 过程难以定义。
-
由于无法将其部署到此类环境中,在发布阶段验证 YAML 管道可能会变得困难或不可能。
现在您已经熟悉了基于 YAML 的管道,让我们结束本章内容。
总结
本章讲解了如何使用 YAML 构建和发布管道。与在 Azure DevOps 门户上使用经典编辑器相比,这种方法对开发人员来说更具优势。开发人员可以将 YAML 文件保存在他们的 Azure Repos 中,这有助于他们查看每个版本的管道。使用基于 YAML 的管道,你可以提供更高效、透明和以开发者为中心的 CI/CD 流程。你还学习了复杂场景以及如何简化 YAML、重用模板,并通过表达式添加动态行为。最后,你了解了基于 YAML 的管道的优缺点。
在下一章中,你将深入学习如何使用 YAML 实现构建和发布管道,以及如何使用 Node.js、NPM、.NET 和 Docker 重用构建任务来构建管道。
第二部分:Azure Pipelines 的实际应用
现在我们已经学会了基础知识,是时候学习如何使用我们的管道进行构建和部署工作,从应用程序到基础设施的自动化配置和管理,包括这些过程中涉及的测试和安全工具。
本部分包含以下章节:
-
第五章,使用部署任务实现构建管道
-
第六章,集成测试、安全任务和其他工具
-
第七章,监控 Azure Pipelines
-
第八章,使用基础设施即代码(Infrastructure as Code)进行基础设施配置
第五章:5
使用部署任务实现构建流水线
在上一章中,我们使用 YAML 创建了一个流水线,并学习了如何在 YAML 格式中创建作业和任务,以及如何导出和导入构建流水线。本章将深入探讨如何使用标准任务创建流水线。到本章结束时,你将学会如何为 Web 应用开发创建构建流水线,包括 Node.js、.NET Core、Docker 和 Microsoft SQL Server,无论是在本地还是 Azure 上,使用初学者友好的任务,使你更容易理解这一概念。
我们将涵盖以下主题:
-
使用 Node.js 和 Node 包管理器 (NPM) 任务
-
使用 .NET Core CLI 任务
-
使用 Docker 任务
-
使用 SQL Server 部署任务
让我们从学习如何使用 Node.js 和 NPM 任务创建流水线开始。
使用 Node.js 和 NPM 任务
你需要使用 Node.js 和 NPM 命令来构建和部署 Node.js 应用程序。在 Azure 流水线中,有许多预定义的任务可以构建此类应用程序。按照以下步骤使用 Node.js 和 NPM 任务创建流水线:
- 登录到 Azure DevOps 门户后,选择你的组织,导航到 流水线 页面,然后点击 新建流水线:

图 5.1 – 新建流水线
- 选择 Azure Repos Git,这是本演示的源代码库:

图 5.2 – 选择 Azure Repos Git
- 选择我们在 第二章 中创建的 PacktAzureDevOps 仓库:

图 5.3 – 选择一个代码库
- 点击 显示更多:

图 5.4 – 显示更多任务
- 选择 Node.js 选项:

图 5.5 – 选择 Node.js
- 你可以重命名默认文件名
azure-pipelines-1.yml,点击它并将其改为node.yml:

图 5.6 – 编辑文件名
- 点击 保存并运行 | 保存:

图 5.7 – 保存流水线文件
在选择了 Node.js 和 NPM 任务模板后,你可以继续修改与 Azure 流水线匹配的默认 NPM 命令,例如选择你需要的 Node.js 版本。接下来的部分将展示如何为 .NET Core 创建任务。
使用 .NET Core CLI 任务
对于 .NET 应用程序,你必须使用 .NET Core CLI 命令来构建和部署 .NET 应用程序。在 Azure 流水线中,有许多预定义的任务可以构建 .NET 应用程序。按照以下步骤使用 .NET Core CLI 任务创建流水线:
-
按照上一部分的 步骤 1 到 3 创建 Node.js 和 NPM 任务。
-
选择 入门流水线:

图 5.8 – 选择启动管道选项
- 将文件从默认名称重命名,以便更容易理解 YAML 文件的用途:

图 5.9 – 重命名管道文件
- 选择使用 .NET Core任务并点击添加:

图 5.10 – 选择使用 .NET Core 任务
- 更新
version属性以使用 .NET 6:

图 5.11 – 更新 .NET 版本
- 选择.NET Core任务并点击添加:

图 5.12 – 选择 .NET Core 任务
-
审查两个预定义的 .NET 任务:
-
UseDotNet@2用于安装 .NET 编译器版本 6.0.x -
DotNetCoreCLI@2是运行特定命令的 .NET 命令,即build命令
以下截图显示了这些内容:
-

图 5.13 – .NET Core 构建任务的视图
在为 .NET CLI 命令创建了启动任务后,您可以继续使用 DotNetCoreCLI@2 命令自定义您的任务,该命令指定了用于构建 .NET 应用程序的 build 命令,将源代码构建为 .NET 二进制文件。接下来的部分将展示如何处理用于容器化应用程序的 Docker 任务。
使用 Docker 任务
对于云原生应用程序,您需要使用 Docker 命令来构建和部署云原生应用程序。在 Azure 管道中,有许多用于构建云原生应用程序的预定义任务。您可以执行以下步骤,通过 Docker 任务创建管道:
-
按照前一部分中描述的步骤 1 到 4,进行 .NET Core CLI 任务的操作。
-
重命名 Docker 管道的文件:

图 5.14 – 重命名文件
-
选择
DockerInstaller@0:- task: DockerInstaller@0 inputs: dockerVersion: '17.09.0-ce'以下截图展示了如何添加一个Docker CLI 安装程序任务并填写任务的详细信息:

图 5.15 – 添加 Docker CLI 安装程序任务
-
选择Docker任务并点击添加,您将看到以下代码。这是一个用于在一个任务中构建并推送镜像的 Docker 任务:
- task: Docker@2 inputs: command: 'buildAndPush' Dockerfile: '**/Dockerfile'以下截图展示了如何添加 Docker 任务并填写详细信息:

图 5.16 – 添加 Docker 任务
为了方便错误处理,您可以将 buildAndPush 任务替换为独立的build和push任务。在push任务中,您将 condition 值设置为 succeeded(),这确保任务仅在前面的步骤(在本例中为构建任务)成功完成时运行。
您可以使用以下代码来替换 buildAndPush 任务:
- task: Docker@2
displayName: 'Build Docker image'
inputs:
command: build
Dockerfile: '**/Dockerfile'
tags: latest
- task: Docker@2
displayName: 'Push Docker image'
inputs:
command: push
condition: succeeded()
区别在于,buildAndPush 任务将在一个任务中构建并推送镜像,这意味着如果您需要在构建和推送之间添加任务,您是做不到的。
-
接下来,选择 命令行 任务并点击 添加。如以下代码片段所示,更新命令行:
- task: CmdLine@2 inputs: script: | docker login <docker hub url> -u <your username> -p <your password> docker push <your repository>:<your tag>以下截图显示了如何添加 命令行 任务并填写详细信息:

图 5.17 – 添加命令行任务
运行此管道后,您将在 Docker Hub 上看到 Docker 镜像。
使用 SQL Server 部署任务
您需要使用 SQL Server 命令来构建和部署 SQL Server 应用程序。在 Azure Pipelines 中,有许多任务需要完成以构建 SQL Server 应用程序;请按照以下步骤创建一个使用 SQL Server 部署任务的管道:
-
您可以按照 步骤 1 到 4,如 与 .NET Core CLI 任务 部分所述操作。
-
如以下截图所示,重命名文件:

图 5.18 – 重命名文件
-
搜索
SQL Server 数据库,选择 SQL Server 数据库部署 任务,并输入以下内容:- task: SqlDacpacDeploymentOnMachineGroup@0 inputs: TaskType: 'sqlQuery' SqlFile: 'migrate.sql' ExecuteInTransaction: true ServerName: 'localhost' DatabaseName: 'your_database' AuthScheme: 'sqlServerAuthentication' SqlUsername: 'your_username' SqlPassword: 'your_password'让我们详细查看每个属性:
-
TaskType:这可以是dacpac、sqlQuery或sqlInline:-
dacpac表示此任务将执行dacpac文件中的 SQL 命令 -
sqlQuery表示此任务将执行 SQL 文件中的 SQL 命令,如SELECT、UPDATE、INSERT和DELETE命令 -
sqlInline表示此任务将直接执行 SQL 文件中的 SQL 命令作为 NOT 值
-
-
SqlFile:这是 SQL 文件的完整路径 -
ExecuteInTransaction:如果设置为true,则任务将在事务范围内执行 SQL 文件 -
ServerName:这可以是数据库服务器名称或 IP 地址 -
DatabaseName:这是执行的数据库名称 -
AuthScheme:这可以是sqlServerAuthentication,使用 SQL Server 的身份验证,或windowsAuthentication,使用 Windows 的身份验证 -
SqlUsername:这是 SQL Server 的用户名 -
SqlPassword:这是 SQL Server 的密码
-
以下截图显示了如何添加 SQL 数据层应用程序包(DACPAC)部署任务并填写详细信息:

图 5.19 – 添加 SQL Server 数据库部署任务
创建 SQL Server 数据库部署任务后,您可以继续进一步自定义您的任务。例如,您可以指定 ServerName 参数,表示数据库主机名或 SQL Server 的服务器名称,该任务将连接到此服务器。完成这些自定义后,您可以保存管道文件。
总结
本章向你介绍了如何构建和发布管道,使用标准的 NPM、.NET Core CLI、Docker 和 SQL Server 部署任务。这些预定义任务在构建和部署 Node.js 和.NET 应用程序时非常受欢迎。它们减少了开发人员在运行管道时创建手动命令的时间,从而加快了构建应用程序的过程。
在下一章,你将深入学习如何集成测试和安全任务,以使你的代码和应用程序更加可靠。
第六章:6
集成测试、安全任务和其他工具
现在我们已经学习了构建和发布流水线的基础知识,是时候了解如何通过其他工具扩展 Azure Pipelines,以执行更多任务,并能够在内置功能之外增加额外的功能了。本章结束时,你将具备超越基础的技能,能够包括任务来提高构建中产生代码的质量,部署前发现漏洞,并使用来自其他仓库的源代码和来自其他位置的完整工件。
在本章中,我们将讨论以下主题:
-
理解 Azure DevOps 的可扩展性模型
-
为你的构建包含自动化测试
-
提高代码质量
-
与 Jenkins 集成以进行工件和发布流水线管理
技术要求
为了完成本章内容,你需要某些扩展。首先,让我们了解Azure DevOps 可扩展性模型以及如何访问这些扩展。你可以在 GitHub 仓库中找到本章的代码,链接为 github.com/PacktPublishing/Implementing-CI-CD-Using-Azure-Pipelines/tree/main/ch06。
理解 Azure DevOps 的可扩展性模型
Azure DevOps 及其子服务提供了多个默认包含的功能,但你可以使用扩展来定制和扩展你的体验,这些扩展可以使用标准技术(如 HTML、JavaScript 和 CSS)开发。
所有子服务背后都有一个非常灵活的模型,你可以通过市场上由个人和知名第三方组织发布的扩展来增强该模型。如果你没有找到需要的功能,你也可以创建自己的扩展并发布。
扩展的目的是简化可重用任务的封装,使用外部工具,甚至增强 Azure DevOps 的外观和感觉。对于 Azure Pipelines,你会找到以下扩展:
-
简化复杂和重复的任务
-
轻松使用常见的基础设施即代码(IaC)工具,如 Terraform 或 Ansible
-
与 SaaS 产品集成以提高代码质量和安全性
-
促进部署任务到如 Azure 和亚马逊 Web 服务等云服务提供商
你可以访问 Visual Studio Marketplace for Azure DevOps,网址是 marketplace.visualstudio.com/azuredevops。
以下截图展示了它的样子:

图 6.1 – Azure DevOps 的 Visual Studio 市场
每个 marketplace 扩展列表都会显示扩展的名称、发布者、是否经过认证、评分、安装次数和价格。有些是免费的,而其他一些则可能需要付费。你可以通过名称、类别或标签来查找这些扩展,方便找到你需要的内容。
安装代码质量评估工具 SonarQube
在 marketplace 中搜索 SonarQube 并点击列表查看其详细信息。或者,你可以访问 marketplace.visualstudio.com/items?itemName=SonarSource.sonarqube。
你应该看到类似于以下内容:

图 6.2 – Visual Studio Marketplace 上 SonarQube 扩展的列表
找到你想要的扩展后,点击 免费获取 或 获取 按钮。你将能够选择要安装的 Azure DevOps 组织(如果你有多个组织),如下图所示:

图 6.3 – 从 marketplace 安装 SonarQube 扩展
审核完权限和服务条款后,选择要安装的组织,点击 安装 按钮。安装扩展通常只需几秒钟,安装完成后,你可以选择继续前往 Azure DevOps 组织,或者返回 marketplace 查找更多扩展:

图 6.4 – SonarQube 扩展已安装
你可以在 github.com/PacktPublishing/Implementing-CI-CD-Using-Azure-Pipelines/tree/main/ch06 找到本章的代码。
现在,我们已经完成了所有技术要求的设置,接下来让我们看看如何将自动化测试运行添加到你的构建管道中。
包括对构建的自动化测试
所有现代应用程序都需要某种形式的验证,以确保它们正确工作,无论有多少开发人员同时在代码上进行开发。这就是自动化测试的作用,它会在应用程序构建完成后立即执行,验证是否在做出更改时没有丧失质量或引入 bug。
有多种类型的测试,例如 单元测试、集成测试 和 负载测试,可以对应用程序执行。根据所使用的编程语言以及开发团队的偏好,还有许多自动化测试框架可供选择。
为什么自动化测试很重要?
自动化测试可以帮助你减少在应用程序发布中出现 BUG 的机会,通过在开发周期的早期阶段发现它们,同时减少测试团队执行验证的时间,并避免在重复任务上浪费人力,这些时间本可以用来开发应用程序中的更多功能和能力。
在本节中,你将学习如何使用NUnit 测试框架和在.NET Core 6.0 中创建的示例 C#.NET 应用程序集成单元测试的执行到你的构建流水线中。
我们假设你使用的是一个 Visual Studio 解决方案,其中包含一个CalculusService类库项目,测试项目已包含在内。以下是该类库项目中此类的示例代码:
namespace CalculusService
{
public class Additions
{
public int Add(int number1, int number2)
{
return number1 + number2;
}
}
}
以下对应的单元测试定义在一个单独的测试项目中。确保你引用了Nunit、NUnit3TestAdapter和NUnit.Analyzers NuGet 包:
namespace CalculusService.Tests
{
[TestFixture]
public class AdditionsTests
{
private Additions additions;
[SetUp]
public void Setup()
{
additions = new Additions();
}
[TestCase(1, 2, 3)]
[TestCase(2, 4, 6)]
[TestCase(5, -10, -5)]
public void TestAdd(int number1, int number2, int result)
{
Assert.That(result, Is.EqualTo(additions.Add(number1, number2)));
}
}
}
你需要使用 YAML 流水线,如下所示的代码片段,以构建和执行自动化测试:
trigger:
- main
pool:
vmImage: 'windows-latest'
variables:
solution: '**/*.sln'
buildPlatform: 'Any CPU'
buildConfiguration: 'Debug'
steps:
- task: NuGetToolInstaller@1
- task: NuGetCommand@2
inputs:
restoreSolution: '$(solution)'
- task: VSBuild@1
inputs:
solution: '$(solution)'
msbuildArgs: '/p:PackageAsSingleFile=true /p:PackageLocation="$(build.artifactStagingDirectory)"'
platform: '$(buildPlatform)'
configuration: '$(buildConfiguration)'
- task: VSTest@2
inputs:
testSelector: 'testAssemblies'
testAssemblyVer2: |
**\*.Tests.dll
!**\*TestAdapter.dll
!**\obj\**
codeCoverageEnabled: true
platform: '$(buildPlatform)'
configuration: '$(buildConfiguration)'
该流水线中最重要的部分是最后一步,它使用了VSTest@2任务。这是一个通用的现成任务,Azure Pipelines 中可用于运行单元和功能测试,支持多个测试框架,充分利用 Visual Studio 的 Test Explorer。设置codeCoverageEnabled属性为true也很重要,这样你就可以收集数据,指示应用程序中有多少代码被测试。
专业提示
配置VSTest任务并使用testAssemblyVer2属性时,确保提供一组模式列表,以便准确找到要执行测试的测试程序集。否则,你将遇到难以理解的错误。
执行单元测试并启用代码覆盖率后,你将在概览窗口中看到结果:

图 6.5 – 概览窗口中的测试和覆盖率结果
使用此任务的好处是它提供对自动发布测试结果和内置的 UI 报告的支持,这些功能已集成在 Azure Pipelines 中,如下所示的截图所示:

图 6.6 – Azure Pipelines 运行中包含的测试结果
VSTest@2任务还支持更高级的场景,比如在多个代理上并行执行测试,这在你有很多测试需要运行时非常有用,或者执行 UI 测试时,这需要在执行测试的代理中进行额外配置。
如果你的应用程序是使用其他编程语言构建的,你必须使用相应的测试运行器,并确保将结果发布为PublishTestResults@2任务支持的任何格式,以便能够导入并包含在 UI 中。
现在我们已经了解了如何以自动化方式运行测试,让我们学习如何提高代码质量。
提高代码质量
通常,开发人员太忙于关注代码质量,最终会利用各种自动化工具来确保自己编写的是最佳且最安全的应用程序。
在这个空间中有两个重要的区域需要了解:
-
静态应用程序安全测试:这可以帮助您检测代码中的漏洞
-
软件组成分析:这可以帮助您检测代码中引用的外部包和库中的漏洞
为什么要使用工具来提高代码质量?
开发人员和测试人员只能在有限的时间内尽力满足时间表并处理应用程序功能。提前引入这些工具可以帮助他们发现 bugs 和漏洞,否则这些问题在应用程序发布给最终用户时可能会造成很高的成本。
有许多知名的第三方工具可以用于扫描和评估代码质量。在本章中,我们将使用SonarQube,因为它是最流行且易于使用的工具之一。它允许开发人员确保通过识别 bugs 和安全漏洞、检测常见的反维护性模式和重复代码等来编写清晰的代码。
它提供不同的定价层级,从免费的 Community Edition 开始,本章中的示例将使用该版本。如果您希望在工具中获得更多的编程语言支持或高级漏洞检测,则需要付费版本。
Checkmarx、Veracode、OWASP、WhiteSource 和 HP Fortify 等都是可用的工具。比较这些工具超出了本书的范围,但您可以在网上找到很多比较。
按照以下步骤设置 SonarQube 分析您的代码:
-
配置 SonarQube 项目。
-
在 Azure DevOps 中创建到 SonarQube 的服务连接。
-
创建一个 Azure pipeline 来分析您的代码。
我们将在接下来的章节中逐步介绍这些步骤。
配置 SonarQube 项目
在您的 SonarQube 实例中,通过选择来自 Azure DevOps选项,从向导中创建项目,如下图所示:

图 6.7 – 使用来自 Azure DevOps 选项在 SonarQube 中创建项目
如果这是您第一次设置与 Azure DevOps 的连接,SonarQube 会提示您提供配置名称、Azure DevOps URL和个人访问令牌的详细信息,以便它可以配置项目:

图 6.8 – 创建配置
然后,通过从可用列表中选择 Azure DevOps 项目并点击设置所选 代码库按钮,在 SonarQube 侧选择要配置的 Azure DevOps 项目:

图 6.9 – 在 SonarQube 中选择 Azure DevOps 项目
完成此操作后,你就可以继续在 Azure DevOps 和管道端进行配置了。
在 Azure DevOps 中创建到 SonarQube 的服务连接
下一步是创建一个服务连接到 SonarQube 实例。这将允许 Azure Pipelines 使用 SonarQube 扩展并与 SonarQube 实例进行通信。
你可以通过选择Sonar来创建和管理服务连接,应该列出 SonarQube 选项:

图 6.10 – SonarQube 服务连接选项
下一步是在Server Url、Token、Service connection name和Description (optional)框中提供详细信息。除非你想分别为每个管道管理服务连接的访问权限,否则不要忘记勾选Grant access permission to all pipelines选项:

图 6.11 – SonarQube 的服务连接详情
重要提示
服务连接是定义与 Azure DevOps 外部服务通信所需凭证的简单且集中化的方式,避免了在各个地方输入凭证的需求。
创建一个 Azure Pipeline 来分析你的代码
下一步是将 SonarQube 扩展中的两个任务添加到你的构建管道中。让我们看一下以下管道:
pool:
vmImage: 'windows-2019'
variables:
solution: '**/*.sln'
buildPlatform: 'Any CPU'
buildConfiguration: 'Release'
steps:
- task: SonarQubePrepare@5
inputs:
SonarQube: 'SonarQube'
scannerMode: 'MSBuild'
projectKey: 'PacktAzureDevOps_SonarQubeIntegration_########'
- task: NuGetToolInstaller@1
- task: NuGetCommand@2
inputs:
restoreSolution: '$(solution)'
- task: VSBuild@1
inputs:
solution: '$(solution)'
msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactStagingDirectory)"'
platform: '$(buildPlatform)'
configuration: '$(buildConfiguration)'
- task: SonarQubeAnalyze@5
SonarQubePrepare任务用于提供执行分析所需的上下文,必须提供 SonarQube 中的服务连接、扫描器模式和项目密钥。此任务必须在执行任何编译任务之前放置。
SonarQubeAnalyze任务负责执行安全扫描,必须放在所有编译代码的任务之后。它将使用自SonarQubePrepare任务执行以来收集的信息来执行所有必要的数据收集和分析,以进行安全扫描。如果未通过 SonarQube 项目中定义的条件,该任务将导致管道失败。
审查 SonarQube 分析结果
安全扫描的结果将在 SonarQube 门户中提供。根据项目的性质,你将获得不同的质量指标和改善代码的建议,如下图所示:

图 6.12 – SonarQube 分析结果
重要提示
可以使用SonarQubePublish任务在 Azure Pipeline 摘要中包含简短的摘要和完整报告的链接。然而,这仅适用于 SonarQube 的付费版本。
SonarQube 将为您提供有关代码中可能存在的错误、漏洞、安全热点、代码重复和其他问题的洞察,帮助开发者通过发现这些问题并提供修复建议来解决它们。
将这种类型的工具集成到您的管道中,能为开发者提供快速反馈回路,帮助他们在开发过程早期修复和缓解应用程序中的风险,避免在将应用部署到生产环境时发生代价高昂的错误。
Azure Pipelines 也可以用来协调在其他系统中创建的工件的部署,例如流行的 CI/CD 工具Jenkins。我们将在下一节详细介绍这一点。
与 Jenkins 集成以进行工件和发布管道
在本节中,我们将演示如何设置一个简单的示例,展示如何将 Azure Pipelines 与 Jenkins 连接,从而能够下载在 Jenkins 中生成的工件并通过发布管道进行部署。
Jenkins 任务类似于 Azure Pipeline,是一组自动化步骤,执行特定的操作并可以生成工件或执行部署。让我们来学习如何创建一个简单的 Jenkins 任务。
创建一个生成工件的 Jenkins 任务
这个场景假设我们在 Jenkins 服务器中有一个名为PacktFamily的项目,如下图所示:

图 6.13 – 带有 PackFamily 项目的 Jenkins 实例
在这个场景中,Jenkins 任务的配置非常简单,主要是为了演示如何在 Azure Pipelines 端下载一个工件。以下图显示了生成artifact.txt的构建步骤:

图 6.14 – Jenkins 任务中用于创建工件的构建步骤
以下图显示了artifact.txt的构建后操作:

图 6.15 – 发布 Jenkins 工件的构建后操作
Jenkins 任务的执行将生成一个可由 Azure Pipelines 下载的单一工件:

图 6.16 – Jenkins 任务结果与工件
现在我们有了一个 Jenkins 任务,接下来让我们学习如何将 Azure Pipelines 与它集成。
在 Azure DevOps 中创建 Jenkins 服务连接
这个过程类似于我们在在 Azure DevOps 中创建 SonarQube 服务连接一节中讨论的内容。在 Azure DevOps 的项目设置中,点击Jenkins并点击下一步:

图 6.17 – 新建服务连接对话框
提供服务器 URL、用户名、密码和服务连接名称的详细信息。如果需要,请不要忘记勾选授予所有管道访问权限框。最后,点击验证并保存按钮以继续:

图 6.18 – 新建 Jenkins 服务连接对话框
现在,我们可以继续创建一个使用工件的管道。
创建一个用于 Jenkins 工件的发布管道
现在,是时候配置发布管道了。你可以按照以下步骤进行:
- 导航到项目 | 管道 | 发布,然后点击新建发布管道。你将有机会选择一个模板,如下图所示。我们将从空作业开始:

图 6.19 – 选择发布管道的模板
- 点击添加工件小部件,然后选择Jenkins选项,你将能够使用之前创建的服务连接,选择在 Jenkins 中的项目,以便使用工件。只需选择与之前步骤中创建的名称匹配的服务连接,然后选择相应的源(作业)选项:

图 6.20 – 将 Jenkins 工件添加到发布管道
-
你可以选择更改源别名详细信息,这将作为管道执行后下载工件的目录。当你有多个来自不同来源的工件时,这一点非常重要,可以避免在管道执行时文件被覆盖。在这种情况下,默认值将有效。
-
完成此操作后,我们可以向部署阶段添加步骤,验证甚至打印出工件的内容。点击部署阶段中的1 个作业,0 个任务选项,将允许我们自定义管道。在此场景中,我们将使用 Linux 代理。点击代理作业选项,如下图所示,将使我们进入代理选择部分。现在,我们可以从代理池下拉菜单中选择Azure Pipelines,并从代理规格下拉菜单中选择ubuntu latest:

图 6.21 – 在部署阶段选择代理
- 完成此操作后,点击Agent Job部分右侧的+按钮,查找并添加命令行任务。此任务可以在代理中执行自定义脚本,并会根据操作系统切换到适当的底层进程:

图 6.22 – 用于列出工件内容的命令行任务
让我们来看一下我们用来显示内容的脚本:
ls -la
cd _PacktFamily
ls -la
echo "Show content of file artifact.txt file:"
cat artifact.txt
这个脚本将执行以下操作:
-
列出当前目录的内容,这里应该是代理运行当前管道的地方
-
进入 Jenkins 构件下载的目录
-
列出当前目录的内容,这里应该是 Jenkins 构件被下载到的地方
-
打印出一个标签,表示将要显示的文件内容
-
打印
artifact.txt文件的内容
- 一旦你保存了管道并创建了一个发布来执行它,你应该能够看到它有效地从 Jenkins 下载了构件并列出了文件的内容,如下图所示:

图 6.23 – 管道下载 Jenkins 构件的日志
重要提示
Azure Pipelines 选项在 代理选择 部分提供了访问 Microsoft 托管代理的功能。这些代理由 Azure DevOps 平台管理,无需你管理底层基础设施。支持多个操作系统,不同版本的代理中也已安装不同的工具,以便于构建和部署应用程序。你还可以选择购买并行作业容量,以便同时运行多个作业。如果需要,你也可以在管道执行过程中安装这些代理所需的任何软件。只要记住,这些将增加执行时间。
到此为止,我们已经完成了本章内容。
总结
在本章中,我们了解了 Azure DevOps 的扩展性模型,以及扩展市场如何使我们轻松找到可以轻松集成到构建和发布管道中的额外功能。这将加速你创建构建和发布管道的能力,并将它们与其他工具集成。我们还学习了如何通过集成自动化测试和安全扫描来提高应用程序的质量,及时提醒开发人员在出现故障或漏洞时,从而减少寻找 bug、修复它们和减少安全风险所需的时间,在将应用程序部署到最终生产环境之前。然后,我们学习了如何集成 Azure Pipelines,从另一个 CI/CD 工具下载构件并用于部署,这在不同团队使用不同 CI/CD 工具的混合环境中非常有用。最后,我们了解了 Azure Pipelines 中可用的 Microsoft 托管代理的灵活性,这使得你能够实现你的 CI/CD 需求,而无需管理底层基础设施。
在下一章,我们将学习如何监控 Azure DevOps Pipelines,这是一个确保一切正常工作的重要任务,如果出现问题,我们能够获得所需的可见性,及时修复问题。
第七章:7
监控 Azure Pipelines
到目前为止,我们已经了解了使用 Azure Pipelines 进行 CI/CD 所需的大多数构建模块。在本章结束时,你将具备理解如何高效运行构建和发布流水线的操作能力,利用内置功能来衡量代理的健康状况,确保作业按时执行,并验证应用程序在部署后能够顺利运行。
在本章中,我们将涵盖以下主题:
-
理解监控概念
-
监控流水线任务及其性能
-
监控流水线代理
-
使用监控来衡量应用程序质量
但首先,让我们讨论一下本章的一些技术要求。
技术要求
要完成本章,你需要安装 Microsoft 提供的构建质量检查 Marketplace 扩展。与上一章类似,在 Visual Studio Marketplace 中搜索该扩展,并将其安装到你的 Azure DevOps 组织中。你可以在marketplace.visualstudio.com/items?itemName=mspremier.BuildQualityChecks找到该扩展。在github.com/PacktPublishing/Implementing-CI-CD-Using-Azure-Pipelines/tree/main/ch07中可以找到本章的代码。
既然我们已经讲解了技术要求,接下来让我们讨论在使用 Azure Pipelines 时你应该熟悉的监控概念。
理解监控概念
在使用 Azure Pipelines 时,有几个关键概念需要在考虑监控时牢记:
-
流水线状态:确保流水线始终在运行且没有问题,同时检查是否有构建失败、测试失败或部署过程中出现错误。
-
代码质量指标:这涉及验证代码覆盖率、代码复杂度和代码异味等指标,以便在部署应用程序之前发现潜在的性能或功能问题。
-
安全漏洞:这涉及评估和衡量应用程序代码、依赖项或流水线配置中的安全漏洞。这样有助于确保流水线的安全性,并避免在应用程序中引入安全风险。
-
资源利用率:这有助于确保构建和发布流水线不会过度消耗代理的 CPU 或内存,也不会让流水线执行过长时间,避免影响其运行其他作业的能力。
-
部署健康状况:这涉及监控已部署的应用程序,确保其正确运行,并且没有连接性、可用性或功能问题。
-
发布周期时间:这包括监控发布周期时间,确保应用程序部署按时进行,并且任何延迟都能被尽快识别和修复。发布周期时间是指从应用程序的初始开发阶段到其在生产环境中部署所需的时间。
这些概念对于最小化检测时间(TTD)、缓解时间(TTM)和修复时间(TTR)等度量至关重要,这些度量在行业中用来衡量按时交付应用程序以及修复/恢复任何可能发生的问题的能力。
在本章中,我们将重点介绍这些概念中的一些,首先从管道任务及其性能开始。
监控管道任务及其性能
本节将介绍两种监控任务和性能的方法:
-
使用管道的用户界面
-
使用仪表盘
让我们先深入了解用户界面。
使用管道的用户界面
关于管道、任务和工作流的持续时间的度量信息可以在整个用户界面中找到,以强调执行时间的重要性,如下图所示:

图 7.1 – 管道摘要中的持续时间度量
这些持续时间度量帮助你立即了解管道及其中所有任务的执行时长。你可以点击每个任务并查看单独的步骤时长,以判断是否有需要你审查并改善的任务,如下图所示:

图 7.2 – 任务中的持续时间度量
你总是要确保构建或发布管道的总执行时间尽可能小。这可以确保你能够更快速地发布软件,并将其更快速地部署到任何环境中。管道中的时间增加可能表明最近的更改引入了问题,你应该审查每个任务的执行时间,以确定增加是否是预期的并且有理由的,或者是否需要修复某些内容。
你还可以通过点击导航菜单中的Pipelines选项并切换到Runs标签页,查看所有管道的总执行时间,如下图所示。或者,你也可以使用筛选选项(在下图中突出显示)来查找特定的运行:

图 7.3 – 你的管道的经过时间
随着时间推移查看这些指标可能会变得乏味。为此,Azure Pipelines 为每个管道提供了一个分析视图,您可以通过点击以下截图中标记为1、2和3的元素来访问。首先,导航到管道。从最近运行的管道列表中,选择UnitTests-YAML:

图 7.4 – 所有管道的列表
接下来,打开分析选项卡:

图 7.5 – 访问管道的分析视图
一旦加载了分析视图,如以下截图所示,您将看到三个不同的报告,它们提供了管道的见解:

图 7.6 – 管道分析报告
每个报告提供不同的信息,这些信息是随时间聚合的,并且可以过滤以显示过去的 7 天、14 天、30 天或 180 天。如前面的截图所示,提供以下报告:
-
管道通过率:此报告显示管道执行的成功或失败随时间的变化
-
测试通过率:此报告显示单元测试随时间的结果,能够显示所有可能的测试结果,如通过、失败和未结论
-
管道持续时间:此报告显示管道的总持续时间及按持续时间排序的前 10 个步骤,如以下截图所示:

图 7.7 – 管道持续时间报告
您可以使用这些报告来确保您的管道健康且及时执行。建议定期频繁地修订这些报告,例如每周一次,以确保没有意外添加任何对管道执行时间有不利影响的内容。
使用仪表板
监控管道的另一种方式是通过 Azure DevOps 项目概览部分的仪表板功能。您可以使用多个可用小部件创建自定义仪表板,这些小部件显示不同的数据点,这些数据点对每个团队成员在宏观层面上非常有用,并且可以轻松快捷地访问。
Azure DevOps 包含三个开箱即用的 Azure Pipelines 小部件:
-
构建历史,它添加了一个瓷砖,显示构建的直方图,指示成功或失败,并提供每个构建的链接
-
部署状态,它添加了一个瓷砖,显示多个环境中部署状态和测试通过率的综合视图
-
发布管道概览,该功能添加了一个瓷砖,允许您查看和跟踪发布管道的状态
以下截图显示了一个名为管道的自定义仪表板,其中包含我们刚才讨论的所有小部件,显示来自不同管道的信息:

图 7.8 – 带小部件的自定义管道仪表板
你可以在learn.microsoft.com/en-us/azure/devops/report/dashboards/widget-catalog了解更多关于默认提供的小部件目录,并且你可以通过在 Visual Studio Marketplace 中搜索它们来找到更多小部件,搜索链接为marketplace.visualstudio.com/search?term=widgets&target=AzureDevOps。
重要说明
另一个有效的监控管道的方式是通过Azure Pipelines Microsoft Teams 应用市场扩展,你可以在市场目录中找到它;点击后,它将带你进入Microsoft App Source商店。此应用是一个 Teams 应用,并且会安装在你的 Teams 租户中,安装过程超出了本书的范围。安装完成后,你可以配置订阅,以便在管道状态或审批时收到通知。
现在,让我们学习如何监控管道代理。
监控管道代理
在 Azure DevOps 中,管道代理提供了一些通用的报告功能。你可以通过点击组织设置来访问它们:

图 7.9 – 访问组织设置
一旦进入组织设置,你就可以在导航菜单中的管道部分访问代理池选项:

图 7.10 – 代理池
让我们逐一了解可用的报告。
作业运行
你可以使用每个代理池的作业运行报告,它将显示正在执行的作业的摘要,包括它们的 ID、管道名称、项目、代理规格、排队时间、等待时间和持续时间:

图 7.11 – 代理池中的作业报告
此报告中最相关的数据之一是等待时间,如前面的截图中所突出显示的。这是需要密切关注的指标。如果这个数字在作业之间开始增加,可能是需要购买并发性并添加额外代理的信号。
本节稍后会介绍如何购买并发性以及增加代理数量的方法。现在,让我们继续查看可用的报告。
代理状态
在代理池详情中,你会看到一个代理标签页,显示了每个正在运行的代理的详细信息,例如其名称、可用性、最后运行、当前状态、版本,并且可以启用/禁用它。
以下截图显示了一个不可用的或离线代理:

图 7.12 – 带离线代理的代理池
以下截图显示了一个可用的或在线代理:

图 7.13 – 具有在线代理的代理池
在使用代理池时,必须确保自托管的代理处于在线并启用状态。否则,如果没有可用的代理,管道作业将被排队并永远不会执行。
专业提示
始终设置代理作为服务运行。这将利用操作系统的服务管理器来确保代理的生命周期得到适当管理,同时在自动升级代理时也能改善体验。
代理作业
从之前的代理状态报告中,你还可以查看特定代理的作业报告,如下图所示:

图 7.14 – 代理作业报告
这对于判断某个特定代理是否存在异常行为或在运行作业时偶尔失败非常有用。在这种情况下,可能需要对代理进行版本升级,检查是否需要在代理中安装依赖工具,或者作为最后的手段,应该移除该代理及其运行所在的基础设施,并用新的代理替换它。
现在,让我们来看看代理池最重要的报告之一:分析报告。
分析报告
分析报告可以帮助我们了解代理池中代理的聚合使用情况,图表中显示了并发、排队作业和运行作业的情况,如下图所示:

图 7.15 – Azure Pipelines 代理池的分析报告
你会注意到该报告包含两个直方图——一个是公共托管并发,另一个是私人托管并发。公共并发指的是 Azure Pipelines 为公共项目提供的并发配额,默认为 10,并且无法更改。类似地,私有并发是针对私有项目的,你可以额外购买并发作业,或者利用每个 Microsoft Visual Studio Enterprise 订阅中包括的自托管管道代理。
该报告帮助我们了解何时需要更多代理,特别是当多个作业排队时。
如果作业排队的情况不频繁出现,可以忽略它,但当这种情况变得越来越常见时,我们必须考虑以下方面:
-
购买并发
-
增加可用代理
让我们首先讨论购买并发。
购买并发
向代理池添加并发适用于 Microsoft 托管和自托管代理,决定是否增加并发取决于你是否希望作业执行之间没有等待时间。这可以通过以下步骤实现:
- 首先,按照下图所示,在组织层面设置账单:

图 7.16 – 为 Azure DevOps 组织设置账单
- 点击设置计费按钮将打开一个对话框,你可以在其中将你的 Azure DevOps 组织与 Azure 订阅关联,这个订阅用于支付 Azure DevOps 中的服务费用。如果你有 Azure 订阅访问权限,选择它并点击保存:

图 7.17 – 选择现有的 Azure 订阅用于计费
需要提到的是,你登录的 Active Directory 租户用于查找你可以访问的 Azure 订阅并将其链接到组织。你必须是项目汇总管理员组的成员才能完成此步骤。
- 如果你没有可用的 Azure 订阅,你将看到类似以下的消息:

图 7.18 – 为计费添加新的 Azure 订阅
- 然后,你可以点击新建 Azure 订阅按钮,完成创建新订阅的步骤,并提供信用卡信息用于购买的计费。一旦计费配置完成,你就可以通过进入项目设置中的并行作业选项来购买并发执行。你可以根据需要增加/减少并行作业的数量:

图 7.19 – 购买并行作业
现在我们知道如何购买并发执行,我们来讨论如何增加可用代理的数量。
增加可用代理
对于微软托管的代理来说,购买额外的并发执行就是立即获得执行多个作业的能力所需要的,因为无需管理基础设施。
对于自托管代理,你有一些选项:
-
在第一章的设置代理池部分中讨论了一个选项,它需要手动将一个代理添加到代理池中。
-
另一个选项是使用Azure 虚拟机规模集池类型,这是一个在添加新代理池时可用的选项,如下图所示。这种类型的代理池会在需要时自动添加额外的代理,方法是每隔 5 分钟监控当前代理的状态和队列中作业的数量。你可以配置最大代理数量以及其他控制每个代理在池中处理方式的参数:

图 7.20 – Azure 虚拟机规模集代理池类型
Azure 虚拟机规模集代理池类型对于以下原因非常有用:
-
当你需要更多资源(例如 CPU 和内存)来执行作业,而不是使用微软托管的代理且你又不想管理底层基础设施时
-
为了简化这些代理的基础镜像如何维护,或每次执行任务后是否需要重新镜像代理,代理池类型将优雅地处理这些问题。
-
最后一个需要考虑的选项是在 Docker 容器中运行自托管代理,这使你可以在任何容器编排器中运行,例如你自己的自管理 Kubernetes 集群或云托管服务,如Azure Kubernetes Service(AKS)和 Amazon Elastic Kubernetes Service(EKS)。在这种情况下,你需要一个自定义实现,以根据 Azure DevOps REST API 中可用的指标来扩展代理的数量。
现在,我们已经学会了如何监控作业运行、代理状态和任务性能,并增加并发和代理数量,让我们了解如何在管道中使用监控。
使用监控来衡量应用程序质量
Azure Pipelines 提供了许多功能来衡量构建和发布管道的成功。首先,我们将学习如何通过衡量代码质量指标来提高单元测试结果的成功率。
代码质量指标
构建一个全面的单元测试管道包括分析单元测试运行框架产生的每个数据点以及在管道中执行这些任务的任务。然而,通常情况下,任务用来确定失败的指标存在局限性,除了实际执行的单元测试之外,其他因素可能也会被考虑在内。
例如,假设一个开发团队最近为一个已经开发多年的项目添加了单元测试,并且他们只是开始实施测试以实现自动化,减少手动测试的需求。
在这种情况下,通用做法是从少量自动化测试开始,并逐步增加测试数量。强制执行这一点的唯一方法是持续监控单元测试结果,并设置自动化门禁进行评估,以确保每次运行时测试数量都会增加。
假设你已经完成了前一章,接下来的任务将通过将其添加到单元测试构建管道的 YAML 文件末尾来实现此场景:
- task: BuildQualityChecks@8
inputs:
checkCoverage: true
coverageFailOption: 'build'
coverageType: 'blocks'
forceCoverageImprovement: true
coverageUpperThreshold: '80'
你可以在以下链接找到完整文件:github.com/PacktPublishing/Implementing-CI-CD-Using-Azure-Pipelines/blob/main/ch07/azure-pipelines.yml
让我们分解一下,理解这个任务的作用:
-
checkCoverage: true选项启用需要代码覆盖率结果存在的策略。 -
接下来,
coverageFailOption: 'build'表示如果与上次构建相比,代码覆盖率没有增加,构建将会失败。 -
coverageType: 'blocks'表示分析将基于代码块的数量进行。其他选项包括lines、branches和custom。 -
forceCoverageImprovement: true将强制要求代码覆盖率指标的值始终高于上次运行的值。 -
最后,
coverageUpperTreshold: '80'是代码覆盖率提升的上限阈值。通常,你不会追求 100% 的代码覆盖率,因为这意味着每一行代码都有相关的测试,而在非常大的应用中,这可能不现实,因为这需要更多的开发时间。一旦达到此值,就不再强制要求进一步改进。
将此添加到之前配置的UnitTests-YAML管道,并且不做额外的更改,将导致执行失败,因为没有添加测试来增加代码覆盖率指标:

图 7.21 – 质量检查失败的构建
现在我们已经学习了如何使用代码质量指标来增强我们的管道,让我们看看如何改进部署。
部署健康
CI/CD 允许你自动化部署过程的每个方面,包括在应用部署后验证目标环境中的应用。这种场景提供了一种机制,确保不需要人工干预来验证新版本的应用是否按预期工作,且开发人员或环境配置未引入新的错误或漏洞。
让我们先看一个简单的场景。在这里,我们将考虑上一章中讨论过的 Jenkins Artifacts 发布管道,其中我们从 Jenkins 作业中部署了一个构件。
我们没有显式添加一个步骤来验证预期的artifact.txt文件是否已被复制并提供给代理。可以通过添加带有自定义脚本的命令行任务来解决这个问题,正如下面的截图所示:

图 7.22 – 验证发布管道中的任务
上面截图中的脚本适用于 Ubuntu 代理,并验证_PacktFamily目录中的artifact.txt文件是否存在;如果不存在,它将打印一条消息,指示文件未找到,并且将以返回代码1退出。由于任务始终期望返回代码0来表示成功,因此这将被视为一个错误。
我们来看另一个场景,比如部署一个 Web 应用或 Web API,在这种情况下,你可以编写脚本,发出 HTTP/HTTPS 请求到应用,等待响应,并验证响应代码和内容。
一个更好的场景是使用 UI 自动化测试框架,并在应用部署后将其作为发布管道的一部分执行,就像我们在上一章中探讨的单元测试一样。以下是一些可以考虑的 UI 自动化测试框架:
-
开源:
-
Appium:
github.com/appium/appium -
Robot Framework:
robotframework.org/ -
Selenium:
www.selenium.dev/
-
-
第三方:
-
Cypress:
www.cypress.io/ -
Sauce Labs:
saucelabs.com/ -
Telerik Test Studio:
www.telerik.com/teststudio
-
在更高级的场景中,您可以在 Azure Pipelines 中使用 gates,这使您能够引入自动化的控制点,根据所使用的任务评估已定义的条件。在使用发布管道时,gates 可用作部署前和部署后的条件,但在使用带有环境的多阶段管道时,gates 仅作为附加到环境的后置条件可用。
接下来,我们将通过 Azure Monitor 探索其中一个部署 gate。
与 Azure Monitor 的集成
Azure Monitor 是一个用于收集、分析和响应来自云端和本地环境日志与指标的监控解决方案。这可以帮助你了解应用程序和服务的性能,并提供手动或程序化响应需要关注的条件的能力,确保这些应用程序按预期工作。
Azure Pipelines 中的集成功能是通过一个 AzureMonitor 任务提供的,它允许你查询活动警报的规则,并确定应用程序新版本的部署是否触发了新的警报。
在本节中,您将使用一个现成的发布管道模板,轻松配置 Azure Monitor 任务。
为此,请执行以下步骤:
- 创建一个新的发布管道,如下图所示:

图 7.23 – 创建新的发布管道
- 在搜索字段中找到
monitor,然后点击 应用 按钮:

图 7.24 – 从模板创建新的发布管道
- 您将得到一个阶段,如下图所示,您必须填写 App Service 名称、应用程序洞察的资源组名称 和 应用程序洞察资源 名称 字段:

图 7.25 – 带有持续监控的 Azure App Service 部署
- 最重要的一步是设置
Availability_$(Release.DefinitionName)、FailedRequests_$(Release.DefinitionName)、ServerResponseTime_$(Release.DefinitionName)和ServerExceptions_$(Release.DefinitionName),并设定一些默认阈值。您可以使用这些默认值,调整它们,或者根据应用程序的需求创建新的警报定义:

图 7.26 – 应用洞察警报
- 配置好这个阶段后,你可以切换到管道视图并点击部署后条件来配置门控,在这种情况下,应该已经启用了查询 Azure Monitor 警报:

图 7.27 – 阶段中的发布管道部署后条件
- 然后,你可以根据需要调整与部署门控相关的许多设置,包括所需的Azure 订阅和资源组名称,尤其是评估前的延迟:

图 7.28 – 查询 Azure Monitor 警报门控
- 配置完成后,你可以依赖 Azure Pipelines 在部署步骤完成后执行门控,以验证监控警报是否已配置,并提供一个可视化指示器:

图 7.29 – 使用部署门控的成功发布
在完整的场景中,你需要将自动化测试与此发布管道结合起来,确保监控警报基于对最近部署的应用程序执行的测试进行评估。
-
最后,你可以使用与之相应的第三方应用性能监控工具,并通过相应的市场扩展进行配置:
现在你已经了解了部署门控,我们已经进入本章的总结部分。让我们回顾一下到目前为止所学的内容。
总结
在本章中,你学习了关于在 CI/CD 项目中需要考虑的监控概念,以及如何监控你的管道任务、任务性能,如何使用图形小组件构建仪表板来理解随时间变化的行为,甚至如何与协作工具集成以获取实时通知。你还学习了如何监控作业运行、任务性能和代理,何时购买并发性,以及如何增加代理数量以确保管道及时执行。最后,你学习了如何通过利用代码质量指标、应用程序运行时检查和应用程序监控工具来衡量管道中的质量。
在下一章中,我们将学习如何使用 Azure Pipelines 自动化部署基础设施。
第八章:8
使用基础设施即代码进行基础设施配置
之前我们讨论了与构建、测试、打包和部署应用程序相关的 CI/CD 主题。在本章中,我们将学习如何使用自动化来配置部署目标及其配置过程,理解这种过程的好处,并在过程中提供一些提示和技巧。你将明白为什么在当前快速交付且要求高质量的时代,这一点至关重要,甚至是必需的。
在本章中,我们将深入探讨以下主题:
-
理解基础设施即代码(IaC)
-
使用Azure 资源管理器(ARM)模板
-
使用AWS CloudFormation
-
使用Terraform
让我们首先处理技术要求。
技术要求
根据你在本章中感兴趣的部分,你需要在工作站上安装以下软件。你可以在 GitHub 仓库 github.com/PacktPublishing/Implementing-CI-CD-Using-Azure-Pipelines/tree/main/ch08 找到本章的代码。
安装 Azure 工具
Azure CLI 是一个跨平台命令行工具,用于连接 Microsoft Azure 并执行命令来创建、更新或销毁资源。根据你工作站的操作系统(OS),你可以选择适合的安装方法,详情请见 learn.microsoft.com/en-us/cli/azure/install-azure-cli。由于每个操作系统的安装指南不同,具体安装过程需要你自己完成。
安装完成后,运行 az version 命令,你会看到类似这样的响应:
PS C:\Users\user> az version
{
"azure-cli": "2.48.1",
"azure-cli-core": "2.48.1",
"azure-cli-telemetry": "1.0.8",
"extensions": {}
}
你可以选择任何你喜欢的文本编辑器来工作。然而,我们推荐使用Visual Studio Code(简称VS Code),因为它是社区中最流行的编辑器之一;它是免费的,并提供了大量社区支持的扩展,支持多种编程语言和工具,尤其是支持ARM 模板、AWS CloudFormation和Terraform,正如我们在接下来的章节中将看到的那样。
要安装 VS Code,请前往 code.visualstudio.com/。在那里,你将看到根据你工作站的操作系统安装的选项。
此外,你还需要从 marketplace.visualstudio.com/items?itemName=msazurermtools.azurerm-vscode-tools 安装 ARM Tools VS Code 扩展。
安装 AWS 工具
AWS CLI 是一个跨平台的命令行工具,用于连接 Amazon Web Services(AWS)并执行命令来创建、更新或销毁资源。根据你的操作系统,安装指南可以在docs.aws.amazon.com/cli/latest/userguide/getting-started-install.xhtml找到。
安装完成后,在终端运行aws --version命令,你将看到类似以下的响应:
PS C:\Users\user> aws --version
aws-cli/2.11.18 Python/3.11.3 Windows/10 exe/AMD64 prompt/off
此外,你还必须安装 AWS Toolkit 的 VS Code 扩展,下载地址为marketplace.visualstudio.com/items?itemName=AmazonWebServices.aws-toolkit-vscode。
安装 Terraform 工具
Terraform CLI 是一个跨平台的命令行工具,用于执行各种子命令,如plan、apply或destroy,这些我们将在本章后续部分介绍。你可以按照developer.hashicorp.com/terraform/tutorials/azure-get-started/install-cli#install-terraform中的说明进行安装。安装完成后,在终端运行terraform version,你将看到类似以下的响应:
PS C:\Users\user> terraform version
Terraform v1.4.6
on windows_amd64
此外,你还可以从marketplace.visualstudio.com/items?itemName=HashiCorp.terraform安装 HashiCorp Terraform 的 VS Code 扩展。
安装 Terraform Marketplace 扩展
必须安装 Terraform Marketplace 扩展,适用于 Azure DevOps。你可以在marketplace.visualstudio.com/items?itemName=ms-devlabs.custom-terraform-tasks找到它。
访问 Azure 账户
你必须拥有一个 Azure 账户才能完成本章中的步骤。如果你没有账户,可以在azure.microsoft.com/en-us/free/创建一个免费的账户。
访问 AWS 账户
你必须拥有一个 AWS 账户才能完成本章中的步骤。如果你没有账户,可以在aws.amazon.com/free创建一个免费的账户。
现在我们已经处理了所有技术要求,接下来让我们一步步了解如何使用代码自动化基础设施。
理解基础设施即代码(IaC)
过去,基础设施通常是通过手动配置和记录的步骤以及/或脚本组合来完成的。这使得整个过程容易出错且速度较慢。
就像你对应用程序代码使用严格的流程一样,你也应该对基础设施采取相同的做法。这样做的目的是使部署过程可重复、不可变,减少错误的机会,并通过尽可能避免/消除人工干预,加速部署过程。
IaC 是一种将描述模型编写为代码并存储在源控制中的实践,该模型定义并部署运行应用程序和任何支持依赖项所需的所有基础设施。它可以包含网络配置、负载均衡器、虚拟机以及应用架构运行所需的任何其他应用程序或数据服务,适用于本地数据中心和云平台。
最好将这一切工作流程想象成如下所示:

图 8.1 – 融合 IaC 的 CI/CD
让我们现在开始使用 ARM 模板,并看看如何在 Microsoft Azure 云平台上进行操作。
使用 ARM 模板
ARM 模板是 Azure 中可用的 IaC 部署基础设施的选项之一,Microsoft 的云平台在全球多个地区提供。
微软还提供了其他工具,如 Azure CLI、Azure PowerShell 和一种更新的、特定领域的语言 Bicep,Bicep 使用声明式语法来部署资源。你还可以使用 Azure 门户,一个基于 Web 的 UI,提供访问你在 Azure 中的所有资源的功能,并允许你创建、更新和删除资源。
ARM 模板是具有以下结构的 JSON 文件:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "",
"apiProfile": "",
"parameters": { },
"variables": { },
"functions": [ ],
"resources": [ ],
"outputs": { }
}
ARM 模板可以定义必需和可选的输入参数、可以计算供模板中使用的变量、用户定义的函数(除了内置函数外,还可以在模板中使用),资源(定义要配置的一个或多个资源的所有属性)以及输出(可以包含从已部署资源中计算出来的属性或值)。ARM 模板还可以嵌套,以逻辑上分离你的服务。
在本节中,我们将专注于如何使用 Azure Pipelines 部署 ARM 模板,而不涉及如何创建它们的细节,因为这超出了本书的范围。
部署 ARM 模板的步骤如下:
-
在 Azure 中创建服务主体
-
创建 Azure 的服务连接
-
创建 ARM 模板
-
验证 ARM 模板
-
部署 ARM 模板
让我们从在 Azure 中创建服务主体开始。
在 Azure 中创建服务主体
服务主体是 Azure 中的一种身份类型,由应用程序、服务和自动化工具使用,用于提供精细化控制,以根据角色访问资源并执行操作。
重要提示
本节假设你已经使用 az login 命令登录到 Azure。
我们可以通过以下 Azure CLI 命令创建一个服务主体:
az ad sp create-for-rbac -n azure-pipelines --role Contributor --scopes /subscriptions/<subscription-id>
你需要替换 <subscription-id>,它应该是来自 Azure 门户的类似 GUID 的值。将作用域设置为订阅级别在测试过程中是可以的,但在理想的设置中,你可能希望进一步限制它——比如说,限制到一个资源组:
/subscriptions/<subscription-id>/resourceGroups/<name>
一旦你执行这个命令,你应该会得到类似于以下的响应:

图 8.2 – 服务主体详情
您必须将服务主体详情视为密钥。这是敏感信息,提供对您的 Azure 环境的访问权限。您将需要这些信息来完成接下来的步骤。
现在,让我们看看如何使用服务主体创建 Azure 服务连接。
创建 Azure 服务连接
正如前几章所见,Azure Pipelines 中与外部服务集成需要一个服务连接。您可以通过以下步骤完成此操作:
- 导航到项目设置 | 管道 | 服务连接,然后点击新建服务 连接按钮:

图 8.3 – 新建服务连接
- 在此,您将选择Azure 资源管理器选项并点击下一步按钮:

图 8.4 – 选择服务连接类型
- 接下来,选择认证方法,使用服务主体(手动)选项,并点击下一步按钮:

图 8.5 – 为 Azure 服务连接选择认证方法
尽管上面的截图显示服务主体(自动)作为推荐,但这仅适用于刚开始使用 Azure 和 Azure Pipelines 的用户。当您有多个 Azure 订阅和资源组时,这种认证方法会使设置过程变得困难。
-
下一步让您输入以下参数的详细信息:
-
来自上一节的
appId,如图 8.2所示 -
来自上一节的
password,如图 8.2所示 -
来自上一节的
tenant,如图 8.2所示 -
azure-packt-rg
一旦输入了所有这些值,您可以选择点击验证并保存按钮,这将测试是否可以建立到 Azure 的连接,并保存服务连接的详细信息。
-
现在我们已经有了服务连接,让我们继续创建一个 ARM 模板。
创建 ARM 模板
有许多方法可以创建 ARM 模板:
-
通过 Azure 门户,您可以在部署前从市场或现有资源组下载模板,具体操作可参考
learn.microsoft.com/en-us/azure/azure-resource-manager/templates/export-template-portal -
修改现有的示例模板,链接:
learn.microsoft.com/en-us/samples/browse/?expanded=azure&products=azure-resource-manager
在本章中,为了简便,我们将使用一个修改版的现有示例,该示例部署了一个 Azure 应用服务资源。您可以在此链接找到模板:github.com/PacktPublishing/Implementing-CI-CD-Using-Azure-Pipelines/blob/main/ch08/azure/azuredeploy.json
此 ARM 模板将部署两个资源:
-
一个 Azure 应用服务计划,它定义了定价层、操作系统以及其他平台级功能。
-
一个 Azure 应用服务 web 应用,它定义了应用程序级堆栈,如 PHP 运行时及版本。
现在让我们看看如何使用 Azure Pipelines 验证这个模板。
验证 ARM 模板
在您的构建或 CI 管道中,您应该考虑验证您的模板,以确保格式正确。为此,提供了 ARM 模板部署任务,如下所示的代码片段:
# ARM Template Validation
trigger:
- main
pool:
vmImage: ubuntu-latest
steps:
- task: AzureResourceManagerTemplateDeployment@3
inputs:
deploymentScope: 'Resource Group'
azureResourceManagerConnection: 'azure-packt-rg'
subscriptionId: $(AzureSubscriptionId)
action: 'Create Or Update Resource Group'
resourceGroupName: 'packt'
location: 'East US'
templateLocation: 'Linked artifact'
csmFile: 'azure/azuredeploy.json'
deploymentMode: 'Validation'
重要提示
YAML 是一种非常严格的语言,区分空格和大小写。在处理 YAML 文件时,请确保使用一个能正确处理这些要求的编辑器,并确保在格式化内容时注意任何潜在问题。
让我们将其拆解为不同的参数:
-
deploymentScope决定了此操作适用的层级。可能的值有Management Group、Resource Group和Subscription。这些是 Azure 平台中的不同治理层次,模板遵循不同的架构。 -
azureResourceManagerConnection是指向现有AzureResource Manager类型服务连接的引用。 -
subscriptionId是 Azure 订阅 ID 的 GUID 值。在这种情况下,您可以通过$(AzureSubscriptionId)表示法看到它作为变量引用。我们将在下一节中看到如何创建这个,创建一个 管道变量。 -
action表示将创建、更新或删除资源组。 -
resourceGroupName是 Azure 目标中的资源组名称。如果操作设置为create或update且资源组不存在,它将由任务创建。 -
location是任何现有的 Azure 区域,可用于部署。 -
templateLocation表示 ARM 模板文件是作为Linked artifact还是URL of the file提供。在后者情况下,它必须是一个完全合格的 URL。 -
csmFile是指向 ARM 模板文件的路径,当templateLocation = 'Linked artifact'设置时是必需的。否则,您将使用csmFileLink。 -
最后,
deploymentMode表示如何处理部署。在这种情况下,Validation值将仅执行文件格式验证。我们将在下一节讨论此属性接受的其他值。
创建流水线变量
要创建此任务中使用的变量,请执行以下任务:
- 点击 Azure Pipelines 编辑页面上的变量按钮:

图 8.6 – 访问流水线变量
- 点击新变量按钮定义您的第一个变量:

图 8.7 – 添加新变量
- 然后,您继续分别填写
AzureSubscriptionId和从 Azure 门户获取的订阅 ID。同时确保勾选保持此值为秘密选项,以便安全地存储它并确保在流水线执行过程中无法看到它。在此页面上点击确定会暂时存储该值:

图 8.8 – 带有秘密值的新变量
- 在下一个页面上,您必须点击保存,以确保变量已存储在流水线中:

图 8.9 – 保存流水线变量
一旦完成所有设置,流水线应该能够成功运行并验证 ARM 模板。
重要提示
在第一次部署 ARM 模板时,必须确保相应的 Azure 资源提供程序已经在订阅中注册,否则会出现错误。对于本节中使用的模板,必须注册 Microsoft.Web 资源提供程序。您可以通过运行以下 Azure CLI 命令并等待其完成来实现此操作:
az provider register –namespace Microsoft.Web --wait
现在我们已经了解了如何验证模板,接下来让我们看看如何部署它。
部署 ARM 模板
从验证模式切换到部署模式需要将 deploymentMode 属性的值更改为 Incremental 或 Complete。
Incremental 部署模式告诉 ARM,如果模板中的资源不存在,则会创建这些资源;如果已存在,则会更新这些资源以匹配模板。资源组中未在模板中定义的其他资源将被忽略。
同样,如果使用 Complete 部署模式,ARM 会确保资源组仅包含模板中定义的资源,创建不存在的资源,更新现有资源以匹配,并删除模板中未定义的资源。
下面是它的样子:
# ARM Template Deployment
trigger:
- main
pool:
vmImage: ubuntu-latest
steps:
- task: AzureResourceManagerTemplateDeployment@3
inputs:
deploymentScope: 'Resource Group'
azureResourceManagerConnection: 'azure-packt-rg'
subscriptionId: $(AzureSubscriptionId)
action: 'Create Or Update Resource Group'
resourceGroupName: 'packt'
location: 'East US'
templateLocation: 'Linked artifact'
csmFile: 'azure/azuredeploy.json'
deploymentMode: 'Incremental'
重要提示
Complete 部署模式必须谨慎使用,并确保您有严格的流程,确保资源仅通过模板创建。否则,可能会导致意外的破坏性结果,例如服务或应用程序停止工作或数据丢失。
一旦管道执行完毕,您可以在 Azure 门户中验证资源:

图 8.10 – 通过 Azure Pipelines 部署的 Azure 资源
如果您想了解更多信息,请访问aka.ms/arm-syntax。现在,让我们看看如何在 AWS 中做类似的事情。
使用 AWS CloudFormation
AWS CloudFormation是一项服务,它允许您定义一个模板,描述一组将一起部署的资源,使用 JSON 或 YAML 语法。这些模板遵循 JSON 格式的结构:
{
"AWSTemplateFormatVersion" : "version date",
"Description" : "JSON string",
"Metadata" : { template metadata },
"Parameters" : { set of parameters },
"Rules" : { set of rules },
"Mappings" : { set of mappings },
"Conditions" : { set of conditions },
"Transform" : { set of transforms },
"Resources" : { set of resources },
"Outputs" : { set of outputs }
}
使用 AWS CloudFormation 部署包括以下步骤:
-
使用 AWS CLI 创建 IAM 用户
-
创建到 AWS 的服务连接
-
创建 AWS CloudFormation 模板
-
验证 AWS CloudFormation 模板
-
部署 AWS CloudFormation 模板
让我们从讨论如何创建 IAM 用户开始。
使用 AWS CLI 创建 IAM 用户
IAM 用户是定义在 AWS 的身份和访问管理(IAM)服务中的用户,并提供精细化的访问控制,用于创建、更新或删除 AWS 中的资源,也用于授予/拒绝与其他服务交互的权限。
重要说明
本节假设您已经使用aws configure命令配置了您的 AWS 凭据。
执行以下命令:
aws iam create-group --group-name resources-admin
aws iam attach-group-policy --group-name resources-admin --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
aws iam create-user --user-name azure-pipelines
aws iam add-user-to-group --group-name resources-admin --user-name azure-pipelines
aws iam create-access-key --user-name azure-pipelines
这些命令将执行以下操作:
-
创建一个名为
resources-admin的用户组 -
为用户组附加安全策略
-
创建一个名为
azure-pipelines的用户 -
将
azure-pipelines用户添加到resources-admin用户组 -
为
azure-pipelines用户创建访问密钥
执行这些命令后,您应该会看到类似于以下截图的输出:

图 8.11 – azure-pipelines用户的 AWS 访问密钥
AccessKeyId和SecretAccessKey值将在下一步中使用。请确保将它们保存在安全的地方,因为它们提供了对 AWS 的编程访问权限。
重要
在此步骤中使用的arn:aws:iam::aws:policy/AdministratorAccess策略权限非常宽松。它提供了最高级别的 AWS 控制台访问权限。这不推荐在您的环境中使用。相反,您应该始终按照最小权限原则提供访问,并在需要时增加更多权限。
现在让我们看看如何从 Azure Pipelines 创建到 AWS 的服务连接。
创建到 AWS 的服务连接
如前几章所示,Azure Pipelines 中的外部服务集成需要服务连接,这可以在项目设置|管道|服务连接中完成,在此您将点击新建服务连接按钮并选择AWS选项:

图 8.12 – AWS 服务连接类型
下一步允许您输入以下详细信息:
-
the AccessKeyId value来自前一步 -
the SecretAccessKey value来自前一步 -
aws-packt
现在我们已经建立了服务连接,接下来让我们定义 AWS CloudFormation 模板。
创建 AWS CloudFormation 模板
你可以按照参考文档从头创建这些模板。可以从示例模板开始,或使用 AWS CloudFormation Designer,这是一个图形化工具,帮助你创建、可视化并修改模板,无需担心格式问题。欲了解更多信息,请访问 docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-guide.xhtml。
在本章中,我们将使用一个修改版的现有示例,该示例通过 弹性计算云 (EC2) 服务部署虚拟机。你可以在 github.com/PacktPublishing/Implementing-CI-CD-Using-Azure-Pipelines/blob/main/ch08/aws/template.json 找到该模板:
-
一个 EC2 密钥对 —— 用于连接到 Linux 实例的安全凭证集
-
一个 EC2 实例,依赖于密钥对,并使用 Amazon Linux OS 基础镜像
现在,让我们来看看如何验证模板。
验证 AWS CloudFormation 模板
在你的构建或 CI 管道中,你应该考虑验证你的模板,以确保格式正确。为此,AWS CLI 任务是可用的:
# AWS Cloud Formation Validation
trigger:
- main
pool:
vmImage: ubuntu-latest
steps:
- task: AWSCLI@1
inputs:
awsCredentials: 'aws-packt'
regionName: 'us-east-1'
awsCommand: 'cloudformation'
awsSubCommand: 'validate-template'
awsArguments: '--template-body file://template.json'
让我们来逐步分析代码中的参数:
-
awsCredentials是对服务连接的引用。 -
regionName是任何可用的 AWS 区域。通常,你会将其设置为与模板部署所在的相同区域,以确保根据该区域中服务的可用性进行正确验证。 -
awsCommand是 AWS CLI 中的顶级命令,提供 AWS CloudFormation 操作。 -
awsSubcommand表示你想执行模板验证操作。 -
awsArguments包含完成模板验证所需的选项。在这种情况下,由于文件被放置在仓库的根文件夹中,因此通过传递file:操作符读取文件内容并执行验证。
执行后,验证应该会显示如下截图中所示的消息:

图 8.13 – AWS CloudFormation 模板成功验证
如果出现错误,你会看到不同的消息和任务失败,如下图所示,其中故意输入错误的资源类型:
An error occurred (ValidationError) when calling the ValidateTemplate operation: Template format error: Unrecognized resource types: [AWS::EC2::KeyXXXPair]
现在我们知道如何验证模板了,让我们看看如何部署它。
部署 AWS CloudFormation 模板
部署 AWS CloudFormation 模板被称为创建 AWS CloudFormation 堆栈,它是 AWS 中的一项服务,允许您以逻辑方式对模板中的所有资源进行分组,并具有一些附加的好处,例如跟踪漂移、实施回滚策略以防出错,以及能够删除堆栈及其所有相关资源。
在 Azure Pipelines 中,您有两种选择来部署模板:
-
一个带有
aws cloudformationcreate-stack命令的 AWS CLI 任务 -
使用
Cloud Formation Create or Update Stack任务,如以下代码块所示:# AWS Cloud Formation Deployment trigger: - main pool: vmImage: ubuntu-latest steps: - task: CloudFormationCreateOrUpdateStack@1 inputs: awsCredentials: 'aws-packt' regionName: 'us-east-1' stackName: 'packt' templateSource: 'file' templateFile: 'template.json' capabilityIAM: false capabilityNamedIAM: false onFailure: 'DELETE'
让我们分解前面的代码:
-
awsCredentials是对服务连接的引用。 -
regionName是任何可用的 AWS 区域。 -
stackName是用于标识此堆栈的名称,必须在 AWS 控制台中唯一。 -
在这种情况下,
templateSource是file;但是,它也可以是指向模板的url,可能位于 Azure Pipelines 外部;s3,即 AWS 中的存储服务,您需要提供存储桶和对象键;或者usePrevious,表示您希望使用现有堆栈中的模板。 -
templateFile用于提供包含模板的文件位置,只有在file被设置为templateSource时才需要。 -
capibilityIAM和capabilityNamedIAM参数设置为false。这些是一些部署类型所需的附加属性,在这些部署中 IAM 改动会被应用。在本示例中使用的模板中,它们不是必需的。 -
最后,
onFailure属性指示如果出现问题时应如何处理堆栈。如果值为DELETE,堆栈将被删除,任何可能已经成功部署的资源也将被删除。值为DO_NOTHING会停止应用模板,您将能够在 AWS 控制台中看到到目前为止发生的情况。最后,ROLLBACK值是默认值,它会在应用模板之前回退任何更改。
部署成功完成后,您应该能够在 AWS 控制台中看到状态,如下图所示:

图 8.14 – AWS CloudFormation 堆栈成功部署
您还可以看到作为 CloudFormation 堆栈一部分部署的资源,如下图所示:

图 8.15 – AWS CloudFormation 堆栈资源
到目前为止,我们已经了解了针对 Azure 和 AWS 云平台特定的 IaC 功能;然而,这些仅适用于各自的平台。如果您希望以通用方式进行 IaC,并且面向多个目标类型——比如本地环境和其他云服务提供商,市场上还有其他工具可以完成这个任务。
其他一些基础设施即代码(IaC)工具包括 Ansible、Chef、Pulumi、Puppet、SaltStack 和 Terraform 等等。在本章中,我们将专注于Terraform,因为它是开源社区中最受欢迎的选择之一,因其多功能性和声明性特点。接下来让我们学习如何使用 Terraform 进行 IaC。
使用 Terraform
首先让我们了解 Terraform 是如何工作的,然后我们将学习如何在 Azure Pipelines 中使用它。
Terraform 是如何工作的?
Terraform 是一个工具,允许你编写 IaC 并使用领域特定语言为云资源和本地资源定义资源。它通过提供商封装受支持目标的资源定义。
下图展示了 Terraform 的高层架构:

图 8.16 – Terraform 架构
它通过以下三个步骤进行工作:
-
写入:你在模板中定义资源,以便在所需的目标上部署所有资源。可能有多个目标。
-
计划:Terraform 创建执行计划,以确定需要进行哪些更改以匹配定义,计算操作的顺序并理解资源依赖关系。这可能意味着创建资源、更新资源或销毁资源。
-
应用:一旦你同意计划,Terraform 就会按计划计算的顺序执行必要的操作。
Terraform 过程的一部分涉及计算目标的状态与计划的对比。这被称为Terraform 状态文件。该文件将包含关于资源、目标位置中的元数据以及它们的依赖关系的详细信息。
根据你使用的 Terraform 版本,你可能需要负责管理此文件的位置,或者使用内置功能来为你管理状态文件。
管理状态文件的位置在 Terraform 中至关重要。它可以通过后端配置存储在本地或远程。当你刚开始使用时,可能会使用本地后端;然而,随着配置的演进以支持多个环境,你将切换到远程后端。要了解更多关于远程后端的信息,请访问developer.hashicorp.com/terraform/language/settings/backends/remote。
重要说明
Terraform 按绝对值工作,这意味着它期望通过每次与当前状态文件进行比较来控制你在模板中定义的每个资源。这意味着环境中不应该在没有 Terraform 的情况下进行任何更改。否则,下次运行terraform apply命令时,这些更改将会丢失。
Terraform 有三个版本:
-
开源:免费、可下载,并且可以灵活地与你现有的源代码管理和 CI/CD 工具一起使用。
-
云:一种 SaaS 应用程序,允许你在稳定的远程环境中运行它,确保状态文件和机密的安全存储
-
企业版:允许你设置一个私有的 Terraform Cloud 实例或自托管的发行版,具有可自定义的资源限制和更严格的安全性。
现在让我们来看一下如何创建一个简单的 Terraform 模板。
创建一个 Terraform 模板
Terraform 模板通常采用被称为配置语法的伪 JSON 格式编写。所有文件都有 .tf 扩展名,通常你会有以下这些文件:
-
providers.tf:定义基础配置和要使用的提供程序 -
main.tf:模板的入口点——即你的起始点 -
variables.tf:定义将在模板中使用的值,这些值可以在规划/应用配置时被覆盖 -
outputs.tf:提取已部署资源的值
在本章中,我们将使用一组简单的模板,针对 Azure 云平台创建资源组,并使用 azurerm 后端。可以在 github.com/PacktPublishing/Implementing-CI-CD-Using-Azure-Pipelines/tree/main/ch08/terraform 查看模板。
有关 Terraform 配置语言的更多详细信息,请访问 developer.hashicorp.com/terraform/language。有关教程和培训资料,请访问 developer.hashicorp.com/tutorials/library?product=terraform。
现在我们已经学习了 Terraform 创建模板的基础知识,接下来让我们搭建一个管道来验证它们。
管理 Terraform 状态文件
在我们构建构建管道之前,需要确保 状态文件 已适当管理——在这种情况下,是远程管理。由于我们的模板将针对 Azure,因此我们需要为其设置相应的后端。
为此,你可以执行以下 Azure CLI 命令,登录后即可使用:
az group create --name tfstate --location eastus
az storage account create --name tfstate --resource-group tfstate --location eastus --sku Standard_LRS
az storage container create --name tfstate --account-name tfstate
这些命令执行以下操作:
-
在
eastus区域的 Azure 中创建一个名为tfstate的资源组 -
在
eastus区域的tfstate资源组中创建一个名为tfstate的 Azure 存储账户,使用Standard_LRS定价层 -
在
tfstate存储账户中创建一个名为tfstate的 Blob 容器
重要
上述命令中的存储账户名称必须在全球范围内唯一,因此你需要调整这些命令中的名称以及任何后续部分,以确保一切正常工作。
现在我们已经设置好了状态文件管理,接下来让我们搭建一个管道来验证模板。
验证 Terraform 模板
在你的构建或 CI 管道中,你应该考虑验证你的模板,以确保格式正确。为此,Terraform Marketplace 扩展提供了相关任务:
# Terraform pipeline
trigger:
- main
pool:
vmImage: ubuntu-latest
steps:
- task: TerraformInstaller@0
displayName: 'install'
inputs:
terraformVersion: 'latest'
- task: TerraformTaskV4@4
displayName: 'init'
inputs:
provider: 'azurerm'
command: 'init'
backendServiceArm: 'azure-packt-rg'
backendAzureRmResourceGroupName: 'tfstate'
backendAzureRmStorageAccountName: 'tfstate'
backendAzureRmContainerName: 'tfstate'
backendAzureRmKey: 'terraform.tfstate'
- task: TerraformTaskV4@4
displayName: 'validate'
inputs:
provider: 'azurerm'
command: 'validate'
现在,让我们解析一下这段代码:
-
TerraformInstaller@0任务会在代理中安装 Terraform CLI(如果需要的话)。如果你需要确保模板使用特定版本的工具,则需要此任务。微软托管的代理通常会安装一个版本的 Terraform,但如果你想使用较旧或较新的版本,这个任务将允许你使用所需的版本。 -
TerraformTask@4任务允许你运行任何 Terraform CLI 命令。对于第一个任务displayName: init,它将相应地执行terraform init命令,并带有以下参数:-
backendServiceArm表示要使用的 ARM 服务连接的名称 -
backendAzureRmResourceGroupName表示在 Azure 中将存储状态文件的资源组 -
backendAzureRmStorageAccountName表示将存储状态文件的 Azure 存储账户名称 -
backendAzureRmContainerName表示 Azure 存储账户中将存储状态文件的 blob 容器名称 -
backendAzureRmKey表示状态文件的名称
-
-
最后,最后一个任务
displayName: validate将执行terraform的validate命令。
一旦管道运行,验证应该会成功完成,你应该会看到类似以下的验证任务消息:

图 8.17 – 成功的 Terraform 模板验证
现在我们已经学会了如何验证模板,接下来让我们继续使用 Terraform 部署资源。
部署 Terraform 模板
如前所述,使用 Terraform 部署资源是一个两步过程,需要你执行 plan 和 apply 命令:
# Terraform pipeline
trigger:
- main
pool:
vmImage: ubuntu-latest
steps:
- task: TerraformInstaller@0
displayName: 'install'
inputs:
terraformVersion: 'latest'
- task: TerraformTaskV4@4
displayName: 'init'
inputs:
provider: 'azurerm'
command: 'init'
backendServiceArm: 'azure-packt-rg'
backendAzureRmResourceGroupName: 'tfstate'
backendAzureRmStorageAccountName: 'tfstate'
backendAzureRmContainerName: 'tfstate'
backendAzureRmKey: 'terraform.tfstate'
- task: TerraformTaskV4@4
displayName: 'plan'
inputs:
provider: 'azurerm'
command: 'plan'
environmentServiceNameAzureRM: 'azure-packt-rg'
- task: TerraformTaskV4@4
displayName: 'apply'
inputs:
provider: 'azurerm'
command: 'apply'
environmentServiceNameAzureRM: 'azure-packt-rg'
让我们解析一下这段代码:
-
TerraformInstaller@0任务会在代理中安装 Terraform CLI(如果需要的话) -
TerraformTask@4任务与displayName: init一起初始化 Terraform,正如上一节所解释的那样 -
TerraformTask@4任务与displayName: plan执行terraform plan命令,执行必要的比较并根据要进行的更改更新状态文件 -
TerraformTask@4任务与displayName: apply执行terraform apply命令,并在 Azure 中执行必要的更改
本章涵盖了很多内容。让我们最后总结一下我们学到的知识。
总结
在本章中,我们学习了如何使用不同的工具来创建、更新和删除 Microsoft Azure 和 AWS 云平台上的资源。
我们学习了如何创建、验证和部署 ARM 模板,了解了 Azure 中服务主体的作用,以及使用自动化进行部署的安全考虑。
我们还学习了 AWS CloudFormation 模板和堆栈,如何从 Azure Pipelines 创建和更新它们。同时,我们也了解了 AWS 与 Azure 有类似的安全模型,并且学习了凭证的安全影响。
最后,我们了解了 Terraform 作为一种抽象语言,用于定义本地和云平台(如 Azure 和 AWS)的基础设施即代码(IaC),以及如何在 Azure Pipelines 中验证模板并使用它部署资源。
无论你选择使用哪种 IaC 工具,它们都很重要,因为它们可以让你做到以下几点:
-
拥有一个可重复且不可变的部署过程
-
加速部署
-
减少或消除在 Azure Pipelines 中部署时的错误
-
轻松从问题中恢复
-
将更多时间花在应用程序代码上
在下一章中,我们将把我们所学的内容结合起来,进行端到端管道构建和应用程序打包,以便将它们部署到不同的 Microsoft Azure 云平台服务上。
第三部分:真实场景中的 CI/CD
最后,在本书的最后部分,我们将整合到目前为止所学的一切,创建通常在现实世界中遇到的端到端场景,使用云平台,并通过一些最佳实践进行演练。
本部分包含以下章节:
-
第九章,为 Azure 服务实现 CI/CD
-
第十章,为 AWS 实现 CI/CD
-
第十一章,通过使用 Flutter 自动化跨移动应用程序的 CI/CD
-
第十二章,导航 Azure Pipelines 中的常见陷阱与未来趋势
第九章:9
实现 Azure 服务的 CI/CD
在本章中,您将把迄今为止学到的所有知识应用于一个端到端的解决方案,部署一组应用程序,并将其从测试环境推广到生产环境。从最简单到最复杂的解决方案架构,本章展示了 Azure Pipelines 在处理 Azure 中应用程序的配置、部署和资源管理时的灵活性,无论涉及不同的服务和编程语言。
本章将涵盖以下主题:
-
介绍解决方案架构
-
构建和打包应用程序以及基础设施即代码(IaC)
-
创建环境
-
部署 Python 目录服务到Azure Kubernetes 服务(AKS)
-
部署 Node.js 购物车服务到Azure 容器应用(ACA)
-
部署 .NET 结账服务到Azure 容器实例(ACI)
-
将 Angular 前端应用部署到Azure 应用服务(AAS)
-
批准环境部署
让我们首先处理本章的技术要求。
技术要求
您需要准备好以下 GitHub 仓库的 URL,作为本章的基础:github.com/PacktPublishing/Implementing-CI-CD-Using-Azure-Pipelines/tree/main/ch09。
和上一章一样,您必须拥有一个 Azure 账户才能完成本章中的步骤;如果您没有账户,可以在azure.microsoft.com/en-us/free/创建一个免费的账户。
技术要求部分就到这里;现在我们开始本章内容。
入门
首先您需要做的是导入示例仓库;我们来开始吧。
导入示例仓库
您需要从 GitHub 导入本书所用的应用程序和 IaC 源代码,以便能够完成本章和下一章的端到端流水线。
您可以从Azure Repos | 文件部分开始操作,在屏幕顶部的仓库下拉菜单中点击,然后选择导入仓库选项,具体操作如截图所示:

图 9.1 – 导入仓库
在名称字段中输入github.com/PacktPublishing/Implementing-CI-CD-Using-Azure-Pipelines.git,然后点击导入按钮:

图 9.2 – 导入示例仓库
几分钟后,流程将完成,您将能够浏览仓库中的所有代码。您所需的一切都将在e2e目录中。
导入仓库后,我们来了解一下示例架构。
介绍解决方案架构
对于我们的示例架构,您将使用一个虚构的 Packt 商店,由四个不同的应用程序组成,代表了一个复杂的分布式架构,在这个架构中,使用不同编程语言的团队可以利用不同的 Azure 平台服务来交付他们的功能。
-
一个 Angular 前端应用程序,即商店的用户界面。
-
一个 Python 产品目录服务,作为 REST API 实现。
-
一个 Node.js 购物车服务,作为 REST API 实现。
-
一个 ASP.NET 结账服务,作为 REST API 实现。
以下解决方案图展示了一个网络商店的环境,其中每个应用程序独立地运行在不同的 Azure 服务中:

图 9.3 – 解决方案图
在本章稍后部分,您将为每个应用程序实施 Azure Pipeline,步骤如下:
-
构建和打包应用程序及其相应的 IaC。
-
将它们部署到测试环境中。
-
将它们部署到生产环境中,包括手动审批检查。
以下图示展示了 CI/CD 过程:

图 9.4 – CI/CD 端到端过程
重要提示
在本章中,应用程序中的代码细节将不会涉及,因为那与 CI/CD 无关。我们将重点介绍使 CI/CD 过程正常运行所需的 Azure Pipelines 的细节。
为了实现 CI/CD 过程,您将利用具有环境和模板的多阶段流水线:
-
阶段允许我们以逻辑方式封装所有需要一起发生的工作,并控制依赖关系,同时还提供并行执行工作的能力;这将帮助我们在需要时减少总时间。
-
环境与作业相关联,并允许我们添加额外的控制,例如本例中的手动审批,以确保部署只有在人工干预并批准后才会继续到生产环境。
-
模板在第一章中介绍;在这里,您将实践它们,展示如何使用模板来构建模块化和可重用的流水线。
让我们来看一下以下流水线定义;在您导入的Implementing-CI-CD-Using-Azure-Pipeline仓库中的ch09/azure/azure-pipeline.yml文件中创建此文件:
# Multi-Stage pipeline
trigger:
- main
pool:
vmImage: ubuntu-latest
stages:
- stage: build
displayName: Build
jobs:
- template: build-apps.yml
- template: build-iac.yml
- stage: deployTest
displayName: Deploy Test
dependsOn: build
jobs:
- template: deploy.yml
parameters:
envName: test
- stage: deployProduction
displayName: Deploy Production
dependsOn: deployTest
jobs:
- template: deploy.yml
parameters:
envName: production
让我们分解这段代码:
-
build阶段没有依赖关系,包含来自build-apps.yml和build-iac.yml文件的模板中的工作。稍后您将在构建和打包应用程序和 基础设施即代码(IaC)部分中查看这些内容。 -
deployTest阶段必须等待构建阶段完成,并将运行deploy.yml模板中的作业,传递一个envName参数,其值为test,以唯一标识此环境。 -
deployProduction阶段会等待deployTest阶段完成,并使用相同的deploy.yml模板,传递一个名为envName的生产环境值。
该管道定义展示了模板的灵活性,以及将需要执行的工作拆分成更小部分的能力,这为团队根据其责任专注于管道的不同阶段提供了一种方式。
文件添加到仓库后,作为新管道添加并将其重命名为 E2E-Azure。为了使一切顺利运行,您还需要添加一些安全配置。
如果您之前没有重命名过管道,请点击 最近运行的管道 屏幕右侧的子菜单,如下图所示,然后重命名它:

图 9.5 – 重命名管道
关于模板的小贴士
在使用 job.template 引用另一个文件中定义的模板时,请确保在引用该模板之前先创建该文件,并在文件中至少定义一个步骤。一个简单的实现方法是使用 script 任务运行 echo 命令,例如 echo hello。
现在让我们进入构建阶段。
构建和打包应用程序以及基础设施即代码(IaC)
该解决方案中的所有应用程序都支持容器化,这是一个标准的打包机制,包含所有操作系统依赖项,以使它们能够在多种不同的托管环境中运行,使得它们非常轻量且易于移植。
为了简化,仓库中包含一个 docker-compose.yml 文件,该文件便于处理由多个服务组成且必须同时运行的应用程序。
该文件定义了服务以及它们对应的 Dockerfile 的位置,Dockerfile 文件定义了容器的构建方式,以及其他一些内容,例如容器运行所需的端口或环境变量。
在本章中,SUB_ID 占位符是您可以访问的 Azure 订阅的 ID;请在适当的时候替换它。
在继续之前,您必须拥有一个可用的 Azure 容器注册表,供管道存储容器镜像。您可以使用以下 Azure CLI 命令轻松创建一个:
az acr create -n packtadocicd -g packt –sku Standard -l eastus –admin-enabled
我们还需要确保我们在 第八章 中 创建 AWS 服务连接 部分创建的服务主体具有管理此服务的用户访问权限;您可以通过在 PowerShell 窗口中运行以下命令来实现:
$id=az ad sp list –display-name azure-pipelines –query "[].id" -o tsv
az role assignment create –assignee-object-id $id –scope /subscriptions/build-apps.yml file, which will be used to build the application containers and push them to the Azure Container Registry.
To build and push the containers, you can use the Docker Compose task. However, this must be done as a two-step process; you must build the images first and then push them. To make the task easier to read, let’s first look at the build portion in the following YAML code:
parameters:
- name: azureSubscription
type: string
default: 'azure-packt-rg'
- name: azureContainerRegistry
type: string
default: '{"loginServer":"packtadocicd.azurecr.io", "id" : "/subscriptions/SUB_ID/resourceGroups/packt/providers/Microsoft.ContainerRegistry/registries/packtadocicd"}'
jobs:
- job: BuildAndPushContainers
displayName: 构建并推送容器
steps:
- task: DockerCompose@0
displayName: '构建容器'
inputs:
containerregistrytype: 'Azure 容器注册表'
azureSubscription: ${{parameters.azureSubscription}}
azureContainerRegistry: ${{parameters.azureContainerRegistry}}
dockerComposeFile: 'docker-compose.yml'
projectName: 'packt-store'
action: '构建服务'
additionalImageTags: '$(Build.BuildNumber)'
includeLatestTag: true
Let’s break this code down:
* The `azureSubscription` parameter is a reference to the Azure Resource Manager Service connection created in the previous chapter.
* The `azureContainerRegistry` parameter is a little tricky; it is a JSON document that includes the `loginServer` and `id` properties related to the Container Registry resource in Azure.
* The Docker Compose task with the `'Build Containers'` `displayName` uses the `docker-compose.yml` file to build container images locally in the agent, as indicated by the `action` property. Note the use of `additionalImageTags`, where you provide a predefined variable, `$(Build.BuildNumber)`, and set to `true` the `includeLatestTag` property; we will elaborate on this in the *Understanding container image* *tags* section.
With the images built, the next step is to push them to the registry, which is done with the same task, with just a change to the `action` property, as shown in the following code snippet:
- task: DockerCompose@0
displayName: '推送容器'
inputs:
containerregistrytype: 'Azure 容器注册表'
azureSubscription: ${{parameters.azureSubscription}}
azureContainerRegistry: ${{parameters.azureContainerRegistry}}
dockerComposeFile: 'docker-compose.yml'
projectName: 'packt-store'
action: '推送服务'
additionalImageTags: '$(Build.BuildNumber)'
includeLatestTag: true
The Docker Compose task with `displayName` `'Push Containers'` uses the `docker-compose.yml` file to push the previously built container images to the Container Registry in Azure as indicated by the `action` property.
Remember that the two portions of YAML presented here are part of the `build-apps.yaml` file.
Now that we have covered how to build and push container images, let’s take a break to discuss how container image tags work and why they are important.
Understanding container image tags
Building a container is like compiling an application and packaging all its files into a ZIP archive that you can then use for deployment, along with all the OS dependencies needed for the application to run.
However, the result is called a container image and it is typically a complex artifact made up of multiple layers stored in a registry and is not manageable via the filesystem. For this reason, just like you would name a ZIP file based on a versioning convention to track when the artifact was generated, when working with containers, it is important to tag them.
The `latest` tag mentioned previously is a convention in the container world that allows you to retrieve the newest image available without specifying a specific tag. This is very helpful during development cycles for experimentation purposes.
Important note
Always tag your containers with a specific version number and deploy that version number across all your environments for proper traceability. The `latest` tag is only a convenience to easily pull the newest version of a container image and it should never be used for environment deployments, because it can be a reference to different builds depending on the date and time that you pull it.
Now that you understand the importance of container image tags, let’s see what you can use within Azure Pipelines to name them.
Understanding your pipeline build number
The `$(Build.BuildNumber)` predefined variable is a convenient way to get a unique label in every pipeline run to version your artifacts, and its default value is a timestamp and revision number with the format `YYYYMMDD.R`, where `YYYY` is the current year, `MM` is the month, `DD` is the day, and `R` is a sequential automatically incremented number.
If you don’t set your build name explicitly, it will use the following default format for your YAML pipelines:
name: \((Date:yyyyMMdd).\)(Rev:r)
This special notation will use the current date in the given format, automatically increase the number generated by the `$(Rev:r)` token, and reset it to `1` if the portion of text before it is changed.
Most organizations prefer to use semantic versioning for artifacts or APIs, which follows a `MAJOR.MINOR.PATCH` format, like `1.0.1`:
* `MAJOR` changes indicate incompatible API changes
* `MINOR` changes indicate that functionality is added with backward compatibility
* `PATCH` changes indicate a bug fix without impact on functionality
If you need to use semantic versioning in your pipelines, an easy way to implement this is by adding `name` at the very top of your YAML file, as follows:
name: 1.0.$(Rev:r)
Notice that in this case, you are responsible for increasing the `MAJOR` and `MINOR` portions of the name based on your code changes.
Important note
Always consider the implications of your pipeline build number, its format, and where you will be using it. This value can have adverse effects depending on where you use it.
Now that you have the container images available, let’s talk a bit about Helm, a tool you will be using to deploy applications in Kubernetes environments.
Understanding Helm
**Helm** is a package manager for Kubernetes. Typically, you take advantage of it to deploy third-party or open source applications into your Kubernetes clusters. The packages created with Helm are referred to as **Helm charts**.
Helm is also extremely useful for packaging your own applications, since they will likely have more than one manifest needed to configure all required components in Kubernetes, and Helm provides facilities to override parameters with ease.
For example, a simple Helm chart will contain the following files:

Figure 9.6 – Basic Helm chart contents
If you want to learn more about Helm, go to [`helm.sh/`](https://helm.sh/).
Validating Helm charts is not a trivial task; several options are available for this. Helm provides a basic `lint` command to accomplish this task, but it covers only basic format issues. In this book, you are using an open source tool called **kube-linter**, available as a Docker container. This will validate the YAML syntax of the Kubernetes manifests used to deploy the application and perform a series of best-practice checks. If you want to learn more about this tool, go to [`docs.kubelinter.io/`](https://docs.kubelinter.io/).
Now that you have learned about Helm, let’s work on the IaC.
Verifying and packaging IaC
You learned how to work with Azure Resource Manager templates in the previous chapter, so you need to validate the templates and publish them as artifacts to the pipeline.
To do this, you will create a `build-iac.yml` file in the repository and add the following seven segments to it (they have been separated in this section only to make it easier to read):
* `azure-pipeline.yaml` file:
```
parameters:
- name: azureSubscription
type: string
default: 'azure-packt-rg'
- name: resourceGroupName
type: string
default: 'packt'
- name: location
type: string
default: '东部美国'
```
* **The jobs segment**: This segment groups all the subsequent segments that include only tasks:
```
jobs:
- job: VerifyAndPackageIaC
displayName: 验证和打包基础设施即代码 (IaC)
steps:
```
* **IaC catalog tasks segment**: This can be written as follows:
```
- task: AzureResourceManagerTemplateDeployment@3
displayName: '验证目录模板'
inputs:
deploymentScope: '资源组'
azureResourceManagerConnection: ${{parameters.azureSubscription}}
resourceGroupName: ${{parameters.resourceGroupName}}
templateLocation: '关联的构件'
csmFile: 'e2e/iac/azure/catalog/template.json'
deploymentMode: '验证'
location: ${{parameters.location}}
- task: PublishPipelineArtifact@1
displayName: '发布目录构件'
inputs:
targetPath: 'e2e/iac/azure/catalog'
artifact: catalog-iac
publishLocation: '管道'
```
Let’s break it down:
* The `AzureResourceManagerTemplateDeployment@3` task is used to validate the ARM templates for the catalog application
* The `PublishPipelineArtifact@1` task is then used to publish the artifacts to be used for deployment * **Catalog helm chart segment**: You can write this block as follows:
```
- script: |
docker run --rm -v $(pwd):/manifests stackrox/kube-linter lint /manifests --config /manifests/.kube-linter.yml
displayName: 'Lint 目录 Helm 图表'
workingDirectory: e2e/iac/helm-charts/catalog
- task: HelmInstaller@1
displayName: '安装 Helm'
- task: HelmDeploy@0
displayName: '打包目录 Helm 图表'
inputs:
command: package
chartPath: e2e/iac/helm-charts/catalog
destination: $(Build.ArtifactStagingDirectory)
- task: PublishPipelineArtifact@1
displayName: '发布目录 Helm 图表'
inputs:
targetPath: $(Build.ArtifactStagingDirectory)
artifact: catalog-helm-chart
publishLocation: '管道'
```
Let’s break it down:
* The script task with `displayName` `'Lint Catalog Helm Chart'` performs a validation of the Helm chart
* The `HelmInstaller@1` task installs the Helm tool
* The `HelmDeploy@0` task is used to package the Helm chart * **IaC cart tasks segment**: An example of this is as follows:
```
- task: AzureResourceManagerTemplateDeployment@3
displayName: '验证购物车模板'
inputs:
deploymentScope: '资源组'
azureResourceManagerConnection: ${{parameters.azureSubscription}}
resourceGroupName: ${{parameters.resourceGroupName}}
templateLocation: '关联的构件'
csmFile: 'e2e/iac/azure/cart/template.json'
deploymentMode: '验证'
location: ${{parameters.location}}
- task: PublishPipelineArtifact@1
displayName: '发布购物车构件'
inputs:
targetPath: 'e2e/iac/azure/cart'
artifact: cart-iac
publishLocation: '管道'
```
Let’s break it down:
* The `AzureResourceManagerTemplateDeployment@3` task is used to validate the ARM templates for the cart application
* The `PublishPipelineArtifact@1` task is then used to publish the artifacts to be used for deployment * **IaC checkout tasks segment**: This section looks like the following:
```
- task: AzureResourceManagerTemplateDeployment@3
displayName: '验证结账模板'
inputs:
deploymentScope: '资源组'
azureResourceManagerConnection: ${{parameters.azureSubscription}}
resourceGroupName: ${{parameters.resourceGroupName}}
模板位置: '链接的工件'
csm 文件: 'e2e/iac/azure/checkout/template.json'
部署模式: '验证'
位置: ${{parameters.location}}
- 任务: PublishPipelineArtifact@1
显示名称: '发布结账工件'
inputs:
目标路径: 'e2e/iac/azure/checkout'
工件: checkout-iac
发布位置: '管道'
```
Let’s break it down:
* The `AzureResourceManagerTemplateDeployment@3` task is used to validate the ARM templates for the checkout application
* The `PublishPipelineArtifact@1` task is then used to publish the artifacts to be used for deployment * **IaC frontend tasks segment**: Here is a sample of this part of the code:
```
- 任务: AzureResourceManagerTemplateDeployment@3
显示名称: '验证前端模板'
inputs:
部署范围: '资源组'
azureResourceManagerConnection: ${{parameters.azureSubscription}}
resourceGroupName: ${{parameters.resourceGroupName}}
模板位置: '链接的工件'
csm 文件: 'e2e/iac/azure/frontend/template.json'
部署模式: '验证'
位置: ${{parameters.location}}
覆盖参数: '-catalogAppUrl catalogAppUrl.com -cartAppUrl cartAppUrl.com -checkoutAppUrl checkoutAppUrl.com'
- 任务: PublishPipelineArtifact@1
显示名称: '发布前端工件'
inputs:
目标路径: 'e2e/iac/azure/frontend'
工件: frontend-iac
发布位置: '管道'
```
For simplicity, let’s break down what is happening:
* The `AzureResourceManagerTemplateDeployment@3` task is used to validate the ARM templates for the frontend application
* The `PublishPipelineArtifact@1` task is then used to publish the artifacts to be used for deployment
That brings us to the end of the `build-iac.yaml` file; make sure you keep all the segments together in the same file.
Now that you have all the artifacts ready, let’s move on to create our environments.
Managing environments
In this section, you will learn about how to create environments and deploy to them.
Configuring environments
In this section, you will define the environments in Azure Pipelines, which will be logical representations of the deployment targets. This will allow us to add approval and checks to control how the pipeline advances from one stage to the next:
1. You start by clicking on the **Environments** option under **Pipelines** in the main menu on the left, as follows:

Figure 9.7 – Accessing the Environments option in the menu
1. If you have no environments, you will see a screen like the following; click on **Create environment**:

Figure 9.8 – Creating your first environment
Otherwise, you will see a **New environment** option in the top-right part of the screen above your existing environments.
1. Once the pop-up screen shows up to create the new environment, enter `test` for `Test Environment` for **Description**, leave the **Resource** option as **None**, and click on the **Create** button, as shown here:

Figure 9.9 – Creating the test environment
1. Repeat *steps 2 and 3* to create another environment using `production` for `Production Environment` for **Description**.
2. With the two environments created, click on the **production** one in the list, as shown here:

Figure 9.10 – Environments
1. In the next screen, you are going to add an approval check, to ensure you can only deploy to production once a human indicates it is possible.
For this, start by clicking on the ellipsis button in the top-right part of the screen and then on the **Approvals and checks** item, as shown here:

Figure 9.11 – Adding an environment approval gate
1. Since no checks have been added yet, you should see a screen like the following; selecting **Approvals** will get us to the next step to complete the check configuration:

Figure 9.12 – Adding an approval check
In this form, provide the required approvers (it could be yourself initially). You can optionally provide instructions, such as manual steps to verify by the approver, and change **Timeout**. If you are the approver, you must make sure the **Allow approvers to approve their own runs** option is checked under **Advanced**. Finally, hit the **Create** button and you will be ready to move on to the next steps:

Figure 9.13 – Creating an approval check
1. Lastly, you must also add permissions for each environment to allow them to be used in the **E2E-Azure** pipeline. Like before, click on the ellipsis option in the top-right part of the screen and click the **Security** option from the menu.

Figure 9.14 – Environment security settings
1. Then click on the **+** button and search for the **E2E-Azure** pipeline to add the permissions; just click on the name to add it.

Figure 9.15 – Adding pipeline permissions to environment
A properly configured environment will look like the following:

Figure 9.16 – Environment with pipeline permissions
Important
It is a good practice to create environments for all deployment stages; this allows us to be modular and templatize deployment steps, giving us the opportunity to add approvals or gates later if need be.
Now that you have our environments configured, let’s move on to the deployment steps.
Deploying to environments
You will deploy the environment by creating a `deploy.yml` file and start by adding the steps needed for AKS deployment and the Python catalog service.
The `deploy.yml` file will start with the following content; you will be adding to it in every section hereafter:
参数:
- 名称: envName
类型: 字符串
默认值: 'test'
- 名称: azureSubscription
类型: 字符串
默认值: 'azure-packt-rg'
- 名称: resourceGroupName
类型: 字符串
默认值: 'packt'
- 名称: 位置
类型: 字符串
默认值: '东部美国'
作业:
- 部署: deployment_${{ parameters.envName }}
显示名称: 部署到 ${{ parameters.envName }}
环境: ${{ parameters.envName }}
策略:
运行一次:
部署:
步骤:
So far, you don’t have much in this file, but let’s break it down:
* The `parameters` section defines all the values available for reuse within the pipeline definition. The only one being used from the main pipeline is the `envName` one, but this gives you the flexibility to change them when needed.
* The `jobs` collection includes a new job type you haven’t used before called `deployment`, which allows us to implement different rollout strategies. For simplicity, here you will be using the `runOnce` strategy, but you can also use `canary` and `rolling` where appropriate. To learn more about these, go to [`learn.microsoft.com/en-us/azure/devops/pipelines/yaml-schema/jobs-deployment-strategy`](https://learn.microsoft.com/en-us/azure/devops/pipelines/yaml-schema/jobs-deployment-strategy).
Now, you can proceed with the first service deployment steps.
Deploying the Python catalog service to AKS
The deployment of the Python catalog service to Azure Kubernetes Service is performed as a two-phase process:
1. Deploy the ARM template to create and configure the AKS cluster using IaC. Refer to *Chapter 8* for more information on how to do this.
2. Deploy the application using the Helm chart provided in the repository.
In our `deploy.yml` file, you will add the following six steps:
1. The `download` task `Download catalog iac` retrieves the pipeline artifact:
```
- 下载: 当前
显示名称: '下载目录 iac'
工件: catalog-iac
```
2. The `AzureResourceGroupDeployment@2` task performs the AKS deployment:
```
- 任务: AzureResourceGroupDeployment@2
名称: catalogInfra
显示名称: '部署目录基础设施'
inputs:
azureSubscription: ${{ parameters.azureSubscription }}
动作: '创建或更新资源组'
resourceGroupName: ${{ parameters.resourceGroupName }}
位置: ${{ parameters.location }}
模板位置: '链接的工件'
csm 文件: '$(Pipeline.Workspace)/catalog-iac/template.json'
覆盖参数: '-environmentName ${{ parameters.envName }}'
部署模式: '增量'
部署名称: 'catalog-$(Build.BuildNumber)'
部署输出: 'catalogInfraOutputs'
```
Notice that you are using the `deploymentOutputs` property to set the name of a variable that will contain the outputs generated in the ARM template; this will be needed later for the frontend deployment.
3. The `PowerShell@2` task parses out the cluster name from the output parameters of the ARM template deployment and makes it available as another variable for the duration of the job:
```
- 任务: PowerShell@2
显示名称: '获取 AKS 集群名称'
inputs:
目标类型: '内联'
脚本: |
$var=ConvertFrom-Json '$(catalogInfraOutputs)'
$value=$var.clusterName.value
Write-Host "AKS 集群名称: $value"
Write-Host "##vso[task.setvariable variable=clusterName;]$value"
```
4. The `download` task `Download catalog helm chart` retrieves the artifact:
```
- 下载: 当前
显示名称: '下载目录 Helm 图表'
工件: catalog-helm-chart
```
5. The `HelmInstaller@1` task installs Helm in the agent:
```
- 任务: HelmInstaller@1
显示名称: '安装 Helm'
inputs:
要安装的 Helm 版本: 3.11.3
```
6. The `HelmDeploy@0` task performs an `upgrade` command with the `install` option set to `true`; this will guarantee that, if it’s not found, it will be created:
```
- 任务: HelmDeploy@0
显示名称: 将目录应用部署到 AKS
inputs:
连接类型: 'Azure 资源管理器'
azureSubscription: ${{ parameters.azureSubscription }}
azureResourceGroup: ${{ parameters.resourceGroupName }}
kubernetesCluster: $(clusterName)
发布名称: catalog
图表类型: 文件路径
图表路径: "$(Pipeline.Workspace)/catalog-helm-chart/packt-store-catalog-1.0.0.tgz"
覆盖值: 'image.tag=$(Build.BuildNumber)'
command: upgrade
install: true
waitForExecution: true
"##vso[task.setvariable variable=name;]value"
```
This is called a `name` and `value`.
If the variable does not exist before running the command, then it will be created and made available at runtime.
To learn more about how to work with variables in Azure Pipelines, go to [`learn.microsoft.com/en-us/azure/devops/pipelines/process/set-variables-scripts`](https://learn.microsoft.com/en-us/azure/devops/pipelines/process/set-variables-scripts).
Now let’s move on to the Node.js cart service.
Deploying a Node.js cart service to ACA
The deployment of the cart service to ACA is a bit simpler; it just requires the use of the `AzureResourceGroupDeployment@2` task after the artifact is downloaded:
- download: current
displayName: '下载购物车 iac'
artifact: cart-iac
- task: AzureResourceGroupDeployment@2
name: cartInfra
displayName: '部署购物车基础设施'
inputs:
azureSubscription: ${{ parameters.azureSubscription }}
action: '创建或更新资源组'
resourceGroupName: ${{ parameters.resourceGroupName }}
location: ${{ parameters.location }}
templateLocation: '链接的工件'
csmFile: '$(Pipeline.Workspace)/cart-iac/template.json'
overrideParameters: '-environmentName \({{ parameters.envName }} -containerTag "\)(Build.BuildNumber)"'
deploymentMode: '增量'
deploymentName: 'cart-$(Build.BuildNumber)'
deploymentOutputs: 'cartInfraOutputs'
Notice, in this case, the use of `overrideParameters` to pass in the value of the `containerTag` parameter using `BuildNumber`.
Next, you will add the deployment of the checkout service.
Deploying a .NET checkout service to ACI
The deployment of the checkout service is very similar to the ACA deployment; the only difference is the use of the ACI service instead. See the following steps:
- download: current
displayName: '下载结账 iac'
artifact: checkout-iac
- task: AzureResourceGroupDeployment@2
name: checkoutInfra
displayName: '部署结账基础设施'
inputs:
azureSubscription: ${{ parameters.azureSubscription }}
action: '创建或更新资源组'
resourceGroupName: ${{ parameters.resourceGroupName }}
location: ${{ parameters.location }}
templateLocation: '链接的工件'
csmFile: '$(Pipeline.Workspace)/checkout-iac/template.json'
overrideParameters: '-environmentName \({{ parameters.envName }} -containerTag "\)(Build.BuildNumber)"'
deploymentMode: '增量'
deploymentName: 'checkout-$(Build.BuildNumber)'
deploymentOutputs: 'checkoutInfraOutputs'
Just like you did in the Node.js deployment to ACA, in this section, you used `overrideParameters` to pass in the value of the `containerTag` parameter using `BuildNumber`.
Now let’s move on to the last application, the frontend.
Deploying an Angular frontend app to AAS
For the frontend application, there are a few more steps necessary because of the need to gather information before being able to use the ARM template.
You will be adding the following steps to `deploy.yaml`:
1. The `download` task retrieves the frontend pipeline artifact:
```
- download: current
displayName: '下载前端 iac'
artifact: frontend-iac
```
2. The `AzureCLI@2` task `Get Catalog App IP from AKS` is a script needed to retrieve the IP assigned to the exposed entry point of the catalog application. It uses the `az` CLI, the `kubectl` CLI, and the `jq` tool in Linux to parse out the information from Kubernetes. This is very specific to how this application was deployed. This script might not be reusable, but it is meant to show the flexibility of the tools if needed:
```
- task: AzureCLI@2
displayName: '从 AKS 获取目录应用程序 IP'
inputs:
azureSubscription: 'azure-packt-rg'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az aks get-credentials -g ${{ parameters.resourceGroupName }} -n $(clusterName) --overwrite-existing
ip=`kubectl get service catalog-packt-store-catalog -o json | jq ".status.loadBalancer.ingress[0].ip"`
echo "目录应用程序 IP: $ip"
echo «##vso[task.setvariable variable=catalogAppIp;]$ip"
```
3. The `PowerShell@2` task is used to parse out the fully qualified domain name contained in the output variables generated by the previous steps. It is used to deploy the catalog, cart, and checkout services:
```
- task: PowerShell@2
displayName: '设置应用程序 URL'
inputs:
targetType: 'inline'
script: |
# 设置目录应用程序 URL
$value="http://" + $(catalogAppIp) + ":5050/"
Write-Host "目录应用程序 URL: $value"
Write-Host "##vso[task.setvariable variable=catalogAppUrl;]$value"
# 设置购物车应用程序 URL
$var=ConvertFrom-Json '$(cartInfraOutputs)'
$value=$var.containerAppFqdn.value
$value="https://" + $var.containerAppFqdn.value + "/"
Write-Host "购物车应用程序 URL: $value"
Write-Host "##vso[task.setvariable variable=cartAppUrl;]$value"
# 设置结账应用程序 URL
$var=ConvertFrom-Json '$(checkoutInfraOutputs)'
$value="http://" + $var.containerFQDN.value + ":5015/"
Write-Host "结账应用程序 URL: $value"
Write-Host "##vso[task.setvariable variable=checkoutAppUrl;]$value"
```
4. The `AzureResourceGroupDeployment@2` task deploys the Azure App Service instance and provides all the information necessary for the service to pull in the image, including the given `BuildNumber` as the tag. There are also the application URLs necessary to be stored in the service for the application to work:
```
- task: AzureResourceGroupDeployment@2
name: frontendInfra
displayName: '部署前端基础设施'
inputs:
azureSubscription: ${{ parameters.azureSubscription }}
action: '创建或更新资源组'
资源组名称: ${{ parameters.resourceGroupName }}
位置: ${{ parameters.location }}
模板位置: '链接的工件'
csm 文件: '$(Pipeline.Workspace)/frontend-iac/template.json'
覆盖参数: '-environmentName ${{ parameters.envName }} -containerTag "$(Build.BuildNumber)" -catalogAppUrl $(catalogAppUrl) -cartAppUrl $(cartAppUrl) -checkoutAppUrl $(checkoutAppUrl)'
部署模式: '增量'
部署名称: 'frontend-$(Build.BuildNumber)'
部署输出: 'frontendInfraOutputs'
```
5. The `PowerShell@2` task `Get Frontend URL` then uses another script to parse the output of the ARM template deployment to provide it both in the logs and as a variable that could ultimately be used in additional steps, such as a web request to smoke test the endpoint. Alternatively, it could be used in automated test execution:
```
- 任务: PowerShell@2
显示名称: '获取前端网址'
输入:
目标类型: '内联'
脚本: |
# 获取前端应用网址
$var=ConvertFrom-Json '$(frontendInfraOutputs)'
$value=$var.frontendUrl.value
Write-Host "前端网址: $value"
Write-Host "##vso[task.setvariable variable=frontendAppUrl;]$value"
```
Wow, that was a lot of deployments, but you are not done! Once the test environment is complete, you get a chance to approve the continuation of deployment to production in the next section.
Approving environment deployments
With the deployment to the test environment complete, you should be able to see the pipeline in the **Waiting** state, as follows:

Figure 9.17 – Stage awaiting for checks
This will only look like this if you are the reviewer configured for the manual approval check. Click on the **Review** button and a new screen will pop up with **Reject** and **Approve** options and the ability to provide a comment, as shown here:

Figure 9.18 – Approving an environment check
If click the **Approve** button, the deployment will proceed. If you click the **Reject** button, the deployment will be canceled; also, if you don’t do anything and the timeout runs out, the pipeline will be canceled.
Now that you have completed all the deployments, it is worth pointing out what to do if you run into issues with deployments; let’s talk now about some of the typical ones.
Troubleshooting deployment issues
Creating a stable and reliable CI/CD pipeline takes time, especially when performing deployments to a cloud platform such as Azure. Let’s walk through some of the typical issues you can run into.
Issues deploying IaC
You used ARM templates to deploy the infrastructure to host the services that run the applications in Azure in this chapter, which means you are relying on that infrastructure to succeed before you can deploy the applications.
There are several situations in which the `AzureResourceGroupDeploment` task can fail:
* **Internal server errors**: These can occur if the Azure region you are trying to deploy is suddenly going through capacity issues or undergoing maintenance, or even if the Azure Pipelines service is going through issues itself.
*How to fix it*: Usually there is no recovery from this except for attempting to run the failed pipeline again. If the region in Azure becomes unavailable, you will have to wait until it becomes available again or target a different region for deployment as part of your disaster recovery strategy.
* **Timeout**: The deployment took too long, which could have been caused by the pipeline agent or the Azure deployment. The default timeout for Microsoft-hosted agents is 60 minutes in the free tier and 360 minutes when paying for parallel jobs.
*How to fix it*: You have a way to increase the timeout at the job level if required, but most likely there are other reasons why your deployment is failing. You will have to analyze the errors in the pipeline to find the root cause.
For other tips regarding deployments using ARM templates, head to [`learn.microsoft.com/en-us/azure/azure-resource-manager/templates/best-practices`](https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/best-practices).
Issues with scripts
Scripts used in your pipelines need to be written in an *idempotent* manner, meaning that for every command or task to execute properly, the script must verify whether the operation is required and whether the result code is what is expected. This approach ensures that a script only performs the operations required to reach the desired state and, in doing so, checks every step of the way whether the operation is indeed required. Not following this approach leads to brittle scripts (scripts that are easily broken), especially when interacting with Azure resources where the current state might not match the desired state.
To address this issue, always write your scripts in an idempotent way. Follow the `if-not-then` pattern for every operation.
Issues with Helm
Here are some issues commonly observed when working with Helm:
* Helm is a very convenient tool, but you are still responsible for the proper formatting of each of the manifests and making sure that they conform with and are valid for the Kubernetes API of your cluster. In this chapter, you learned how to use **kube-linter** to validate your Helm charts, but this is only a tool, and as such it can fail to detect issues. This tool only validates against the latest stable Kubernetes API, and if your cluster is not running this version, the validation will not catch issues that will arise when performing a deployment.
*How to fix it*: There are other open source tools that can validate against specific Kubernetes versions and perform different checks against your Kubernetes objects. A couple of examples are **kube-score** ([`kube-score.com/`](https://kube-score.com/)) and **Kubeconform** ([`github.com/yannh/kubeconform`](https://github.com/yannh/kubeconform)); put each of them to the test and evaluate which works better for your applications.
* Another issue to expect with Helm is the deployment of Kubernetes objects and the underlying consequences of this in Azure, such as deploying additional services in the case of ingress controllers. This scenario entails the creation of other Azure resources that in turn can sometimes fail.
*How to fix it*: If an operation failed due to a platform timeout or retriable error, there is nothing else to do but deploy again.
With this, we’ve finished the chapter. Let’s wrap it up.
Winding up
If you completed all the steps, you will have deployed test and production environments, so it is time to clean up! This is important because you have deployed many resources into Azure. Make sure to delete them if you do not want to keep paying for them. You can do this via the Azure portal or the following Azure CLI command:
az group delete -n packt -y
If you missed anything or got stuck and are having trouble putting the entire solution together, the complete pipeline definitions can be found in the GitHub repository mentioned in the *Technical requirements* section; look in the `ch09/azure` directory, specifically the **complete** branch.
Now, let’s recap what we have learned in this chapter.
Summary
In this chapter, we took a complex solution and learned how to create CI/CD pipelines in a modular way, taking advantage of stages, environments, and templates. We also learned about adding checks throughout the stages of a pipeline. In this case, we added manual approval, but we saw that there are other controls that can be put in place to implement more complex scenarios. We learned briefly about containers and how building container-based applications with Docker Compose is easy and facilitates working with different programming languages at the same time in your pipelines; it also reduces the complexities of compiling and packaging them. We learned about semantic versioning and its applicability while learning about how build numbers can be used to tag or name artifacts from your pipelines, along with the importance of tracking artifacts. Lastly, we walked through the deployment of different services in Azure using ARM templates, learning about some of the intricacies of tying them together and the flexibility of pipelines to coordinate templates, regardless of the number of services to be deployed.
Now that we have learned how to build and deploy this complex solution to different Azure services, the next chapter will be about doing this using **Amazon Web** **Services** instead.
第十章:实施 AWS 的 CI/CD
在本章中,我们将构建一个端到端的解决方案,类似于上一章,但它将目标定向到亚马逊 Web 服务(AWS)云平台,部署相同的应用程序,并将它们从测试环境推广到生产环境。本章展示了 Azure Pipelines 的灵活性,可以根据你的环境需求进行适应,无论目的地如何,都能提供类似的 CI/CD 功能,并且能够控制整个过程。
我们将涵盖以下主题:
-
解释解决方案架构
-
构建并打包应用程序和 IaC
-
将 Python 目录服务部署到 Elastic Kubernetes Service(EKS)
-
将 Node.js 购物车服务部署到 Fargate
-
将 .NET 结账服务部署到 Elastic Container Service(ECS)
-
将 Angular 前端应用程序部署到Lightsail
在我们正式开始之前,先处理一些技术要求。
技术要求
你可以在 github.com/PacktPublishing/Implementing-CI-CD-Using-Azure-Pipelines/tree/main/ch10 查找本章的代码。
要完成本章描述的任务,你将需要以下内容:
-
访问 AWS 账户和服务连接:假设你已经完成了第八章中的“访问 AWS 账户”和“创建 AWS 服务连接”部分。如果你跳过了这些步骤,请返回并完成这些步骤,以便能够完成本章内容。
-
导入示例仓库:同样假设你已经从 GitHub 导入了示例仓库。如果你还没有,请查看第九章了解如何完成此操作。
重要说明
如果在使用管道时遇到任何问题,请查看 complete 分支中的完整代码。
现在我们已经处理了技术要求,接下来让我们回顾一下解决方案架构。
解释解决方案架构
对于我们的解决方案,我们将使用与第九章相同的虚构的 Packt Store。然而,在本章中,它已被调整为托管在不同的 AWS 服务中:

图 10.1 – 解决方案架构图
我们将通过对每个应用程序执行以下步骤来实施 Azure Pipelines:
-
构建并打包应用程序及其相应的 IaC。
-
部署到测试环境。
-
部署到生产环境。
-
自动化环境部署检查。
下图展示了 CI/CD 过程:

图 10.2 – CI/CD 过程
在本章中,我们不会涉及应用程序代码的任何细节,因为这些内容与 CI/CD 无关。相反,我们将专注于使其正常工作的 Azure Pipelines 细节。
为了实现 CI/CD 流程,我们将利用多阶段管道、环境和模板,就像在上一章中所做的那样。让我们从以下 ch10/aws/aws-pipeline.yml 中的管道定义开始。这可以在我们导入的 Implementing-CI-CD-Using-Azure-Pipeline 仓库中找到:
# Multi-Stage pipeline
trigger:
- main
pool:
vmImage: ubuntu-latest
stages:
- stage: build
displayName: Build
jobs:
- template: build-apps.yml
- template: build-iac.yml
- stage: deployTest
displayName: Deploy Test
dependsOn: build
jobs:
- template: deploy.yml
parameters:
envName: awstest
- stage: deployProduction
displayName: Deploy Production
dependsOn: deployTest
jobs:
- template: deploy.yml
parameters:
envName: awsproduction
如你所见,这个管道定义与我们在上一章使用的相同,因此它将以与上一章相同的方式工作。然而,build-apps.yml、build-iac.yml 和 deploy.yml 将有所不同。一旦文件被添加到仓库中,将其作为新管道添加,并重命名为 E2E-AWS。最后,我们还需要添加一些安全配置,使一切顺利进行,比如批准部署到不同环境,类似于我们在第九章中所做的。
让我们继续进行构建阶段。
构建和打包应用程序以及 IaC
本解决方案中的应用程序都是容器化的,正如我们在上一章中看到的那样。所以,在本章中,我们将逐步讲解如何构建并将容器镜像推送到 Amazon docker-compose.yml 文件。
请注意,docker-compose.yml 文件保持不变,这意味着使用容器构建应用程序可以提供灵活性,便于部署到多个目的地。
首先,让我们在 ECR 中创建所需的仓库。
创建 ECR 仓库
默认情况下,当你在 AWS 中创建账户时,会提供一个 ECR 注册表,但你需要负责为每个镜像创建相应的仓库。
我们可以通过以下 AWS CLI 命令轻松完成此操作:
aws ecr create-repository --repository-name packt-store-cart
aws ecr create-repository --repository-name packt-store-catalog
aws ecr create-repository --repository-name packt-store-checkout
aws ecr create-repository --repository-name packt-store-frontend
配置好这些后,让我们继续进行管道中的应用程序构建。
创建构建应用程序的作业
让我们创建 e2e/pipelines/aws/build-apps.yml 文件,文件内容如本节所述。我们已将其分为两个部分,便于阅读。
第一部分只是定义了参数、作业头和所需的唯一步骤。这使用了 AWSShellScript@1 任务来运行需要在 AWS CLI 上下文中执行的自定义脚本。此操作必须通过我们之前创建的服务连接与 AWS 进行身份验证:
parameters:
- name: awsConnection
type: string
default: ‹aws-packt'
- name: location
type: string
default: ‹us-east-1›
jobs:
- job: BuildAndPushContainers
displayName: Build and Push Containers
steps:
- task: AWSShellScript@1
displayName: 'Build and Push Containers'
inputs:
awsCredentials: ${{ parameters.awsConnection }}
regionName: ${{ parameters.location }}
failOnStandardError: false
scriptType: 'inline'
inlineScript: |
配置好后,让我们描述一下需要的自定义脚本,用于登录到 ECR、构建容器、标记并将它们推送到 ECR。脚本的内容必须与前面 YAML 中的 inlineScript 属性正确对齐,才能正常工作。这意味着所有行必须从此属性开始的列后面准确地缩进两个空格。本章仅以这种方式展示,便于阅读:
# Set variables for AWS Region and Account ID
L="${{ parameters.location }}»
ID=`aws sts get-caller-identity --query Account --output text`
# Login to ECR
aws ecr get-login-password | docker login --username AWS --password-stdin $ID.dkr.ecr.$L.amazonaws.com
# Build images with docker-compose
docker-compose build
# Tag and push images to ECR
declare -a services=("catalog" "cart" "checkout" "frontend")
declare -a tags=("$(Build.BuildNumber)" "latest")
for s in "${services[@]}"
do
echo «Pushing images for $s"
for t in «${tags[@]}"
do
docker tag packt-store-$s:latest $ID.dkr.ecr.$L.amazonaws.com/packt-store-$s:$t
docker push $ID.dkr.ecr.$L.amazonaws.com/packt-store-$s:$t
done
done
让我们逐步解析这段代码:
-
L和ID变量代表 AWS 区域和 AWS 账户 ID,这两者在构建登录 ECR、标记镜像并将其推送到 ECR 的命令时都需要用到 -
登录 ECR 是一个两步操作:
-
从 ECR 服务获取登录令牌。
-
使用
docker login命令,这样可以在接下来的步骤中使用docker和docker-compose工具。
-
-
镜像构建使用
docker-compose build命令,在这个上下文中,它将使用仓库中的现有docker-compose.yaml文件 -
最后一步是对两个数组进行循环,定义服务名称和标签,用于应用
docker tag和docker push命令
你可能会问为什么我们没有像在 第九章 中那样使用 DockerCompose@0 任务。原因在于这个任务支持的容器注册表类型和 ECR 在 AWS 中支持的认证机制。DockerCompose@0 任务支持 Azure 容器注册表和通用 Docker 注册表。对于后者,你可以使用 Docker 注册表服务连接,但 ECR 提供的授权令牌是短期有效的,只能维持 12 小时。这将迫使你定期更新服务连接。
相反,这种方法使用 AWSShellScript@1 任务和自定义脚本,利用现有的 AWS 服务连接,并在每次运行时协商一个新密码,在构建阶段将其存储在本地,避免了维护工作。
现在我们的容器镜像已经准备好,接下来让我们验证并打包基础设施即代码。
验证和打包 IaC
我们在前一章学会了如何使用 AWS CloudFormation 模板,现在,我们需要验证这些模板并将它们作为工件发布到管道中。
为此,我们将在仓库中创建一个 build-iac.yml 文件,并添加以下六个阶段:
-
aws-pipeline.yaml文件:parameters: - name: awsConnection type: string default: 'aws-packt' - name: region type: string default: 'us-east-1' -
作业:在这里,我们只添加以下各个阶段的标题,这些阶段都是一系列步骤,用于验证和发布部署每个应用程序所需的 IaC 工件:
jobs: - job: VerifyAndPackageIaC displayName: Verify and Package IaC steps: -
目录服务 IaC:添加以下代码:
- script: docker run --rm -v $(pwd):/manifests stackrox/kube-linter lint /manifests --config /manifests/.kube-linter.yml displayName: 'Lint Catalog Helm Chart' workingDirectory: e2e/iac/helm-charts/catalog - task: HelmInstaller@1 displayName: 'Install Helm' - task: HelmDeploy@0 displayName: 'Package Catalog Helm Chart' inputs: command: package chartPath: e2e/iac/helm-charts/catalog destination: $(Build.ArtifactStagingDirectory) - task: PublishPipelineArtifact@1 displayName: 'Publish Catalog Helm Chart' inputs: targetPath: $(Build.ArtifactStagingDirectory) artifact: catalog-helm-chart publishLocation: 'pipeline'让我们来拆解一下:
-
displayName为'Lint Catalog Helm Chart'的脚本任务用于验证 Helm 图表 -
HelmInstaller@1任务安装 Helm 工具 -
HelmDeploy@0任务用于打包 Helm 图表 -
然后使用
PublishPipelineArtifact@1任务发布 Helm 图表工件,用于部署
-
-
购物车服务 IaC:添加以下代码:
- task: AWSCLI@1 displayName: 'Validate CloudFormation cart' inputs: awsCredentials: ${{parameters.awsConnection}} regionName: ${{parameters.region}} awsCommand: 'cloudformation' awsSubCommand: 'validate-template' awsArguments: '--template-body file://e2e/iac/aws/cart/template.json' - task: PublishPipelineArtifact@1 displayName: 'Publish Artifacts cart' inputs: targetPath: 'e2e/iac/aws/cart' artifact: cart-iac publishLocation: 'pipeline'让我们来拆解一下:
-
使用
AWSCLI@1任务验证 AWS CloudFormation 堆栈模板 -
然后使用
PublishPipelineArtifact@1任务发布 AWS CloudFormation 堆栈模板工件,用于部署
-
-
检出服务 IaC:添加以下代码:
- task: AWSCLI@1 displayName: 'Validate CloudFormation checkout' inputs: awsCredentials: ${{parameters.awsConnection}} regionName: ${{parameters.region}} awsCommand: 'cloudformation' awsSubCommand: 'validate-template' awsArguments: '--template-body file://e2e/iac/aws/checkout/template.json' - task: PublishPipelineArtifact@1 displayName: 'Publish Artifacts checkout' inputs: targetPath: 'e2e/iac/aws/checkout' artifact: checkout-iac publishLocation: 'pipeline'让我们来拆解一下:
-
使用
AWSCLI@1任务验证 AWS CloudFormation 堆栈模板 -
然后使用
PublishPipelineArtifact@1任务发布 AWS CloudFormation 堆栈模板工件,用于部署
-
-
前端应用程序 IaC:需要添加的代码如下:
- task: AWSCLI@1 displayName: 'Validate CloudFormation frontend' inputs: awsCredentials: ${{parameters.awsConnection}} regionName: ${{parameters.region}} awsCommand: 'cloudformation' awsSubCommand: 'validate-template' awsArguments: '--template-body file://e2e/iac/aws/frontend/template.json' - task: PublishPipelineArtifact@1 displayName: 'Publish Artifacts frontend' inputs: targetPath: 'e2e/iac/aws/frontend' artifact: frontend-iac publishLocation: 'pipeline'让我们分解一下:
-
AWSCLI@1任务用于验证 AWS CloudFormation 堆栈模板 -
然后使用
PublishPipelineArtifact@1任务发布 AWS CloudFormation 堆栈模板工件,用于部署
-
在完成我们的 IaC 工件构建阶段后,我们可以继续进行环境部署。
管理环境
在本节中,我们将学习如何创建环境并将 IaC 和应用程序部署到这些环境中。首先,让我们配置我们的环境。
配置环境
如我们在第九章中所做的,你需要创建两个名为awstest和awsproduction的环境,以完成本章内容。一旦你创建了这两个环境,我们就可以继续进行部署。
部署到环境
我们将通过创建一个deploy.yml文件来部署这两个环境,并开始添加每个应用程序所需的步骤。此文件将以以下内容开始;我们将在后续的每一部分中继续添加内容:
parameters:
- name: envName
type: string
default: 'test'
- name: awsConnection
type: string
default: 'aws-packt'
- name: location
type: string
default: 'us-east-1'
- name: containerTag
type: string
default: '$(Build.BuildNumber)'
parameters部分定义了在流水线定义中可以重用的所有值,其中envName是从主流水线中使用的唯一一个值,但这使你在需要时可以灵活地更改它们。
jobs集合包括deployment任务类型,它允许我们实现不同的部署策略:
jobs:
- deployment: deployment_${{ parameters.envName }}
displayName: Deploy to ${{ parameters.envName }}
environment: ${{ parameters.envName }}
strategy:
runOnce:
deploy:
steps:
为了简化操作,在此我们使用runOnce策略,但你也可以根据需要使用canary和rolling策略。设置好之后,让我们继续进行应用程序部署。
将 Python 目录服务部署到 EKS
最近,部署到 EKS 变得越来越复杂。为了简化操作,AWS 推荐使用eksctl,这是一个由WeaveWorks创建的开源 CLI 工具。它是一个用于在 EKS 上创建集群的简单 CLI 工具,使用Go语言编写,并且遵循最佳实践,利用 CloudFormation 模板。它负责创建或更新集群、添加节点组及其他中介任务,等待集群准备就绪,这些任务本来需要你自己编写脚本来完成。要了解更多关于 eksctl 的信息,请访问eksctl.io。
从此部分开始的每个段落都将左对齐。但是,在deploy.yml文件中,它们必须与最后一个steps:指令对齐,以确保从相同的位置开始。
让我们添加以下三步:
-
download任务用于检索目录 Helm 图表流水线工件:- download: current displayName: 'Download catalog helm chart' artifact: catalog-helm-chart -
HelmInstaller@1任务在代理上安装 Helm:- task: HelmInstaller@1 displayName: 'Install Helm' inputs: helmVersionToInstall: 3.11.3 -
AWSShellScript@1任务用于协调在自定义脚本中执行的一系列步骤,同时使用现有的 AWS 服务连接:- task: AWSShellScript@1 displayName: 'Deploy catalog iac and container' inputs: awsCredentials: ${{ parameters.awsConnection }} regionName: ${{ parameters.location }} arguments: '${{ parameters.envName }}-catalog ${{ parameters.location }} ${{ parameters.containerTag }}' disableAutoCwd: true workingDirectory: '$(Pipeline.Workspace)/cart-iac' failOnStandardError: true scriptType: 'inline' inlineScript: | # Confirm Parameters echo "SERVICE_NAME: $1" echo "AWS_REGION: $2" echo "CONTAINER_TAG: $3" # Install EKSCTL ARCH=amd64 PLATFORM=$(uname -s)_$ARCH curl -sLO "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$PLATFORM.tar.gz" tar -xzf eksctl_$PLATFORM.tar.gz -C /tmp && rm eksctl_$PLATFORM.tar.gz sudo mv /tmp/eksctl /usr/local/bin # Create Cluster eksctl create cluster -n $1 --version 1.27 -t t3.large -m 1 -M 2 --full-ecr-access # Retrieve AWS Account ID AWSACCOUNTID=`aws sts get-caller-identity --query Account --output text` # Deploy Catalog App to EKS helm upgrade --install --set image.tag=${{ parameters.containerTag }},image.repository=$AWSACCOUNTID.dkr.ecr.${{ parameters.location }}.amazonaws.com/packt-store-catalog --wait catalog $(Pipeline.Workspace)/catalog-helm-chart/packt-store-catalog-1.0.0.tgz CATALOG_HOSTNAME=`kubectl get service catalog-packt-store-catalog -o json | jq -r ".status.loadBalancer.ingress[0].hostname"` CATALOG_URL="http://$CATALOG_HOSTNAME:5050/" echo "Catalog URL: $CATALOG_URL" echo "##vso[task.setvariable variable=CatalogUrl;]$CATALOG_URL"脚本执行如下操作:
-
安装
eksctl工具 -
创建一个简单的 EKS 集群
-
安装目录 Helm 图表
-
检索为目录服务创建的 AWS 弹性负载均衡器的主机名
-
设置一个环境变量
CatalogUrl,并使用正确格式化的目录服务 URL,用于前端部署
-
或者,你可以将自定义脚本存储在仓库中的 shell 脚本文件中,使用 filePath 选项为 scriptType 属性,并在 filePath 属性中提供文件的路径。有关更多详细信息,请参考 docs.aws.amazon.com/vsts/latest/userguide/awsshell.xhtml。
既然目录服务已经完成配置,现在是时候继续处理购物车服务了。
将 Node.js 购物车服务部署到 Lightsail
购物车服务将被部署到 Lightsail 服务,它是 AWS 管理的计算资源,用于运行容器。
以下步骤将帮助你完成部署:
-
download任务检索目录 Helm 图表管道工件:- download: current displayName: 'Download cart iac' artifact: cart-iac -
CloudFormationCreateOrUpdateStack@1任务创建运行服务所需的基础设施。购物车服务的 URL 将自动解析,并作为环境变量提供,在这种情况下是CartUrl变量:- task: CloudFormationCreateOrUpdateStack@1 displayName: 'Create cart stack' inputs: awsCredentials: ${{ parameters.awsConnection }} regionName: ${{ parameters.location }} stackName: '${{ parameters.envName }}-cart' templateSource: 'file' templateFile: '$(Pipeline.Workspace)/cart-iac/template.json' onFailure: 'DELETE' captureStackOutputs: 'asVariables' captureAsSecuredVars: false -
AWSShellScript@1任务通过执行以下操作来进行应用部署:-
将服务连接到
packt-store-cart私有注册表 -
使用相应的容器版本创建部署
-
通过检查其状态等待部署完成
让我们看看用于实现此操作的代码。你会注意到,代码已经分成了几个部分,方便理解:
-
以下几行简单地将参数输出到控制台,以确认它们的值,确保管道在运行时正确:
- task: AWSShellScript@1 displayName: 'Deploy cart container' inputs: awsCredentials: ${{ parameters.awsConnection }} regionName: ${{ parameters.location }} arguments: '${{ parameters.envName }}-cart ${{ parameters.location }} ${{ parameters.containerTag }}' disableAutoCwd: true workingDirectory: '$(Pipeline.Workspace)/cart-iac' failOnStandardError: true scriptType: 'inline' inlineScript: | # Confirm Parameters echo "SERVICE_NAME: $1" echo "AWS_REGION: $2" echo "CONTAINER_TAG: $3" -
添加私有注册表访问需要执行 CLI 命令并等待属性更新,以确认主 ARN 已被分配。这需要每 5 秒执行一次 CLI 命令来检查:
# Add Private Registry Access aws lightsail update-container-service --service-name $1 --region $2 --private-registry-access file://private-registry-access.json echo "Waiting for container service to be ready..." principal_arn="" until [ "$principal_arn" != "" ] do sleep 5 principal_arn=`aws lightsail get-container-services --service-name $1 --region $2 --query "containerServices[0].privateRegistryAccess.ecrImagePullerRole.principalArn" --output text` done echo "" echo "Principal ARN: $principal_arn" -
应用 弹性容器策略(ECR)需要删除任何现有的策略,然后设置新的策略:
# Apply ECR policy echo "Applying ECR policy..." sed "s|IamRolePrincipalArn|$principal_arn|g" ecr-policy-template.json > ecr-policy.json # redirect stderr to /dev/null to avoid error if policy does not exist aws ecr delete-repository-policy --repository-name packt-store-cart 2>/dev/null aws ecr set-repository-policy --repository-name packt-store-cart --policy-text file://ecr-policy.json # Wait until container service is ready for update state="UPDATING" until [ "$state" != "UPDATING" ] do sleep 5 state=`aws lightsail get-container-services --service-name $1 --region $2 --query "containerServices[0].state" --output text` done echo "" -
前面的部分将每 5 秒执行一次 CLI 命令,等待 Lightsail 服务完成更新,然后我们才能继续执行。这是必要的,因为我们之前执行的 CLI 命令用于分配私有注册表访问权限,需要一段时间才能完成。这样可以确保在服务仍在更新时不会尝试创建部署。
-
最后,在使用 CLI 命令创建部署并等待它在管道中完成时,确保任何需要服务运行的后续步骤不会失败:
# Create Deployment account_id=`aws sts get-caller-identity --query "Account" --output text` sed "s|SERVICENAME|$1|g ; s|AWSACCOUNTID|$account_id|g ; s|AWSREGION|$2|g ; s|CONTAINERTAG|$3|g" deployment-template.json > deployment.json echo "Creating deployment..." aws lightsail create-container-service-deployment --service-name $1 --region $2 --cli-input-json file://deployment.json state="DEPLOYING" until [ "$state" != "DEPLOYING" ] do sleep 5 state=`aws lightsail get-container-services --service-name $1 --region $2 --query "containerServices[0].state" --output text` done echo "" if [ "$state" == "RUNNING" ] then echo "Deployment created successfully!" else echo "Deployment failed!" exit 1 fi
-
既然我们已经完成了购物车服务的配置,接下来让我们继续处理结账服务。
将 .NET 结账服务部署到 ECS
为此,我们必须创建一个任务执行 IAM 角色。首先,让我们创建一个 ecs-tasks-trust-policy.json 文件,并填入以下内容:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
以下命令将创建一个 IAM 角色,并附加运行私有注册表中容器镜像所需的策略:
aws iam create-role --role-name ecsTaskExecutionRole --assume-role-policy-document file://ecs-tasks-trust-policy.json
aws iam attach-role-policy --role-name ecsTaskExecutionRole --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
完成这些配置后,我们可以继续进行 deploy.yml 文件的内容:
-
download任务用于获取结账 IaC 管道工件:- download: current displayName: 'Download checkout iac' artifact: checkout-iac -
CloudFormationCreateOrUpdateStack@1任务创建运行服务所需的基础设施:- task: CloudFormationCreateOrUpdateStack@1 displayName: 'Create checkout stack' inputs: awsCredentials: ${{ parameters.awsConnection }} regionName: ${{ parameters.location }} stackName: '${{ parameters.envName }}-checkout' templateSource: 'file' templateFile: '$(Pipeline.Workspace)/checkout-iac/template.json' templateParametersSource: 'inline' templateParameters: '[{"ParameterKey":"ContainerTag","ParameterValue":"${{ parameters.containerTag }}"}]' onFailure: 'DELETE' captureStackOutputs: 'asVariables' captureAsSecuredVars: false结账服务的 URL 将通过此部署的输出提供,该输出会被自动解析并作为环境变量提供,在此例中为
CheckoutUrl变量。
既然结账服务已完成,接下来是时候处理前端应用了。
将 Angular 前端部署到 Fargate
Angular 前端应用将部署到带有 Fargate 后端的 ECS 上,Fargate 是 AWS 提供的无服务器选项。此部署更为简化,因为它只需创建 CloudFormation 堆栈,其中包含要在模板中部署的容器定义。
让我们在 deploy.yml 文件中添加以下内容,并逐步演示:
-
download任务用于获取前端 IaC 管道工件:- download: current displayName: 'Download frontend iac' artifact: frontend-iac -
CloudFormationCreateOrUpdateStack@1任务创建运行服务和部署应用所需的基础设施:- task: CloudFormationCreateOrUpdateStack@1 displayName: 'Create frontend stack' inputs: awsCredentials: ${{ parameters.awsConnection }} regionName: ${{ parameters.location }} stackName: '${{ parameters.envName }}-frontend' templateSource: 'file' templateFile: '$(Pipeline.Workspace)/frontend-iac/template.json' templateParametersSource: 'inline' templateParameters: '[{"ParameterKey":"ContainerTag","ParameterValue":"${{ parameters.containerTag }}"}, {"ParameterKey":"CatalogUrl","ParameterValue":"$(CatalogUrl)"}, {"ParameterKey":"CartUrl","ParameterValue":"$(CartUrl)"}, {"ParameterKey":"CheckoutUrl","ParameterValue":"$(CheckoutUrl)"}]' onFailure: 'DELETE' captureStackOutputs: 'asVariables' captureAsSecuredVars: false注意在
templateParameters属性中为模板提供参数时使用的不同符号。这是由于这些值在管道执行上下文中的可用方式。在向任务注入值时,管道参数和变量之间存在区别,并且它们的评估方式不同。${{ parameters.name }}符号只会在编译时处理,在运行时开始之前。这是参数的典型用法,因为它们在运行时不应更改。$(variable)符号在任务运行前会在运行时进行处理,这意味着它会在每个任务执行之前进行评估;通过执行所做的任何更改都会反映在其值中。这是变量的典型用法。欲了解更多内容,请阅读了解变量语法,详细信息请参阅官方文档
learn.microsoft.com/en-us/azure/devops/pipelines/process/variables。
完成所有配置后,最终通过添加管道使其生效。
添加管道
现在我们已经完成所有 YAML 文件,接下来是通过添加一个新的管道让一切生效。请按照以下步骤操作:
- 在项目的 Pipelines 部分,点击 New pipeline 按钮,如下图所示:

图 10.3 – 添加管道
- 选择 Azure Repos Git YAML 选项:

图 10.4 – 从 Azure Repos Git YAML 添加管道
- 选择你创建流水线的仓库:

图 10.5 – 选择要添加 YAML 流水线的仓库
- 然后,选择现有的 Azure Pipelines YAML 文件选项:

图 10.6 – 选择现有的 Azure Pipelines YAML 文件选项
- 最后,输入
/e2e/pipelines/aws/aws-pipeline.yml,并点击继续:

图 10.7 – 选择现有的 YAML 文件
有了流水线后,你可以手动触发它或通过更改仓库来触发它。现在我们已经准备好了,让我们总结一下。
总结
如果你完成了所有这些步骤,那么你已经部署了测试和生产环境,接下来是清理工作!在整个章节中,你已经向 AWS 部署了许多资源,所以如果你不想继续为它们付费,请确保删除它们。你可以通过 AWS 控制台或以下 AWS CLI 命令来完成:
eksctl delete cluster -n test-catalog
aws cloudformation delete-stack --stack-name test-cart
aws cloudformation delete-stack --stack-name test-checkout
aws cloudformation delete-stack --stack-name test-frontend
eksctl delete cluster -n production-catalog
aws cloudformation delete-stack --stack-name production-cart
aws cloudformation delete-stack --stack-name production-checkout
aws cloudformation delete-stack --stack-name production-frontend
如果你错过了任何步骤或遇到问题,无法将整个解决方案整合在一起,可以在技术要求部分提到的 GitHub 仓库中找到流水线定义,这些定义位于e2e/pipelines/aws目录下的complete分支。
现在,让我们回顾一下本章中学到的内容。
总结
在本章中,我们学习了如何将容器化应用部署到 AWS 云中的不同服务。同时,我们了解了容器如何实现跨云服务商的可移植性,以及如何在同一生态系统内利用多个服务的能力。
接下来,我们学习了如何使用 AWS ECR 和私有仓库来管理所有容器镜像,并且虽然构建和推送这些容器的过程基于相同的docker-compose工具,但根据目标平台的不同,必须以不同的方式实现。
我们还学习了 eksctl CLI 工具,它使得在 AWS 中根据最佳实践更容易地配置和管理 EKS 集群,以及如何使用 Helm charts 将容器化应用部署到基于 Kubernetes 的服务,而不考虑底层基础设施。
最后,我们学习了如何使用 Fargate(无服务器)和 EC2(虚拟机)基础设施将应用部署到 ECS,两者的应用部署模型非常相似且简单。
在下一章,你将学习 跨平台移动应用 的 CI/CD。
第十一章:使用 Flutter 自动化跨平台应用程序的 CI/CD
在上一章中,我们学习了如何创建一个管道来在 AWS 上部署一个容器化的 Web 应用程序。本章将深入探讨如何创建一个管道来自动化 Flutter 移动应用程序的 CI/CD。Flutter 是最著名的移动应用程序开发工具包。开发人员可以仅使用 Flutter 代码编写移动应用,而无需使用 Google 的 Kotlin 代码或 Apple 的 Swift 代码,Azure 管道可以构建并将 Flutter 代码部署到 Google 和 Apple 商店。这些商店是移动应用程序领域中使用最广泛的商店。在本章结束时,您将学会如何使用 YAML 创建一个管道,将 Flutter 应用程序部署到 Google Firebase、Google Play 控制台以及 Apple 环境中。
我们将涵盖以下主题:
-
解释解决方案架构
-
为 Flutter 实现 Google Firebase
-
为 Flutter 实现 Apple 环境
-
为 Flutter 实现 Google Play 控制台环境
-
应对常见挑战
技术要求
您可以在 github.com/PacktPublishing/Implementing-CI-CD-Using-Azure-Pipelines/tree/main/ch11 找到本章的代码。
为完成本章中描述的任务,您需要执行以下操作:
-
按照 Firebase 官方指南中的说明创建 Firebase 账户并创建 Firebase 项目:
firebase.google.com/docs/guides -
按照官方 Flutter 指南中的说明设置 Flutter:
docs.flutter.dev/get-started/install -
从本书的 GitHub 仓库中下载 Flutter 代码示例:
github.com/PacktPublishing/Implementing-CI-CD-Using-Azure-Pipelines -
按照 Google 官方指南中的说明,通过 Google Play 控制台创建并设置您的应用:
support.google.com/googleplay/android-developer/answer/9859152?hl=en -
按照 Apple 官方指南中的说明,在 Apple Developer 中创建并设置您的应用:
developer.apple.com/help/app-store-connect/create-an-app-record/add-a-new-app/
解释解决方案架构
以下是解决方案图,展示了在 Azure Pipelines 工作流中,如何构建 Flutter 代码并将其部署到 Google Firebase、Google Play 控制台和 Apple Store Connect:

图 11.1 – 解决方案图
我们将为开发和生产环境中的 Flutter 创建三个流水线,如前图所示,因为移动应用程序需要在内部用户或客户环境(如 Google Firebase)中进行测试,然后再部署到 Google 和 Apple 商店的生产环境。解决方案图解如下步骤:
-
开发人员在他们的机器上使用 Flutter 开发和测试移动应用程序,并将 Flutter 代码推送到 Azure Repos。
-
在将 Flutter 代码上传到 Azure Repos 后,Azure Repos 将触发一个流水线来构建 Flutter 代码,并将 Flutter 应用程序部署到 Google Firebase。
-
在使用 Google Firebase 测试 Flutter 应用程序后,开发人员将触发另一个流水线来构建 Flutter 代码,并将 Flutter 应用程序部署到 Google Play 商店。
-
在 Google Play 商店上测试 Flutter 应用程序后,开发人员将触发另一个流水线来构建 Flutter 代码并将 Flutter 应用程序部署到 Apple Store Connect。
在构建我们的 Flutter 移动应用程序并将其部署到 Google 和 Apple 商店之前,我们需要生成并上传特定的安全文件。让我们接下来看看这些文件。
管理安全文件
在构建我们的 Flutter 移动应用程序并将其部署到 Google 和 Apple 商店之前,我们需要上传所有必需的文件,告诉 Google 和 Apple 商店你的身份,以便在接受移动应用程序之前检查你的开发者配置文件。要做到这一点,请转到流水线 | 库 | 安全文件部分,并上传以下安全文件以构建和部署 Flutter 应用程序:
-
根据提供的说明在
developer.apple.com/help/account/create-certificates/create-developer-id-certificates创建一个 Apple 证书文件 (distribution.cer) -
根据
developer.apple.com/help/account/manage-provisioning-profiles/create-an-app-store-provisioning-profile中的说明创建一个供应配置文件 (Hello_Flutter_AppStore.mobileprovision) -
你可以从 Apple Store Connect 的
appstoreconnect.apple.com/access/api下载 API 密钥文件 (AuthKey_XXXXXXX.p8),如下截图所示:

图 11.2 – 生成 API 密钥
上传这些文件后,你将看到类似以下的屏幕:

图 11.3 – 安全文件
以下步骤将向您展示如何为 Flutter 应用程序部署准备前述截图中显示的所有安全文件:
-
Certificates_Distribution.p12):对于 Mac,导航至distribution.cer)以将其导出为另一种格式的文件,称为Certificates_Distribution.p12。 -
upload-keystore.jks):运行以下命令以生成一个密钥库文件:keytool -genkey -v -keystore upload-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias upload -
使用 Azure DevOps 门户的 Library 选项创建一个秘密组:

图 11.4 – 添加一个新的变量组
- 为 Google Firebase、Google Play 控制台和 App Store 上的 Android 和 iOS 部署创建变量:

图 11.5 – 添加所有变量
让我们详细讨论一下这些变量:
- ANDROID_APP_ID:导航到 Google Firebase 项目页面,您将找到 Android 项目的应用 ID:

图 11.6 – Android 项目的应用 ID
-
data.b64文件:keytool -genkey -v -keystore upload-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias upload upload. -
upload-keystore.jks。这是一个Java KeyStore(JKS)文件,用于签署 Android 应用。 -
ANDROID_KEYSTORE_PASSWORD:在使用 步骤 2 中生成的密钥库文件时输入的密码。
-
ANDROID_KEYSTORE_PRIVATE_PASSWORD:使用密钥库文件时输入的密码。
-
APPLE_CERTIFICATE_SIGNING_PASSWORD:导出 P12 文件时的密码。
-
FIREBASE_TOKEN:运行以下命令以生成它,然后可以复制命令行结果中的 Firebase 令牌:
.p8 extension:

图 11.7 – 生成 API 密钥
- 接下来,确定 Apple 应用部署所需的 Issuer ID、KEY ID 和 API Key 文件:

图 11.8 – 查找发行者 ID、密钥 ID 和 API KEY 文件
-
运行以下命令将
.p8文件转换为编码字符串,并将编码字符串的值复制到名为 AUTH_KEY_P8 的变量中:certutil -encode AuthKey_XXXX.p8 tmp.b64 && findstr /v /c:- tmp.b64 > data.b64 -
对于 IOS_APP_ID,导航到 Google Firebase 项目页面,在您的 Apple 项目部分下可以找到 App ID:

图 11.9 – Apple 项目的应用 ID
在准备好所有必需的变量和文件之后,您可以开始创建管道,以将 Flutter 应用构建并部署到 Google Firebase、Google Play 控制台和 App Store。然而,在学习如何实现之前,让我们先讨论一些用于将 Flutter 应用部署到 Google Firebase、Apple 环境和 Google Play 的关键任务。
在 Google Firebase、Apple 和 Google Play 控制台上部署 Flutter 应用所需的任务
这里是一些所需的关键任务:
-
JavaToolInstaller@0 (
learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/java-tool-installer-v0?view=azure-pipelines): 该包任务用于设置 Java 编译器。当你需要构建 Flutter 移动应用时,这是一个必需的包任务。 -
Hey24sheep (
marketplace.visualstudio.com/items?itemName=Hey24sheep.flutter): 该包任务用于设置 Flutter 编译器。当你需要构建 Flutter 移动应用时,这是一个必需的包任务。 -
CopyFiles@2 (
learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/copy-files-v2?view=azure-pipelines&tabs=yaml): 该包任务用于运行命令以复制文件。 -
工件。 -
Bash@3 (
learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/bash-v3?view=azure-pipelines): 该包任务用于在 Bash shell 脚本中运行命令。 -
InstallAppleCertificate@2 (
learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/install-apple-certificate-v2?view=azure-pipelines): 该包任务用于安装 Apple 证书文件,这些文件用于验证 Flutter 应用程序,以便部署到 Apple Store Connect。 -
InstallAppleProvisioningProfile@1 (
learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/install-apple-provisioning-profile-v1?view=azure-pipelines): 该包任务用于安装 Apple 配置文件文件,这个文件用于验证开发者,确保其开发的 Flutter 应用程序能够成功部署到 Apple Store Connect。 -
AppStoreRelease@1 (https://marketplace.visualstudio.com/items?itemName=ms-vsclient.app-store#app-store-release): 该包任务用于将 Flutter 应用程序部署到 Apple Store Connect。
-
AndroidSigning@3 (
learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/android-signing-v3?view=azure-pipelines): 该包任务用于在将 Android 包或 APK 文件部署到 Google Play 控制台之前进行签名。 -
GooglePlayRelease@4 (
marketplace.visualstudio.com/items?itemName=ms-vsclient.google-play):此软件包任务用于将 Flutter 应用部署到 Google Play 控制台。 -
DownloadSecureFile@1 (
learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/download-secure-file-v1?view=azure-pipelines):此软件包任务用于从 Variable groups 菜单的 Secure files 部分下载安全文件。在我们的例子中,它是一个签名文件,用于在将 Flutter 应用部署到 Google Play 控制台之前对其进行签名。
在下一节,你将学习如何为 Flutter 实现 Google Firebase。
为 Flutter 实现 Google Firebase
要在 Android 和 iOS 上将 Flutter 应用构建并部署到 Google Firebase,你需要使用上一节中讨论的任务。首先,我们将学习如何为 Android 创建一个构建和部署 Flutter 应用到 Google Firebase 的管道。
为 Android 创建 Google Firebase 管道
要创建 Android 管道,我们首先需要准备一个 Ubuntu 环境并安装 Flutter 编译器。之后,它会将你的代码构建成一个二进制文件并上传到 Google Firebase App Distribution。你可以按照以下步骤操作:
-
创建一个名为
azure-pipeline-for-firebase-android.yml的 Android 管道文件,并粘贴以下代码片段:-
YAML 文件的第一部分是为整个 Azure 管道准备所有变量。它还声明了构建该文件的操作系统,在此场景中为 Ubuntu:
trigger: none pool: vmImage: "ubuntu-latest" variables: - group: secrets - name: androidReleaseDir value: $(build.artifactStagingDirectory)/flutter/hello_world/build/app/outputs/flutter-apk - name: apkFile value: $(androidReleaseDir)/app-release.apk -
这个任务是用于预先安装 Java 库,以构建你的代码:
jobs: - job: android_deployment steps: - task: JavaToolInstaller@0 inputs: versionSpec: '11' jdkArchitectureOption: 'x64' jdkSourceOption: 'PreInstalled' -
这个任务是用于预先安装 Flutter 编译器,以构建你的代码:
- task: Hey24sheep.flutter.flutter-install.FlutterInstall@0 displayName: 'Flutter Install' inputs: version: custom customVersion: 3.10.6 -
这个任务是用于构建你的代码并创建 APK 文件:
- task: Hey24sheep.flutter.flutter-build.FlutterBuild@0 displayName: "Build APK" inputs: target: apk projectDirectory: "./flutter/hello_world" buildNumber: "" -
这个任务是用于签名 APK 文件,以允许 Android 手机启动你的应用:
- task: AndroidSigning@3 displayName: "Signing and aligning APK file(s) **/*.apk" inputs: apkFiles: "**/*.apk" apksign: true apksignerKeystoreFile: "upload-keystore.jks" apksignerKeystorePassword: "$(ANDROID_KEYSTORE_PRIVATE_PASSWORD)" apksignerKeystoreAlias: "$(ANDROID_KEYSTORE_ALIAS)" apksignerKeyPassword: "$(ANDROID_KEYSTORE_PASSWORD)" -
这个任务是将已签名的 APK 文件复制到制品目录,在上传到 Azure Pipelines 存储之前:
- task: CopyFiles@2 displayName: "Copy apk to artifact directory" inputs: contents: "**/*.apk" targetFolder: "$(build.artifactStagingDirectory)" -
这个任务是用于将已签名的 APK 文件发布到 Azure Pipelines 存储:
- task: PublishBuildArtifacts@1 displayName: "Publish signed apk as artifact" inputs: artifactName: "drop" -
这个任务是将已签名的 APK 文件从 Azure Pipelines 存储上传到 Google Firebase App Distribution,并让所有用户下载进行测试:
- task: Bash@3 displayName: "Upload to firebase app distribution" inputs: targetType: "inline" script: | npm i -g firebase-tools ls -la $(androidReleaseDir) firebase appdistribution:distribute "$(apkFile)" \ --app "$(ANDROID_APP_ID)" \ --release-notes "Build Android From Azure Pipeline" \ --groups "beta-testers" \ --token "$(FIREBASE_TOKEN)"
-
-
对于 Android,当管道构建成功时,你可以在 Google Firebase 的 App Distribution 部分看到结果:

图 11.10 – Android 项目的应用分发
在下一节,我们将学习如何在 iOS 上完成此操作。
为 iOS 创建 Google Firebase 管道
要创建一个 iOS 管道,我们首先准备一个 macOS 环境并安装 Flutter 编译器。之后,它将把你的代码构建成二进制文件并上传到 Google Firebase 分发:
-
创建一个名为
azure-pipeline-for-firebase-ios.yml的管道文件,并粘贴以下代码片段:-
YAML 文件的第一部分用于准备将在整个 Azure 管道中使用的所有变量。它还声明了用于构建此文件的操作系统,即最新版本的 macOS:
trigger: none pool: vmImage: "macos-latest" variables: - group: secrets - name: iosReleaseDir value: $(Build.SourcesDirectory)/flutter/hello_world/build/ios/ipa - name: ipaFile value: $(iosReleaseDir)/hello_world.ipa - name: rootPath value: $(System.DefaultWorkingDirectory)/flutter/hello_world -
该任务用于预安装用于构建代码的 Java 库:
jobs: - job: ios_deployment steps: - task: JavaToolInstaller@0 inputs: versionSpec: '11' jdkArchitectureOption: 'x64' jdkSourceOption: 'PreInstalled' -
该任务用于安装构建 iOS 应用程序所需的 Apple 证书:
- task: InstallAppleCertificate@2 displayName: "Install Apple cert dist p12" inputs: certSecureFile: "Certificates_Distribution.p12" certPwd: "$(APPLE_CERTIFICATE_SIGNING_PASSWORD)" keychain: "temp" -
该任务用于安装 Apple 配置文件,这对于构建 iOS 应用程序是必需的:
- task: InstallAppleProvisioningProfile@1 displayName: "Install Apple Mobile Provisioning Profile" inputs: provisioningProfileLocation: "secureFiles" provProfileSecureFile: "Hello_Flutter_AppStore.mobileprovision" -
该任务用于安装 Flutter 编译器以构建你的代码:
- task: Hey24sheep.flutter.flutter-install.FlutterInstall@0 displayName: 'Flutter Install' inputs: version: custom customVersion: 3.10.6 -
该任务用于构建你的代码并创建 iOS 应用商店包 (IPA) 文件:
- task: Bash@3 displayName: "Build IPA" inputs: targetType: "inline" script: | flutter build ipa --export-options-plist=$(rootPath)/ios/Runner/ExportOptions.plist workingDirectory: $(rootPath) -
该任务用于将 IPA 文件上传到 Google Firebase 应用分发:
- task: Bash@3 displayName: "Upload to firebase app distribution" inputs: targetType: "inline" script: | npm i -g firebase-tools ls -la $(iosReleaseDir) firebase appdistribution:distribute "$(ipaFile)" \ --app "$(IOS_APP_ID)" \ --release-notes "Build iOS From Azure Pipeline" \ --groups "beta-testers" \ --token "$(FIREBASE_TOKEN)"
-
-
类似于 Android,当管道构建成功时,你可以在 Google Firebase 的 App Distribution 部分看到结果:

图 11.11 – Apple 项目的应用分发
本节讨论了如何在 Google Firebase 上构建和部署 Flutter 应用程序,以便在将其部署到 Google Play 控制台或应用商店之前进行测试。接下来,我们将讨论如何在 Apple Store Connect 上创建和部署 Flutter 应用程序。
为 Flutter 实现 Apple 环境
要在 Apple Store Connect 上构建和部署 Flutter 应用程序,你需要使用 Azure 管道中的各种任务,正如本章前面讨论的那样。为了减少移动应用程序部署过程所需的时间,有必要构建 Apple Store 部署管道。为此,你可以按照以下步骤操作:
-
创建一个名为
azure-pipeline-for-apple-store.yml的管道文件,并粘贴以下代码片段:-
YAML 文件的第一部分用于准备将在整个 Azure 管道中使用的所有变量。它还声明了用于构建此文件的操作系统,即最新版本的 macOS:
trigger: none pool: vmImage: "macos-latest" variables: - group: secrets - name: iosReleaseDir value: $(Build.SourcesDirectory)/flutter/hello_world/build/ios/ipa - name: ipaFile value: $(iosReleaseDir)/hello_world.ipa - name: rootPath value: $(System.DefaultWorkingDirectory)/flutter/hello_world -
该任务用于预安装用于构建代码的 Java 库:
jobs: - job: ios_to_apple_store steps: - task: JavaToolInstaller@0 inputs: versionSpec: '11' jdkArchitectureOption: 'x64' jdkSourceOption: 'PreInstalled' -
该任务用于安装构建 iOS 应用程序所需的 Apple 证书:
- task: InstallAppleCertificate@2 displayName: "Install Apple cert dist p12" inputs: certSecureFile: "Certificates_Distribution.p12" certPwd: "$(APPLE_CERTIFICATE_SIGNING_PASSWORD)" keychain: "temp" -
该任务用于安装 Apple 配置文件,这对于构建 iOS 应用程序是必需的:
- task: InstallAppleProvisioningProfile@1 displayName: "Install Apple Mobile Provisioning Profile" inputs: provisioningProfileLocation: "secureFiles" provProfileSecureFile: "Hello_Flutter_AppStore.mobileprovision" -
该任务用于安装 Flutter 编译器以构建你的代码:
- task: Hey24sheep.flutter.flutter-install.FlutterInstall@0 displayName: 'Flutter Install' inputs: version: custom customVersion: 3.10.6 -
该任务用于构建你的代码并创建一个 IPA 文件:
- task: Bash@3 displayName: "Build IPA" inputs: targetType: "inline" script: | flutter build ipa --export-options-plist=$(rootPath)/ios/Runner/ExportOptions.plist workingDirectory: $(rootPath) -
该任务用于将签名的 IPA 文件上传到 Apple Store Connect:
- task: AppStoreRelease@1 displayName: "Upload to App Store Connect" inputs: authType: 'ApiKey' apiKeyId: '$(IOS_APP_API_KEY)' apiKeyIssuerId: '$(IOS_APP_API_ISSUER)' apiToken: '$(IOS_AUTH_KEY_P8)' releaseTrack: 'TestFlight' appIdentifier: 'com.company.flutter' appType: 'iOS' ipaPath: $(ipaFile) shouldSkipWaitingForProcessing: true shouldSkipSubmission: true appSpecificId: '$(IOS_APP_ID)'
-
-
当管道构建成功时,你可以在 App Store Connect | TestFlight 标签页中看到结果:

图 11.12 – App Store Connect 上的 TestFlight
在本节中,你学习了如何创建一个管道,以便在 Apple Store Connect 上构建和部署 Flutter 应用程序。在下一节中,你将学习如何在 Google Play 控制台上构建和部署 Flutter 应用程序。
为 Flutter 实现 Google Play 控制台
当你需要将应用程序上传到 Google Play 商店时,你可以创建一个 Azure 管道。Google Play 商店是一个 Android 应用程序市场,允许所有 Android 用户下载应用。按照以下步骤操作:
-
创建一个名为
azure-pipeline-for-google-play-store.yml的管道文件,并粘贴以下代码片段:-
YAML 文件的第一部分用于准备将在整个 Azure 管道中使用的所有变量。它还声明了用于构建该文件的操作系统,即 Ubuntu:
trigger: none pool: vmImage: "ubuntu-latest" variables: - group: secrets - name: androidReleaseDir value: $(build.artifactStagingDirectory)/flutter/hello_world/build/app/outputs/bundle/release - name: aabFile value: $(androidReleaseDir)/app-release.aab -
这个任务是下载 keystore 文件,这是签署Android 应用包(AAB)文件所必需的,签署完成后可以将其上传到 Google Play 控制台:
jobs: - job: android_to_google_play_store steps: - task: DownloadSecureFile@1 displayName: "Download keystore file" name: "KeyStoreFile" inputs: secureFile: "upload-keystore.jks" -
这个任务是预先安装用于构建代码的 Java 库:
- task: JavaToolInstaller@0 inputs: versionSpec: '11' jdkArchitectureOption: 'x64' jdkSourceOption: 'PreInstalled' -
这个任务是安装 Flutter 编译器以构建你的代码:
- task: Hey24sheep.flutter.flutter-install.FlutterInstall@0 displayName: 'Flutter Install' inputs: version: custom customVersion: 3.10.6 -
这个任务是用于在 AAB 文件中构建 Flutter 代码:
- task: Hey24sheep.flutter.flutter-build.FlutterBuild@0 displayName: "Build AAB" inputs: target: aab projectDirectory: "./flutter/hello_world" buildNumber: "" -
这个任务是将 AAB 文件从源文件夹复制到构建产物文件夹:
- task: CopyFiles@2 displayName: "Copy aab to artifact directory" inputs: contents: "**/*.aab" targetFolder: "$(build.artifactStagingDirectory)" -
这个任务是将已签名的 AAB 文件移到 Azure Pipelines 存储:
- task: PublishBuildArtifacts@1 displayName: "Publish signed AAB as artifact" inputs: artifactName: "drop" -
这个任务是将 AAB 文件上传到 Google Play 商店:
- task: GooglePlayRelease@4 displayName: "Upload to Google Play Store" inputs: serviceConnection: 'GooglePlayConsole' applicationId: 'com.company.flutter' action: 'SingleBundle' bundleFile: '$(aabFile)' track: 'internal' isDraftRelease: true
-
-
当管道构建成功时,你可以在 Google Play 控制台查看结果:

图 11.13 – 在 Google Play 控制台上的内部测试
在本节中,你学习了如何创建一个管道,使用 Google Play 控制台的内部测试来构建和部署 Flutter 应用程序。这将帮助你在完成管道设置后专注于应用程序开发。
解决常见挑战
在 Flutter 开发过程中,你可能会遇到一些常见问题,如果遵循一些最佳实践,这些问题是可以避免的。让我们讨论其中的一些:
-
pubspec.yaml:此文件包含你在 Flutter 开发中使用的依赖项的名称,例如数据库依赖项。 -
pubspec.lock:此文件包含你在 Flutter 开发中使用的依赖项的名称和版本。
确保你将pubspec.lock提交到 Azure Repos,因为如果你错过这一步,你会遇到错误。原因是你将获得一个新的pubspec.lock文件,其中包含与你在本地开发 Flutter 应用时所使用的依赖项版本不同的版本。
-
处理平台特定代码:Flutter 允许你编写平台特定的代码,但在 CI/CD 管道中管理这些代码可能会很棘手,特别是当你的应用程序具有自定义的特定平台模块或依赖项时。
在 Azure Pipelines 中使用独立任务来处理特定平台的构建。例如,你可能会为 iOS 和 Android 构建设置不同的阶段或任务。确保每个平台所需的 SDK 和工具已经在你的 Azure Pipelines 环境中安装和配置好。
现在你已经为 Flutter 开发做好了充分准备,让我们总结一下这一章的内容。
总结
本章教你如何为 Flutter 应用程序创建构建管道。内容包括 Google Firebase 的管道——无论是 Android 还是 Apple。还涉及了 Apple Store Connect 和 Google Play Console 部署的管道。这将有助于减少开发人员在构建和部署 Flutter 应用程序时所需的手动命令时间。同时,这也帮助开发人员更专注于开发移动应用程序,因为你只需创建一次管道,让 Azure 管道自动构建你的代码并将应用程序部署到 Google Firebase、Apple Store Connect 和 Google Play Store。这将提升开发人员的生产力。
在上一章中,我们将学习在使用 Azure Pipelines 时需要避免的一些常见陷阱,并讨论该技术的潜在未来应用和趋势。
第十二章:探索 Azure Pipelines 中的常见陷阱和未来趋势
在上一章中,我们学习了如何为移动应用程序创建管道。本章将教你如何避免常见错误,更重要的是,如何高效地高层次地解决和绕过这些错误。
我们还将讨论最佳实践,提供有关优化工作流以实现最大效率和可靠性的见解。这将帮助确保你的管道以其最大潜力运行。
最后,我们将探讨 Azure Pipelines 的未来。通过本章的学习,你将了解如何避免使用 Azure Pipelines 时的常见陷阱,如何通过最佳实践优化工作流,以及如何适应这个至关重要工具不断发展的未来。
本章将涵盖以下主题:
-
常见陷阱
-
最佳实践
-
未来趋势
常见陷阱
理解并主动解决 Azure Pipelines 中某些常见错误和问题是至关重要的。通过学习如何快速识别和解决这些问题,你可以节省宝贵的时间和资源。
在实际项目中,及时解决问题至关重要,以防止延误和昂贵的挫折,从而保持项目的进度,并将精力集中在其他关键方面。
让我们讨论一下这些常见错误的详细情况:
- 有时候,你可能会启动一个管道运行并看到它被排队。这可能是因为在 Microsoft 托管的代理或自托管的代理上没有可用的代理、并发性不足或代理需求未满足。你可以在看到管道中作业的状态后立即看到错误,如下图所示:

图 12.1 – 由于代理不可用而排队的作业
这可能表明必须购买额外的并行作业,以确保这些作业不被排队,如果你希望这些管道尽可能快地运行。
-
一个管道可能无法运行,因为你在 YAML 文件中输入了错误的分支进行过滤,或者禁用了触发器。你需要回到 YAML 文件中,检查 branch 部分下的值,以确保它们是正确的。
-
如果由于变量组或机密文件的访问权限问题,管道无法运行。为了解决此问题,请导航到 Library | secrets | Variable group | Pipeline permissions:

图 12.2 – 转到管道权限
然后,你可以执行以下操作之一:
- 为所有管道打开所有访问权限。此选项将允许所有管道访问变量组中的机密。如果所有管道都需要使用相同的机密,则应选择此选项:

图 12.3 – 授予变量组开放访问权限
- 限制对选定管道的访问(推荐):

图 12.4 – 限制对变量组的权限,仅允许选定的管道访问
此选项将限制仅允许所需的管道访问秘密,因为否则,任何人都可以访问你的管道并使用你的秘密。
在本节中,你学到了如何避免常见错误,以及如何缓解这些错误。在下一节中,你将学到更多关于遵循最佳实践的内容。
最佳实践
遵循最佳实践的重要性在你遇到管道生命周期中的问题时会变得显而易见。它们不仅能帮助你的团队轻松维护管道,还能迅速识别并解决可能出现的任何问题。
以下是一些有助于团队轻松发现问题的实践:
-
使用 YAML 语法,而不是经典版本。YAML 可以在如 Azure Repos、GitHub 和 Bitbucket 等仓库中进行版本控制,这有助于跟踪 YAML 文件的更改,并在结合分支策略时逐步引入更改。更好的是,如果你想强制执行这一点,可以在组织或项目设置级别禁用创建经典构建管道。
-
当有许多任务在各个阶段重复时,将常见任务分离到单独的 YAML 文件中,并在主模板中引用这些文件。这样可以更轻松地维护或更改大规模多阶段配置中的管道行为。
-
如果你有长时间运行的作业,考虑创建更多的阶段。
-
保持管道简洁且聚焦,使用模板、阶段、作业和步骤。
-
构建大型和复杂应用程序的阶段可能需要更多的资源,以便更快地编译和打包。微软托管的代理运行在通用虚拟机上,Linux 和 Windows 系统的配置为双核 CPU、7 GB 内存和 14 GB SSD,macOS 系统为三核 CPU、14 GB 内存和 14 GB SSD。如果你需要提高构建性能,应该考虑使用自托管代理,这些代理运行在资源更多的虚拟机上。
-
考虑使用支持增量编译的编程语言。可以利用部分增量构建的应用程序项目只能托管在自托管代理中,从而确保每次构建后不会丢弃文件系统资源。
-
使用变量组来减少重复,当多个管道使用相同的值时。这使得你的管道更易于维护。
-
使用秘密值来处理私密值或密钥。导航至 库 | 秘密 | 变量组,点击锁定图标限制对变量的访问,如下截图所示:

图 12.5 – 创建秘密变量
- 如果你有来自 Azure 密钥保管库(KV)的变量,Azure 的 密钥管理服务(KMS)将加密并安全存储密钥。它会确保所有变量都是安全的。一旦完成,未经你允许,任何人都无法读取你的密钥。建议将所有来自 KV 的变量链接到变量组,如下所示:

图 12.6 – 从 Azure KV 链接机密
-
考虑使用最小权限原则,这是一个安全概念,要求只授予用户执行工作所需的最少访问权限。该原则用于限制用户的访问权限,只允许他们进行完成工作所必需的操作,最小化恶意活动或意外损坏的风险,减少使用错误管道的风险。
-
若要调查失败管道的问题,你可以直接从管道中下载日志。只需按照以下步骤操作即可:
-
选择你需要下载日志的管道。
-
点击三个点的图标,然后下载日志:
-

图 12.7 – 下载管道日志
-
如果你使用
PublishBuildArtifacts@1,并且需要检查工件,你可以直接从管道中下载工件,方法是按照以下步骤操作:- 选择一个管道,并点击相关列中的值,# 已发布:

图 12.8 – 管道工件
- 点击文件下载它:

图 12.9 – 从工件下载文件
-
为了优化成本,你可以调整 工件 的保留策略,以减少存储大小:
-
转到项目设置 | 设置。
-
根据你的业务目标调整保留策略的参数。例如,如果你的业务计划要求仅保留 Azure 管道结果五天,以实现存储的成本优化,那么遵循此时间框架是非常必要的。保持数据的时间过长将导致额外的存储成本:
-

图 12.10 – 保留策略
-
考虑验证你有多少个并行作业。这将帮助你更好地了解你可以在管道的并行模式下运行多少个进程:
-
转到项目设置 | 并行作业。
-
你可以查看你所有代理的概览:
-

图 12.11 – 并行作业
在本节中,你学习了如何基于最佳实践优化 Azure 管道。在本章的最后一节中,你将了解更多关于 Azure 管道的未来发展。
未来趋势
Azure DevOps 是一个不断发展的产品,因此经常会推出新功能和新能力,通常是因为微软产品团队希望改进产品,或者是因为用户的需求。
让我们来看看与 Azure Pipelines 相关的一些最新功能:
-
改进 Azure Pipelines 任务的创作体验,以支持更新版本的 Node.js,并简化向新版本过渡的过程,使您和其他社区贡献者能够创建自己的扩展。
-
GitHub Advanced Security for Azure DevOps 是一套新的安全工具,旨在提升DevSecOps实践。其功能之一是能够扫描您的代码,以防止常见的漏洞场景,使用专门的任务来检测跨站脚本、SQL 或 XML 注入攻击等常见问题,这些问题开发者往往容易忽视。这些功能支持多种编程语言,并将不断发展,以在未来提供更多支持。
什么是 DevSecOps?
DevSecOps 是将 DevOps 和安全实践全面集成到软件开发生命周期中的一种方式,它促使团队紧密合作,将安全性融入开发过程的各个方面。这可以尽早减少和消除与代码和第三方依赖中的漏洞相关的风险。
- 缩小 YAML 管道与经典发布管理部署管道之间在 CD 场景中的功能差距。为改善检查功能,明年将发布几个新功能。
展望未来,还有其他功能将在您扩展 Azure Pipelines 使用时变得重要:
-
产品内推荐,强调在配置管道时围绕安全性的最佳实践
-
停用和移除不再支持的 Node.js 版本,这些版本包含在 Microsoft 托管和自托管版本的代理软件中
Azure DevOps 功能路线图可以在learn.microsoft.com/en-us/azure/devops/release-notes/features-timeline查看,您可以在这里看到所有正在进行或计划在近期和长期发布的产品功能。如果某个功能正在开发中并计划发布,您将看到该功能的预计发布日期,包含其在云服务或产品服务器版中的可用性。您可以通过门户网站提出建议,从而影响产品的未来,正如下图所示:

图 12.12 – 提出改进 Azure DevOps 的建议
现在我们已经看了一些 Azure Pipelines 的未来趋势,让我们总结一下。
总结
本章是你旅程中的一个关键步骤,使你能够自信并熟练地使用 Azure Pipelines。你现在更能识别并应对常见的陷阱和挑战,从而节省宝贵的资源并保持流畅的工作流程。
遵循最佳实践将帮助优化管道的功能,并增强团队协作,确保管道操作的高效性和可靠性。我们还展示了 Azure Pipelines 的未来发展,帮助你适应并在这个动态领域保持领先。
拥有本书中所获得的知识,你现在可以高效且精准地在 Azure Pipelines 环境中操作,将应用部署到不同的场景和需求中。
祝你在旅程中好运!


浙公网安备 33010602011771号