Azure-DevOps-解决方案实现指南-全-

Azure DevOps 解决方案实现指南(全)

原文:annas-archive.org/md5/5194d46360126922de6c6aa72f8a3efc

译者:飞龙

协议:CC BY-NC-SA 4.0

序言

随着 IT 产品创建和交付的速度在企业成功中的重要性不断提升,具备全面理解 DevOps 的 IT 专业人才需求逐年增加。在本书《实施 Azure DevOps 解决方案》中,你将学习到帮助实现这一目标的微软工具。此外,你所获得的知识将帮助你拥抱 DevOps,并改进持续交付价值给最终用户的方式。

本书适合的人群

本书面向有兴趣在 Azure 云上实施 DevOps 实践的软件开发人员和运维专家。应用程序开发人员和具备一定软件开发经验的 IT 专业人士也会发现本书非常有用。对 Azure DevOps 基础使用的熟悉程度将是一个额外的加分项。

此外,最棒的是,你还可以将本书作为 AZ-400 考试的参考资料之一,因为所涉及的主题与考试内容非常相似。

本书的内容

本书分为四个部分。各章节按推荐的阅读顺序排列,每一章都是在前一章的基础上进行扩展。章节的顺序是与在现实世界中实施不同方面的顺序相匹配的。然而,完成第一部分后,你也可以根据需要选择性阅读其他部分。

第一章,DevOps 简介,为你提供了对 DevOps 的理解,包括它是什么以及它不是什么。定义了 DevOps,并描述了它与敏捷工作方式的关系。本章最后介绍了 DevOps 实践和习惯,这些是 DevOps 文化的核心。

第二章,一切从源代码管理开始,介绍了不同类型的源代码管理系统及其比较。拉取请求和代码审查作为确保每个更改在成为源代码一部分之前都会经过审查的默认方式进行了介绍。本章最后回顾了与持续流动价值的工作方式高度契合的分支和合并策略。

第三章,迁移到持续集成,介绍了如何从源代码到构建工件,以便稍后部署。Azure Pipelines 作为实现这一目标的最重要方式进行了深入讨论。除了传统的可视化编辑器外,还介绍了 YAML 管道作为代码。

第四章,持续部署,讲述了如何使用 Azure Pipelines 来协调部署。再次介绍了经典的可视化发布编辑器。还介绍了多阶段的 YAML 管道,它作为一种方式,可以将部署过程描述为代码。本章最后介绍了不同的部署策略,用于减轻持续部署带来的风险,以平衡变更需求与稳定性。

第五章,依赖管理,介绍了包管理。包管理可以作为一种手段,将大型软件解决方案拆分为多个独立构建和测试的组件,然后再将其组合在一起。包还可以用来在不同的持续集成和部署产品之间分发构建工件。

第六章,基础设施和配置即代码,讲述了如何将创建、管理和配置基础设施的过程,从一个容易出错的手动任务转变为自动化的配置部署,这些配置被作为代码保存在源代码管理中。本章涵盖了 ARM 模板、Azure Blueprints、Azure App Configuration 服务以及一些相关工具。

第七章,数据库管理,深入探讨了如何在持续部署的背景下管理数据库架构。

第八章,持续测试,解释了如何将一切作为代码,持续部署新版本,如果新版本的质量不够高,则几乎没有价值。本章介绍了不同类型的测试,以及如何将它们集成到 Azure DevOps 管道中。

第九章,安全与合规,讲述了如何将安全性和合规性问题集成到 DevOps 实践中。你将学习如何将安全扫描和依赖扫描集成到管道中。引入了 Azure Policy 和 Security Center,用于防止不合规配置,并检测随着时间推移出现的新风险。

第十章,应用监控,是第一章讲述如何从先前部署的更改中学习。为此,使用 Azure Monitor 和 Application Insights 创建指标、仪表板和警报。

第十一章,收集用户反馈,同样涉及学习,但学习的对象是用户,而非系统。你将学习如何与用户互动,以推动你的产品路线图并最大化为用户创造的价值。假设驱动的开发方法被引入,作为一种减少低回报投资并发现需求高、价值大的功能的方式。

第十二章,容器,介绍了容器的相关话题。虽然 DevOps 和容器不是同义词,但容器有助于在那些可能无法实现 DevOps 原则的情况下,推动 DevOps 原则的采纳。

第十三章,规划您的 Azure DevOps 组织,深入讨论了有关设置 Azure DevOps 组织和项目的最终考虑事项。包括了许可和成本模型的内容,以及可追溯性的话题。另一个重要的主题是产品间迁移,以实现 DevOps 工具的标准化。

第十四章,AZ-400 模拟考试,为你提供了通过模拟考试测试你在本书中所学知识的机会。

为了充分利用本书的内容

你应该理解软件开发流程,并且具有作为软件开发人员或 IT 专业人员的应用程序开发和运维经验。无论你的背景是软件开发还是运维,了解软件交付流程及其相关工具的基本知识都非常重要。

如果你已经参加过模拟考试,或者在读完本书后打算参加正式考试,并发现自己在某些考试目标上有困难,可以使用下面的表格找到需要重新阅读的章节。

考试目标 相关章节
设计 DevOps 战略 第 1、8 和 13 章
实现 DevOps 开发流程 第 4、6 和 9 章
实现持续集成 第 2 和第三章
实现持续交付 第四章
实现依赖管理 第五章
实现应用程序基础设施 第 6、9 和 12 章
实现持续反馈 第 10 和第十一章

请记住,某些问题可能会涉及多个类别,而且本书的开发没有使用任何官方考试材料。

虽然本书的很多部分是理论内容,但如果你对这些概念没有实际操作经验,建议进行实践。记住,如果你打算参加 AZ-400 考试,这个考试是为有两到三年实际经验的从业人员设计的。为了完成书中的实践练习,你需要具备以下条件:

本书涵盖的软件/硬件 操作系统要求
Azure DevOps 服务 任何带有现代浏览器的设备
Azure 门户 任何带有现代浏览器的设备
Azure Powershell Windows 10
Azure CLI, Git 客户端 Windows、Linux 或 MacOS
Visual Studio Windows 或 MacOS

为了获得更多的实际操作经验,每章末尾都会提供与练习或实验室相关的链接。这些练习大多数来自 Microsoft Learn,也可以在docs.microsoft.com/en-us/learn/browse/?term=devops查找。微软还发布了一个云端工作坊,让你可以实践本书第 1 至第六章中涉及的许多主题。该云工作坊可以在github.com/microsoft/MCW-Continuous-delivery-in-Azure-DevOps/blob/master/Hands-on%20lab/Before%20the%20HOL.md找到。

下载彩色图像

我们还提供了一份包含本书中截图/图表的彩色图像的 PDF 文件。你可以在这里下载:www.packtpub.com/sites/default/files/downloads/9781789619690_ColorImages.pdf

本书使用的约定

本书中使用了许多文本约定。

CodeInText:表示文本中的代码词汇、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 账号。这里有一个示例:“在命令提示符下,输入hostname并按下Enter键。”

代码块如下所示:

public class FoodClassifier : IFoodClassifier
{
                public FoodClassification Classify(Food food)
                {
                                // Unchanged classification algorithm
                } 
}
public class FoodClassifierToBeRemoved : IFoodClassifer
{
    public FoodClassification Classify(Food food)
                {
                                // Unchanged classification algorithm
                } 
}

当我们希望引起你对代码块中特定部分的注意时,相关的行或项目会以粗体显示:

[default]
exten => s,1,Dial(Zap/1|30)
exten => s,2,Voicemail(u100)
exten => s,102,Voicemail(b100)
exten => i,1,Voicemail(s0)

任何命令行输入或输出都如下所示:

git lfs install
git lfs track "*.mp4"
git add .gitattributes

粗体:表示一个新术语、一个重要词汇,或者是你在屏幕上看到的词语。例如,菜单或对话框中的词汇在文本中会这样显示。这里有一个例子:“从管理面板中选择‘系统信息’。”

警告或重要提示如下所示。

提示和技巧会这样显示。

联系我们

我们欢迎读者的反馈。

一般反馈:如果你对本书的任何方面有疑问,请在邮件主题中注明书名,并通过customercare@packtpub.com联系我们。

勘误:尽管我们已经尽一切努力确保内容的准确性,但难免会出现错误。如果你在本书中发现了错误,我们将非常感激你能将其报告给我们。请访问www.packtpub.com/support/errata,选择你的书籍,点击“勘误提交表单”链接,并填写详细信息。

盗版:如果你在互联网上发现我们作品的任何非法复制品,我们将非常感激你能提供该材料的地址或网站名称。请通过copyright@packt.com与我们联系,并附上该材料的链接。

如果你有兴趣成为作者:如果你在某个领域有专业知识,并且有兴趣写作或为一本书做贡献,请访问authors.packtpub.com

评论

请留下评论。阅读并使用本书后,为什么不在你购买它的网站上留下评论呢?潜在读者可以查看并根据你的公正意见做出购买决策,我们在 Packt 可以了解你对我们产品的看法,而我们的作者也能看到你对他们书籍的反馈。谢谢!

如需了解更多关于 Packt 的信息,请访问packt.com

第一部分:迈向持续交付

在本节中,您将学习 DevOps 的基本原则以及大多数组织开始采用的实践,即持续集成和持续部署。本节为我们的工作奠定了必要的基础,之后大多数其他章节可以按任何顺序阅读。

本节包括以下章节:

  • 第一章,DevOps 简介

  • 第二章,一切从源代码管理开始

  • 第三章,迈向持续集成

  • 第四章,持续部署

第一章:DevOps 简介

DevOps 不是一种可以购买或安装的产品或工具。DevOps 关乎文化,以及你如何编写、发布和运营软件。DevOps 的目标是缩短从新想法到最终用户体验到它所带来的价值的时间。在本书中,你将了解应用这一理念到工作方式的工具和技术。

为了实现这一目标,你可能需要改变你的工作方式,采用新工具或改变使用方式。在第一章中,你将深入了解什么是真正的 DevOps,以及如何识别一个成功的 DevOps 团队。

本章将涉及以下主题:

  • 什么是 DevOps,为什么你不能简单地购买或安装它

  • DevOps 如何补充敏捷方法

  • DevOps 的好处是什么,如何衡量它们

  • 创建理想的 DevOps 和组织结构

  • 探索成功的 DevOps 团队的实践和习惯

  • DevOps 演进的五个阶段

技术要求

本章没有技术要求。

什么是 DevOps?

如果你列出所有关于 DevOps 的不同定义和描述,会有很多。然而,尽管这些定义可能各不相同,它们很可能会共享几个概念。这些概念包括协作、持续交付商业价值和打破孤岛。

在本书其余部分的技术讨论中,重要的是不要忽视采用 DevOps 的价值主张,也就是说,DevOps 将帮助你改善持续向终端用户交付价值的方式。为了做到这一点,你必须减少从开始开发新特性到第一个用户在生产环境中使用它之间的时间。这意味着你不仅要编写软件,还要交付并运营它。

在过去十年中,我们编写软件的方式发生了根本变化。越来越多的公司正在采用敏捷工作方式,以提高软件开发的效率。越来越多的团队现在在短时间内进行迭代或冲刺,快速创建产品的新增量。然而,更快地创建潜在可交付的增量本身并不会创造任何价值。只有当你的每个新版本软件被发布到生产环境,并且被终端用户使用时,它才开始交付价值。

在传统组织中,开发人员和运维人员通常位于不同部门,软件发布到生产环境通常需要交接,并且往往会有正式的仪式。在这样的组织中,加速将软件交付到生产环境,并与开发人员创建新版本的速度相匹配是困难的。

此外,开发和运维部门通常有冲突的目标。开发部门通过尽可能快速地创建更多的变化来获得奖励,而运维部门则通过限制停机时间和防止问题来获得奖励。后者通常通过尽可能少的变化来实现。这里的冲突很明显——两个部门都为一个子目标进行优化,如下图所示:

这会破坏这些子目标的目的,这些子目标源于快速接受新版本的共同宏大目标,同时保持稳定性。正是开发目标和操作目标之间的冲突,应该在 DevOps 文化中消失。在这样的文化中,开发人员和运维团队应该共同努力,以快速、可靠的方式将新版本交付到生产环境,并共同负责这两个子目标。

虽然了解 DevOps 是一种文化运动是很好的,但工具和自动化在这种文化中占有重要地位。本书将重点介绍这些工具,以及如何利用它们实施 DevOps 文化中的许多实践。换句话说,本书将主要关注与 DevOps 相关的产品和流程。如果你想了解更多关于文化方面的内容,关于人的部分,还有很多其他书籍可以阅读。

本节的其余部分将探讨 DevOps 之间的关系,看看它们是如何相辅相成的。重点将放在敏捷技术和工作管理的价格上。我们还将讨论 DevOps 文化的目标和益处。

DevOps 与敏捷的关系

如果你看一下敏捷,可能会注意到其中一部分关注的是业务价值,并缩短新业务价值交付之间的时间。从这个角度来看,采用 DevOps 是敏捷之后的逻辑下一步。敏捷主张,软件开发团队的责任应该通过与用户和其他利益相关者的互动来向前延伸,以更快地交付有价值的、潜在可交付的产品。而 DevOps 不仅仅是创建可以交付的东西,真正的目标是将其交付出去。结合敏捷和 DevOps,你可以为用户创建一个端到端、持续不断的价值流。

做到这一点需要的一项重要条件是,所有参与者都必须有一个共同的工作管理方法。在接下来的章节中,你将找到一些关于如何将操作问题纳入到工作管理方式中的建议。

敏捷工作管理

当你开始增加开发与运维之间的协作时,你会很快注意到他们必须应对不同类型的工作。在开发中,工作很大一部分是计划好的:用户故事和从待办事项列表中提取的缺陷。另一方面,对于运维工作,他们很大一部分是无计划的。他们响应来自系统的警告和提醒,以及来自用户或开发者的请求或工单。

将这两者整合,特别是当开发人员和运维人员位于同一个团队时,可能会遇到挑战。为了看到如何应对这一点,让我们探索以下方法:

  1. 首先,为开发人员切换到流式工作方式。

  2. 接下来,允许运维团队也在与开发人员相同的工作管理系统中列出他们的工作,通过同步来实现。你还可以选择实施fastlaning,一种加速紧急工作的方式。

  3. 最后,如果可能,你可以选择停用现有的运维工单工具。

Fastlaning(快速通道)是一种工作组织方法,通过可视化两条独立的工作通道,允许计划性和无计划的工作并行进行。为此,Scrum 看板会在顶部增加一个类似 Kanban 的看板。这是快速通道。在 Kanban 看板上,添加了紧急但无计划的工作。任何加入这个通道的工作都会被团队以最高优先级拾取。只有当快速通道没有剩余工作时,才会从 Scrum 看板中拾取计划好的工作。每当新的工作加入快速通道时,优先级再次提升。通常,大家会达成协议,工作在切换到快速通道之前,应该先完成正在进行的工作。

转向流式方法

首要考虑的是将开发人员的工作方式从批处理模式转换为流式工作模式。批处理式工作的一个例子是 Scrum。如果你使用的是 Scrum 框架,你习惯了每两到四周拾取一批工作,并专注于在这个时间窗口内完成所有工作。只有当这一批工作完成时,才能交付一个潜在的可发布产品。

当转向流式工作方法时,你应该专注于不是一个批次,而仅仅是一个任务。你专注于那个任务,并将其完全完成后,才开始下一个任务。这样,就不再有冲刺待办事项列表,只有产品待办事项列表。这种方法的好处是,你不再事先决定要执行哪些工作,而是在有空闲时间时,直接从待办事项列表中拾取下一个任务。在优先级迅速变化的环境中,这可以帮助你更快速地应对变化。

开发人员组织工作方式的这些变化使得将操作纳入工作管理变得更加容易,但还有另一个好处。当开发人员专注于完成单个工作项,而不是一次性完成整个迭代时,你也可以增加向用户交付小部分价值的次数。

将工作项同步到一个系统

在开发团队改变工作组织方式后,现在开发人员应该更容易将他们计划的工作列入共享的待办事项列表,并在有时间时从该列表中提取工作。他们现在也有一个地方可以列出未计划的工作。

然而,可能仍然存在一个现有的工单系统,其中请求操作的工单由用户提交或由监控工具自动创建。虽然 Azure DevOps 提供了一个出色的 API,可以重新构建此集成,直接在 Azure DevOps 中创建工作项,但你可能首先会选择在现有的工单工具和 Azure Boards 之间创建同步。这里有许多集成选项,并且这一领域正在进行大量的工作。通过这种方式,操作人员可以逐渐从他们的旧工具迁移到新工具,因为它们现在已经同步。当然,最终目标是让操作人员完全迁移到与开发人员相同的工具上。

快速通道

由于开发人员和运维人员使用同一个工作管理工具,你会注意到系统中既有计划的工作,也有未计划的工作,且这些未计划的工作通常是紧急的。为了确保紧急工作能够得到应有的关注和优先级,你可以在迭代看板中引入所谓的快速通道。在下面的截图中,你可以看到一个为快速处理生产问题设置的 Azure 看板示例:

在看板中使用这种水平分割的目的是当快速通道中没有工作可接时,只在常规通道中处理任务。

停用其他工作管理工具

在开发和运维之间创建共享的工作管理系统后,增加双方之间的协作机会。随着这种协作的展开,运维部门以前使用的旧工单系统可能会随着时间的推移逐渐停用。监控工具的集成可以转移到新的共享工具上,开发人员和运维人员之间的工单数量应该会逐渐减少,因为他们找到新的协作方式。

DevOps 文化的目标与好处

此时,你可能会想知道这一切的意义何在。DevOps 的好处是什么,它对你、你的同事和你的组织又有什么作用?采用 DevOps 的最常见目标是减少周期时间。周期时间是从开始处理新功能到第一个用户可以使用它之间的时间。通过自动化实现这一目标,同时也有助于降低变更失败率、降低平均修复时间MTTR)和减少计划停机时间。

除了这些之外,还有其他潜在的好处,例如员工满意度提高、工作倦怠和压力减少,以及更好的员工留存率。这归因于开发人员和运维人员之间对立目标的消除。

曾一度存在怀疑,是否 DevOps 真的有效,这些目标是否真正达成,额外的好处是否真的实现,因为这一切只是通过案例研究展示的。问题在于,案例研究通常只适用于成功的案例,而不适用于失败的案例。直到 2018 年,书籍《加速》发布,这一切才发生了变化。这本书基于多年的定量研究,表明现代开发实践,如 DevOps,确实有助于实现 IT 目标和组织目标。

结果衡量

为了衡量你所在团队或组织的现状以及 DevOps 对你的影响,有几个指标你可以开始记录。像往常一样,在处理指标或关键绩效指标KPI)时,确保不要鼓励人们仅仅通过关注数字来操控系统。接下来的章节中详细介绍了几个有趣的指标,如果你逐一查看,你会发现它们都在鼓励流程的流畅性。

周期时间和交付时间

周期时间交付时间是来自精益和看板的度量标准,用于衡量实现变更所需的时间。周期时间是从开始处理一个功能到用户能够在生产环境中使用该功能之间的时间。周期时间越短,你就能越快响应变化的需求或新见解。交付时间是从请求一个功能到实现该功能之间的时间。它是从将工作添加到待办事项列表到开始实施之间的时间。

当你将周期时间和交付时间加在一起时,你正在计算另一个指标——上市时间。这个指标通常是软件开发中的一个重要业务指标。最小化周期时间和交付时间将因此产生业务影响。

正在进行中的工作量

另一个可以衡量的方面是任何时候进行中的工作量。DevOps 关注的是价值流向用户。这意味着每个人应该尽可能一次只做一件事,并在完成这件事后再进行下一项任务。这可以减少任务切换和未完成工作的时间。衡量团队并行处理的任务数量并报告这一情况,可以鼓励这种做法。

你甚至可以对正在进行的工作数量设定实际限制。下面是之前截图的一小部分,显示了这些工作进行中的限制甚至可以在工具中显示出来:

目标是尽可能减少同时进行的工作量。

平均恢复时间

第三个度量标准是平均恢复时间。在发生(部分)故障时,恢复服务需要多长时间?过去,企业关注的是减少平均故障间隔时间。这曾是衡量产品稳定性的主要指标。然而,这个度量标准促使公司限制进入生产环境的变更数量。其不良后果通常是,尽管故障可能较少,但一旦发生,持续的时间长且修复困难。

衡量平均恢复时间将注意力转向你能够多快修复故障。如果你能迅速修复故障,那么就能实现同样的目标,即最小化停机时间,同时不牺牲变更速率。目标是最小化恢复时间。

变更速率与变更失败率

最后,你可以衡量交付到生产环境的变更数量,以及其中未成功的百分比。提高变更速率意味着你更频繁地向用户交付价值,从而实现价值流动。此外,通过衡量不仅是失败的数量,还包括失败的百分比,你实际上是在鼓励许多小而成功的变更,而不是鼓励限制总变更数量。

你的目标应该是提高变更的速率,同时降低变更失败率。

此时,你可能会想,我如何改变我的组织来促进这种文化,并获得所有这些好处?下一节将为你解答。

创建理想的 DevOps 组织

或许,你的组织结构根本不需要改变。DevOps 的开始需要一种文化变革:开放、同理心和协作是需要鼓励的价值观。但改变组织结构仍然可能有助于加速这一过程。

传统上,开发人员和运维人员通常被组织成不同的团队,甚至是不同的部门——他们在具有相似技能和责任的团队中工作。组织结构发生的一个常见变化是改变这种结构,通过调整并围绕共同目标、单一产品或一组功能来组织团队,例如。

现在,你将需要拥有不同技能和责任的团队,这些团队很可能会由开发人员和运维人员组成。重要的是要意识到,强行实施这种改变可能不是最佳的前进方式。通常,最有效的方法是从改变文化开始,鼓励合作,然后这种组织结构的变化可能会自然发生。

最后,值得注意的是一个反模式。一些公司试图通过雇佣专门的 DevOps 工程师,并将其定位在开发和运维之间,与两者互动,从而实施 DevOps。虽然这最初看起来是个好主意,但它违背了 DevOps 的价值观。如果这样做,你并没有打破壁垒,而是增加了第三个壁垒。你没有减少交接的次数,很可能会增加交接。此外,开发人员和运维人员之间的合作通常不会通过使用另一种组织结构来分隔而得到增强,你可能根本看不到用户价值的提升。

现在你知道了 DevOps 是什么,并且清楚了如何组建一个 DevOps 团队,是时候探索如何开始实现你的目标了。

探索 DevOps 实践和习惯

由于你并不是第一个走上这条路的团队,你可以从前人的经验中汲取教训。一个例子是微软的团队,他们构建了 Azure DevOps。由于他们处于一个罕见的位置,可以使用自己的产品来开发自己的产品,他们从中学到了很多关于使 DevOps 成功的要素。从中,他们确定了七个关键的 DevOps 实践和七个 DevOps 习惯,这些习惯是许多成功的 DevOps 团队所共有的:

DevOps 实践 DevOps 习惯
配置管理 团队自主性和企业对齐
发布管理 严格管理技术债务
持续集成 专注于客户价值流
持续部署 假设驱动开发
基础设施即代码 在生产中收集的证据
测试自动化 现场文化
应用性能监控 将基础设施作为灵活资源管理

现在,重要的是要意识到,单纯模仿这些步骤并不能保证成功。就像 Agile 一样,你需要花时间真正理解这些实践和习惯,它们的来源以及它们如何为终端用户提供持续的价值流。

以下各节将更详细地探讨所有这些实践和习惯。在阅读本书的其余部分时,请将这些内容记在心里。虽然本书的其余部分将主要关注技术手段,即如何做事情,但不要忘记,这些只是手段。真正的价值来自于思维方式和创造一种文化,专注于为客户持续创造价值的流程。

DevOps 实践

本节依次讨论所有七种 DevOps 实践。正如你很快会发现的,它们彼此之间高度相关,很难做到单独实践其中之一。例如,测试自动化与持续集成和持续部署密切相关。

如果你打算参加 AZ-400 考试,掌握所有这些实践并通过 Azure DevOps 进行实施,将对你大有帮助。

配置管理

配置管理是对你的应用程序及其所依赖组件的配置进行版本控制,同时也涉及到应用程序本身。配置保存在源代码控制中,通常以 JSON 或 YAML 文件的形式存在,描述了你应用程序的期望配置。这些文件是如 Ansible、Puppet 或 PowerShell DSC 等工具的输入,这些工具用来配置你的环境和应用程序。这些工具通常会在持续部署管道中调用。

即使没有对预期配置进行更改,期望状态也可以在间隔时重新应用。这样可以确保实际配置保持正确,并且手动更改会被自动撤销。我们称之为防止配置漂移。配置漂移是指由于随着时间推移服务器的增加或删除,或管理员进行的手动临时干预,配置发生变化。当然,这意味着预期的配置更新是在源代码控制中完成的,并且仅通过工具进行应用。

配置管理或称为“配置即代码”与“基础设施即代码”高度相关。两者经常交织在一起,在某些平台上,它们之间的差异甚至可能显得是人为的。关于“配置即代码”将在第六章《基础设施与配置即代码》中详细讨论。

发布管理

发布管理是关于控制哪一版本的软件被部署到哪个环境。版本通常是通过持续集成和交付管道创建的。这些版本以及所需的所有配置,随后作为不可变的工件存储在一个仓库中。从这里开始,发布管理工具用于计划和控制这些版本如何部署到一个或多个环境中。示例控制包括手动审批和自动查询开放工作项及质量检查,之后才允许部署到新的环境中。

发布管理与持续部署相关,更多关注于控制版本通过持续部署流水线的流程。第六章《基础设施和配置即代码》将作为发布管理的一部分,介绍配置即代码。

持续集成

持续集成是一种实践,其中每个开发者至少每天一次将自己的工作与团队其他开发者的工作集成,最好是更频繁地进行。这意味着每个开发者至少每天一次将自己的工作推送到仓库,并且持续集成构建会验证他们的工作是否编译通过,并且所有单元测试是否运行。重要的是要理解,这种验证不仅仅应针对开发者孤立工作时的代码进行。真正的价值在于将这些工作与他人的工作集成在一起时体现出来。

当频繁且快速地集成变更时,合并变更的问题较少出现,若出现,通常也更容易解决。在第二章《一切从源代码管理开始》中,你将学习如何设置源代码管理仓库,使这一过程成为可能。在第三章《迁移到持续集成》中,你将学习如何设置持续集成构建。

持续部署

持续部署是指将每个新的、符合质量标准的版本自动部署到生产环境的做法。在实践持续部署时,你会有一个完全自动化的流水线,接收应用程序的每个新版本(每次提交),生成新的发布,并开始将其部署到一个或多个环境中。第一个环境通常称为测试环境,最后一个环境是生产环境。

在这个流水线中,有多个步骤用于验证软件质量,确保它能够顺利推进到下一个环境。如果质量不达标,发布将被中止,并且不会继续传递到下一个环境。这种方法的前提是,在流水线中,你需要证明当前版本无法进入下一个环境。如果未能证明这一点,你就认为它已准备好进行下一步。

只有当一个版本通过了流水线中的所有环境,它才会被部署到生产环境。每当一个发布无法进入下一个环境时,该发布将完全被取消。虽然你可能会倾向于修复导致失败的原因,然后从失败点重新开始部署,但重要的是不要这么做。因为此时你所做的更改并没有经过版本已经通过的所有控制。验证新版本的唯一方法是从头开始重新启动流水线。你可以通过以下图示清楚地看到这一点:

在第四章,持续部署中,你将学习如何使用 Azure DevOps Pipelines 设置持续部署。

上述图表可以在 en.wikipedia.org/wiki/Continuous_delivery#/media/File:Continuous_Delivery_process_diagram.svg找到。图像由 Grégoire Détrez 提供,原图由 Jez Humble 创作,采用 CC BY-SA 4.0 许可证,详见 creativecommons.org/licenses/by-sa/4.0/

基础设施即代码

在编写应用程序时,你所构建的二进制文件必须在某个地方运行,即某个应用程序主机上。这样的应用程序主机的一个例子是像 IIS 或 Apache 这样的 web 服务器。在应用程序主机旁边,我们可能还需要数据库和一些消息解决方案。所有这些加在一起,我们称之为应用程序的基础设施。在实践基础设施即代码时,你将这些基础设施的描述保存在源代码库中,与应用程序代码一起。

当需要发布应用程序的新版本,并且要求对基础设施进行一项或多项更改时,你将使用诸如 Chef、Puppet、PowerShell DSC 或 Azure ARM 模板等工具执行你所描述的期望基础设施。执行此类描述是幂等的,意味着它可以执行多次,最终结果是相同的。这是因为你对基础设施的描述是你希望基础设施达到的期望状态,而不是一系列需要执行的步骤。如果有任何步骤需要执行,这些步骤将由你选择的工具自动确定。应用期望状态也可以在持续部署管道中自动完成,通常在更新应用程序代码之前执行。

这有一个很大的优势,你现在可以轻松创建一个新的环境,保证该环境的基础设施与其他环境相同。此外,配置漂移的问题,即不同环境之间的基础设施逐渐偏离,也不再可能,因为每次你都会将所需状态重新应用到每个环境中,并且它们会被强制执行。

本书的第六章,基础设施与配置即代码,将更详细地讨论基础设施即代码。

测试自动化

为了持续为最终用户提供价值,你必须快速且频繁地发布应用程序。这对你测试应用程序的方式有影响。你无法在每隔几分钟就发布一次应用程序时进行手动测试。这意味着你必须尽可能地自动化测试。

你很可能希望为你的应用程序创建多个测试套件,并在交付管道的不同阶段运行它们。快速单元测试应该在几分钟内完成,并且在每次打开新的拉取请求时执行,这将为团队提供快速反馈,帮助他们了解工作的质量,并捕捉大多数错误。接下来,团队应在管道的后期运行一个或多个较慢的测试套件,以进一步增强对应用程序版本质量的信心。

所有这些都应将手动测试的数量限制到最低,并允许你有信心地自动部署新版本的应用程序。

本书的 第八章,持续测试,将详细介绍测试自动化。

应用程序性能监控

最后一项实践是了解您的应用程序在生产环境中的表现。收集诸如响应时间和请求数量等指标将告诉你系统的运行状况。捕捉错误也是性能监控的一部分,它让你能够在不等客户联系你报告问题的情况下开始修复问题。

除此之外,你还可以收集哪些部分的应用程序使用频率较高或较低的信息,以及新功能是否被用户接受。了解使用模式能为你提供深入洞察,帮助你了解客户如何实际使用应用程序以及他们常见的使用场景。

第九章,安全与合规,和 第十章,应用程序监控,将详细介绍如何了解你的应用程序和用户在生产中的行为。

DevOps 习惯

成功的 DevOps 团队的七个习惯更多地关注文化和在开发及交付软件时的态度,而不像 DevOps 实践那样专注于技术手段。然而,了解和理解这些习惯仍然很重要,因为它们将有助于使 DevOps 的采用更加容易。

你会注意到,培养这些习惯会加强之前列举的实践的使用,以及你用来实施这些实践的工具。当然,这也是相互作用的。

团队自主性与企业对齐

在敏捷工作中,一个重要部分是创建那些在很大程度上自我引导的团队,这些团队可以在没有(太多)外部依赖的情况下做出决策。因此,这样的团队通常会包括多个角色,包括拥有一个或多个功能并有权决定如何推进这些功能的产品负责人。

然而,这种自主权也伴随着将团队工作与整个产品方向对齐的责任。开发一种方法来对齐数十个甚至数百个团队的工作是非常重要的,方法应该是每个人可以独立航行,但整个舰队也能保持一致。

最理想的情况是,团队能够自发地与更大的愿景对齐,而不是时不时地接受指令。

严格管理技术债务

另一个习惯是严格管理技术债务。债务这一术语本身就表明,延迟解决问题会带来一定的成本(利息)。为了保持持续的速度而不在时间的推移中逐渐失去进度,至关重要的是将缺陷或架构问题的数量控制在最低限度,并只容忍有限的数量。在某些团队中,这甚至会以协议的形式正式化。例如,一个团队可以约定,未修复的缺陷数量永远不能超过团队成员的数量。这意味着,如果一个团队有四个成员,并且报告了第九个缺陷,则在修复至少一个缺陷之前,不会开展任何新工作。

专注于客户价值的流动

重要的是要接受这样一个事实:用户在没有实际使用代码之前,是无法从中获得任何价值的。专注于为用户提供价值的流动意味着,代码必须被编写、测试、交付,并且应该在生产环境中运行,直到完成为止。专注于这一习惯可以真正促进跨学科和团队之间的合作。

假设驱动的开发

在许多现代开发方法论中,有一个产品负责人负责根据业务价值对待办事项进行排序。这个负责人作为专家,负责通过根据业务价值(按工作量划分)排序所有任务,最大化开发团队交付的价值。

然而,最近的研究表明,即使产品负责人是专家,他们也无法正确预测哪些功能能为用户带来最大价值。大约三分之一的团队工作实际上为用户增加了价值,更糟糕的是,另三分之一的工作实际上减少了价值。基于这一原因,你可以将待办事项从功能或用户故事转变为你希望验证或推翻的假设。你只需要在产品中创建一个最小的实现,甚至只是功能的一个提示,然后测量是否有用户使用它。只有当这种情况发生时,你才会扩展功能的实现。

在生产中收集的证据

性能测量应该在你的生产环境中进行,而不仅仅是在人工负载测试环境中。如果负载测试对你有价值,在生产之前进行负载测试并没有错。然而,真正的性能测试应该在生产环境中进行,并且应该在那里进行测量,并与之前的测量结果进行对比。

这也适用于使用统计数据、模式和许多其他性能指标。它们都可以通过生产指标自动收集。

现场环境文化

现场环境文化倡导的理念是,生产环境中发生的任何事情优先于其他任何事情。接下来,任何威胁生产、即将进入生产或随时妨碍进入生产的事情都优先考虑。只有当这些事情都顺利进行时,才会将注意力转移到未来工作上。

此外,现场环境文化的一部分是确保任何干扰服务操作的事情都经过彻底分析——不是为了找出责任人或解雇,而是找出如何防止再次发生。最好通过向左移动来预防,例如,在管道的较早阶段检测到重复事件的指标。

管理基础设施作为灵活的资源

最后,一个成功的 DevOps 团队将其服务器和基础设施视为牲畜,而不是宠物。这意味着基础设施在需要时启动,并在不再需要时被忽略。能够做到这一点的原因是配置和基础设施作为代码。这甚至可能会走得更远,每次新部署都会创建一个新的生产环境,然后在将所有流量从旧环境切换到新环境后删除旧生产环境。

除了牢记这些 DevOps 实践和习惯外,在您的组织尝试迈向 DevOps 文化时,您将经历一些阶段。下一节将带您了解这一过程。

DevOps 演进的五个阶段

当您的组织尝试在 DevOps 文化中迈进时,这需要时间。在您的组织中,每个人都必须接受他们必须改变个人工作方式的变化。之前已经经历过这一过程的其他人可能会帮助您加速您自己的旅程。了解这些步骤可以帮助您加速自己的旅程。这些步骤首次发表在2018 年 DevOps 报告中,并在以下各节中讨论。

规范化技术栈

通向 DevOps 文化的常见第一步是采用敏捷方法。至少,有良好的源代码控制工具,通常有公司标准,并正在推出持续集成和交付。团队还在共同努力,规范他们开发软件的技术栈。例如,选择一两个云供应商,并淘汰其他部署平台。对于其他用途的工具也是如此——尽可能地标准化。自制解决方案被行业标准替代。

标准化和减少变异性

在这个阶段,团队致力于进一步减少应用程序之间、以及开发和运维团队之间的差异,双方共同协作,统一操作系统、库和工具。此外,在这个阶段,部署流程也发生了变化,以减少它们之间的差异,配置和基础设施通常会迁移到源代码管理中。

扩展 DevOps 实践

开发和运维之间的剩余问题被清理掉,确保开发团队的输出正是运维团队所期望的。此外,双方的合作开始增加,他们能够在没有外部依赖的情况下共同工作,创建和交付变更。

自动化基础设施交付

在这个阶段,开发和运维使用的基础设施完全对齐。所有内容都从源代码管理中部署,两个团队都使用相同的脚本。

提供自助服务功能

在 DevOps 之前,虚拟机或托管环境通常是由开发人员手动或通过工单系统向运维团队请求的。资源配置由运维人员手动完成,这可能需要几天,甚至有时需要几周。

自助服务功能意味着环境不再通过手动创建,而是通过运维团队为开发人员提供的自助服务 API 来创建。

通过这种方式,开发人员可以完全自主地创建和销毁环境。他们可以独立创建和测试变更,并自行发送或安排自动部署。

总结

在这一章中,你了解了 DevOps 是什么(以及它不是)以及它与敏捷方法的关系。转向 DevOps 文化有助于你打破开发人员和运维人员之间相互冲突的目标。这将使他们能够共同工作,持续为最终用户交付价值,将工作组织在一个共同的待办事项列表中,并在同一个看板上工作,同时尊重彼此工作方式的差异。将开发人员和运维人员组织成面向产品的团队,是创建志同道合、目标导向团队的下一步重要工作。

转向 DevOps 可以带来许多好处,你现在已经知道如何衡量这些好处,并持续进行改进。接下来,你学习了许多成功的 DevOps 团队展现出的 DevOps 习惯和实践。掌握这些技能,不仅是你自己,还是你的团队,将使你能够进行自己的 DevOps 评估。所有这些都是为了持续为用户交付价值。

下一章将讨论源代码管理的话题,以及如何组织你的应用程序源代码以支持 DevOps 流程。

问题

在我们总结时,这里有一组问题供你测试关于本章内容的知识。你将在附录的评估部分找到答案:

  1. 对还是错:开发和运维部门经常有相互冲突的目标。

  2. 对错:本章讨论的七个 DevOps 实践是相互独立的,可以轻松地单独实践。

  3. 以下哪项不是 DevOps 演变的五个阶段之一?

    1. 标准化技术栈

    2. 自动化基础设施交付

    3. 标准化并减少变异性

    4. 招聘一组 DevOps 工程师来自动化应用程序交付

  4. 什么是 fastlaning?

  5. 用你自己的话,简要描述 DevOps 的本质。

进一步阅读

还有许多其他资源,你可能会发现它们对学习 DevOps 文化和 DevOps 思维方式非常有帮助。以下是其中的一些:

第二章:一切从源代码管理开始

源代码管理是软件开发中最基本的工具之一。因此,可以合理假设你之前已经使用过源代码管理。基于这一点,本章将仅简要介绍源代码管理,并迅速过渡到更高级的话题,帮助你设置源代码管理以支持 DevOps 实践。

多种 DevOps 实践依赖于源代码管理,因此,设置你的代码库以持续为用户提供价值是一个很好的起点,也是接下来章节中许多主题的前提条件。

本章将涵盖以下主题:

  • Azure DevOps 中的源代码管理类型

  • 源代码管理系统

  • 选择分支和合并策略

  • 使用分支策略确保源代码管理的安全性

  • 可用于源代码管理的其他工具

技术要求

要实践本章涉及的内容,你可能需要一个 Azure DevOps 组织。

Azure DevOps 中的源代码管理类型

虽然存在许多不同的源代码管理系统,但它们可以分为两类:集中式和去中心化源代码管理,具体如下:

  • 集中式源代码管理系统中,只有服务器拥有完整的历史记录和组成代码库的所有分支。

  • 去中心化源代码管理中,所有与代码库一起工作的人员都拥有该代码库的完整副本,包括所有分支和历史记录。

Azure Repos 是 Azure DevOps 服务的一部分,通过 TFVC 和 Git 提供两种类型的源代码管理。接下来的两个部分将更详细地讨论这两种源代码管理。

集中式源代码管理

在集中式源代码管理系统中,服务器是唯一存储完整代码库(包括所有历史记录)的地方。当你创建内容的本地版本时,你只会接收到代码的最新版本。接收这个最新版本被称为检出代码库。除了这个最新版本,你自己的计算机只会有你本地所做的更改。

显然,不检出完整的历史记录可以节省本地计算机的存储空间。然而,如今磁盘空间几乎不再是问题。其缺点是,你需要持续连接到服务器才能执行查看文件历史记录、他人最近提交或某文件的某一行最后由谁修改等操作。

集中式源代码管理系统的一个优点是,它们通常提供对谁可以访问哪些分支、目录甚至文件的精细化控制选项。

去中心化源代码管理

使用去中心化源代码管理系统时,所有文件、历史记录和分支也存储在服务器上。与集中式源代码管理的区别在于,当你克隆代码库时,你会在自己的计算机上获得一个本地副本。

由于你有该仓库的完整克隆,现在可以查看文件的历史记录和其他分支,而无需再次连接到服务器。这显然减轻了服务器的负担,并且即使在断开连接时也能继续工作,这是去中心化源代码管理的两个优点。

缺点是,去中心化的源代码管理可能比集中式源代码管理更难学习。总体来说,去中心化源代码管理系统的学习曲线更陡峭。此外,单个目录和文件的访问控制通常更为有限。

无论你使用哪种类型的源代码管理,都必须制定一个分支和合并策略,以便开发者能够并行开发不同的功能,同时始终保持主分支处于可发布状态。

源代码管理系统

有许多源代码管理系统在使用,但在本章中,我们只会查看当前使用最广泛的三种系统。它们如下:

  • 团队基础版本控制 (TFVC)

  • Git

  • Subversion

在 Azure DevOps 中,仅支持 TVFC 和 Git。Subversion 是一个由 Apache 基金会创建的集中式源代码管理系统。在接下来的小节中,我们将更详细地了解 TFVC 和 Git,并学习如何在它们之间迁移源代码。Subversion 在本章最后的其他源代码管理工具部分中讨论。

团队基础版本控制

团队基础版本控制 (TFVC) 是一个由微软在 2013 年推出的集中式源代码管理系统,作为团队基础服务器 (TFS)的一部分,TFS 后来发展成了 Azure DevOps。TFVC 仍然在 Azure DevOps 中得到支持,但不推荐用于新项目。如果你还没有使用 TFVC,学习它没有什么价值。现在,TFVC 不推荐用于新项目,微软很可能不会为其发布新特性,但没有其他驱动因素的情况下,没有必要从 TFVC 中迁移。

在 Azure DevOps 中,每个团队项目最多只能有一个 TFVC 仓库。

Git

除了 TFVC,Azure DevOps 还支持托管 Git 仓库。Git 是一种去中心化的源代码管理方式,正在迅速流行。Git 不仅仅是 Azure DevOps 特有的,它是一种通用协议,许多提供源代码托管服务的平台都在使用。除了 Azure DevOps,GitHub 和 GitLab 也是著名的例子。

要使用 Git 仓库,首先必须克隆它:

  1. 打开命令提示符,导航到你希望存储仓库的目录。

  2. 执行以下命令,并将示例网址替换为你 Git 仓库的 URL。示例 URL 展示了 Azure DevOps 中 Git 仓库位置的构建方式:

git clone https://{organization}@dev.azure.com/{organization}/{teamProject}/_git/{repository}

现在,你可以开始进行你想要的修改了。在这个例子中,添加了一个新文件,NewFile.txt

  1. 接下来,这个文件必须被暂存以供提交。暂存文件是为了区分你想要提交的文件和你想保留自己更改的文件:
git add NewFile.txt
  1. 在将所有想要分组到单个提交的更改暂存后,实际创建 commit 是通过调用提交命令并指定更改的描述来完成的:
git commit -m "Added a new file that contains an important text"
  1. 最后,你可以通过执行以下命令,将你的更改推送回中央仓库,即远程仓库:
git push

要进行更多更改,你可以根据需要随时暂存并提交更改。你可以一次推送一个提交,但也可以一次推送多个提交。

你也可以通过 Visual Studio 或 VS Code 界面来使用 Git。在这里,你执行的步骤完全相同,但你可以使用可视化界面。

大文件存储

Git 被设计并优化用于处理纯文本文件,并跟踪版本之间的变化。然而,你可能希望将除文本文件之外的其他内容存储到源代码控制中。例如,图像或二进制文件应该在运行时与应用程序一起包含。虽然这些是有效的用例,但开箱即用时,它们与 Git 的兼容性不好。为了解决这个问题,大文件存储LFS)被引入。

Git LFS 允许你存储一个小的文本文件,作为指向二进制文件的指针,而不是直接存储二进制文件。这样的文件包含该文件的哈希值,这样客户端在克隆或获取更改时可以下载该文件,并且在你更新二进制文件时能够更新该文件。

要使用 Git LFS,你必须在 Git 客户端旁边安装 LFS 客户端。这是一个单独的客户端,每个仓库的用户都必须下载。没有这个客户端,其他用户只会看到指向文件的指针文件,而不是实际的二进制文件。安装客户端后,你必须准备仓库以便使用 LFS。以下示例命令启用 MP4 文件的 LFS 使用:

git lfs install
git lfs track "*.mp4"
git add .gitattributes

从此开始,你可以像操作任何文件一样操作 MP4 文件,幕后它们将与文本文件的更改分开存储。

在控制系统之间迁移

DevOps 旅程中的一步是工具的整合。这意味着,在某个时刻,你可能会被要求将源代码从一个源代码控制系统迁移到另一个系统。这意味着公司可能会决定将所有源代码从 GitLab 或 Subversion 移动到 Azure Git Repos。你可以选择多种方式进行这样的迁移。

最有可能发生的情况是,你会收到将源代码迁移到一个或多个 Azure Git 仓库的请求。可能的源包括其他 Git 仓库、TFVC 或 Subversion。可以使用一些工具和方法进行这样的迁移,同时保留原仓库的变更历史。

如果没有现成的迁移流程,或者你必须从另一个系统导入源代码,你也可以选择创建一个新的空仓库,并将其初始化为现有代码库。这种方法的缺点是,所有历史记录都会丢失。

迁移现有的 Git 仓库

在迁移源代码时,与其他类型的迁移相比,将 Git 仓库迁移到其他位置进行托管是相对简单的。让我们学习如何做到这一点:

  1. 首先,将现有仓库克隆到本地计算机。请注意末尾的点,这将把仓库放置在当前目录中:
git clone https://{organization}@dev.azure.com/{organization}/{teamProject}/_git/{repository} .
  1. 添加另一个远程仓库,指向你希望将源代码迁移到的新空仓库:
git remote add migrationTarget https://{organization}@dev.azure.com/{organization}/{teamProject}/_git/{newRepository}
  1. 最后,你将更改推送到这个新仓库。对于每个你想迁移到主分支的分支,你必须单独执行这个操作:
git push migrationTarget master

与此同时,其他开发人员可能已经继续在现有仓库中工作。

  1. 要将这些内容也包括在新的仓库中,你必须先从原始仓库将它们获取到本地计算机,然后再推送到新的仓库。再次重复此操作,适用于每一个分支:
git fetch origin master
git push migrationTarget master
  1. 重复执行这最后两个命令,直到没有开发人员继续在源仓库中工作为止。

  2. 成功迁移后,通常最好删除旧仓库。这可以防止任何人不小心继续在旧仓库中工作。

上述步骤适用于任何 Git-to-Git 迁移。

现在,如果你特别想迁移到 Azure Git 仓库,你也可以使用 Azure DevOps 提供的导入功能。要执行此操作,请按照以下步骤操作:

  1. 导航至 Git 仓库,并可以选择先创建一个新的 Git 仓库。

  2. 选择导入一个现有的仓库。

  3. 提供所需的相关信息。

  4. 点击“导入”开始导入仓库。

以下截图展示了这些步骤:

这种方法的缺点是,你不能持续将更改从源仓库推送到新仓库。这意味着,你团队中的所有其他开发人员必须确保自己将更改迁移过来,或者在迁移仓库时没有未完成的工作。

从 TFVC 迁移到 Azure Git 仓库

对于从 TFVC 迁移到 Git 仓库,你可以使用与从任何 Git 仓库迁移到 Azure 仓库相同的导入仓库功能。此向导在执行导入时最多可以处理 180 天的历史记录。如果这不足够,而且你需要将超过 180 天的历史记录迁移到新仓库,还有其他更复杂的方法可以使用,但它们涉及更多的步骤。更详细的建议链接将在本章结束时提供。

从 Subversion 迁移到 Azure Git 仓库

你可能收到的另一种请求是将一个 Subversion 仓库迁移到 Git 仓库。对于这种情况,微软没有现成的解决方案。不过,Atlassian 开发了一款工具,可以在保留变更历史的同时,将 Subversion 仓库迁移到本地 Git 仓库。

运行完该工具后,剩下的就是为一个新的空仓库添加远程,并推送所有的分支。这些步骤与从 Git 迁移到 Git 的步骤相同,起始于添加一个新的远程仓库。

不保留历史记录的迁移

如果你被要求进行迁移且不保留历史记录,可以仅在本地计算机上创建一个新的空仓库,并使用以下命令将现有的更改推送到该仓库。

从包含应该放入主分支的文件的目录执行以下命令:

git init
git add 
git commit -m “Initial import of existing sources”
git remote add https://{organization}@dev.azure.com/{organization}/{teamProject}/_git/{repository}
git push

这些命令初始化一个新的仓库,基于当前目录中所有文件创建第一次提交,添加目标服务器位置的引用,并将新创建的仓库推送到该位置。

如果你想保留多个分支,必须为每个其他分支重复以下步骤:

  1. 首先,进入该分支的正确目录:
Git checkout {branchName}
  1. 现在,将需要放入该分支的文件复制到你的工作目录中。然后,继续执行以下命令:
git add .
git commit
git push

这完成了迁移,现在你本地计算机上的最新源代码版本已在 Git 中可用。你团队的其他成员现在可以克隆该仓库并开始使用它。接下来,我们将学习分支和合并。

选择分支和合并策略

版本控制允许你记录对文件所做的所有更改,同时如果需要,也能暂时与团队成员分开工作。我们称之为分支。当你在版本控制中进行分支时,你会复制当前注册的更改路径。我们称这种复制为分支。分支允许你暂时将某些工作与其他工作隔离开。如果你希望在某个时刻将某个分支的更改与另一个分支的更改合并,你可以合并这些更改。分支通常用于处理尚未完成的功能、概念验证或热修复。使用分支可以让你稍后决定将哪些更改包含在下一个版本中,哪些不包含。

分支策略

目前有许多分支策略可供选择,但如今最常用的三种策略如下:

  • GitHub 流程

  • GitFlow

  • 发布流程

以下小节将更详细地讨论这些内容。

作为分支管理的替代方案,基于主干的开发(trunk-based development)如今变得越来越流行。想了解更多内容,请访问 paulhammant.com/2013/04/05/what-is-trunk-based-development/

GitHub 流程

GitHub flow 是一种简单但通常足够的分支策略。在 GitHub flow 中,只有一个master分支,始终应该保持在可部署的状态。master 上不允许有未完成的更改。

如果你想开始处理一个新的功能或错误修复,你需要从 master 创建一个新的主题分支,在这个分支上提交你的工作。只有当你完全完成这项工作时,才将这个分支合并回 master。一个示例提交流程可能如下所示:

由于这是涉及分支最少的分支策略,这可能是一个很好的起点。

GitFlow

GitFlow 是另一种著名的复杂分支策略,可以应对在软件开发中可能出现的几乎所有情况。GitFlow 描述了每当开始处理一个新版本时,都会创建一个Develop分支,作为 master 的分支。Develop是集成分支,用于合并新功能并进行集成测试。它只应包含那些你认为已经准备好发布的工作。

Develop分支,你可以创建一个或多个功能分支,开始处理新的功能。只有当某个功能完成后,你才会将这个分支合并回Develop分支。

当你想发布一个新版本的应用程序时,你需要创建一个Develop分支的发布分支。在这个分支上的代码上,你进行最终测试,并在必要时进行一个或多个错误修复。当你对代码质量满意时,将这个分支合并到 master,并打上版本标签。你还需要将这些错误修复合并回Develop,以便它们也会被纳入到新的开发中。这个流程可以通过以下图示来展示:

如果遇到需要尽快修复的关键性错误,或者你需要做热修复,也可以使用这种流程。在这种情况下,你需要从 master 创建一个新的分支,修复错误。测试完成后,你将这个分支合并到 master 和 developer 分支,就像处理发布分支一样。

Release Flow

Release Flow是 Azure DevOps 团队用于开发 Azure DevOps 的分支系统。它同样基于使用短生命周期的主题分支,这些分支从 master 分支创建,并最终合并回 master。

区别在于,部署到生产环境的并不是 master 分支上的代码。而是每当需要发布新版本时,会从master创建一个新的分支,命名为release-{version}。这个分支上的代码会被部署到生产环境。一旦新的发布分支被部署,之前的发布分支可以被忽略。最终形成以下流程:

这种模型的优点是,它允许拍摄主分支当前状态的快照,并将其推向生产。如果生产环境中出现需要在新完整版本之前修复的错误,那么可以将正确的提交从主分支合并到当前发布分支。

基于主干的开发

在许多公司中,分支和合并是为了在发布新版本时保留灵活性,并且能够在最后时刻只选择该版本的更改。这种灵活性是以必须在某个时刻合并或集成更改为代价的。这一代价不仅仅是所需的时间,还有合并操作带来的风险。从两个不同分支中合并包含完美工作的代码,可能仍然会产生无法工作的代码。

出于这个原因,你可能会考虑切换到基于主干的开发。在基于主干的开发中,你不再使用分支来选择进入版本的更改。相反,团队中的每个开发人员都持续在同一个分支(通常是主分支)上工作,并且只为准备一个单一的更改而创建短生命周期的分支,然后将其合并回主分支。

当你采用这种方式时,你将需要另一种方法来确定在发布新版本软件时,哪些更改将可用,哪些更改尚未可用。你可以通过使用抽象分支来做到这一点。

抽象分支

当进行抽象分支时,你不会将代码的两个版本并排使用分支,而是将它们并排放在代码库中。例如,当你想要更改一个名为FoodClassifier的类的实现,而该类实现了IFoodClassifier接口时,你会执行以下步骤:

  1. 你将FoodClassifier类的名称重构为FoodClassifierToBeRemoved

  2. 你创建了一个完整的FoodClassifierToBeRemoved类的副本。

  3. 你将这个副本命名为FoodClassifier

此时,你的修改应该看起来像这样:

public class FoodClassifier : IFoodClassifier
{
 public FoodClassification Classify(Food food)
 {
 // Unchanged classification algorithm
 } 
}
public class FoodClassifierToBeRemoved : IFoodClassifer
{
 public FoodClassification Classify(Food food)
 {
 // Unchanged classification algorithm
 } 
}

请注意,在运行时,应用程序的行为将与之前一样。你只是添加了一个新的、尚未使用的类,并且改变了其行为。提交这些更改甚至将新的二进制文件发布给用户是安全的。现在,你可以开始更改新的FoodClassifier类的实现,进行测试,并建立对其实现的信任。同时,你可以继续提交并推送你的更改,甚至是推送给客户。切换到新实现可以通过依赖注入配置、布尔标志或环境变量来完成。只需选择在你的场景中最合适的方式。

只有在你完全满意新实现工作正常时,你才会删除FoodClassifierToBeRemoved类,并将任何引用更新回FoodClassifier

我们将在第四章中进一步探讨通过抽象进行分支,持续部署时讨论功能切换。虽然通过抽象进行分支是一种推荐的加速交付的方法,但它也是一把双刃剑。如果您没有流程来控制并行实现的数量,并在切换实现后清理它们,您的代码库质量可能会下降。

合并策略

根据您使用的源代码管理系统,可能有多种方式将您的更改从一个分支合并到另一个分支。

TFVC

当您使用 TFVC 时,您需要在本地准备合并,选择源分支和目标分支,然后选择要合并的更改列表。TFVC 然后执行合并,并将合并结果显示为本地更改。您可以检查这些更改,纠正或修改它们,并解决任何冲突。之后,您就可以像处理常规更改一样提交更改。

Git

使用 Git 进行合并时,可以通过切换到目标分支,然后合并源分支的所有更改来执行。如果分支之间存在冲突的更改,您必须像从服务器获取新更改时那样解决这些冲突。合并源分支的更改并解决所有冲突后,您可以提交更改。这将生成一个合并提交,您像提交任何其他更改一样将其推送到远程。

这可以使用 Visual Studio 或 VS Code 的图形界面完成,也可以使用以下命令序列:

git checkout targetBranch
git merge sourceBranch

如果存在任何冲突,您必须在此时解决这些冲突。否则,您无法继续:

git commit -m “Merged changes from sourceBranch”
git push

正如在保护仓库部分中所述,您可以通过禁止以这种方式合并来保护某些分支。尤其是对于主分支的更改,您可能希望使用另一种合并更改的机制,即拉取请求。使用拉取请求,您向其他人发出请求,让他们从您的分支拉取更改到目标分支。这样,其他团队成员可以首先审查您的更改,只有在它们满足所有约定的标准后才会合并。其他人可以对您的更改发表评论或要求更新,然后再执行合并。这是使用 Git 强制实施源代码四眼原则的最常见方式。四眼原则规定,每个更改或操作应由至少两个人查看。

现在,当您批准一个拉取请求时,您可以使用不同的策略来生成合并提交。最常用的策略是合并提交、压缩提交或变基。

合并提交

一个常规的合并提交是一种保持所有先前提交可见的提交类型。它有两个父引用,展示了变化的两个来源,即源分支和目标分支。这和你使用 Git 合并时手动执行的合并类型相同。此类型提交的优点是能够清楚地显示目标分支的新状态来源。

压缩提交

当执行所谓的压缩提交时,你将从源分支合并的所有单独提交合并为一个新的提交。这在源分支上的所有提交都与某个功能相关时非常有用,这样可以在目标分支上保留清晰简洁的变更历史。特别是当源分支中有错误修复或清理操作时,这种方法更为合适。缺点是你可能会丢失一些增量更改的背景信息,这些更改是在源分支上进行的。

重新基准化

重新基准化一个分支意味着将你的分支领先于主分支的所有提交暂时放到一边。同时,主分支领先于本地分支的所有提交将合并到本地分支中。最后,你之前放到一边的所有个人提交将被重新应用。下图展示了重新基准化提交前后的分支状态:

在重新基准化源分支后,它现在被合并到主分支中。这种合并的优点是你可以在一个单一的提交历史中保留所有的个人更改。

管理仓库

在使用 Azure Repos 时,每个团队项目最多可以拥有一个 TFVC 仓库。然而,在使用 Git 时,你可以在同一个团队项目中拥有多个仓库。最近越来越受到关注的讨论是,应该为所有应用程序使用一个仓库,还是为每个应用程序使用一个仓库。管理仓库时,其他重要的主题包括创建和删除仓库、保障仓库安全以及为仓库设置策略。

单体仓库或多仓库

当你使用单体仓库monorepo)时,你将所有项目和应用程序的代码存储在一个单一的源代码管理仓库中。与此相对的是,你可能使用多个仓库,每个应用、库或项目都存储在它自己的仓库中。两种方法各有优缺点,且从小公司到大公司都有使用这两种方式的案例。

单体仓库的潜在优势包括以下几点:

  • 现有代码的更容易复用:如果所有代码都存放在一个仓库中,任何人都可以访问并查看这些代码。这意味着复用的机会增加。

  • 将所有应用程序存放在一个仓库中,也意味着任何影响多个应用程序的更改都可以在一个提交中完成,在一个仓库中实现。一个典型的例子是 API 变更。

  • 由于所有代码都可以被每个人访问和维护,因此开发人员或团队不太可能将特定仓库视为自己的。这鼓励彼此之间的学习。

多个仓库的可能优点包括以下几点:

  • 单一仓库(monorepo)可能会变得非常非常大,甚至会达到开发人员仅签出或克隆单一部分仓库的程度。这实际上会抵消单一仓库的多数优点。

  • 拥有一个包含所有代码的仓库,可能会导致组件或应用程序之间的紧密耦合。如果你有多个仓库,你可以更新一个 API,并以新版本发布,逐一升级客户端。在单一仓库中,你可能会倾向于在一次提交中升级 API 并更改所有消费者,这样做的风险很大。

哪种方法最适合你,除了受讨论的优缺点影响,还受到团队和组织背景以及构成的影响。如果你有一个单一团队负责内部应用程序的所有开发,那么一个单一仓库(monorepo)可能更有意义。如果你有多个团队负责不同客户的不同应用程序,那么多个仓库可能更有意义。

创建和删除仓库

在 Azure DevOps 中,每个团队项目可以拥有多个 Git 仓库。试着做以下操作:

  1. 首先,访问“管理仓库”界面。下图显示了如何访问该界面:

  1. 打开此界面后,会弹出一个新界面(如下面的截图所示)。在这里,你可以通过点击带有加号的“添加...”按钮(在下图中标记为1)并填写仓库名称来添加新的仓库。

  2. 通过点击仓库名称并选择删除(标记为2),仓库也可以被删除:

删除仓库并不是经常发生的事情。将不再使用的仓库设置为只读或删除其所有授权,可能更为合适。

现在,让我们学习如何保护我们创建的仓库。

保护仓库

尽管分布式源代码控制的安全选项通常不如集中式源代码控制那样广泛,但 Azure Repos 提供了一些方法,可以为仓库或服务器端分支设置授权。在上一节的最后一张图片中,你还可以看到如何在中间列选择一个组或用户,然后更新仓库的授权。默认情况下,所有授权都会从项目默认设置继承。

建议尽量少更改授权,如果需要更改,通常最好通过组来操作并允许授权。

你还可以通过在左侧下拉菜单中打开仓库的分支,并点击你希望覆盖授权的分支来更改特定分支的授权。在前面的截图中,已标记为 3。

分支策略

最后,可以在特定分支的拉取请求上强制执行一个或多个策略。分支策略的界面如下面的截图所示,可以通过在管理仓库分支的授权时选择“分支策略”选项来访问:

前四个复选框与默认策略相关,可以根据个人偏好启用(或不启用)。默认情况下,它们都是禁用的。

构建验证可用于禁止合并任何拉取请求,如果选定的构建之一或多个未成功完成。如何设置这样的构建,你将在下一章学习。

除了构建外,你还可以调用外部服务来检查拉取请求,并决定是否允许合并。这里常用的集成是与代码质量工具的集成。你也可以在此调用自己的 API,以便在拉取请求标题、与工作项的关系或更复杂的约束等方面执行团队协议。

最后,你可以强制要求特定用户或团队必须参与拉取请求的审查。这可能是为了确保特定质量标准,但也可能成为影响开发速度和流畅性的限制因素。

其他源代码控制工具

除了 Azure Repos 中可用的源代码控制系统外,还有一些你应该了解的其他知名系统:

  • GitHub

  • GitLab

  • Subversion

我们将在接下来的子章节中逐一介绍这些内容。

GitHub

GitHub是一个托管源代码控制提供商,提供托管的 Git 仓库。GitHub 允许任何人创建任意数量的公开仓库。只有在创建需要三位或更多贡献者的私有仓库时,才必须切换到付费订阅。

这种允许在公开开发时无限制、免费使用平台的模式,使得 GitHub 成为目前全球最大的开源软件托管平台。

GitHub 于 2018 年被微软收购,之后微软与 GitHub 合作,创建了 GitHub 仓库和 Azure DevOps 之间的良好集成体验,特别是在 Azure Boards 和 Azure Pipelines 方面。除此之外,微软表示 GitHub 和 Azure Repos 将继续并行存在,目前没有计划终止其中任何一项产品。

GitHub 还提供了一种本地部署的版本,称为 GitHub Enterprise。

GitLab

GitLab 是另一个提供托管 Git 仓库的平台。与 Azure DevOps 一样,源代码控制托管是它提供的服务之一。

Subversion

一种较老的源代码控制系统是 Subversion。Subversion 开发并首次使用于 2004 年,由 Apache 软件基金会维护。Subversion 是一种集中式源代码控制系统,支持你期望的所有功能。

关于 Subversion 比 Git 较差的论点有很多,然而这些论点大多并不适用于最新版本的 Subversion。事实上,Subversion 是一种广泛使用的源代码控制系统,尤其适用于非常大的代码库或有特定授权需求的代码库。

虽然 Azure DevOps 无法托管 Subversion 仓库,但它可以连接并与存储在 Subversion 中的源代码协同工作。

总结

在本章中,你学习了源代码控制。我们了解到源代码控制有两种类型:集中式和去中心化,Azure DevOps 都支持这两种类型。TFVC 不再推荐用于新项目。你应该在启动新项目时使用 Git。

使用 Git 时,你可以在团队项目中拥有多个代码库。每个代码库都可以为特定分支分配策略,以锁定并强制执行“四眼原则”。你还了解了访问控制以及如何为用户提供访问一个或多个代码库的权限。最后,你学习了其他工具,并了解了如何将源代码从一个工具迁移到另一个工具。

你可以运用所学来决定在你的产品中使用哪种类型的源代码控制系统。你现在能够专业地组织你所工作的代码库。你也能处理不同的分支策略,并使用策略来强制执行安全性或质量要求。

下一章将会利用你所学到的源代码控制知识,帮助你设置持续集成。

问题

在我们总结时,以下是一些问题,帮助你测试对本章内容的理解。你可以在附录的 评估 部分找到答案:

  1. 集中式与去中心化源代码控制之间有什么区别?在什么情况下哪种更适用?

  2. 判断正误:Git 是一种去中心化的源代码控制系统的例子。

  3. 以下哪项不是常见的分支策略?

    1. 发布流程

    2. 变基

    3. GitFlow

    4. GitHub 流程

  4. 许多公司希望在代码合并到主分支之前进行代码审查。使用 Git 时,采用何种方式来执行此操作?如何在 Azure DevOps 中强制执行这一操作?

  5. 以下哪些不是有效的合并策略?

    1. 变基

    2. 基于主干的开发

    3. 合并提交

    4. 压缩提交

深入阅读

第三章:迁移到持续集成

在为你的组织设置源代码控制,并决定支持并行工作的分支和合并策略之后,你就可以继续进行持续集成。持续集成是一种方法,其中每个开发人员将自己的工作与他人的工作进行集成,并验证合并后的工作质量。这样做的好处是在流水线的早期提高质量,减少后续合并代码更改时出现错误的风险,减少生产环境中发现的错误数量,从而降低成本并保护你的声誉。

持续集成只有在具备必要工具和适当设置时才能实现。在本章中,你将学习如何使用 Azure DevOps 管道来设置持续集成。

本章将涵盖以下主题:

  • 引入持续集成

  • 创建构建定义

  • 运行构建

  • 使用 YAML 管道

  • 代理和代理队列

  • 其他工具

技术要求

要进行本章中的操作示例,你需要一个 Azure DevOps 组织。

引入持续集成

持续集成是一种方法论,开发人员将自己的更改与项目中所有其他开发人员的更改进行集成,并测试合并后的代码是否仍然按预期工作。通过这种方式,你可以创建一个快速反馈的循环,及时了解自己工作的效果。

在使用广泛的分支策略来隔离代码更改时,开发人员经常会在一个独立的分支上工作几天、几周,甚至几个月。虽然这种做法可以确保他们的更改不会影响其他人,但它也是确保以后不会出现合并问题的好方法。如果你曾经将几周或几个月的工作合并回主分支,你就会知道这需要多少工作,而且通常会导致错误或其他问题。

为了防止这种情况,开发人员应该养成每天至少将自己的更改与其他开发人员的更改集成一次的习惯。这里的集成至少指合并、编译和运行单元测试。这样,开发人员的更改质量会不断收到反馈,并且由于这些反馈是合并的,因此它是一种防止后续合并问题的好方法。

持续集成还可以使你在管道中嵌入其他关注点,自动保持代码的质量。测试和安全扫描就是两个典型的例子。这些话题将在后续章节中讨论,但一个良好的持续集成管道是这些实践的基础。

在本章的其余部分,你将学习如何使用 Azure Pipelines 设置持续集成的技术手段。但首先,我们来看一个常见的误解以及持续集成的四大支柱。

虽然自动化的持续集成构建是执行持续集成的一个重要组成部分,但持续集成不仅仅是拥有一个构建管道。需要记住的重要一点是,持续集成是一个过程,其中每个开发者至少每天将他们的工作与同事的工作进行整合。然后,将整合后的源代码进行编译和测试。价值来自于编译和测试整合后的工作,而不是孤立的工作。

持续集成的四大支柱

持续集成成功采用的四大支柱:

  • 版本控制系统:用于存储自系统创建以来所做的所有更改。版本控制系统在前一章中已有讨论。

  • 软件包管理系统:用于存储你在自己应用程序中使用的二进制包以及你创建的包。将在第五章中详细讨论,依赖管理

  • 持续集成系统:可以将所有开发者的更改合并到一起——每天多次——并创建一个整合后的源版本。可以使用 Azure DevOps 管道来实现这一点。

  • 自动化构建过程:用于编译和测试合并后的源代码。我们将展示如何使用 Azure DevOps Pipelines 实现这一过程。

可以在 Azure DevOps 中设置持续集成和自动化构建。下一节将解释如何在 Azure DevOps 中设置这两者。

在 Azure DevOps 中创建构建定义

执行持续集成的主要方式是使用持续集成构建。在 Azure DevOps 中,构建可以作为 Azure Pipelines 服务的一部分进行配置。目前有两种方法可以创建构建定义:

  • 通过视觉设计器(也叫做经典构建与发布

  • 通过另一种标记语言 (YAML) 文件(也称为 YAML 管道多阶段管道

本节其余部分重点介绍视觉设计器。下一节,YAML 构建定义,将更详细地讲解 YAML 管道。两种方法支持大致相同的功能,尽管存在一些差异。一些在经典构建和发布中可用的功能目前(尚未)在 YAML 构建定义中提供。此外,一些新功能仅在 YAML 管道中提供。

如果你没有管道的经验,经典编辑器是一个很好的一步,可以帮助你熟悉持续集成/持续开发管道的工作方式,然后再过渡到 YAML 管道。经典构建中的几乎所有概念也都可以转化为 YAML 构建。

在接下来的几节中,我们将从构建经典构建管道开始。

连接到源代码管理

要开始创建构建定义,请按照以下简单步骤操作:

  1. 打开 Pipelines 菜单。

  2. 在此菜单中,点击构建。这里,你将看到一个按钮用于创建新的构建。点击该按钮后,将打开一个新的视图用于创建构建,如以下截图所示:

  1. 然后,你将被引导到新的 YAML 体验界面,但你仍然可以通过选择经典编辑器选择返回。

选择经典编辑器后,你可以配置如何连接到源代码控制系统。经典编辑器是以下各节中所有截图中可见的编辑器。

支持许多源代码控制系统。如果你使用的是托管的 Git 仓库,选择你的具体产品(如果有),如果没有可用的产品,选择“其他 Git”;目前,支持 GitHub、GitHub 企业服务器和 BitBucket Cloud。之所以如此,是因为使用“其他 Git”进行持续集成时采用的是轮询模型,而所有特定产品都使用它们已知的集成 Webhook。以下示例适用于位于同一 Azure DevOps 实例中的 Git 仓库。

当你选择流水线头部时,你可以设置构建定义的名称,并选择默认运行阶段的代理池。代理负责实际执行任务,代理的详细信息将在本章的“代理和代理队列”部分进行更深入的探讨。

在流水线头部下方,你可以看到构建定义的时间顺序布局。首先是下载源代码。在这里,你可以再次选择连接到源代码控制系统。你还可以指定更多与获取源代码方式相关的高级设置,如是否先清理构建目录、选择分支或添加标签。

配置作业

在源节点下方,你可以添加一个或多个作业,执行你想要完成的大部分工作。可以通过流水线头部的省略号来添加作业。这里有两种类型的作业:

  • 无代理作业:无代理作业可用于运行不需要代理的任务。

  • 代理作业:代理作业用于运行需要在代理上运行的任务,这适用于大多数任务。

一些无代理任务的示例如下:

  • 等待手动批准后继续

  • 插入延迟后再继续

  • 调用 REST API

  • 调用 Azure 函数

无代理作业的主要好处是它在运行时不会占用代理。这样可以释放代理去做其他工作,意味着你需要的代理更少,从而节省成本。此外,你可以并行使用的代理数量受你在 Azure DevOps 中购买的并行流水线数量的限制。限制代理作业的数量也能节省费用。

让我们回顾一下配置作业的过程:

  1. 选择任何任务。你将看到以下截图所示的视图。在此视图中,你可以更改任务的名称,并且对于代理任务,可以覆盖执行此任务的代理池:

  1. 接下来,指定要用于执行该任务的代理池。在这里,还指定了你对执行该任务的代理的需求。需求将在本章的代理和代理队列部分进行讨论。

  2. 作为代理执行计划的一部分,你可以指定并行性,并选择以下三种选项之一:

    • 无:这将依次在同一代理上执行你添加到代理任务中的所有任务。

    • 多配置:在这里,你可以指定一系列变量来确定要运行的构建变体的数量。如果你想从相同代码创建例如 x86 和 x64 构建,这非常有用。

    • 多代理:在这里,你可以指定将并行运行相同任务的代理数量。

  3. 接下来,你可以指定一个或多个依赖项。这些是需要在选定的任务运行之前完成的其他任务。

  4. 此外,对于任何任务,你都可以指定如何处理前一个任务中的错误,告诉它是继续执行还是停止。

作为第 3 步和第 4 步的替代方案,你还可以指定一个自定义表达式来判断是否应运行某个任务。此表达式应返回布尔值,并支持基本操作,如or()and()eq()。以下是一个示例条件:

and(succeeded(), ne(variables['Build.SourceBranch'], 'refs/heads/master'))

此条件指定,只有在所有先前的任务都成功并且构建不是从主分支启动时,任务才会执行。详细描述条件语法的链接将包含在本章的末尾。

无代理任务的选项比代理任务少。例如,无法在多个变量值的并行任务中执行相同的构建。

向任务中添加任务

在添加一个或多个任务之后,你可以将任务添加到工作中。任务定义了在构建执行过程中要完成的实际工作。以下截图展示了如何添加任务并进行配置:

  1. 点击你想要添加任务的工作旁边的加号:

  1. 然后,你将看到一个任务选择器,在其中可以找到任何与搜索输入匹配的任务,并通过点击添加按钮来添加一个或多个任务。然后将打开一个新屏幕,你可以在其中配置各个任务。此处提供的选项因任务而异。

  2. 一个任务可以有多个版本,你可以在任务的主要版本之间切换。这意味着维护者可以推送非破坏性的更新,你将自动接收这些更新。主要或破坏性更新可以通过新的主要版本号推送,你可以根据自己的需要进行升级。

可以根据需要向管道任务添加任意数量的任务。

发布构建工件

构建定义的一个重要部分是它的结果。构建通常用于生成一个或多个工件,这些工件稍后会用于应用程序的部署。工件的示例可以是可执行文件或安装程序文件。这些文件需要在流水线完成后可供使用。

如上图所示的发布构建工件任务是专门设计用于此目的的任务。它允许您选择一个文件或目录,并将其发布为工件名称。其结果是,所选路径中的文件会在每次流水线执行时保留,以便手动下载或稍后在发布定义中使用。发布定义将在下一章第四章《持续部署》中讨论。

接下来,我们将学习如何将我们的流水线与其他工具集成并配置我们的服务连接。

调用其他工具

在构建流水线时,我们通常需要将它们与其他工具集成。对于源代码控制系统,这是创建流水线时的一部分,您只能选择内置的选项。对于任务,您可以使用服务连接创建对任何工具或位置的引用。以下截图展示了一个使用服务连接连接到 Azure 应用服务的任务示例。

服务连接是指向第三方系统的指针,具有一个名称和一系列不同于每种类型的服务连接的属性。通常,您需要输入一个 URL 来定位另一个服务,并提供身份验证机制。以下步骤将帮助您配置服务连接:

  1. 在定义一个或多个服务连接后,您可以从下拉菜单中选择要使用的服务连接:

  1. 服务连接在项目设置中集中管理。您可以通过直接从当前配置的任务访问管理视图,如上图所示。您也可以通过导航到项目设置,然后转到服务连接来访问,如下图所示(见标签 1):

  1. 在此视图中,您可以添加新的服务连接或更新现有的服务连接(见上图中的标签 2)。

默认情况下,服务连接的作用域是项目级别,这意味着它们并非对整个 Azure DevOps 组织中的所有人可用。为了鼓励服务连接的重用,Azure 从 2019 年中期起允许在项目之间共享它们。

任务市场

Azure Pipelines 内置了一组常用任务;然而,使用 Azure DevOps 的 Visual Studio 市场可以找到更多任务。如果你是管理员,你可以在这里查找并安装扩展,这些扩展会添加任务。如果你是普通用户,你也可以在这里找到任务;然而,你不能安装它们,只能请求它们。你的 Azure DevOps 管理员会收到通知,如果他们批准,便可以代表你安装该扩展。

当然,你也可以编写和分发自己带有任务的扩展。

创建变量和变量组

当你配置构建时,可能会有一些值需要多次使用。通常明智的做法是将这些值提取为变量,而不是在任务中反复使用这些值。

变量可用于记录你不希望存储在源代码控制中的值。像密码和许可证密钥这样的值,可以在使用锁定符号锁定后,安全地存储为不可检索的值(请参见下图标签 1)。保存构建定义后,这些值会被加密,并且只能被属于它们的构建使用。你将无法再检索这些值,它们将会从日志和其他输出中自动清除。

要学习如何在 Azure Pipelines 中使用变量,请按照以下步骤操作:

  1. 在 Azure Pipelines 中,你可以通过进入变量 | 管道变量选项卡来添加变量到你的构建定义中(请参见下图标签 3)。在这里,你可以按名称值的形式输入它们,正如下图所示:

  1. 一旦定义好,你可以在同一构建的所有任务和所有工作中使用这些变量。为此,你可以使用以下符号:
$(variableName)
  1. 最后,你可以将变量标记为“队列时可设置”(请参见前面截图中的标签 2),这意味着每当有人排队新的构建时,可以更改这些变量的值。一个使用此功能的变量示例是system.debug内置变量当此变量设置为true时,构建中将包含详细的调试日志记录。

除了你自己的变量外,还定义了系统变量。这些是包含当前正在运行的构建信息的变量,包括版本号、代理名称、构建定义详情、源代码版本等。系统定义变量的完整列表链接将在本章末尾提供。

变量组

除了为特定构建创建变量外,你还可以创建变量组。这些变量组可以与一个或多个构建关联。这是共享变量的一种有效方式;这些变量的示例可能是你公司的名称、商标文本、产品名称等。让我们来看一下如何使用变量组:

  1. 通过在“管道”菜单中点击“库”来访问变量组(参见下方截图中的标签 1)。这将显示现有的变量组列表,你可以在这里编辑并添加新的变量组,如下截图所示:

  1. 在这里,你可以像处理构建中变量那样处理变量。唯一的区别在以下列表中有突出显示:

    • 你不能将组中的变量标记为在队列时可设置。

    • 你可以允许或拒绝在所有管道中使用此组。如果你拒绝在所有管道中的使用,则只有你自己可以使用该变量组。你可以通过安全选项(在前面的截图中标记为 2)授权其他用户或组。

    • 你可以参考一个 Azure 密钥库,变量组将作为占位符。登录 Azure 后,你可以选择一个密钥库,并选择你希望通过变量组访问的密钥库中存储的值。

Azure 密钥库是一个 Azure 服务,用于安全存储机密。密钥库中的机密会自动版本控制,因此较旧的值不会被覆盖,而是被更新的版本所替代。此外,你可以指定隔离的访问策略,按用户指定是否可以读取、写入、更新或删除值。所有这些操作都会在密钥库中进行审计,因此你也可以查找是谁进行了哪些更改。如果你将 Azure DevOps 与密钥库连接,则会在你的活动目录中创建一个新的服务主体,并授予它对该密钥库的访问权限。现在,每当 Azure DevOps 需要从变量组中获取变量时,实际的值将从密钥库中提取。

变量组可以链接到构建中的变量,位于“变量组”选项卡下(参见前一部分的截图)。

除了处理变量组,你还可以处理库中的文件。你可以上传一些其他用户无法访问的文件,但这些文件可以在构建中使用。这对包含私钥、许可证密钥及其他机密文件非常有用。

就像你使用变量组一样,你可以指定每个安全文件是否可以被任何构建使用,或者只授权特定用户使用。

触发构建

构建定义中的下一个选项卡控制着什么应该启动或触发构建。要实现持续集成,按照以下步骤操作:

  1. 点击触发器选项卡,然后选择左侧的第一个标题:

  1. 勾选启用持续集成框。这意味着 Azure DevOps 将监听你仓库中的变化,并在有新机会时立即排队进行新的构建。

  2. 接下来,您可以选择是否在每次有新更改时单独构建每个更改,或者在多个新更改到达时将它们批量构建。建议您尽可能单独构建每个更改。

  3. 除了连续集成触发器外,您还可以指定一个或多个分支和路径过滤器。在这里,您可以指定哪些分支和文件需要排队进行新的构建。您可以根据需要指定包含或排除的项。一个常见的例子是将构建限制在主分支上。如果您的代码库中有名为docsrc的文件夹,并且所有源文件都在后者文件夹中,那么将触发器限制为该路径可能更为合理。

  4. 除了选择使用连续集成触发器外,您还可以选择按计划定期执行构建,选择一个或多个工作日和一个时间。

  5. 您还可以安排在另一个构建完成时自动启动一个构建。这被称为构建链

接下来,让我们学习如何更改构建定义的配置。

构建选项

您可以更改构建定义的高级配置选项。这些选项包括描述、构建号格式以及在失败和超时时自动创建工作项。要进行设置,请按照以下步骤操作:

  1. 点击选项卡。您应该到达以下屏幕:

  1. 现在,创建您的构建号格式。如果该字段留空,您的应用程序构建号将设为一个不断增加的数字,每次构建时增加 1。这个数字在团队项目内是唯一的,并且在所有构建定义中递增。您还可以使用您可用的变量来指定您自己的格式。常见的做法是手动指定主版本号和次版本号,然后使用变量添加递增的数字。以下示例指定了一个版本为 4.1.xx,其中最后部分由一个递增的两位数字替代:
4.1($Rev:.rr)
  1. 在右侧,有一些高级(但很少使用的)选项,用于指定每个作业在构建定义中的授权范围和构建超时设置。

  2. 还可以指定每个作业在构建定义中应该满足的代理需求。我们将在本章的代理和代理队列部分进一步讨论需求。

左侧的其他选项使您能够暂时暂停流水线。

构建历史

最后一个标签页,称为“历史记录”,显示了对构建定义所做的每一个更改的列表。构建定义以 JSON 格式存储,您可以查看每个更改的并排比较。保存构建时添加的评论也会存储在这里,并可以用于提供更改的理由。

由于构建是保证质量的重要手段,因此重要的是跟踪谁修改了它们,以确保不会删除自动化质量指标。

现在,您已准备好运行第一次构建了。您可以直接使用本节大多数截图中可见的保存排队按钮来运行它。本章的运行构建部分将教您如何处理所获得的结果。

任务组

在一个具有多个管道的团队或组织中工作时,通常不久就会出现多个具有相同结构的管道。例如,在某些公司,所有管道都包含安全扫描、运行测试和计算测试覆盖率的任务。

不必在各处重复这些任务,可以将它们从现有管道中提取到任务组中。任务组本身可以像任务一样在多个管道中使用。这样做可以减少创建新管道或更新所有管道以满足新需求的工作量。这样做还确保使用任务组的所有管道具有相同的任务配置。

要创建新的任务组,请打开任何现有的构建定义并按以下步骤操作:

  1. 通过点击它们并同时按住Ctrl键,或使用鼠标悬停在任务上时出现的选择器来选择一个或多个任务。

  2. 右键单击所选项并选择创建任务组。

  3. 在弹出窗口中(截图未显示),选择任务组的名称、描述和类别。如果选择的任务中有指定变量值,现在可以为这些参数提供默认值和描述。这些参数将在使用任务组时可用,并且需要在使用任务组时进行配置。

  4. 单击创建(截图未显示)后,现有的构建定义将被更新,删除所选任务并用新的任务组替换它们。

将已有的任务组添加到构建或发布定义中的方法与添加常规任务完全相同。任务组显示在可供选择的任务列表中。

可通过导航到管道菜单然后选择任务组找到所有现有的任务组列表。要编辑现有任务组,请在显示的列表中选择它,然后选择编辑选项。编辑任务组的方式与编辑构建定义完全相同。

本节讲解了创建构建定义及描述应用程序构建方式的内容。下一节是执行构建。

运行构建

在本节中,您将学习如何处理构建结果,并用它们来报告和生成构建。您还将学习如何在每次拉取请求时运行构建,并将变更的质量报告回到拉取请求,以帮助审阅者。

查看构建结果

在构建运行时,代理将按顺序执行所有配置的步骤。Azure Pipelines 会捕获所有这些步骤的详细信息和日志。如以下截图所示,构建会在左侧显示其执行的所有步骤列表。点击任何一个步骤将打开一个详细视图,显示每个步骤的日志:

每当构建过程中出现警告或错误时,它们分别以橙色或红色显示。

构建拉取请求

在设置好构建定义并运行第一次构建后,你可能会看到第一次失败的出现——例如,当有人不小心提交并推送了无法编译或包含无法成功运行的单元测试的更改时。你可以通过在拉取请求到达时自动运行构建定义来防止这种情况发生。要进行配置,请按照以下步骤操作:

  1. 在项目设置中点击“策略”(Policies)。会打开以下屏幕,点击“添加构建策略”(Add build policy):

  1. 选择一个构建定义,用于验证拉取请求。

  2. 接下来,你可以配置另外三项内容:

    • 触发器:定义构建定义何时启动,可以是自动启动或手动启动。当然,真正的价值来自于自动运行验证构建。

    • 策略要求:这决定了如果构建失败,拉取请求是否可以完成。换句话说,这决定了你是否可以忽略失败的构建。建议尽可能避免将此设置为“可选”(Optional)。

    • 构建过期时间:这决定了一个成功的构建结果有效的时间长度。默认值为12小时,但你应该考虑在主分支更新时将其更改为立即。这样做的好处是,无法在未先运行构建并验证当前分支状态与拟议更改组合的情况下合并更改。

你可以添加多个构建策略。如果你有很多可以自动验证的内容,并且希望将自动验证时间保持在最低,那么这种方法是一个不错的选择。

访问构建构件

除了编译、测试和验证源代码外,构建还可以用于生成所谓的构件。构件是构建过程中的输出,可以是你希望从构建中保存和发布的任何内容,例如测试结果和应用程序包。

应用程序包是指应用程序某个版本的不可变构建。该包稍后可以在发布过程中被提取,并部署到一个或多个环境中:

在前面的截图中,你可以看到,在执行构建的摘要部分,发布了两个工件。可以通过屏幕右上角的“工件”下拉菜单或“摘要”标签访问工件。你可以从此页面下载并浏览工件,在下一章中,你将看到如何使用这些工件来设置持续交付。

太好了!通过这个,你已经学会了如何使用可视化设计器创建定义。但等等——正如我们之前提到的,还有另一种方法,那就是使用 YAML 文件。让我们在下一节中看看这个方法是如何工作的。

使用 YAML 流水线

你已经看过如何使用可视化设计器创建构建定义。从 2019 年初开始,另一种新的替代方法是使用 YAML 流水线。在使用 YAML 流水线时,你需要在 YAML 文件中指定完整的构建定义,并将其存储在源代码控制中,通常与构建所针对的源代码一起存储。

虽然两种流水线系统并存,但现在使用 YAML 流水线是定义流水线的首选方法。这意味着新特性很可能只会出现在 YAML 流水线中。

使用构建定义作为代码的原因

当你第一次开始使用 YAML 构建定义时,你可能会发现学习曲线比使用可视化设计器时更陡峭。这可能会引发一个问题,为什么你要使用 YAML 定义的构建。YAML 构建定义相较于可视化设计定义有两个主要优点。

当你在 YAML 中编写定义时,它可以与代码一起托管在源代码控制中。这样做的结果是,你在更改源代码控制时所制定的所有策略现在会自动应用于你的构建定义。这意味着任何更改都必须通过拉取请求,经过同行审查,并且可以提前进行构建和验证。在你的构建定义和代码中强制执行四眼原则,有助于提高构建过程的稳定性。当然,这也有助于安全性和合规性,这些话题将在后续章节中讨论。

除了提高安全性之外,将构建定义存储在源代码控制中,还意味着它在每个分支中都是可用的。这意味着它可以在每个分支中进行更改,以便在合并到主分支之前构建该特定分支。当使用可视化设计的构建定义时,这个单一的定义负责构建不仅仅是你的主分支,还包括你希望通过拉取请求合并的所有分支。

这意味着你必须执行以下操作之一:

  • 更新构建定义,以便合并你将要合并的更改。然而,这将导致当前主分支的构建中止。

  • 合并更改,这也会导致构建失败,因为构建定义尚未更新。

这两种选项都有可能允许错误的更改流入目标分支,从而破坏持续集成构建的目的。通过为每个分支创建一个构建定义,我们消除了这个问题。

虽然将构建定义存储在源代码控制中是有益的,但这在经典构建中也同样适用。每个更改都会被记录,您可以看到谁在何时更改了什么,并且可以查看更改作者的可选说明。

编写基本的 YAML 管道

要开始使用 YAML 构建,您需要做两件事:

  1. 首先,您需要编写您的 YAML 文件。

  2. 然后,您需要从中创建一个构建定义。

那么,让我们开始吧。

编写 YAML 文件

以下代码示例包含了构建 .NET Core 应用程序并运行单元测试的示例 YAML 定义。将文件保存为任意名称,例如 pipeline.yaml,并放入 Azure DevOps 中的任意 Git 仓库中。然后,稍后可以使用它创建一个管道:

trigger: 
- master

pool:
  name: Azure Pipelines
  vmImage: windows-2019

steps:
- task: DotNetCoreCLI@2
  displayName: 'dotnet build'
  inputs:
    projects: '**/*.csproj' 
- task: DotNetCoreCLI@2
  displayName: 'dotnet test'
  inputs:
    command: test
    projects: '**/*.csproj'

这个示例 YAML 定义了一个基本的管道。每个管道都需要以某种方式被触发。就像经典构建一样,可以通过将管道连接到源代码仓库中的变更来触发管道。默认的仓库是包含 YAML 定义的仓库。trigger 关键字用于指定哪些分支的推送应触发管道。一个好的起点是 master 分支。由于 trigger 关键字接受一个列表,可以指定多个分支并使用通配符。

触发器不是必需的,因为管道也可以手动启动。

还有其他选项可以替代使用 trigger 关键字,例如在仓库中包括或排除一个或多个分支、标签或路径。这些选项在 docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema#triggers 中有详细描述。

除了触发器外,每个管道还包含一个或多个任务,就像经典构建定义中的任务一样。所有这些任务需要在代理池上执行——同样,就像经典构建定义中的任务一样。pool 关键字用于指定一组键/值对,通过指定池的名称来决定任务将在哪个池上运行。在使用 Microsoft 提供的默认代理时,可以使用 Azure Pipelines 的默认名称。在使用此特定池时,必须指定一个虚拟机映像。这决定了将执行任务的代理上可用的操作系统和软件。

可以在 docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted#use-a-microsoft-hosted-agent 找到所有可用的虚拟机映像的最新列表。

最后,定义包含了构成管道本身的一系列步骤。这些步骤与经典构建管道中你可以拖入的任务一一对应。通过指定任务的名称和版本(用@符号分隔),可以添加任务。接下来,你可以选择性地为任务指定显示名称。这个显示名称稍后会在显示执行结果的视图中可见。最后,为任务指定一个或多个输入。这些输入与任务特定的配置相关,你之前在可视化设计器中已经看到过。

创建 YAML 管道

在将 YAML 文件保存在代码库后,你可以从中创建构建定义。在创建新的构建定义时(请参阅本章的创建构建定义部分),你应按照以下步骤进行操作:

  1. 启动向导时,选择 Azure Repos Git YAML 选项。

  2. 从这里开始,通过向导选择并查看你想要构建的 YAML 文件,如下图所示:

  1. 在第一步中,定位包含你想用作管道的 YAML 文件的代码库。

  2. 接下来,选择一个示例 YAML 文件开始配置管道,或者引用一个已经存在的文件。

  3. 最后,你可以查看已选择的 YAML 文件,并从中启动构建。

你的管道会自动保存。一旦管道被保存,就可以启动并像使用经典构建管道一样与其交互。

多作业管道

你在上一节中看到的管道没有指定任何作业,正如你从经典构建部分回忆起来的那样。相反,它在steps关键字下包含了一组任务。这意味着它隐式地只包含一个作业。使用 YAML 管道时,也可以创建一个包含多个作业的定义。为此,可以使用以下结构:

trigger: 
- master

pool:
  name: Azure Pipelines
  vmImage: windows-2019

jobs:
- job: job1
  displayName: A pretty name for job1
  steps:
  - task: DotNetCoreCLI@2
    ...
- job: job2
  displayName: My second job
  pool:
    name: Azure Pipelines
    vmImage: ubuntu-18.04
  ...

与其直接在管道中添加steps关键字,不如首先创建一个作业列表。在该列表中,添加一个或多个job关键字,然后是该作业的名称。在此技术名称旁边,可以为每个作业指定一个显示名称(displayName)。

如第二个作业所示,你还可以为每个作业指定使用哪个代理池。当未为作业指定代理池时,将使用文件顶部指定的默认池。

本节讨论的作业称为代理作业。除了代理作业外,还有服务器作业、容器作业和部署作业可供选择。有关这些类型作业的更多信息,请访问docs.microsoft.com/en-us/azure/devops/pipelines/process/phases#types-of-jobs

默认情况下,管道中的所有作业并行运行,但可以使用控制选项来改变这一点。

控制选项

要控制作业的顺序,可以在作业定义中使用 dependsOn 关键字。这表明该作业只能在一个或多个作业完成后才可以开始。除此之外,还可以使用 condition 关键字来指定作业应在何种条件下运行。可以将这两个关键字结合起来,实现更复杂的场景,如下所示:

jobs:
- job: compile
  steps:
  ...
- job: test
  dependsOn: compile
  steps:
  ...
- job: build_schema
  dependsOn: compile
  steps:
  ..
- job: report
  dependsOn:
  - test
  - build_schema
  condition: or(succeeded('test'), succeeded('build_schema'))
  steps:
  ..

这个管道将首先运行名为 compile 的作业。一旦该作业完成,接下来的两个作业,testbuild_schema,将并行运行,因为它们都依赖于 compile 任务。在这两个任务完成后,report 任务将运行,因为它声明依赖于 testbuild_schema 作业。在这个作业实际开始之前,会评估条件,以决定该作业是否真正运行或跳过。条件可以使用类似于许多编程语言的语法来构建。它检查作业是否成功完成,使用 succeeded()failed() 函数。此外,还支持 or()and()ne() 等布尔运算符。

你可以根据需要组合使用 dependsOncondition 关键字。唯一的要求是,至少应有一个作业不依赖于任何其他作业。

变量

就像经典构建管道一样,YAML 管道支持使用变量。变量可以在 YAML 管道的每个级别(任务内除外)使用以下语法定义:

variables:
  name: value
  anotherName: otherValue

变量可以使用你已经熟悉的经典构建管道语法来检索——$(name)$(anotherName)

还可以在 YAML 管道中引用现有的变量组。这是通过使用 group 关键字来完成的,而不是指定变量的名称。要从名为 myVariableGroup 的变量组中检索所有变量,可以将前面的 YAML 扩展如下:

variables:
  name: value
  anotherName: otherValue
  group: myVariableGroup

变量可以在 YAML 管道的每个级别设置,但只有在根级别设置的变量可以在手动排队新执行时被覆盖。

管道工件

就像经典构建一样,YAML 管道可以用于构建和发布工件。由于用于执行此任务的任务与其他任务一样,它可以直接添加到作业的步骤列表中。

然而,随着 YAML 管道的引入,一种新的工件类型——所谓的管道工件——已经可用。这带来了提高大规模工件上传和下载速度的好处。在使用经典发布时,管道工件不会自动下载,而构建工件会。

要发布管道工件,可以在作业的 steps 关键字中使用以下 YAML:

steps:
- publish: folder/to/publish
  artifact: artifactName

管道工件主要用于在多阶段 YAML 管道中下载,后续章节也会涉及此内容。

编写 YAML 管道的技巧

从零开始编写 YAML 管道在你刚开始时可能会比较复杂。此时有两个工具可以帮助你。

首先,可以从可视化设计器导出 YAML。对于每个任务,都有一个带有“查看 YAML”标题的链接。点击该链接会弹出一个小窗口,显示你当前打开的任务和配置对应的 YAML。同样的操作也可以用于作业,并且在特定条件下也可以用于完整的构建定义。

另一个用于编写 YAML 的工具是内置的 YAML 编辑器:

每当你打开一个 YAML 构建定义时,有两个工具可以帮助你。首先,在 YAML 文件的每个位置都有自动完成功能。它会显示该位置可用的选项。此外,在右侧的任务选择器中也有代码片段可供选择。当选择右侧的任何任务时,你可以通过可视化配置它们,然后点击添加按钮将生成的 YAML 添加到你的定义中。

这两个工具旨在将可视化设计器的简便性带入 YAML 构建体验,结合两者的优势。

代理和代理队列

到目前为止,你创建的构建定义可能包含代理作业,而这些作业又包含任务。这些任务并不会直接在你的 Azure DevOps 组织中执行,而是由在虚拟机或容器中运行的代理执行。代理被分组到代理池中。你可以使用两种类型的代理池:

  • 内置代理池

  • 自托管代理池

让我们逐一了解它们。

内置代理池

内置代理池由微软管理,作为产品的一部分提供给你。根据你的需求,提供不同的代理池。这些池运行不同版本的 Windows 和 Visual Studio,同时也有运行 Linux(Ubuntu)和 macOS 的池。

这些托管池的缺点是,如果你需要,你无法在托管代理的机器或容器上安装额外的软件。这意味着,在这些情况下,你必须创建自己的私有代理池。

创建私有代理池

私有池在你的 Azure DevOps 组织中定义,并从那里提供到一个或多个团队项目。然而,你也可以在团队项目级别创建私有池,以便一次性创建和提供它们。为此,进入 项目设置 | 代理池。你应该会看到以下的添加代理池选项:

给池命名并确定是否希望自动为所有管道提供访问权限后,你可以保存池。在创建池之后,你可以添加或移除代理。

添加和移除代理

添加代理分为两步:

  1. 下载并提取代理运行时。你可以通过访问代理池概览部分并打开任何私有代理池的详细信息来找到代理运行时。打开池的详细信息后,点击右上角的“新建代理”:

  1. 在打开的对话框中,你可以下载一个包含代理和提取及安装代理说明的 ZIP 文件。

在配置阶段,系统会提示你使用你的 Azure DevOps 组织进行身份验证,并提供你希望安装代理的代理池名称。虽然有 x86 和 x64 代理可用,但建议你使用 x64 代理,除非你有特殊原因不使用它。

要从池中移除代理,你可以使用两种方法:

  • 你可以返回 PowerShell 命令行,就像安装时一样,使用以下命令:
.\remove.cmd
  • 另外,你还可以通过代理标签页从代理池概览中移除代理。前往“项目设置” | “代理池”(参见以下截图中的标签 1)| “代理”(参见以下截图中的标签 2),然后选择你要移除的代理的选项按钮(参见以下截图中的标签 3)。接着,点击“删除”(参见以下截图中的标签 4):

在上面的截图中,你可以看到使用界面移除代理的步骤。请注意,这不会清理主机上的二进制文件和任何文件;然而,如果托管代理的机器出现故障或虚拟机被移除,这就是移除代理的唯一方法。

代理选择

每当构建作业开始运行时,系统会从代理池中选择一个代理来执行你在管道中定义的任务。选择代理的过程分为两个步骤:

  1. 只有属于选定池的代理才有资格运行任务。这意味着,在使用私有代理池时,最好池中有多个代理。这样,当你将一个代理下线进行维护时,依赖该代理池的代理作业仍然可以继续运行。

  2. 在代理作业可以运行之前,会收集每个作业的需求及其包含的任务。如同你在变量组部分中学到的,代理作业可以指定它所使用的代理的需求。任务也是如此——它们也可以指定需求。要运行作业,只有符合所有这些需求的代理才会被使用。需求和功能是键值对,其中值是整数。例如,功能是msbuild=15.0,相应的需求是msbuild>15.0

当没有合适的代理来满足构建定义时,构建会在超时后最终失败。

查找代理功能

要查找各个代理上可用的功能,请按照以下步骤操作:

  1. 导航到“组织设置” | “代理池”:

  1. 导航到正确的代理池(无论是托管的还是私有的),然后点击“Agents”,接着打开代理详情(在前面的截图中未显示)。

  2. 打开“Capabilities”标签。

在这里,你可以使用顶部块(称为用户定义的能力)为代理指定一个或多个自定义能力。对于自托管(私有)代理,在安装代理时,机器上发现的所有能力也会显示。

Azure DevOps 并不是唯一可用于运行持续集成构建的工具。下一节将带你了解其他几种工具。

其他工具

除了 Azure DevOps 之外,还有许多其他工具可用。另有两个著名的工具是 GitLab CI 和 Jenkins。对这些工具的基本了解将帮助你理解如何在必要时与它们集成。此外,有限的了解其他工具将帮助你更快速地理解概念并概括你如何与这些工具一起工作。

为了突出这些工具如何与相同概念协作,本节中的两个示例与 “编写 YAML 构建定义” 部分中的 Azure DevOps YAML 管道是等效的。

GitLab CI

GitLab 提供了通过 GitLab CI 功能构建管道。GitLab CI 通过将一个名为.gitlab-ci.yml的文件放置在仓库的根目录中进行配置。在这个文件中,你可以定义一个或多个阶段和任务,以及它们应该执行的任务。GitLab CI 的 YAML 示例文件如下所示:

stages:
  - build
  - test

build:
    stage: build
    script: dotnet build **/*.csproj

test:
    stage: test
    script: dotnet test **/*.csproj

正如 Azure DevOps 使用代理池和代理一样,GitLab CI 依赖于 runners 来执行实际的工作。在 GitLab CI 中,目前不支持视觉化创建或编辑管道。

Jenkins

Jenkins 是另一种用于运行构建管道的工具。复杂的构建可以通过 Jenkins 管道运行,这些管道从 Jenkinsfile 获取工作。Jenkinsfile 是用 Jenkins 特定的符号编写的,如下代码所示:

pipeline {
    agent any 

    stages {
        stage(‘build’) {
        agent any 
            steps {
                dotnet build **/*.csproj
            }
        }

        stage('test') {
            agent any
            steps {
                dotnet test **/*.csproj
            }
        }
    }
}

Jenkins 对于视觉化创建和编辑管道的支持有限。这被称为自由式项目(freestyle project)。

概述

在这一章中,我们了解了持续集成,并学习了它是你的思维方式、流程和工具的结合。你学习了如何使用 Azure Pipelines 创建构建定义,既可以使用图形设计器也可以使用 YAML,同时学习了如何运行构建。你了解了可以使用构建管道来编译和测试代码,并将结果报告回拉取请求。

你学到构建可以生成结果,称为工件。工件存储并保留在 Azure 管道中,可以用来存储报告,但也是部署管道的起点,你将在下一章学习这些管道。你还了解了运行构建所需的基础设施——即代理和代理池。最后,你看到了一些简短的示例,展示了如何使用 GitLab CI 和 Jenkins 运行持续集成构建,这两个工具也可以用于构建管道。

通过这些知识,你现在能够为你的项目创建构建管道。你可以连接到源控制,生成你将在下一章中用于部署应用程序的构建。通过深入了解任务、作业、阶段和管道的基本结构,你可以解决复杂的应用程序构建问题。

在下一章中,你将继续学习有关管道的内容,但这一次是针对发布的管道。你将学习如何获取构建并将其发布到一个或多个环境中。

问题

总结时,这里有一组问题,供你测试自己对本章内容的掌握情况。你可以在附录的 评估 部分找到答案:

  1. 对错题 – 如果你每天至少编译一次项目中的所有分支,那么你实现了持续集成。

  2. 对错题 – 经典构建定义总是与源代码仓库连接的。

  3. 对错题 – YAML 管道定义总是与源代码仓库连接的。

  4. 以下哪项是从 Azure 管道中调用外部工具所需的?

    1. 外部服务定义

    2. Azure 服务连接

    3. 服务连接

    4. 服务定位器

  5. 使用自托管代理的一些常见原因是什么?(从以下选项中选择所有正确答案:)

    1. 需要访问封闭的网络。

    2. 需要为代理提供特定的扩展任务。

    3. 并行管道执行的数量需要大于 10。

    4. 需要安装特定软件,以便代理能够使用它。

进一步阅读

第四章:持续部署

在上一章中,您学习了如何使用 Azure DevOps 流水线进行持续集成。基于此,您现在知道如何提取源代码的版本并创建可以部署的工件。在本章中,您将学习如何通过持续交付和持续部署扩展这一实践,从而自动将这些工件部署到代码运行所在的服务器或平台。

为了实现这一点,我们将首先介绍 Azure DevOps 发布定义,以便您可以定义和运行应用程序的发布。接下来,将介绍一系列策略,您可以使用这些策略以低风险的方式进行部署。通过这样做,您可以实现自动化部署新版本的过程,且在没有人工干预的情况下,风险较低。然后,我们将把注意力转向自动化生成发布说明。之后,我们将介绍用于部署移动应用程序的 App Center。最后,我们将介绍其他用于持续部署的工具。

本章将涵盖以下主题:

  • 持续交付和持续部署

  • 使用 Azure DevOps 发布功能

  • 编写多阶段 YAML 流水线

  • 实施持续部署策略

  • 部署移动应用程序

  • 自动化发布说明

  • 其他工具

技术要求

为了尝试本章中描述的技术,您可能需要以下一项或多项:

  • 用于构建发布定义和多阶段 YAML 流水线的 Azure DevOps 账户

  • 用于部署移动应用程序的 App Center 账户

这两者都有免费试用选项。

持续交付和持续部署

持续交付和持续部署之间的区别是一个常见的困惑来源。有些人认为这些术语可以互换,并将它们视为同一概念的两个同义词,但事实上,它们有两个不同的含义。

持续交付是一种实践,团队确保他们构建的工件不断得到验证,并随时准备部署到生产环境中。通常,这通过将工件部署到类似生产的环境中(例如验收环境或甚至是暂存环境),并应用一系列测试(如验证测试)来确保应用程序正常工作。

持续部署是一种实践,每一个部署到类似生产环境并通过所有测试和验证的版本,都会自动部署到生产环境。

在使用 Azure DevOps 时,Azure Pipelines 是实施持续交付和部署的首选工具。这可以通过使用可视化经典编辑器或多阶段 YAML 流水线来实现,接下来的章节将详细讨论这两种方法。

使用 Azure DevOps 发布功能

在 Azure DevOps 中,可以通过使用发布来实现持续交付和部署。在创建新的发布定义时,会创建发布过程的轮廓。此过程通常以触发新发布创建的工件开始。接下来,可以定义一个或多个阶段,发布可以部署到这些阶段。这些阶段通常对应于不同的应用程序环境,例如测试环境和生产环境,但这不是强制性的。

让我们学习如何创建新的发布定义,并探索我们拥有的各种选项。首先,导航到管道,并从菜单中选择发布。从这里,可以开始创建新的发布管道,这将带我们进入一个类似于以下截图的界面:

从前面的屏幕中,我们可以执行以下操作(这些操作在前面的截图中已经编号):

  1. 首先,请注意,在左侧,可以看到发布管道的轮廓,其中有一个框。您可以在此选择一个或多个可以在发布管道中使用的工件。

  2. 在这一部分的右侧,有一个框,可以看到发布的不同阶段。默认情况下,已经创建了一个阶段。

  3. 可以选择一个模板作为这个预创建阶段的部署管道起点。选择从空白作业开始,允许您从头开始构建自定义部署管道。

选择作业模板或空白作业作为起点后,右侧窗格会关闭,然后就可以开始从左到右编辑发布管道,从工件开始。

一旦骨架发布管道可见,您需要配置的第一件事是发布所需的工件。这是下一节的内容。

创建工件和发布触发器

前一章描述了构建定义和 YAML 管道,这些管道创建工件。这些工件会在发布中被拾取,并形成部署应用程序的基础。

要开始编辑发布管道,请按照以下步骤操作:

  1. 点击添加工件按钮,开始构建发布定义的起点。这将打开右侧窗格,如下图所示:

  1. 在项目选择器中,当前项目会默认被选中。

  2. 现在,指定发布管道应拾取的工件。

  3. 之后,默认版本和源别名会自动选择。默认版本可以在手动启动发布时始终被覆盖,因此“最新”是一个合理的默认值。

  4. 源别名是指我们稍后将作业添加到发布阶段时,工件所在文件夹的名称。默认情况下通常是可以接受的。

  5. 完成添加工件后,点击添加

现在我们已经指定了要使用的工件,接下来是指定何时应创建新发布。让我们学习如何操作:

  1. 要配置新工件触发发布的可用性,请点击工件旁边的闪电符号以打开配置面板。如下截图所示:

  1. 在此面板中,可以使用顶部滑块创建新发布(如果有可用发布)。这将展开一个新区域,在此区域中可以定义一个或多个过滤器,以便指定新的工件在何种条件下触发发布。

  2. 点击“添加”按钮以开始添加条件。

  3. 一个常见的例子是只包括来自主分支的工件,如下所示。

  4. 除了来自常规构建的工件,还可以允许来自拉取请求构建的工件来启动新发布。

  5. 最后,可以按固定时间表创建新发布。

如果没有指定计划或触发条件,则只有在有人手动执行时,才会创建新发布。

指定用于部署发布的阶段

在指定了要发布的工件后,是时候指定一个或多个阶段以部署发布了。通常,每个环境(测试、验收和生产)都会对应一个阶段。但如果情况需要,也可以有其他阶段。

让我们学习如何添加新阶段并探索各种选项。首先,点击“管道”以进入以下屏幕:

现在,完成以下步骤:

  1. 点击“添加”按钮以创建新阶段。一个阶段可以是全新的,也可以是现有阶段的克隆。

  2. 选择已存在的阶段后,可以使用右上角的“删除”按钮将其移除。

  3. 在此屏幕上可以执行的其他操作包括重命名阶段和指定阶段负责人。当发布部署到环境时,负责人将收到通知。

  4. 创建并命名一个阶段后,可以像在构建管道中一样,向阶段添加作业和任务。为此,请点击框中表示该阶段的链接。

从这里开始,操作与构建管道完全相同。唯一的区别是:除了代理作业和无代理作业外,还可以使用部署组作业。有关这些内容将在后续的与部署组协作部分进行讨论。但首先,让我们了解一下需要哪些阶段。

我需要哪些阶段?

在处理发布时,常见的一个问题是,我在发布管道中需要哪些阶段?根据文档,阶段应表示发布管道的主要分区。在开始使用发布时,这通常意味着每个环境对应一个阶段。适当的阶段包括测试验收生产

在长期与发布版本合作时,我们可能会在流水线中加入更多的自动化,并希望为其添加额外的检查阶段。例如,可能会有一个名为负载测试的阶段,它与测试阶段并行执行。另一个例子可能是引入一个自动化 UI 测试阶段。

无论添加哪些阶段,传播发布版本到生产环境的方法应始终保持一致。当发布版本从一个阶段传播到下一个阶段,并且逐渐接近生产时,这应表明对该发布版本充满信心,它工作正常,并且可以推广到生产环境。

阶段触发器、审批和门控

在定义所需的阶段并向其添加作业和任务之后,接下来需要配置何时触发发布到特定阶段。具体步骤可以在以下截图中看到:

请注意,以下步骤需要针对每个阶段单独执行:

  1. 要触发发布到特定阶段,点击带有闪电图标和人形图标的按钮,该按钮位于表示阶段的方块左侧。

  2. 在这里配置的第一件事是发布版本何时应传播到此阶段。可以选择在发布版本可用时、完成另一个阶段后,或者仅在手动请求时进行传播。你在这里做出的选择也会反映在流水线的可视化表示中。

  3. 与触发器分开,你可以定义一个或多个过滤器,限制哪些工件将触发部署到该阶段。每个工件可以有一个或多个包含或排除的分支过滤器。

  4. 也可以在固定的时间表上重新部署。

  5. 最后,如果为从拉取请求启动的构建指定了新发布版本的创建,则还可以允许使用滑块将发布传播到当前阶段。

在这些触发器旁边,可以添加审批人和门控,以便你可以配置如何处理部署队列设置。这些设置可以从触发器部分下方的标签页中访问,如以下截图所示:

第一个标签页是关于审批人的。在这里,可以指定组或用户。他们必须在发布到此阶段之前给予批准。可以添加多个人员,如果是这样,还可以定义他们批准的顺序,或者可以指定只需要一个批准即可。向下滚动,你会找到以下选项:

左侧的第二个选项卡允许您添加一个或多个关卡。关卡是自动化检查,必须成功才能继续发布。目前,这里显示的是配置工作项查询和结果数阈值的配置详情,例如,确保在发布继续之前没有未解决的漏洞。还有一些关卡可以调用 Azure Monitor、Azure Functions 或 RESTful API。此关卡集可以使用 Azure DevOps 扩展机制进行扩展。其中一些扩展还与常见的变更管理系统集成。

最右侧的最后一个选项卡允许您配置如何处理不同版本的发布准备好部署到同一阶段的情况。在这里,您可以指定有多少个发布可以并行运行。如果有更多发布进来,您可以将它们排队,并依次部署,或者只部署最新的发布。

使用部署组

另一个您可能会遇到的话题是将应用程序部署到本地服务器或位于防火墙后面的服务器。您也可能遇到需要在托管应用程序的所有机器上运行脚本的情况,或者目标环境没有提供部署应用程序机制的情况。

本章的与 Azure DevOps 发布配合使用部分展示的发布方法依赖于能够连接到将托管应用程序的目标机器或服务。我们称之为基于推送的部署,但这并非总是可能的。

当部署到无法连接的目标机器时,需要采取另一种方法。这种方法称为基于代理的部署。在基于代理的部署中,Azure DevOps 代理会安装在每台要安装应用程序的机器上。接下来,这些代理必须被分组到部署组中。一旦完成此操作,就可以将部署组任务添加到发布中。

这与代理任务非常相似,除了一个区别。在代理任务中,任务将在其中一台代理上针对目标机器执行。而在部署组任务中,所有任务将在目标机器上的所有代理执行。以下图示可以看到这两种方法的区别:

使用这种方法时,必须在需要部署应用程序的机器上安装代理。这些代理会监听 Azure DevOps,并在请求新的发布时,它们会获取工作并在本地机器上执行。

管理部署组

在将部署组任务添加到发布管道之前,您需要创建一个部署组。为此,请执行以下步骤:

  1. 导航到管道菜单。

  2. 打开部署组菜单。

  3. 输入部署组名称和描述,然后点击“创建”。

创建新部署组后,右侧会出现一个脚本,如下图所示:

在目标机器上执行此脚本将安装代理,并自动将该机器注册为新创建的部署组的一部分。

如果一个应用程序必须通过部署组部署到三个阶段(测试、验收和生产),那么需要为每个环境创建三个独立的部署组。

使用部署组创建发布管道

创建了所需的部署组后,可以在任务视图中将这些部署组用于发布,如下图所示:

为此,请执行以下步骤:

  1. 向管道中添加一个新的部署组。

  2. 通过从下拉菜单中选择,指定作业应在哪个部署组上运行。

  3. 添加一个或多个任务来执行作业。用户界面的功能与常规代理作业相同。

除了在一个组中对所有代理执行任务的不同方法之外,部署组作业的行为与常规代理作业相同。

编写多阶段 YAML 管道

除了发布定义的可视化设计器外,还可以使用 YAML 管道实现持续部署。在这种情况下,仍然建议区分管道的构建(CI)阶段和发布(CD)阶段。阶段的概念使得这一点成为可能。一个 YAML 管道可以划分为一个或多个阶段。一个阶段可以代表一个环境,如测试、验收或生产,但并非总是如此。如果在应用场景中,添加额外的阶段如预生产或暂存是有意义的,也是可以实现的。最佳实践是将管道工件发布到早期阶段,并在后续阶段使用或下载工件

多阶段 YAML 管道是 Azure DevOps 中创建管道的默认方式。由于与经典发布相比,使用 YAML 管道可能需要较陡的学习曲线,部分用户发现先使用经典发布再转向 YAML 管道会更容易。就像构建一样,经典发布的许多概念也可以应用到多阶段 YAML 管道中。

向 YAML 管道添加阶段

如果在 YAML 管道中没有定义任何阶段,则始终存在一个隐式阶段,包含所有作业。要将管道转换为多阶段管道,需要添加 stages 关键字和阶段列表,如以下代码所示:

stages:
- stage: stage1
 displayName: My first stage
 jobs:
 - job: job1
 steps:
 - task: DotNetCoreCLI@2
 displayName: ‘dotnet build’
 inputs:
 projects: '**/*.csproj'

- stage: stage2
 jobs:
 ...

上述语法展示了在 YAML 文件顶部定义了一个阶段列表。每个阶段通过定义一个名称来开始。这个名称可以在后续使用,以便引用该阶段。

虽然作业(除非另有指定)默认情况下是并行运行的,但阶段默认情况下始终是按顺序运行的。但就像作业一样,阶段也接受dependsOncondition关键字来更改顺序、并行性,并(可能)跳过阶段。

下载工件

多阶段流水线的常见用法是将构建阶段与部署阶段分开。为了实现这一点,构建阶段通常会发布一个或多个流水线工件。这在前面的章节中有讨论。

可以使用download任务下载在当前流水线前一个阶段中发布的所有工件:

steps:
- download: current
  artifact: artifactName

也可以从其他流水线下载工件。为此,必须将current常量替换为该流水线的名称。流水线工件将下载到$(Pipeline.Workspace)目录。

如果您想对下载流水线工件有更精细的控制,例如,控制使用哪个版本的工件或将工件下载到哪个位置,您也可以使用下载流水线工件任务,文档详细描述了这些任务:docs.microsoft.com/bs-cyrl-ba/azure/devops/pipelines/tasks/utility/download-pipeline-artifact?view=azure-devops

在流水线中发布和下载工件可以确保第一阶段构建的代码也是第二阶段部署的代码——即使这些阶段相隔几天。本质上,每次流水线运行都会构建与该特定运行相关联的所有工件的本地阶段。

审批

在多阶段流水线中,无法像经典发布流水线那样定义审批者。原因是流水线——构建和部署过程——被视为代码。代码仅由开发人员和操作人员处理。而审批则由例如产品负责人处理。然而,这并不意味着无法为流水线进度到下一个阶段实现审批流程。

为了控制是否允许流水线继续进行到某个阶段,需要引入环境的概念。环境通过给它命名和描述来定义。可以将一个或多个审批者附加到这些环境中。完成此操作后,可以配置作业以针对这些环境。如果阶段中至少有一个作业是针对某个环境的,那么该环境就被认为是该阶段使用的环境。如果该环境上配置了审批,那么部署到该阶段将不会继续,直到审批者给出许可。

要开始使用环境,您需要访问环境列表。该列表可以在流水线菜单中找到,如下图所示:

要添加一个新环境,请执行以下步骤:

  1. 打开 Pipelines 菜单并选择 Environments。

  2. 在右上角选择“新建环境”。

  3. 指定名称和描述。

  4. 点击“创建”。

可以将资源与环境关联。仅当管道也针对该环境时,环境中与资源关联的资源才能在管道中使用。为了保护环境的资源,环境的所有者可以添加一个或多个审批者。以下截图显示了配置审批者的示例:

审批者可以通过以下方式添加到环境中:

  1. 转到 Environments 概览面板。

  2. 通过点击环境打开它。

  3. 点击右上角带有三个点的菜单,选择“审批与检查”。

  4. 点击“创建”按钮。

  5. 从列表中选择一个用户或组,并在需要时添加额外的说明。

  6. 再次点击“创建”按钮。

审批使您能够控制管道向下一个阶段的推进,前提是该管道目标环境正确。通过指定特定类型的任务:部署任务,来实现目标环境。以下 YAML 显示了如何操作:

jobs:
- deployment: deplyoymentJobName
  displayName: Friendly name
  strategy:
  runOnce:
    deploy:
      steps:
      …

部署任务不像代理任务那样直接包含执行步骤。相反,它们首先需要为 steps 关键字下列出的任务指定执行策略。撰写本文时,唯一支持的策略是 runOnce。预计将来会宣布更多策略。

在撰写本文时,仅支持 Kubernetes 集群作为环境资源,但未来已宣布将支持更多类型的资源。

现在我们已经了解了创建发布定义和编写多阶段 YAML 管道的技术手段,是时候看看我们可以在实践中应用的不同策略了。这些持续部署策略旨在最小化自动部署新版本应用程序的风险。

实施持续部署策略

在我们持续部署应用程序之前,考虑我们应使用的策略非常重要。仅仅进行一次次部署可能带来的风险可能超过业务所能接受的范围。考虑如何处理在部署新版本的应用程序时,或者部署之后可能发生的问题是很重要的。

有一些部署策略可以应用来降低部署可能带来的风险,本节将涵盖所有这些策略。请注意,可以将一个或多个以下模式组合使用。例如,在基于环的部署中,完全可以对每个环使用蓝绿策略。同时,所有部署策略都可以与功能标志一起使用。

蓝绿部署

蓝绿部署是一种技术,在这种技术中,应用程序的新版本从不直接部署到生产服务器。相反,它首先部署到另一组服务器中。一旦部署成功,用户会被引导到新的部署上。

假设一个应用程序默认在三台主机上运行。蓝绿部署的典型设置是两组三台主机:蓝色组和绿色组。在这两组前面有一个反向代理,它作为负载均衡器,将传入的请求重定向到蓝色组。以下图示说明了这种工作原理:

在这种情况下,要部署应用程序的新版本,需要将其部署到绿色服务器组。由于这些服务器没有接收到终端用户的流量,因此这对它们完全没有影响。

部署后,可以验证新部署是否成功,并且应用程序是否正确运行。验证完成后,负载均衡器被重新配置,将流量重定向到绿色组。现在,应用程序的新版本就会被提供。

如果突然出现任何意外问题,通过重新配置负载均衡器回到蓝色组,非常容易将系统切换回先前的部署。如果部署成功且没有问题,可以通过相同的程序启动下一个版本的部署,但现在绿色组和蓝色组的角色会互换。

不可变服务器

蓝绿部署模式的一个变体是不可变服务器。在使用不可变服务器时,不再在两组服务器之间来回切换。相反,服务旧版本应用程序的服务器组会被完全丢弃或移除。通常,这会在一个宽限期之后完成。

这样做的结果是,仍然有办法将系统回滚到以前的版本——如果旧的服务器暂时保留,那么回滚几乎是瞬时的。另一个好处是,现在可以保证先前的部署中没有遗留内容被带入新的部署中。使用不可变服务器时,活动服务器随时间变化的方式可能如下所示:

当然,像这样的做法只有在使用容器或虚拟机等技术时才可行。没人会期望在每次重新部署后都丢弃物理服务器。

渐进式曝光

渐进式曝光是一种部署策略,在这种策略中,能够访问新部署或新功能的用户数量会随着时间的推移而逐渐增加。该策略的目标是限制在故障版本的功能发布时,出现问题的用户数量。

我们也可以更加积极地看待这一点,并与持续部署的思维方式相一致:首先仅向少数用户暴露新特性,并随着时间的推移逐步增加这个数量,这使我们可以在将新版本或特性暴露给所有用户之前,逐步增加对其的信任度。

金丝雀部署

渐进式暴露的第一种策略是使用金丝雀部署。在金丝雀部署中,并不是所有用户都立即被引导到新版本——只有一小部分用户可以访问该版本。这些用户就是金丝雀用户,并会受到密切监控。如果他们遇到任何问题,或者测量到性能下降或服务问题,新部署会迅速回滚。

实现金丝雀部署的典型方法是将其与蓝绿部署结合使用。不同之处在于,与同时切换所有用户不同,初始时只有少数用户被切换到新版本,然后逐渐增加切换的用户数量。可能会类似于以下内容:

如果因为观察到错误而回滚部署,这对用户来说并不是一种愉快的体验。为了防止同一小部分用户重复遇到问题,之后选择一个不同的金丝雀用户组可能会有益。

基于环的部署

在基于环的环境中,不仅仅有一个生产环境——而是有多个。每个生产环境仅服务一部分用户。它与金丝雀部署的不同之处在于,不仅只有两个环境,所需的环境数量可以根据需要进行设置。此外,每个新版本都会依次推送到所有的环。

因此,在基于环的环境中,与其重新定向用户,不如将新版本传播到这些用户使用的服务器。新版本会从一个环逐步传播到下一个环,直到所有环完成:

基于环的部署架构特别适合全球各地客户访问的产品。不同的环可以分布在全球各地,从而将部署的优势与减少用户延迟的附加优势相结合。

特性标志

第三种渐进式部署的形式可以通过特性标志实现,也叫做特性开关。在金丝雀部署和基于环的部署依赖于将新的二进制文件逐步暴露给越来越多的用户时,特性标志则用于逐步向越来越多的用户暴露新特性。这即使在所有用户都向同一个服务器发送请求的情况下也可以实现。特性标志用于将部署新版本应用程序二进制文件与发布新特性解耦,通过在运行时启用或禁用特定特性来实现。

功能标志的最佳示例是显示或隐藏一个按钮,该按钮使用户可以访问新功能。应用程序设置、数据库或外部服务用于跟踪哪个功能已为哪个用户启用。根据该设置,功能会显示或隐藏。此类外部服务的示例包括 LaunchDarkly、Split.IO 和 Prefab.cloud。

其他功能标志可能会开启或关闭错误修复或性能改进。这有助于逐步暴露这些内容,确保没有问题。在代码库中较深的地方使用功能开关进行这类变更时,引入功能开关也有成本,因此应该制定一个相关的过程。这个过程不仅应该描述如何添加功能开关,还应该描述如何尽快移除它们。以下是一个此类过程的示例。

当业务需要独立于开发团队的部署发布新功能,或者对于开发团队认为风险较高的变更并希望随时撤回而不重新部署时,开发者会引入新的功能标志。引入功能标志意味着在应用程序设置中应用一个新的数据库条目或声明一个新的设置。

在引入功能开关后,新的功能或变更将被开发和测试。这意味着代码库中会有一个或多个if语句,根据功能标志的状态执行不同的代码路径。此时,应用程序必须保持两个代码执行路径,直到再次移除功能标志。最好使用现有的工程实践(如依赖注入)尽可能地将这两个代码路径分开。

尽管代码持续交付给用户,但功能对任何人都不可用。只有当开发团队完全满意该变更,或产品负责人觉得时机成熟发布新功能时,功能标志才会开启。

重要的是不要止步于此。在开启功能标志后,应该积极判断该功能或变更是否正常工作。如果正常工作,功能标志应尽快移除。这样,两个代码路径需要维护的时间就尽可能短。

同时需要注意的是,除了维护更多的执行路径外,现在还需要测试更多的路径。如果引入了功能标志之间的依赖关系或排斥关系,这个后果的影响会迅速扩大。功能标志只能根据另一个功能标志的状态开关的情况可能会造成很高的成本,建议避免这种情况。

如果实施得当并尽早移除,功能标志所带来的额外成本通常是值得的。与每一项工程实践一样,先从小处开始,评估在给定上下文中有效的方法,再根据实际情况在大规模中应用。

回滚或前进失败

无论使用哪种策略,都必须考虑回滚一个或多个版本的能力,以及所需的时间。例如,蓝绿部署让我们能够几乎瞬间回滚到上一个版本,只要新的版本尚未部署到非活动服务器上。另一方面,在基于环的部署中执行回滚将需要完全重新部署先前版本,这可能需要更长时间,并且本身伴随部署风险。甚至可能需要在多个环上执行回滚,这使得问题更具挑战性。

另一种可以采用的方法是前进失败。当采用这种方法时,声明绝不会回滚到之前的版本。相反,当遇到任何问题时,通过重新部署包含修复问题的新版本来解决问题。这种策略最近越来越受欢迎,因为它节省了时间,因为我们不必准备、测试和实践回滚。然而,这个过程可能涉及风险:

  • 并不能保证修复一定是正确的。问题可能没有被新部署的版本解决,甚至更糟,新的版本可能导致从一个问题转到另一个问题。

  • 解决任何问题的详细根本原因需要时间,就像编写修复方案一样。其后果可能是,修复可能比回滚所需的时间更长。

无论采取哪种方法,都要考虑后果并做好准备。

到目前为止,我们主要关注的是基于 Web 的应用程序。在下一节中,我们将把注意力转向移动应用。

部署移动应用

需要特殊部署方法的一种应用类型是移动应用。这些应用通常不是由最终用户直接下载和安装的,而是通过用户手机上的应用商店进行消费。

App Center 是微软提供的服务,可以通过应用商店将移动应用分发(部署)给最终用户,也可以通过私人分发列表进行分发。

登录 App Center 后,你将进入以下界面:

在这里,你可以创建一个新的应用定义。每个应用的目标操作系统都应该创建一个应用定义。如果同一应用程序要部署到 Android 和 iOS,则至少需要创建两个应用。

创建一个应用程序的步骤如下:

  1. 登录到 App Center。

  2. 点击蓝色的“添加新应用”按钮。如果没有现有应用程序,此按钮将在屏幕中央;否则,它将在右上方(隐藏在前面截图中显示的弹出窗口下)。

  3. 输入应用程序的名称。

  4. 选择发布类型。

  5. 选择操作系统。

  6. 选择要使用的平台。

  7. 点击“添加新应用”来创建应用。

一旦应用被创建,就可以将其连接到正确的应用商店,并可以创建分发组。

连接到应用商店

应用商店是为所有移动平台分发应用程序的主要机制。一旦构建交付到应用商店,用户就可以安装并使用该应用。当前连接到应用商店的列表可以通过左侧 App Center 中的 Stores 标签查看。在此列表中,可以打开单个商店连接,这将带我们进入一个类似于以下屏幕截图的界面:

此视图显示所有已发布到连接的商店账户的应用版本列表。这也是选择要发布到商店的新版本的地方。通过顶部的蓝色发布按钮完成此操作。这将打开一个弹出窗口,您可以在其中选择正确的发布版本。只需确认一次,即可发布此版本。

可以通过返回到所有商店连接的列表并点击添加按钮来创建新的商店连接。这将打开一个向导,要求输入两个信息:

  • 商店连接类型:该列表仅限于适用于所选应用类型的商店。例如,对于 iOS,限制为 Apple App Store 和 Intune 公司门户。

  • 连接详情:通常包括 App Center 和应用商店之间的认证方式。

一旦新连接被创建,它可以在之前显示的列表中找到,并可用于分发应用。

另一种分发方式是使用分发组,我们将在下一节介绍。

使用分发组

分发组用于创建一个或多个用户的命名列表,这些用户通常是测试人员或早期用户,通过邀请而非通过应用商店安装应用。分发组可以在左侧菜单中找到,位于 Groups 下:

在这里,可以添加一个新组,如下所示:

  1. 使用左侧菜单导航到分发组。

  2. 点击标有加号(+)的蓝色按钮(隐藏在前面截图中的弹出窗口下)。

  3. 选择组的名称。

  4. 添加一个或多个成员。

  5. 保存新组。

一旦创建了分发组,就可以用于发布版本,我们将在下一节讨论。

发布应用

要发布应用的第一个版本或新版本,必须将其与 App Center 共享。这可以通过左侧的 Releases 标签完成。打开发布选项后,会显示以下视图,详细列出当前的所有发布。在这里,可以选择任何一个发布版本,以查看该发布的详细信息:

在此视图中,中间列显示了最近的发布列表。选择某个发布后,将显示该版本的详细信息,包括正式版本、已共享的商店和/或分发组,以及其他细节。

在此,你可以通过右上角的“分发”按钮直接将此特定版本分发到商店连接或分发组。

在此,你也可以通过上传应用程序的新构建来创建新的发布。为此,请按照以下步骤操作:

  1. 点击“新发布”按钮,该按钮可从所有发布的列表中找到。(可能需要先关闭某个特定发布的详细信息。)这将打开以下视图:

  1. 将打开一个新的向导,在该向导中需要上传构建文件。根据应用类型,将请求正确类型的文件。上传二进制文件后,点击“下一步”。

  2. 现在,需要填写发布说明。详细描述此版本中的更改后,再次点击“下一步”。

  3. 现在,是时候指定新构建应分发到哪里了。至少需要选择一个目的地——可以是分发组或商店。选择一个或多个目的地后,再次点击“下一步”。

  4. 最后的向导选项卡将显示你迄今为止所做的选择。检查细节后,点击“分发”以完成新版本的创建及其初步分发。

通常,同一版本或发布需要随着时间的推移分发到其他组或商店。每次都创建新发布并不是必须的(也没有意义)。相反,进入新目的地商店连接或分发组的详细页面,也可以将现有发布发布到该目的地。

作为一种替代方法,除了使用 App Center 进行这种发布管理外,还可以使用 Azure Pipelines 进行发布管理。

通过 Azure Pipelines 使用 App Center

App Center 还可以与 Azure Pipelines 集成。如果团队熟悉 Azure Pipelines 中的发布流程,那么将应用程序构建在 Azure Pipelines 中,并仅在 App Center 中使用分发到商店和分发组,可能是一个明智的选择。

为了实现这一点,Azure Pipelines 中提供了允许上传发布并触发发布到商店或分发组的任务。通过这种方式,发布管理可以在 Azure Pipelines 中完成,同时在适用的情况下仍能利用 App Center 的特定功能。

本节专注于移动应用程序,而下一节将适用于所有类型的发布。当发布的创建自动化且新版本快速迭代时,自动化发布说明的创建和发布也变得十分有用。此内容将在下一节中讨论。

自动化发布说明

在自动化构建、发布应用程序并致力于提高向最终用户提供价值的流程后,许多开发人员发现,保持文档和发布说明的更新变得越来越困难。随着发布次数的增加,这项工作变得越来越繁重,最终,团队可能会落后甚至完全放弃。

为了解决这个问题,可以自动化发布说明的创建和发布。一种方法是使用 Azure DevOps 发布说明生成器。该生成器是一个 Azure Functions 应用,托管在 GitHub 上。要使用发布说明生成器,需完成以下操作:

  1. 从 GitHub 下载或克隆功能代码。

  2. 在 Azure 中创建一个 Azure App Service Plan、功能应用和存储账户。

  3. 在存储账户中创建一个新的 blob 容器,命名为 releases

  4. 编译功能代码并将其部署到 Azure App Service。

  5. 创建一个新的 Azure DevOps WebHook,以便在创建新版本时调用已部署的功能。

设置完成后,生成器将在每次创建新版本时运行。然后,它将执行以下操作:

  1. 查询创建的版本,获取其名称、所有相关工作项以及自上一个版本以来的所有新提交。

  2. 生成一个包含所有这些信息的 Markdown 文件。

  3. 将该文件上传到 blob 容器,即 releases

当然,Azure DevOps 发布说明生成器只是自动化发布相关任务的一个示例,市场上还有其他可选方案。此外,许多公司也会为更新和发布文档及其他任务创建量身定制的内部自动化脚本。

其他工具

除了 Azure DevOps 和 App Center,还有其他工具可用于部署和发布软件。前一章节中讨论过的 GitLab CI/CD 和 Jenkins,也可以用于发布。除此之外,Octopus Deploy 也是一个常用的工具,与 Azure DevOps 集成良好。

Octopus Deploy

Octopus Deploy 是一个部署自动化工具,它基于在一个或多个目标机器上运行一系列任务的概念。

Octopus 通过安装在这些机器上的触角(代理)来访问这些机器。在 Octopus Deploy 中,可以定义应用程序和环境,并为每个环境分配一个或多个机器。要进行部署,可以在图形编辑器中定义执行步骤,这类似于 Azure DevOps 的可视化发布编辑器。

主要的区别之一是,这些步骤不是按环境定义的,而是每个管道只定义一次。接下来,可以指定每个任务应在哪些环境中运行。这样,更容易查看不同环境之间的部署差异。

Azure DevOps 和 Octopus Deploy 之间有一个集成,形式为构建和发布任务。通过这个集成,你可以从 Azure DevOps 的构建或发布流水线启动 Octopus Deploy 部署。

总结

在本章中,你学习了持续交付和持续部署,以及如何使用 Azure DevOps 实现它们。除了视觉发布编辑器外,你还了解了多阶段 YAML 流水线,你可以使用它将软件发布到多个阶段,一直到生产环境。接下来,我们讨论了一系列可以用于发布的策略。现在,你知道了蓝绿部署、使用不可变服务器以及渐进曝光的不同策略。你还学会了如何在确保回滚能力和接受失败前进策略之间做出选择。

接下来,你了解了如何自动化发布说明和文档,并且你可以将这些作为流水线的一部分自动生成。之后,你了解了移动应用程序的持续部署以及它与 Web 应用程序交付的区别。最后,你了解了 Octopus Deploy 的存在、它的操作方式以及它与 Azure DevOps 的集成。

在下一章中,你将学习使用 Azure Artifacts 进行主题依赖管理。Azure Artifacts 可用于托管自己的 NuGet 包,或者在使用其他产品构建或发布应用程序时托管构建工件,并结合 Azure Pipelines 使用。

问题

在本章结束时,这里有一份问题清单,供你测试对本章内容的掌握情况。你可以在附录的评估部分找到答案:

  1. 判断对错:Azure DevOps 经典发布总是由新的工件版本的可用性触发。

  2. 以下哪些平台可以通过 App Center 发布应用?(你可以选择多个)

    1. Google Play 商店

    2. Apple App Store

    3. Microsoft Intune

  3. 以下哪些技术使用渐进曝光来最小化部署新版本的风险?(你可以选择多个)

    1. 功能切换

    2. 基于环的部署

    3. 金丝雀部署

  4. 判断对错:部署组可以用于将软件部署到本地服务器,只要在将运行该软件的机器上安装了 Azure Pipelines 代理。

  5. 如果你在 Azure Pipelines 发布定义中触发了 App Center 的操作,将 App Center 与 Azure Pipelines 集成的优势是什么?

进一步阅读

第二部分:扩展你的 DevOps 管道

在本书的第一部分,你学习了可以用来建立结构化、自动化交付应用程序给用户的技术和方法。通过使用敏捷工作项管理、源代码控制、持续集成和持续部署,你现在可以持续地部署新版本的软件。完成这些设置后,是时候扩展这个管道,并整合更多的 DevOps 实践和技术了。这就是第二部分的内容:扩展你的 DevOps 管道。

本节包括以下章节:

  • 第五章,依赖管理

  • 第六章,基础设施与配置即代码

  • 第七章,在 DevOps 场景中处理数据库

  • 第八章,持续测试

  • 第九章,安全与合规

第五章:依赖管理

在本书的第一部分,你学习了如何持续部署你的应用程序。在此过程中,你可能遇到的一个主要问题是构建应用程序所需的总时间过长。因此,开发人员不得不长时间等待对更改的反馈。应对这一问题的一种方法是将解决方案拆分成多个构建。

其中一种方法是引入包管理。通常,你会发现你希望在新项目中重用以前项目中的代码。与其将代码从一个项目复制粘贴到另一个项目中,不如创建一个共享库。本章将教你如何识别共享组件,并如何使用 Azure Artifacts 使其可重用。此外,你还将学习如何在异构架构中使用 Azure Artifacts 存储流水线工件。在这里,你还将使用不仅仅是 Azure DevOps 的其他 CI/CD 工具。为此,你将学习如何使用 Azure Artifacts 来处理通用包。

本章将涵盖以下主题:

  • 识别共享组件

  • 创建一个源

  • 发布包

  • 使用包

  • 使用通用包

  • 探索其他工具

技术要求

要实验本章中提到的主题,仅需要一个 Azure DevOps 组织。

识别共享组件

采用 DevOps 实践,例如持续集成/持续交付,可以大大减少构建和测试应用程序所需的时间。除了构建应用程序,还有许多其他问题可以在流水线中解决。

当你开始向流水线中添加越来越多的任务时,你可能会遇到流水线的单次执行开始耗时过长的情况,有时甚至超过 5 分钟。请注意,这是 CI 流水线最大持续时间的一般推荐。为了解决这个问题,你可能会想将解决方案拆分成更小的构建,甚至可能拆分成多个仓库。为此,你可以将应用程序的部分构建隔离开来,然后将这些构建的结果作为现成的组件,使用在主应用程序中。

CI 流水线的最大持续时间的一般推荐为 5 分钟。

希望将解决方案拆分成多个部分的另一个原因是使用共享项目。假设你有两个紧密协作的解决方案:一个是 REST API,另一个是你发给客户以供与该 API 配合使用的客户端包。这两个解决方案可能至少共享一个项目,其中包含用于建模在两者之间传输的数据的所有对象。在这种情况下,你可以创建一个只有共享项目的第三个解决方案,并将其作为包在其他解决方案中使用。

或者,如果您在一个负责维护一系列解决方案的团队工作,并且发现您在这些解决方案之间复制和粘贴了完整的命名空间,这不是一种理想的情况,而且很可能伴随许多问题。如果您只需编写一次这段代码,构建它,打包它,然后在所有这些解决方案中重用它,那会怎样?

总结一下,开始使用包和工件喂养的三个原因如下:

  • 通过将较大的解决方案拆分为多个部分来减少构建和 CI 时间

  • 提取共享组件到包中

  • 构建由其他团队使用的包

在本章的剩余部分,您将学习通过将(部分)应用程序代码构建为包、将其托管在集中位置,并在一个或多个其他解决方案中重用它们来实现这一目标的技巧。

在这三种场景中,您可能希望提高代码的可重用性,但也希望减少检查更改与通过自动化测试结果收到反馈之间所花费的时间。在开始拆分应用程序之前,请记住,将应用程序的部分内容移动到一个独立的组件中,并不总能实现这一目标。

如果将应用程序拆分为三个组件和一个剩余的主部分,请确保您可以完全独立构建和测试这三个组件,或者至少接近 100%。如果您不能独立测试应用程序的某个组件,为该组件创建一个独立的仓库和构建实际上会增加检查更改与反馈之间的时间。虽然两个独立的构建可能运行得更快,但现在您需要等待两个构建,才能收到任何反馈。

如果您将应用程序拆分为独立的组件,请确保每个组件可以在高程度上独立构建和测试。

此外,您还需要确保将应用程序的一部分做成可重用组件从概念上是合理的。例如,解决跨切面问题的组件,如日志库或数据库抽象层,非常适合提取到共享库中。(顺便提一下,在这样做之后,您还可以考虑在可能的情况下用现成的替代品替换您自己的通用库。)

然而,如果将解决方案拆分为组件是合理的,它可以带来巨大的好处。

喂养的类型

在 Azure Artifacts 中,可以托管多种类型的包喂养。您将如何使用工件喂养取决于应用程序使用的语言和生态系统。Azure Artifacts 支持以下生态系统:

  • NuGet: 在使用 Microsoft .NET 语言时,用于包管理的协议是 NuGet。

  • npm: 在使用 JavaScript 或 TypeScript 构建应用程序时,会使用 npm 协议。

  • Maven 或 Gradle:Maven 和 Gradle 用于 Java 生态系统。

  • Pip 和 Twine:在使用 Python 软件包时,可以通过这些协议获取它们。

  • 通用软件包:通用软件包与特定的生态系统无关,而是一种通用的上传和检索软件包的方式。

每当创建新的 Feed 时,无需指定类型。实际上,每个 Feed 都可以通过任何协议进行访问,甚至可以随着时间的推移使用不同的协议。然而,通常情况下,这种做法并没有太大意义。

创建 Feed

一旦确定了要发布的一个或多个软件包,您需要一个地方来存储它们。为此,您可以使用 Azure Artifacts 提供的服务。以下图表显示了 Azure Artifacts 的结构构成:

在 Azure Artifacts 中,您可以创建一个或多个 Feed 来存储您的软件包。每个软件包在 Feed 中可以有多个版本。Feed 是您设置发布软件包授权的层级。在一个 Feed 中,您可以创建一个或多个视图,用于设置软件包消费的授权。任何给定软件包的特定版本可以同时出现在多个视图中。以下部分将更详细地讨论所有这些概念。

设置 Feed

在 Azure Artifacts 中,Feed 是存储您的软件包的位置。每个 Feed 是一个独立且完全隔离的存储库。要创建一个新的 Feed,请按照以下步骤操作:

  1. 首先,导航到左侧菜单中的 Azure Artifacts,然后点击“创建 Feed”按钮(部分可见,用于访问创建新 Feed 的视图):

  1. 为 Feed 指定一个名称。名称不能包含空格,最好仅包含字母和数字,因为它将成为 URL 的一部分。

  2. 接下来,您可以指定初始的可见性设置。这将决定哪些用户可以查看该 Feed。稍后会更详细地讨论这个问题。

  3. 配置使用上游源。这将在后续部分更详细地介绍。

  4. 选择“创建”后,几秒钟内您的 Feed 就会可用。

创建 Feed 后,您可以配置各种设置,如隐藏已删除的软件包、启用软件包批次以及配置保留策略。要了解如何操作,请按照以下步骤进行:

  1. 创建 Feed 后,通过点击右上角的齿轮图标来访问 Feed 的设置。

  2. 在下图所示的视图中,选择“Feed 设置”。在这个视图中,您可以配置更多设置:

  1. 除了更改名称和添加描述外,您还可以选择隐藏已删除的包。这样做后,已删除的包的版本对于订阅的管理员不再可见。普通用户永远无法查看或使用已删除的包,但此设置启用了与管理员相同的视图逻辑。

  2. 另一个可以启用的设置是包徽章。包徽章是一个可视元素,显示包的名称和最新可用版本。如果启用此选项,您可以为每个包检索一个 URL,该 URL 将成为该包的包徽章。这对于希望跟踪包的最新版本的人很有用。

  3. 最后,您可以配置保留策略。在这里,您可以配置当包的版本数量超过一定阈值时自动删除。虽然这有助于节省磁盘空间和因此的成本,但可能会意外地影响订阅的下游用户。为了防范此类情况,您可以防止在最后一次下载包后 x 天内删除包。此外,请记住,当前作为订阅成员的任何包版本都不会被删除。

  4. 完成后,单击保存按钮。

创建和配置订阅后,现在是指定哪些用户可以访问该订阅及其权限的时候了。让我们学习如何进行下一步操作。

访问安全

您可以为用户或组分配四种角色,其中每个下一个角色的权限都包括前一个角色的权限:

  • 读者能够列出订阅中的所有包,并可以下载它们。

  • 协作者还能够使用上游源中的包。

  • 贡献者还可以发布自己的包,取消列出和弃用包。

  • 最后,所有者对订阅有完全控制权,还可以更改权限、重命名或删除订阅。

要更改用户的权限,请按照以下步骤操作:

  1. 转到权限视图,您可以在以下截图中看到。在此视图中,您可以查看已分配权限的每个用户或组的列表:

  1. 要删除权限,请选择行并单击删除。

  2. 要添加新行,请单击添加按钮。这将打开您在右侧看到的视图。

作为将用户或组添加为整个订阅的读者的替代方案,还可以在订阅中创建一个或多个视图,并针对每个视图设置访问权限。

管理订阅中的视图

Feed 是一个包的仓库,你可以将包发布到此处,也可以从此处下载包。然而,很多情况下,你可能不希望每个上传的包都可以供下载。通常,你可能希望控制哪些用户可以使用某个包的哪些版本;例如,当你在实现共享库的持续交付时,但希望只与组织中的其他成员共享稳定版本。

为了实现这一点,你可以创建视图。视图是 Feed 中包版本的子集。在使用视图时,作为消费者,它的表现就像一个 Feed。

视图可以按如下方式进行管理:

  1. 导航并点击“视图”;你应该能看到类似于以下截图的内容:

  1. 在这里,你可以看到所有当前视图的列表,并通过选择行并点击“删除”来移除任何视图。

  2. 添加新视图可以通过使用添加按钮来完成,点击后将打开右侧显示的视图界面。

  3. 你也可以在这里设置从视图的读取权限。你可以允许整个 Azure DevOps 组织的读取访问权限,或指定特定的用户。你在此添加的任何用户或组将仅在此视图上获得读取权限。

  4. 编辑权限可以通过选择任何一行并选择“编辑”来进行设置。

一旦一个或多个视图可用,包可以通过视图进行推广,以供通过该视图使用。

配置上游源

你可以在 Feed 上配置的最后一项内容是上游源。Azure Artifacts Feed 为你提供了一个仓库,你可以在此发布你自己的包,供其他地方重用。

然而,你也可能会使用在像 NuGet.orgnpmjs.org 这样的仓库中公开可用的包。在这种情况下,你可以结合使用 Artifacts Feed 和 NuGet.org,但你也可以配置 Feed 以便从 NuGet.org 提供包。如果你这么做,NuGet.org 就被称为上游源。

除了简化操作外,这还带来了一个额外的好处:你可以在一个中央位置查看你在解决方案中使用的所有包。这使你能够快速检查你正在使用哪些包及版本,这对于合规性或安全性检查非常有用。通过在读取者和合作者角色之间设置不同的权限,你还可以配置哪些用户被授权从NuGet.org拉取包到你的 Feed,而哪些用户没有此权限。

当然,你可以对任何通过互联网可访问并实现 Azure Artifacts 支持的协议的仓库执行此操作。要配置上游源,请按照以下步骤进行:

  1. 在导航到以下屏幕后,可以配置上游源:

  1. 上游源的配置方式与权限和视图相同。你可以使用菜单栏中的“删除”按钮删除上游源。

  2. 通过点击“添加上游源”按钮来添加上游源,这将打开右侧视图。

关于使用上游源的最后一点需要注意的是,如果某个包的相同版本已经在上游源中可用,就无法将该版本的包发布到你自己的源。

本节讨论了如何创建和连接源。既然这些已经设置好了,接下来我们将学习如何将包发布到这些源。

发布包

现在你已经了解如何创建和管理源,是时候学习如何将包发布到这些源了。如果你有将包发布到公共源的经验,你会发现发布到 Azure Artifacts 的过程完全相同。有两种方式可以将包发布到源:

  • 从你自己的电脑手动操作

  • 通过使用 Azure Pipelines

以下章节将探讨这两种选项。

手动上传包

手动上传包时,需要执行以下步骤:

  1. 首先,你需要获取源的 URL。为此,点击任何源的“连接到源”按钮,如下图所示:

  1. 在左侧列表中,选择用于访问源的协议。

  2. 选择正确的视图进行操作。记住,发布包时需要使用完整的源 URL,因为视图是只读的。

  3. 在做出正确选择后,使用复制按钮将正确的 URL 复制到剪贴板。

  4. 执行以下命令,从常规的.csproj文件创建 NuGet 包。如果你没有NuGet.exe工具,可以通过本章末尾提供的链接下载它:

nuget.exe pack DemoSolution\MyPackage.csproj -Version 1.1.0
  1. 执行最终命令,将包上传到 NuGet:
nuget.exe push 
    -Source "{feedUrl}" "MyPackage.1.1.0.nupkg"

执行完最后一个命令后,包将被发布并在你的源中可用。

从管道发布包

如果需要多次上传包,手动上传包并不是一个方便的解决方案。在需要频繁生成并发布新版本库的情况下,你可以使用 Azure 管道。除了提供自动化功能外,它还是一种引入重复性和可靠性的绝佳方式,因为你现在可以充分利用管道所提供的所有好处。

作为示例,你可以找到一个用于创建npm包并将其发布的可能构建定义,具体如下。此构建的源来自一个名为tfs-cli的开源 Microsoft GitHub 仓库。

在这个管道中,有三种使用内置npm任务的方式:

  • 第一次出现的是npm install命令。此命令用于安装该包的依赖项:

  • 第二个操作是运行自定义命令build。该命令在源代码中使用package.json定义,用于将源代码从 TypeScript 转换为 JavaScript:

  • 最后,第三个任务是运行npm publish命令,将生成的包发布到npm源。在此情况下,没有选择外部源,而是选择了一个内置的目标注册表:Azure Artifacts 源:

运行此构建后,您的包就可以在您的源中使用了。

包版本控制

使用上传npm包的任务(或大多数类型的包)时,自动完成的其中一项操作就是管理版本号。当然,确保包有适当版本的方法有很多种,但一种常见的方法是在包构建过程中设置(部分)版本号。

扩展之前我们演示的npm包构建,可以对构建定义进行三处更改:

  1. 首先,构建定义的构建号格式更新为:1.0$(Rev:.rrr)。这确保每次构建都会自动生成一个唯一的编号。Ref:.rrr变量将生成一个三位数的编号,必要时以零为前缀。第一次生成的编号将是000,每次构建号其余部分未更改时,编号将增加 1。

  2. 其次,添加一个任务,用{#Build.BuildNumber#}令牌替换当前在源控制中指定的版本号。这个令牌引用了名为Build.BuildNumber的构建变量,它包含在步骤 1中指定的构建编号。

  3. 最后,在所有其他任务之前,构建中添加了一个替换令牌任务。以下是替换魔术固定版本号为自动版本号的任务配置示例:

该任务可以配置为替换一个或多个目标文件中的令牌(1)。它会查找任何以{#开头、以#}结尾的字符序列,提取这两个标记之间的文本,然后用对应变量的值替换整个文本。

通过此设置,使用定义构建的每个包都将拥有唯一且不断增长的修订版本号。每当需要更新主版本或次版本号时,可以通过更新构建号格式来完成。

作为这种方法的替代方案,扩展市场上提供了许多任务,可以帮助管理版本控制,包括更复杂的场景。

本节讨论了如何将包发布到源中。将包发布到源后,下一节将详细说明如何在 Visual Studio 或 Azure Pipeline 中使用这些包。

消费包

将软件包上传到 Azure Artifacts 源或仓库,使它们在许多不同的场景中可供使用。两个常见的场景是使用您的软件包与 Visual Studio 或 Azure Pipelines。接下来的部分将详细介绍这两个场景。

从 Visual Studio 使用软件包

一旦您的共享库作为 NuGet 软件包存在于 Azure Artifacts 源中,您就可以开始在 Visual Studio 中使用它们。在此之前,您需要在 Visual Studio 实例中注册您的源。

为此,您首先需要获取您的源的 URL。为此,请参考 手动上传软件包 部分。一旦您准备好 URL,请像平常一样去管理您解决方案中的 NuGet 文件。如果您不熟悉如何在 Visual Studio 中使用 NuGet 软件包,您可以在解决方案资源管理器中找到此选项,在解决方案和项目头部:

一旦您到达这里,请按照以下步骤操作:

  1. 点击右上角的小齿轮按钮,打开对话框,在其中您可以配置使用哪些 NuGet 源。

  2. 添加一个新源。

  3. 填写您自己源的名称和来源。

  4. 完成此操作后,不要忘记点击更新;否则,您对名称和来源字段的更改将不会被保存,而且不会有任何提示警告您存在未保存的更改。

  5. 在进行这些更改之后,您现在可以在屏幕右上角选择将您的源作为软件包来源。

从这里开始,您就可以像使用 NuGet.org 的软件包一样,使用来自您自己源的软件包。

从管道中使用软件包

一旦开始在 Visual Studio 中使用您的软件包,您很可能还需要在 Azure Pipelines 中使用它们。这样做是为了对依赖您的软件包的应用程序执行 CI/CD。

幸运的是,您可以通过对 NuGet 恢复任务进行小的配置更改来实现这一点,如下图所示。以下截图涉及的是 NuGet 恢复任务,这个任务既可以与 Visual Studio 构建任务一起使用,也可以与 .Net Core 构建任务一起使用。两者的界面相同,可以以相同的方式使用:

默认情况下,仅选中了使用来自 NuGet 的软件包的单选按钮;因此,要同时包含来自您自己源的软件包,您需要在下拉列表中选择正确的源。

如果您需要从多个源中包含软件包,您将不得不创建一个聚合源,并使用其他源作为该聚合源的上游源。

本节介绍了如何从 Visual Studio 使用组件软件包。下一节将深入探讨如何使用通用软件包来共享通用的二进制软件包。

使用通用软件包

前面的章节都集中在使用 Azure Artifacts 作为重新分发应用程序包(例如库或其他共享组件)的方式。然而,Azure Artifacts 还有一个重要用途,即使用源来存储任何类型的二进制包,这些包被称为通用包。

由于通用包源可以用于存储任何类型的包,因此如果您使用多个 CI/CD 工具,它是存储构建产物的好选择。在这种情况下,您可以使用通用包来存储并提供构建产物,以便随时从您使用的工具进行获取。这特别有用,因为经典构建和发布管道的内置存储无法被其他工具访问。

要在异构架构中使用通用包来暂存构建产物,您需要理解四个基本操作:从 Azure pipeline 上传和下载通用包,以及使用 Azure CLI 上传和下载通用包。最后一个操作可以从其他工具中调用。

从 Azure Pipelines 上传和下载通用包

将构建产物上传到通用包源的方式与上传常规构建产物类似。您需要考虑两个更改。

首先,您必须使用另一个任务来执行上传。您不能再使用发布构建产物发布管道产物任务,而需要使用名为“通用包”的任务。在使用此任务时,您仍然可以为产物命名,并指定从构建代理的文件系统上传它的位置。接下来,您可以指定目标源和版本。此版本可以在每次上传新包时自动递增,或者使用构建变量来指定。

其次,您需要考虑上传的包与产生它的构建之间不是一对一关联的—与常规构建或管道产物不同。这意味着无论您在哪里使用已上传的包,都需要找到另一种方法来找到正确的版本进行下载。

要执行实际的下载,您可以再次使用通用包任务,如下截图所示:

请参考截图并按照以下步骤操作:

  1. 添加任务后,您可以在上传和下载之间切换。

  2. 您还可以指定一个目录,将其作为产物上传。

  3. 或者,您可以指定下载产物的位置。

  4. 此外,需要指定源的名称。

  5. 此外,指定包的名称。

  6. 指定版本以决定是上传还是下载。

请注意,您还可以通过选择在第5步使用其他源来使用非自己组织的源。如果这样做,您需要创建一个服务终端以访问该源。

使用 Azure CLI 上传和下载通用包

当您希望与 Azure Pipelines 以外的产品一起使用通用包时,您必须使用 Azure CLI。为此,执行以下步骤:

  1. 使用 Azure CLI 操作通用包的第一步是安装 CLI 本身。CLI 的链接可以在本章末尾找到。

  2. 接下来是安装 Azure DevOps 扩展。可以使用以下命令来进行安装:

az extension add –name azure-devops
  1. 安装 Azure DevOps 扩展后,您需要使用与 Azure DevOps UI 中相同的账户进行登录。您可以通过以下命令登录:
az login
  1. 登录后,您可以使用以下命令将文件作为构件上传:
az artifacts universal publish 
 --feed {yourFeedName} 
 --name {yourPackageName} 
 --version {yourVersion}
 --organization https://dev.azure.com/{yourOrganizationName}
 --path {sourceFileName}
  1. 要重新下载特定版本的构件,您可以使用以下命令:
az artifacts universal download 
 --feed {yourFeedName} 
 --name {yourPackageName}
 --version {yourVersion}
 --organization https://dev.azure.com/{yourOrganizationName}
 --path {targetFileName}

使用 CLI 和这些命令,您可以将 Azure Artifacts 作为多工具之间共享构建构件的手段。当在同一项目中使用多个工具时,通用包是移动二进制文件的好工具。

在下一部分中,将探讨用于包管理的其他可用工具。

探索其他工具

还有许多其他工具可用于二进制管理。三种常用的产品是 MyGet、Artifactory 和 Azure 容器注册表 (**ACR*)。它们提供的功能有重叠,但也有它们在特定方面的优势。

MyGet

MyGet 是托管 NuGet 包的另一种选择。MyGet 允许您创建公共和私人包源,并由您自己管理。MyGet 还支持定义上游源,并提供内置的依赖项扫描功能,能够持续反馈依赖项的安全级别。

由于 MyGet 是 NuGet 协议的实现,您可以使用 Azure Pipelines 的默认 NuGet 任务发布和使用包。

Artifactory

Artifactory 是 JFrog 提供的另一款工具,您可以用它来托管您的包源。Artifactory 最初是一个本地产品,但现在也作为 SaaS 服务提供。与 Azure Artifacts 一样,它支持与包源交互的多种协议。撰写本文时,Artifactory 支持比 Azure Artifacts 更多的仓库协议。例如,PHP Composer 和 Red Hat 包管理器 (**RPM*)。

JFrog 已发布一个用于下载和上传包的 Azure Pipelines 扩展。

Azure 容器注册表

另一种用于存储可重用包的存储类型是 ACR。ACR 专为容器镜像设计,能够理解这些镜像的分层构建。这使得它能够在镜像的新版本可用时,仅在部分层发生变化时接收部分上传。这使得 ACR 成为存储容器镜像的非常好的位置。上传将更快,且 ACR 存储比 Azure Artifacts 存储更便宜。这是一个巨大的优势,因为容器镜像可能非常大。

你可以通过 Docker 集成扩展从 Azure Pipelines 集成 ACR。

总结

在本章中,你学习了如何识别解决方案中的共享组件:这些是不仅出现在多个位置,而且是逻辑上可重用的代码单元。你还学习了如何使用 Azure Artifacts 源来托管包含这些库的包。此外,你学习了如何使用这些托管的包,通过 Visual Studio 和 Azure Pipelines 构建依赖的解决方案。你还了解了如何使用通用包在 Azure Pipelines 和你可能使用的其他 CI/CD 工具之间共享构建工件。

通过这些知识,你现在将能够识别解决方案中的共享组件。一旦你识别出这样的组件,你还可以将其隔离在源代码管理中,构建它,并将其发布到工件源。然后,你可以将其分发到一个或多个消费解决方案中。最后,你现在还可以使用工件源在不同的 CI/CD 产品之间共享构建工件。

在下一章中,你将学习基础设施和配置作为代码。这是 DevOps 的一项基本实践,它允许你将基础设施定义存储在源代码管理中,并将其作为发布管道的一部分使用。

问题

在总结时,以下是一些问题,用于测试你对本章内容的理解。你将在附录的评估部分找到答案:

  1. 判断对错:任何版本的包只能部署到源中的一个视图。

  2. 判断对错:管道工件可以用于将 Azure DevOps 中的构建结果(包)共享到其他产品中。

  3. 判断对错:带有通用包的 Azure Artifact 源可以用于将 Azure DevOps 中的构建结果(包)共享到其他产品中。

  4. 以下哪项是为了在 Visual Studio 中构建使用 Azure Artifacts 源中包的解决方案所需的?(你可以选择多个选项。)

    1. 将完整的包 URL 添加到项目依赖项中,而不仅仅是包名称

    2. 至少具有读取者访问权限,或者具有源中的某个视图访问权限

    3. 至少具有消费者访问权限

    4. 将源的位置配置为 Visual Studio 的包源

  5. 将解决方案拆分成多个通过 Azure Artifacts 源分隔的部分的动机是什么?

深入阅读

第六章:基础设施和配置作为代码

在上一章中,重点是存储和构建应用程序代码以及发布创建的二进制文件。您学习了如何创建一个流水线,从源代码控制到目标环境,自动重复部署您的应用程序。

在本章中,您将学习如何将相同的原则应用于运行应用程序的基础设施和运行时配置。这样做将帮助您进一步提高向生产环境交付变更的速度,增加向最终用户提供价值的流量。

本章将首先解释将所有内容(包括基础设施和配置)作为代码的价值。接下来,它继续解释 ARM 模板。将解释语法以及如何部署 ARM 模板。然后,它继续解释 Azure 云中提供的 Azure 自动化。Azure 自动化可用于按计划运行脚本或加载和应用 PowerShell DSC 模块。接下来是管理 PaaS 提供的应用程序设置,例如 Azure 应用服务。最后,它讨论了几种具有类似功能的其他工具。

本章将涵盖以下主题:

  • 将所有内容作为代码

  • 使用 ARM 模板

  • 部署 ARM 模板

  • 反向工程模板

  • 使用 Azure 自动化

  • 管理应用程序设置

  • 其他工具

技术要求

要实验本章描述的一个或多个技术,可能需要以下一个或多个:

将所有内容作为代码

如果您过去负责创建和维护应用程序基础设施和配置,您很可能已经经历了所谓的配置漂移配置漂移是指接受和生产环境中服务器配置之间存在差异的现象。或者,更糟糕的是,在生产环境中有多台服务器时,这些服务器的配置可能并不总是相同。

配置漂移的最常见原因是手动更改。在手动更改时,可能是在生产问题的压力下进行,始终存在将不同设置应用于不同服务器或主机的风险。如果你需要扩展并向生产环境中添加另一台服务器,且这台服务器需要与所有已存在服务器采用相同配置的可能性非常小。

通过基础设施即代码IaC)和配置即代码CaC),你不再手动更改应用配置和基础设施,而是通过自动化来完成。实现这一目标的第一步是指定所需的配置和基础设施状态。然后,将所需状态输入配置管理工具,工具会在你的基础设施上强制执行该配置。只指定所需状态被称为声明性方法,与命令性方法不同,后者需要指定所有需要执行的步骤。

这些工具通常还能够定期检查你的基础设施和配置的当前状态,并在检测到任何偏差时重新应用所需的状态。这是由于声明性方法的存在。这使得应用配置成为一个幂等操作。如果一个操作是幂等的,意味着它可以重复执行一次或多次,而结果始终保持一致。

在采用 IaC 和 CaC 时,你甚至可以在部署应用程序之前重新创建完整的基础设施,先在新基础设施上部署应用程序,然后在切换到新部署后放弃旧的基础设施。这是一种极端形式的不变服务器。这种方法的附加好处是,你现在可以确保不再留下任何来自上一次部署的配置或二进制文件痕迹。

在接下来的章节中,你将了解不同的 IaC 技术以及如何使用它们。理解它们是互补的,并且通常一起使用非常重要。例如,ARM 模板可用于在 Azure 中创建虚拟机,完成后可以使用 PowerShell DSC 或 Ansible 来配置这些虚拟机。

使用 ARM 模板

在 Azure 平台上工作时,基础设施使用Azure 资源管理器ARM)模板进行描述。ARM 模板是用 JSON 编写的,骨架模板如下所示:

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
  },
  "variables": {
  },
  "resources": [
  ],
  "outputs": {
  }
}

模板本身在最高层次上是一个 JSON 对象。$schema是一个必需的属性,其显示的值也是强制性的。contentVersion属性也是必需的,并且可以用于为内容指定版本。如果需要,作者可以使用此版本来版本化模板。

本章的其余部分将更详细地讨论构成 ARM 模板的不同部分。章节末尾还提供了在线参考链接,此外,还附加了 ARM 模板结构和语法的正式详细解析链接。

参数

每个模板必须以一个参数部分开始。该部分采用 JSON 对象的形式,可以为空,但不能省略。此部分的用途是声明一个或多个可以由 ARM 模板调用者在部署前指定的参数。使用参数部分的常见原因是使用相同的模板,但在测试环境和生产环境之间更改资源名称。一个示例的参数部分可能如下所示:

{
  “appServiceName”: {
    “type”: “string”,
    “metadata”: {
      “description”: “a free to choose text”
    }
}

对于每个参数,指定一个新键,键名为参数的名称。值是一个对象。该对象有一个必需的键,typetype 的允许值为 stringintboolobjectarraysecureStringsecureObjectsecureStringsecureObject 类型用于确保这些参数的运行时值不会出现在任何日志和输出中。它们用于存储密码、密钥或其他机密信息。

元数据对象,带有 description 键,这是可选的,可以用来为参数添加描述,以供将来参考。

其他可以在参数对象上指定的属性如下:

  • minValuemaxValue 用于指定整数值的范围

  • minLengthmaxLength 用于指定字符串值的长度范围

  • defaultValue 用于指定如果在应用模板时未指定值,将使用的默认值

  • allowedValues 用于指定允许值的数组,限制有效的输入值

接下来,让我们了解一下什么是参数文件。

参数文件

在部署模板时指定参数值的一种方式是通过变量文件。通常,一个模板会附带多个参数文件,例如一个用于测试,另一个用于生产。一个参数文件的 JSON 格式如下所示:

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "exampleParameter": {
      "value": "exampleValue"
    }
  }
}

与 ARM 模板一样,每个参数文件都是一个 JSON 对象,具有必需的 $schemacontentVersion 属性。第三个属性参数用于指定一个或多个参数值。对于每个参数,指定其名称作为键,并将一个对象作为值。该对象可以包含 value 键,用于提供参数的实际值。

尽管指定资源名称、扩展选项以及其他在不同环境中需要变化的内容时非常有用,但此解决方案不适用于存储机密。密钥、密码和其他机密信息不应作为明文存储在源代码控制的参数文件中。对于机密信息,可以使用另一种表示方式:

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "exampleParameter": {
      "reference": {
        “keyvault”: {
          "id": "/subscriptions/…/Microsoft.KeyVault/vaults/<vaultname>"
        },
        “secretName”: “myKeyVaultSecret”
      }
    }
  }
}

使用这种表示法时,不是直接指定值,而是指向 Azure 密钥库中存储正确值的位置。部署模板时,此密钥将在 Azure 中从密钥库中取出并用于部署。只有在启动部署的用户或服务具有密钥库的所有者或贡献者角色,并且密钥库启用了模板部署时,才允许这样做。

严格来说,任何包含Microsoft.KeyVault/vaults/deploy/action权限的角色都可以使用。默认情况下,这些角色是所有者和贡献者角色,但你也可以创建包含此操作的自定义角色。

变量

变量部分用于指定在整个模板中将使用的一个或多个值。一种常见的方法是在变量部分根据一个名为environmentName的单一参数构建所有资源的名称。这确保了资源在不同环境中具有相同的名称。变量还用于指定无法从模板外部指定的值,但应视为可配置的。例如,可能会是这样的:

{
  "appServicePlanType": "B1",
  "appServiceName": "[concat('myAppService-', parameters('environmentName'))]"
}

请注意,appServiceName的示例包含了在后面名为函数的部分中详细讨论的函数。

资源

任何 ARM 模板中的第三部分是资源部分。这是模板的主要部分,所有要创建的资源都在此部分指定。这个部分是唯一一个不是对象,而是数组的部分。在该数组内,会指定一个或多个以下形式的对象:

{
    "type": "Microsoft.Sql/servers",
    "apiVersion": "2015-05-01-preview",
    "name": "mySqlServer",
    "location": "West Europe",
    "properties": {
        "administratorLogin": "myUsername",
        "administratorLoginPassword": "myPassword",
        "version": "12.0"
    }
}

每个资源都以对象的形式指定。前四个属性是每种资源类型必需的:

  • 需要指定要创建或更新的资源类型:这通常由resourceprovider的名称后跟一个斜杠,再加上该resourceprovider下属于的资源类型名称组成。

  • 用于此资源的 API 版本:可以从参考中获取受支持的 API 版本列表。

  • 资源的名称:每个资源类型都有自己的规则来确定有效名称的标准。这些规则也可以在参考文献中找到。

  • 创建资源的 Azure 区域:这必须是有效的 Azure 区域。

对象上的其他属性根据资源类型不同而不同,所有这些属性都在资源中指定。

依赖资源

一种特殊类型的资源是依赖资源。例如,SQL 数据库托管在 SQL Server 上,Service Bus Topics 位于 Service Bus 命名空间中。对于嵌套资源类型,类型和名称反映了这种嵌套关系:

{
    "apiVersion": "2017-04-01",
    "name": "myNamespaceName/myTopicName",
    "type": "Microsoft.ServiceBus/namespaces/topics",
    "dependsOn": [
        "Microsoft.ServiceBus/namespaces/myNamespaceName"
    ]
}

除了嵌套类型和名称外,额外的属性dependsOn也是必需的,用于指定此嵌套资源只能在包含资源存在后创建。位置属性不是必需的,因为它将从包含资源中继承。

嵌套模板

第二种特殊类型的资源是模板部署。通过这种方式,一个模板可以触发另一个模板的部署。以下是将模板部署定义为模板中的资源的示例:

{
    "type": "Microsoft.Resources/deployments",
    "apiVersion": "2018-05-01",
    "name": "linkedTemplate",
    "properties": {
        "mode": "Incremental",
        "templateLink": {
            "uri":"https://.../myLinkedTemplate.json"
        },
        "parametersLink": {
            "uri":"https://.../myParameters.json"
        }
    }
}

模板和参数文件的位置可以通过 HTTP 和 HTTPS 来指定,但必须是公开可访问的位置。作为替代方案,也可以指定一个单一的属性模板。该模板应包含一个完整的 JSON 对象作为模板。

输出

模板的第四个也是最后一个部分是输出部分。这里包含了返回给模板调用者的键。调用者可以使用这些值来启动另一个任务或脚本,并使用模板创建或使用的一个或多个值。

这个部分的主要用途是防止在下游自动化中硬编码名称。输出部分是一个 JSON 对象,格式如下:

{
    "outputName":
   {
        "type": "string",
        "value": "myValue"
    }
}

在指定输出时,可以使用与参数相同的类型。当然,硬编码值没有太大意义,因此使用函数从参数、变量甚至是创建的资源中获取值。

函数

函数用于允许在 ARM 模板中动态评估属性。调用函数的语法与许多编程语言非常相似:functionName(arg1, arg2, …) 函数可以返回一个值,如stringint,也可以返回一个对象或数组。当返回一个对象时,可以使用.propertyName符号访问任何属性。访问数组中的元素可以使用[position]。为了标明字符串的哪些部分应作为函数进行评估,可以将其放入括号中:

"myVariable": "[concat('myAppService-', parameters('environmentName'))]"

上面的示例展示了两个示例函数。首先,调用concat函数来连接两个字符串值。一个是硬编码的,另一个是第二个函数调用的结果,用于获取模板参数的值。

有相当多的可用函数。它们可以用于字符串操作、获取当前订阅、资源组或 Azure Active Directory 租户的详细信息,或获取资源的详细信息。

函数还可以用于获取帐户密钥或其他机密。通常,这样做是为了直接从暴露密钥的服务将密钥自动插入应用程序设置或密钥保管库中。这完全消除了手动传输机密的需求。

到目前为止,我们已经学习了组成 ARM 模板的不同部分,你应该能够自己编写这些模板。接下来,我们将学习如何利用各种工具来实际部署它们。

部署 ARM 模板

一旦 ARM 模板及其附带的参数文件编写完成,就可以将其应用于 Azure 环境。PowerShell Cmdlet 和 Azure CLI 命令可用于从脚本环境应用 ARM 模板。当 ARM 模板用于应用程序的基础设施时,Azure Pipelines 可用于部署不仅仅是代码,还可以是 ARM 模板。

无论使用哪种部署方法,所有方法都将有一个部署模式。这可以是增量模式或完全模式。在增量模式下,模板中指定的所有资源将在 Azure 中创建,或者如果资源已经存在,则更新其属性。在完全模式下,所有模板中未指定且已存在于 Azure 中的资源也将被删除。默认部署模式为增量模式。

在接下来的部分中,我们将讨论几种用于执行部署的工具,首先从 PowerShell 开始。

PowerShell

对于在本地机器上进行 ARM 模板的本地开发和测试,PowerShell 提供了一个快速命令来将 ARM 模板应用于资源组:

New-AzResourceGroupDeployment -ResourceGroupName myResourceGroup -TemplateFile "c:\my\template.json" ` -TemplateParameterFile "c:\my\parameters.json"

上述命令将获取指定的模板和参数文件,并将其应用于指定的资源组。此命令假设当前会话已经登录到 Azure。

有几种可用的命令变体:

  • 有一个名为-Mode的参数,具有CompleteIncremental值。可以用来指定deploymentmode

  • 如果未指定参数文件并且模板需要参数,则该命令将提示在命令行上输入这些值。

  • 作为替代,可以使用-TemplateUri-TemplateParametersUri选项来指定从另一个位置检索模板和参数的位置。

接下来我们将讨论的工具是 Azure CLI。

Azure CLI

Azure CLI 是另一种从命令行部署 ARM 模板的方法。CLI 的好处在于它是完全跨平台的,可以在 Windows、macOS 和 Linux 上运行。用于部署 ARM 模板的 Azure CLI 命令如下:

az group deployment create –resource-group myResourceGroup –template-file "c:\my\template.json" –parameters "c:\my\parameters.json"

PowerShell 中可用的所有其他选项在 CLI 中也都可以使用。

Azure Pipelines

部署 ARM 模板的第三种机制是通过 Azure pipeline。这对于部署应用程序的基础设施和配置以及二进制文件特别有用。要从 pipeline 部署 ARM 模板部署,需要配置至少一个 Azure Resource Manager 的服务连接。完成此配置后,可以按照以下截图配置 pipeline:

在此示例中,有两个 ARM 模板的部署,围绕着应用程序代码的部署展开。第一个部署是增量类型,而第二个部署是完整类型。使用这种方法,第一个部署将创建新版本应用程序所需的所有新基础设施。该部署以增量模式进行,因此模板中不再存在,但当前已部署版本的应用程序仍在使用的基础设施将不会被删除。第二个部署将在新版本代码部署后负责删除这些元素。

反向工程模板

从头开始编写 ARM 模板可能是一项繁琐且耗时的任务。幸运的是,有两种方法可以从现有基础设施生成 ARM 模板:

  • 使用导出模板

  • 使用资源浏览器

在接下来的子章节中,我们将讨论这两种方法。

使用导出模板

第一种方法是使用在 Azure 门户中每个资源和资源组上都能找到的“导出模板”选项。这将生成资源(组)当前状态的 ARM 模板,如下截图所示:

请注意,并非所有服务目前都支持使用此方法进行反向工程提取 ARM 模板。对于任何不支持的服务,屏幕顶部会显示警告。为了绕过这一限制并提取单个资源的 JSON 模板,还有另一种方法,这是我们接下来的讨论主题。

使用资源浏览器

为了提取单个资源的 JSON 模板,我们可以使用资源浏览器. 资源浏览器如图所示,并且可以通过 Azure 门户中的菜单 (1) 找到:

打开资源浏览器后,会打开两个新的窗格。左侧窗格可以用来浏览订阅,并逐层深入资源组,直到找到单个资源。每当选择一个元素时,相应的 JSON 将显示在右侧。在上述示例中,显示的是硬盘的 JSON。这个 JSON 与可以在 ARM 模板的资源数组中使用的 JSON 是一样的。

订阅级模板

到目前为止,关于 ARM 模板的讨论都集中在资源组部署的 ARM 模板上。模板描述了一个或多个部署到资源组的资源。此外,还有订阅级模板。以下是一个资源组的 ARM 模板示例:

{
    "$schema": "https://schema.management.azure.com/schemas/2018-05-01
                                                ... /subscriptionDeploymentTemplate.json#",
    "contentVersion": "1.0.0.1",
    "parameters": { },
    "variables": { },
    "resources": [
        {
            "type": "Microsoft.Resources/resourceGroups",
            "apiVersion": "2018-05-01",
            "location": "West Europe",
            "name": "myResourceGroup",
            "properties": {}
        }
    ],
    "outputs": {}
}

订阅模板的格式与资源组的格式完全相同。不同之处在于 $schema,它指向另一个模式位置,以及所支持的资源类型。订阅模板不支持直接创建资源,仅支持创建资源组、启动模板部署、创建和分配 Azure 策略以及创建角色分配。

Azure 蓝图

除了订阅级别模板,还有另一种可用的选项:Azure 蓝图。蓝图可以用来描述 Azure 订阅的期望状态,并将其应用于现有订阅。

使用蓝图可以完成的所有任务,如今也可以通过 ARM 模板来实现。然而,反过来就不成立。Azure 蓝图仅支持以下称为工件的构造:

  • 策略分配

  • 角色(RBAC)分配

  • 资源组创建

  • 订阅或资源组级别的嵌套 ARM 模板

这些是构建 Azure 订阅默认布局或蓝图所需的所有元素。

蓝图和 ARM 模板之间有一些关键差异:

  • 蓝图存储在 Azure 内部。蓝图是你可以在门户中创建并导航的资源。创建体验也在门户中,而不是在本地计算机上的文本文件中。

  • 订阅与用于创建它的蓝图之间的关系会一直保留,即使部署完成后也是如此。

  • 通过将蓝图分配给订阅,可以将该分配标记为锁定。如果这样做,通过蓝图部署的所有资源都不能被删除或编辑,只要蓝图仍然被分配——即使是应用该蓝图的订阅的所有者也无法进行操作。

  • 提供了许多内置的蓝图,可以用来实施来自 ISO、NIST 或 HIPAA 等知名标准的控制措施。

一般建议在创建多个新订阅时使用蓝图,这些订阅应该遵循相同的布局,而在其他情况下使用 ARM 模板。蓝图在撰写时仍处于预览阶段。

2019 年 11 月更新

自 2019 年 11 月以来,ARM 模板不再必须是纯 JSON 格式。现在允许使用其他几种构造方式,以便更方便地使用 ARM 模板。

要对一行的其余部分进行注释,可以使用 //,或者使用 /* */ 符号来注释一个块。这使得以下片段在 ARM 模板中都是有效的:

{
  “appServiceName”: {
    // this is a single line comment
    “type”: “string” 
      /*
       This is a multi-line comment
     */
    }*}*

另一个与 JSON 的偏离之处在于,ARM 模板允许多行字符串。在使用 Azure CLI 时,必须通过指定--handle-extended-json-format开关来启用此功能。为了使用这些及其他新功能,必须从模板中引用一个新的 JSON 架构。该架构为schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#

此外,还引入了一个新命令来显示应用模板时将进行的更改。此命令仍在预览阶段,名为New-AzDeploymentWhatIf。文档链接将在本章结束时提供。

虽然 ARM 模板是 Azure 中管理基础架构的首选方法,但在某些情况下,它可能不适用。在这些情况下,Azure 自动化可以作为一种替代方案。Azure 自动化将在下一节中讨论。

使用 Azure 自动化

Azure 自动化是 Azure 中的一项服务,旨在帮助用户创建、管理、部署和维护他们的 Azure 资源。Azure 自动化包含多个概念,这些概念去除了这些操作中的一些复杂性和低级细节。Azure 自动化允许以运行簿的形式制定工作流。这些运行簿可以代表用户执行 Azure 资源的操作。

自动化账户资源

在 Azure 自动化账户中,有多个资源使其不仅仅是一个脚本引擎。这些资源在自动化账户级别上共享,因此可以在多个运行簿中重用。

运行身份账户

这些结构中的第一个是运行身份(Run As)账户。此账户是一个服务主体,将在与包含自动化账户的 Azure 订阅关联的 Azure Active Directory 中创建。用于身份验证的服务主体凭据会在自动化账户中安全存储。这些凭据无法检索。该服务主体还会作为贡献者添加到 Azure 订阅中。因此,现在可以设置运行簿,以该账户身份执行。

在创建自动化账户时,可以自动创建运行身份账户。

时间表

自动化工作流的常见方式是安排它们在特定的日期和时间运行,或者按固定的时间间隔运行。可以创建共享计划并在运行簿中重用,而无需为每个工作流指定一个时间表。要创建一个新的计划,首先,打开所有计划的列表。然后,可以添加一个新计划,如下图所示:

一个计划有一个名称和描述。这些值仅供与计划交互的用户使用。接下来,可以配置一个开始日期和时间,并可以选择性地设置一个重复间隔。如果指定了重复间隔,还可以设置一个到期日期和时间。一旦计划创建完成,就可以用于运行手册。

模块

在 Azure 自动化中使用的运行手册是用 PowerShell 编写的。PowerShell 拥有一个功能非常丰富的模块生态系统,其中包含可以使用的预定义功能。要在自动化帐户中使用 PowerShell 模块,只能使用已上传到模块部分的模块。这样做的一个主要好处是,可以固定模块的版本。这样可以确保脚本在依赖项更新的情况下仍能正常工作,而不会中断。

用于与 Azure 交互的 PowerShell 模块默认安装在每个自动化帐户中。此外,管理员还可以添加更多模块,升级或删除现有模块。

变量

在运行手册中,可能会涉及很多变量,例如资源组名称、虚拟机名称、启动或关闭时间等。将这些值硬编码在脚本中并不是一种好的做法,但将它们与运行手册一起存储也有缺点。例如,如果同一个虚拟机有三个运行手册,这意味着一些变量值(例如资源组名称和虚拟机名称)将至少重复三次。为了避免这种情况,可以在自动化帐户级别存储变量值,并且这些值可以在该帐户下执行的每个运行手册中重复使用。

一旦设置了变量,就可以使用以下命令从运行手册中访问它:

$exampleVar = Get-AutomationVariable -Name 'ExampleVar'

除了在运行手册中读取和使用变量外,还可以从运行手册内部更新这些变量:

Set-AutomationVariable -name 'ExampleVar' -value 'ExampleValue'

尽管是一个非常强大的功能,但从运行手册中更新变量可能会产生意外后果。如果一个变量值在多个运行手册中被使用,并且其中一个运行手册更新了该变量值,这可能会导致其他运行手册出现问题。因此,跟踪哪些变量是只读的,哪些是可写的非常重要。

凭证

一种特殊类型的变量是凭证。凭证包含两个值:用户名和密码。凭证在使用的地方都被视为机密。这意味着它们不会出现在日志中,并且必须使用特定的 PowerShell 语法进行检索:

$myCredential = Get-AutomationPSCredential -Name 'MyCredential'

执行此命令后,myCredential 对象可以用来检索用户名和密码。

连接

在运行手册中连接一个或多个外部服务是一个非常常见的场景。一个常见的例子是用于管理 Azure 中所有资源的 Azure 资源管理器。为了避免在运行手册中存储一系列变量并构建相应的连接,自动化账户允许事先创建一个或多个连接。

在大多数情况下,不需要手动创建连接,因为它们会与 Run As 账户一起提供。

一旦所有共享资源到位,就可以开始编写一个或多个运行手册,这是我们接下来要讨论的主题。

运行手册

支持多种类型的运行手册:PowerShell、Python 2 和图形化的。前两种允许用指定语言编写脚本,图形化运行手册则允许通过拖放的方式,从所有上传的 PowerShell 模块、资产和现有的运行手册中组合一个运行手册。

除了这三种基本类型的运行手册外,还有 PowerShell 工作流和图形工作流类型可用。常规运行手册和工作流运行手册的区别在于,工作流运行手册还支持并行处理。PowerShell 工作流的另一个优点是它支持使用检查点,这允许在执行过程中遇到异常时,可以从中断处恢复脚本。

运行手册执行

运行手册编写完成后,有多种方式可以执行它:

  • 手动:任何运行手册都可以在任何时候通过在 Azure 门户中打开并点击“开始”按钮来运行。当然,也可以使用 PowerShell 或 Azure CLI 执行这些操作。

  • 通过附加 Webhook:一旦运行手册发布,就可以生成一个或多个 Webhook 来执行运行手册。每个 Webhook 可以启用或禁用,或设置过期日期。这些工具允许为每个运行手册的用户生成新的 Webhook,并在将来如果不再授予某个用户访问权限时,进行细粒度的控制。

  • 按计划:已发布的运行手册可以附加到一个或多个共享的计划中。能够附加到多个计划意味着可以轻松地为典型的重复事件(如每小时、每日或每周一)预先创建一系列计划,并将这些计划重用或组合到适当的运行手册中。

从 Webhook 或按计划执行运行手册时,仍然可以手动运行该手册。

作业

每次执行运行手册时,都会在作业日志中创建一个新条目。该日志将记录每次运行手册执行的条目,无论执行是如何启动的。每个条目都将包含运行开始的日期和时间、是否有错误,并且包含完整的执行日志。

运行手册画廊

自动化脚本是自动化常见任务的一个好方法。当然,也有一些任务仅适用于特定客户,但也有许多任务适用于所有 Azure 客户。例如,自动化虚拟机每周一早上 8 点启动,或者每天早上自动扩展数据库并在晚上自动缩减。

对于这些常见场景,Azure 提供了一个自动化脚本库,该库在每个自动化账户中都可以启用。在这个库中,可以浏览和搜索成百上千个预制的自动化脚本。一旦找到合适的脚本,可以将其直接导入账户作为自动化脚本。

除了在设定的时间间隔执行脚本或通过 Webhook 触发执行外,Azure 自动化还可以作为 PowerShell DSC 拉取服务器使用。接下来我们将讨论这一点。

PowerShell DSC

PowerShell DSC 是一种用于指定服务器配置的概念。该配置存储在拉取服务器上,虚拟机可以访问该服务器。这些虚拟机会在指定的时间间隔检查该服务器以获取最新的 DSC 配置,并自我更新以符合该配置。

PowerShell DSC 是对 PowerShell 语言规范的扩展,用于编写期望的状态配置。配置使得可以指定一个或多个节点的期望状态。节点指定要配置的服务器或服务器集。节点的配置以一个或多个资源的形式编写。以下是一个配置示例:

configuration ServerFarmConfig
{
     Node FrontEndServer
     {
         WindowsFeature IIS
         {
             Ensure = 'Present'
             Name = 'Web-Server'
             IncludeAllSubFeature = $true
         }

         File LogDirectory
         {
            Type = 'Directory'
            DestinationPath = 'C:\logs’
            Ensure = "Present"
        }
     }
}

在这个示例中,描述了一个只有单一类型服务器的服务器群配置。该服务器包含两个资源,第一个资源是类型为 WindowsFeature,名称为 IIS,确保 IIS 和所有子特性被安装。第二个资源是类型为 File,确保 c:\logs 目录存在。IISFile 等资源类型是 PowerShell DSC 规范中内置的。所有资源的完整参考文档可以在线查阅,链接会在本章结尾提供。

编译并应用 PowerShell DSC

PowerShell DSC 文件通常以纯文本形式保存在 .ps1 文件中。这些文件可以编译成 MOF 文件。然后,可以将这些 MOF 文件推送到一个或多个服务器,以将服务器的状态更新为 MOF 文件中描述的状态。这种方式称为推送模式

除了推送模式,还有另一种部署 MOF 文件的模型。这种模式称为拉取模式。在拉取模式下,MOF 文件不会直接推送到单独的服务器,而是存储在一个名为拉取服务器的中央服务器上。通过这种方式,拉取服务器能够完整记录所有配置和配置中的节点定义。

一旦拉取服务器启动并运行,单独的服务器将配置为在固定的时间间隔获取其 DSC 配置并应用该配置。应用配置意味着,对于每个定义的资源,将实施所描述的状态。如果实际状态已经与期望的状态匹配,则可以什么都不做,或者通过运行命令来实现期望的状态。在此过程中,所有以前的更改——即使是管理员的更改——如果需要,也会被恢复。

使用 Powershell DSC 与 Azure 自动化

Azure Automation 具有内置的 PowerShell DSC 功能,并可以充当一个或多个虚拟机的拉取服务器角色。

要开始使用内置的拉取服务器功能,将一个或多个配置文件上传到自动化帐户。这是通过以下截图中显示的状态配置视图完成的。现在,完成以下步骤:

  1. 点击左侧菜单选项打开。

  2. 在顶部的标签栏中选择配置:

  1. 打开所有配置的概述后,可以使用“添加”按钮添加新配置。在topHere中,可以选择一个本地的ps1文件,并将其添加到列表中。列表中的任何有效配置都可以点击并在原地进行编译。

  2. 现在,配置也将在带有编译配置的标签中显示,并且可以应用到一个或多个虚拟机。

  3. 一旦编译的配置可用,"Nodes" 标签可以用于将一个或多个虚拟机从订阅中添加到配置节点。

  4. 在显示此标签时,点击“添加”按钮会打开如下所示的视图:

  1. 在此视图中,可以选择一个虚拟机,并将所选配置应用到该虚拟机。

  2. 那台机器上的本地配置管理器将被配置为在固定的时间间隔刷新配置。

  3. 每当配置被刷新时,它将重新应用到服务器。

Azure Automation 使用户能够管理虚拟机,例如应用程序配置。在使用 PaaS 服务时,不能使用 PowerShell DSC 等技术,必须使用其他技术来管理应用程序设置。这些将在下一节中讨论。

管理应用程序设置

应用程序的基础设施的另一部分是应用程序配置。在本节中,讨论了存储和加载 Azure 应用服务的应用程序配置的多种方法。包括以下内容:

  • 将配置存储在应用程序设置中

  • 使用托管身份和密钥库的组合

  • 使用 Azure 应用配置服务

第一种方法的缺点是,任何具有管理员(读取)访问权限的用户都可以读取应用程序服务中的应用程序设置。接下来的两种方法没有这个缺点。

来自 ARM 模板的 Azure 应用服务设置

配置应用程序设置为代码的第一种方法是通过在 ARM 模板中将应用设置指定为资源。这应该作为嵌套资源进行指定。可以按照以下屏幕截图所示进行操作:

{
    "name": "[concat(variables(‘websiteName’), ‘/appsettings’)]",
    "type": "config",
    "apiVersion": "2015-08-01",
    "dependsOn": [
        "[concat('Microsoft.Web/sites/', variables('webSiteName'))]"
    ],
    "properties": {
        "key1": " [listKeys(parameters('storagename'), '2018-02-01').keys[0].value]",
        "key2": "value2"
    }
}

listKeys函数在这些场景中特别有用。它允许将机密直接从任何服务复制到应用程序设置,而无需将其存储在任何中间解决方案中。对于非 Azure 来源的机密,应使用模板参数。

ARM 模板中指定的配置对应于在门户中找到的应用服务配置。这些设置用于覆盖appsettings.jsonappsettings.config文件中的相应条目。更新此配置将自动重新加载应用程序。

这种方法的缺点是,以这种方式存储的机密可以通过 Azure 门户查看。任何具有读取访问权限的用户都可以检索所有以这种方式存储的机密。

从密钥库在运行时加载设置

存储应用服务设置的下一个可能位置是 Azure 密钥库,应用程序在运行时从中加载设置。为了实现这一点,必须做到以下几点。

为了能够授权应用程序访问密钥库,应用程序必须首先能够通过Azure Active DirectoryAAD)进行身份验证。当然,可以手动注册一个服务主体,但这会返回一个用户名和密码,必须将其存储在某个地方。用户名和密码是机密,但不能存储在密钥库中,因为它们用于访问密钥库。如何确保密钥安全的问题可以通过使用 Azure 的Managed Identity功能来解决。

安全地存储机密,但访问时返回另一个机密的问题通常被称为turtles all the way down问题。这是一个古老的轶事,章节末尾提供了相关链接。

启用 Azure 托管身份的应用服务,Azure 会自动生成一个服务主体,该主体具有不可检索的用户名和密码。仅在运行时,应用程序才能通过特定代码将自己认证为该主体。Azure 将确保此操作仅适用于运行在属于该托管身份的应用服务中的代码。

现在,应用程序可以拥有自己的身份,必须授予该身份对密钥库的访问权限。这可以通过在 ARM 模板中的密钥库描述中完成,使用以下语法:

{
    "type": "Microsoft.KeyVault/vaults",
    "name": "[parameters('keyVaultName')]",
    "apiVersion": "2015-06-01",
    "location": "[resourceGroup().location]",
    "dependsOn": [
        "[resourceId('Microsoft.Web/sites/', parameters('appServiceName'))]"
    ],
    "properties": {
        "enabledForTemplateDeployment": false,
        "tenantId": "[subscription().tenantId]",
        "accessPolicies": [
          {
            "tenantId": "[subscription().tenantId]",
            "objectId": [reference(concat(resourceId('Microsoft.Web/sites',parameters('appServiceName')),
                    [line continued] '/providers/Microsoft.ManagedIdentity/Idntities/default'), 
                    [line continued] '2015-08-31-preview').principalId]",
            "permissions": {
              "secrets": [ "get", "list" ]
            }
          }
        ],
        "sku": {
          "name": "standard",
          "family": "A"
        }
    }
}

在此示例中,reference()函数用于检索托管身份的信息,并利用这些信息在密钥库上创建访问策略。

最后,设置好密钥保管库及其访问权限后,应用程序必须在启动时检索内容。为此,可以使用配置构建器。它们是在.NET Core 2.0(和.NET Framework 4.7.1)中引入的,并在StartUp类中使用,如以下代码片段所示:

var tokenProvider = new AzureServiceTokenProvider();
var kvClient = new KeyVaultClient((authority, resource, scope) =>
tokenProvider.KeyVaultTokenCallback(authority, resource, scope));

var configurationBuilder = new ConfigurationBuilder().AddAzureKeyVault(
    $"https://{ Configuration["keyVaultName"]}.vault.azure.net/",
    kvClient, 
    new DefaultKeyVaultSecretManager());

Configuration = configurationBuilder.Build();

该代码示例中的所有类型都可以在 NuGet 包Microsoft.Configuration.ConfigurationBuilders.Azure中找到。

Azure 应用程序配置

存储应用程序配置的另一个位置是 Azure 应用程序配置。这是一个新服务,截至本文写作时仍处于预览阶段。应用程序配置允许创建一个可以作为配置使用的键值对中央注册表,这个注册表可以被多个应用程序使用。

应用程序配置是另一种可以通过门户创建的资源。其主要组件是配置资源管理器,如以下截图所示:

除了配置资源管理器外,还有一个密钥部分,用于检索应用程序可以用来读取配置的访问密钥。还有选项可以查看配置的最近更改、恢复早期版本,以及导入或导出所有配置设置。

在创建应用程序配置资源并添加配置键后,可以通过使用IConfiguration框架类型的扩展方法在应用程序中检索它们:

config.AddAzureAppConfiguration(settings["ConnectionStrings:AppConfig"]);

从应用程序配置加载设置的加载器是 NuGet 包Microsoft.Azure.AppConfiguration.AspNetCore的一部分。

与将设置存储在 Azure 密钥保管库相比,应用程序配置有两个缺点:

  • 首先,应用程序需要通过连接字符串配置到应用程序配置,并在应用设置中存储至少一个新的密钥。

  • 其次,应用程序配置没有像密钥保管库那样严格的访问控制选项。因此,根据配置值的类型,将配置分布在应用程序配置和密钥保管库中可能更为合理。

这部分内容结束了我们对 Azure 和 Azure DevOps 在基础设施即代码方面的讨论。接下来的部分将讨论一系列其他提供类似功能的工具。

其他工具

还有许多其他工具可以通过代码管理基础设施和配置。在前述的原生 Azure 和 Windows 选项之外,还有许多广泛使用的替代工具,其中一些在本节中列出。了解哪些工具适用于哪些场景,并如何与它们集成是很重要的。

CloudFormation

CloudFormation 是 AWS 云的基础设施即代码(IaC)语言。CloudFormation 模板可以用 JSON 或 YAML 格式编写。创建一个可以公开读取的 AWS S3 存储桶的示例如下所示:

Resources:
 HelloBucket:
 Type: AWS::S3::Bucket
 Properties:
 AccessControl: PublicRead

有一个扩展可以让你从 Azure DevOps 执行 AWS 上的 CloudFormation 模板。这个扩展提供了创建、更新或删除 AWS 堆栈的任务。堆栈的功能类似于 Azure 中的资源组,任务则类似于应用 ARM 模板的任务。

Chef

Chef 是一个用于配置即代码(CaC)的工具,支持描述和执行服务器配置。Chef 使用一个集中式服务器,Chef Server,该服务器保存所有服务器的配置。在这里,为每个服务器确定正确的期望状态,然后由 Chef Client 拉取,该客户端是一个在受管理节点上运行的代理。

为服务器定义期望状态是通过一系列构造来完成的。最低级别是配方(recipe)。配方包含一个或多个资源,资源是可用的内置功能。例如,execute 资源可以执行一个 bash 命令。另一个资源是 apt_update,它提供与 apt 包管理器交互的方式。一个或多个配方可以组合成烹饪书(cookbooks),描述可以分配给节点的能力。一个或多个烹饪书可以通过运行列表(run list)分配给节点。运行列表包含必须应用到节点的所有烹饪书。

与 Chef Server 的交互是通过一个名为 knife 的命令行工具进行的。

尽管术语完全不同,但 PowerShell DSC 和 Chef 之间存在许多概念上的相似之处。

Puppet

Puppet 是一个部署和配置管理工具,采用服务器-客户端模型。它有一个集中式服务器,称为 Puppet Master,负责接收所有期望状态描述并将它们编译成一个内部目录,保存每个受管理服务器的期望状态。所有由 Puppet 管理的服务器都需要在本地服务器上安装 Puppet 代理。该代理连接到服务器,拉取它所管理的服务器的状态,并在本地应用该状态。受管理的服务器称为节点(node)。

Puppet 使用的基本构建块叫做 资源(resource)。资源通过指定资源类型和一系列属性来定义。有许多资源类型可用,例如管理用户和已安装的应用程序。资源被分组在一个或多个 类(classes) 中,这些类又被分配给一个或多个节点。

Puppet 可以安装在 Azure 中的任何 Linux 虚拟机上。Azure 市场中也有一个预构建的包含 Puppet Enterprise 的镜像。

Puppet 与 Chef 和 PowerShell DSC 相似。三者都有一个类似的模型来描述期望的状态,它们的目的相同。

Ansible

Ansible 是另一种配置管理工具,主要用于 Linux,但也支持 Windows。Ansible 与其他工具的一个不同之处在于,它没有集中式服务器来托管所有期望的状态,也不使用代理。Ansible 执行的所有命令都是通过 SSH 执行的。

任何服务器都可以启动一个剧本,针对清单中的一个或多个进行部署。Ansible 清单包含所有可以由 Ansible 管理的服务器。这些服务器可以分组为一个或多个组,并且可以嵌套在其他组中。每台服务器和每个组都是清单项。在 Ansible 中,期望的状态是写入剧本中的。剧本是一系列需要在目标服务器上运行的任务或角色。角色是任务的集合。角色旨在在多个剧本中重用,因此应该足够通用,可以在多种情况下使用。角色还应该是幂等的。这意味着角色中的任务应该确保无论运行剧本多少次,结果都是相同的。

Ansible 脚本可以通过命令行工具或封装该工具的 Azure DevOps 扩展来执行。还有其他管理系统可用,如 Ansible Tower,它在 Ansible 命令行工具的基础上提供了图形用户界面。

Terraform

Terraform 是一个多云基础设施管理解决方案。它可与 ARM 模板相比,区别在于它还支持 Amazon Web Services、Google Cloud Platform 以及其他云平台。Terraform 使用自定义文件格式来指定一个或多个资源,这些资源将通过一个或多个提供商来创建。这些资源对应于云资源,而提供商负责知道如何与不同供应商的 API 交互。

您还可以选择使用 JSON 格式,而不是 Terraform 专有格式。Terraform 还支持使用模块来创建可重用的组件包。

Terraform 配置文件是通过命令行界面执行的。

总结

在本章中,您学习了基础设施和配置即代码的概念、它的价值以及如何在实践中使用它。为了实现这些,您学习了 Azure 的 IaC 机制——ARM 模板。您还了解了用于管理虚拟机配置的 PowerShell DSC 以及管理应用程序配置的不同技术。最后,您了解了市场上可用的几种其他工具。您学会了在不同情况下可以使用哪个工具,以及这些工具是否能够与 Azure DevOps 集成。

有了这些知识,你现在能够使用你所学到的一种或多种工具,开始在源代码管理中描述你的应用程序的基础设施和配置。你还可以通过自动化手段设置交付基础设施的方式,无论是通过发布管道还是使用专门的基础设施管理工具。但无论你选择哪种解决方案,你现在已经具备将基础设施融入到 DevOps 流程中的能力。

在下一章中,你将学习在实施 DevOps 实践时可能遇到的另一个挑战:数据库。在提高功能流向生产的速度时,你可能还需要改变管理数据库架构和应用更改的方式。下一章将讨论这个话题。

问题

在本章结束时,以下是一些问题,可以测试你对本章内容的理解。你可以在附录的 评估 部分找到答案:

  1. 判断正误:ARM 模板可用于创建、更新和删除 Azure 资源。

  2. 以下哪项不是 Azure 自动化账户资源?

    1. 模块

    2. 容器

    3. 运行账户

    4. 变量

  3. 判断正误:基础设施作为代码的一个缺点是,你必须将敏感信息作为 ARM 模板参数文件放入源代码管理中。

  4. 判断正误:Azure 自动化账户允许在预定的时间表上执行 Powershell 运行簿。

  5. 使用基础设施作为代码有什么好处?

深入阅读

第七章:在 DevOps 场景中处理数据库

在前几章中,您已经学习了软件的持续集成和持续部署。您还学到了如何将相同的原则应用于基础设施配置的交付。一旦您采用了这些原则并开始增加价值交付的流动,您可能会遇到另一个挑战:管理数据库模式的变更。

将 DevOps 应用于数据库可能感觉像是在开动的汽车上更换轮胎。你必须找到某种方法,在不让系统停机维护的情况下,协调数据库模式和应用程序代码之间的更改。

在本章中,您将了解几种不同的方法来实现这一点:管理这些模式变更并避免停机。通过适当的规划和严格的方法,可以以良好的风险管理方式实现这一目标。您将看到如何将数据库模式视为代码,并将了解可用的不同方法。您还将看到另一种完全避免使用数据库模式的方法,即无模式化。

本章将涵盖以下主题:

  • 将数据库模式作为代码进行管理

  • 应用数据库模式更改

  • 无模式化

  • 其他方法和问题

技术要求

为了实践本章中阐述的思想,您需要安装以下工具:

  • 一个安装了 Entity Framework Core NuGet 包的应用程序

  • 配备 SQL Server 数据工具的 Visual Studio

  • 访问 Azure Pipelines

  • 一个 Azure 订阅,用于访问 Cosmos DB

将数据库模式作为代码进行管理

对于那些熟悉通过应用程序代码与关系数据库交互的用户来说,很可能他们已经在使用对象关系映射器ORM)。ORM 的出现是为了填补面向对象编程语言与使用表格的关系数据库模式之间的阻抗不匹配。知名的例子有 Entity Framework 和 NHibernate。

ORM 提供了一个抽象层,允许从数据库中存储和检索对象,而不必担心底层的表结构。为了自动将对象映射到表,或反向操作,ORM 通常内建有描述数据库模式、相应对象模型及其之间映射的功能,这些通常使用标记语言来实现。大多数情况下,这些内容不需要手动编写。它们通常可以从对象模型或现有数据库中生成,并且它们之间的映射通常通过约定生成或在可视化编辑器中绘制。

虽然这些方法允许当前的数据库模式以代码的形式定义,但单靠这些方法尚不足以应对模式变化。为了将模式变化作为代码进行处理,有两种常见的方法。一种是在代码中描述每一个变化;另一种只在代码中描述最新版本的模式。这些方法分别被称为基于迁移和基于状态的方法。两者都可以依赖第三方工具,将这些变化应用到数据库中。

迁移

第一种方法基于保持一组必须应用于数据库的有序变更。这些变更通常被称为migrations,可以由工具生成,如 Microsoft Entity Framework、Redgate SQL Change Automation,或者可以手动编写。

工具可以基于当前数据库模式与源代码控制中新的模式定义的比较,自动生成迁移脚本。这被称为脚手架生成。工具生成的脚本并不总是完美的,它们可以通过程序员所掌握的领域知识进行改进,但工具本身没有这些知识。一旦一个或多个新的迁移被脚手架生成或手动编写,它们就可以通过所选工具应用到数据库中。下面是一个展示这一过程的图示:

在这里,我们可以看到一系列不断增长的迁移(从 m1 到 m4),用于描述数据库的增量变化。为了将数据库更新到最新版本,需要确定已应用的最新迁移,并依次添加其后所有的迁移。

在手动编辑迁移脚本时,必须记住以下几点:

  • 迁移脚本应当是有序的。迁移描述了需要执行的 SQL 语句,以便将数据库从版本x迁移到版本x+1。只有当这一过程完成后,才能开始下一个迁移。

  • 一个迁移脚本不仅应迁移模式,还应迁移数据。这可能意味着需要一些中间步骤。例如,将两列数据移动到另一个表通常意味着先创建新的列,然后将旧列的数据填充到新列中,最后才删除旧列。

  • 建议在迁移脚本中包含所有数据库对象。额外的索引和约束不应仅应用于生产数据库,还应应用于测试环境。使用迁移时,已经有机制可以将这些内容从源代码控制传送过去。将它们包含在相同的迁移脚本中,也确保了索引和约束按相同的顺序应用,避免了它们只存在于生产环境中而意外阻塞迁移的情况。

  • 如果可能,迁移脚本应保持幂等性。如果出现问题或怀疑出现问题,能够重新执行最后一个迁移是确保其完全应用的一个好方法。

这种方法的一个缺点是,生成和应用迁移脚本时对顺序的严格要求。这使得将这种方法集成到依赖分支使用的开发工作流中变得困难。不同分支中创建的迁移在合并后可能会破坏迁移的顺序,或者更糟糕的是,合并迁移路径的分裂。例如,假设在现有迁移a之后,在两个不同的分支中创建了两个迁移bc。这两个迁移如何合并?无论是按顺序应用a, b, c 还是 a, c, b 都不正确,因为bc都是在a之后直接执行的。修复此类错误的唯一方法是执行以下步骤:

  1. 除了第一个新迁移(例如,在此情况下为c)之外,删除所有其他迁移。

  2. 将所有其他迁移应用于没有应用任何新迁移的数据库;在这种情况下,如果a已经应用,则只需应用b,或者同时应用ab

  3. 为其他迁移生成一个新的迁移;在这种情况下,c的替代。

这种方法的一个优点是,每个单独的模式变更都会以相同的方式部署到数据库中。无论一个或多个迁移是否同时应用于生产数据库,它们仍然会按可预测的顺序一个接一个地执行,并且与它们在测试环境中运行的方式相同,即使它们是逐个应用的。

结束状态

管理模式变更的另一种方法是,不跟踪单个变更(或迁移),而是仅将模式的最新版本存储在源代码管理中。然后使用外部工具将源代码管理中的当前模式与数据库的实际模式进行比较,生成迁移脚本,并在运行时应用这些脚本。迁移脚本不会被存储,并且仅用于一次。

与编写迁移不同,手动执行此类任务是不可行的。虽然手动在源代码管理中跟踪最新版本的模式是可以管理的,但对于结束状态方法则不可行。在比较现有模式和新模式时生成迁移脚本并应用该迁移脚本只能通过工具完成。这些工具的例子包括 Redgate SQL 源代码控制和 SQL Server 数据工具。如何使用这些工具,在此展示:

在这里,我们看到如何将当前实际的数据库模式与期望的数据库模式的描述进行比较,以生成升级脚本,并直接应用这个脚本以进行必要的更改,使实际模式与期望模式相同。

这种方法的一个优势是,不需要生成一系列必须按特定顺序执行的脚本。因此,这种方法与广泛分支的架构非常契合,尤其适合逐步集成变更的场景。它还消除了手动编写迁移的需求,适用于简单的场景,如添加或删除列、表或索引。

这种方法的缺点是,它使得处理需要数据操作的变更变得更加困难。再举个例子,假设将两列移动到另一个表中。由于工具仅强制执行新的架构,如果没有进一步的干预,这将导致数据丢失。

一种可能的干预方式是向架构包中添加部署前和部署后的脚本。在部署前脚本中,当前数据会被暂存到一个临时表中。然后,在应用新架构后,数据会从临时表复制到新位置,过程在部署后脚本中完成。

本节内容介绍了如何以可存储在源代码管理中的格式管理数据库架构变更。下一节将讨论如何在部署时拾取这些变更,并将其应用到数据库中。

应用数据库架构变更

有了数据库架构,并可选地定义了一系列迁移存储在源代码管理中,接下来就该考虑何时将这些变更应用到数据库架构中。有两种方法可以做到这一点。数据库架构的变更可以在部署新版本的应用程序之前应用,或者由应用程序代码本身来应用。

作为发布的一部分进行升级

应用数据库变更的第一种方法是作为发布流程的一部分。当这种情况发生时,负责读取和执行迁移脚本的工具会通过管道中的一个步骤来调用。

这种调用可以通过 PowerShell 或其他脚本语言中的自定义脚本完成。然而,这种方式容易出错,并且每次工具发生变更时,都有可能需要更新脚本。幸运的是,对于大多数基于迁移的工具,Azure Pipelines 提供了现成的任务,能直接从发布阶段开始执行迁移。

例如,Azure Pipelines 提供了一个扩展,用于直接从定义它们的 dll 文件将 Entity Framework Core 迁移应用到数据库中。此任务可以添加到发布管道中,用于在部署新应用程序代码之前更新数据库。

另一种变体是将应用程序的构建和发布阶段进行分离。在这种情况下,迁移脚本会作为独立的构建产物导出,可以直接从源代码导出(如果是 SQL 编写的),或者通过执行一个工具生成必要的 SQL 脚本作为输出。然后,在发布阶段再次下载该构建产物,并使用 Azure Pipelines 的 SQL 执行任务将其应用到数据库中。

通过应用程序代码进行升级

架构变更可以由应用程序本身而非发布管道来应用。一些带有内置迁移支持的 ORM 具备自动检测数据库架构是否与最新迁移匹配的能力。如果不匹配,它们可以自动将架构迁移到最新版本。

支持此功能的 ORM 之一是 Entity Framework。Entity Framework 的核心版本不内置自动迁移支持。在 Entity Framework Core 中,一行应用程序代码就可以在对应用程序而言方便的时刻启动升级。以下代码片段展示了实现此功能的代码:

using (var context = new MyContext(...))
{
    context.Database.Migrate();
}

这种方法的优点在于启用非常简单。例如,Entity Framework 中的一个布尔开关就可以启用此工作流。然而,缺点是大多数支持此功能的 ORM 会对数据库强制执行全局锁定——在迁移运行时停止所有数据库事务。对于任何需要几秒钟以上的迁移或一组迁移,这种方法可能不可行。

这种方法通常仅用于基于迁移的方法。使用终态方法的方案需要一个外部第三方工具,用于生成所需的迁移脚本并应用它们。通常这会通过发布管道完成,而不是由应用程序本身来处理。

添加过程

如前节所示,考虑如何以及何时应用数据库架构或使用该架构的应用程序(或多个应用程序)的变更是非常重要的。但无论架构变更和代码部署的安排如何,总会有一个时期,其中以下情况之一为真:

  • 新的应用程序代码已经运行,而架构变更尚未应用或正在应用过程中。

  • 在架构变更已应用或正在应用的情况下,旧版应用程序代码仍在运行。

  • 在应用架构变更时,应用程序代码未运行。

第三种情况是非常不希望出现的。这通常是这样,尤其是在实践 DevOps 时。如果频繁发布变更并且在工作时间内进行,频繁因每次架构变更停机是无法接受的。

为了避免在应用架构变更时需要停机,必须满足以下条件之一:

  • 架构变更具有向后兼容性,使得旧版应用程序代码能够在架构变更已经应用或正在应用的数据库上无错误地运行。

  • 新的应用程序代码具有向后兼容性,能够在旧版和新版架构上运行。

满足第一个条件可以确保旧的应用程序代码在应用模式更改时仍然能够运行。满足第二个条件则确保新的应用程序版本可以首先部署,完成后可以在该代码运行时升级数据库。虽然两者都能工作,但通常希望满足第一个条件。原因是模式更改通常会支持应用程序代码的更改。

这意味着,以下过程是安全的,在不发生停机的情况下部署模式更改:

  1. 创建一个新的数据库。

  2. 应用数据库更改。

  3. 验证更改是否已正确应用,或者中止部署管道。

  4. 部署新的应用程序代码。

需要意识到,这个过程假设前向失败。这意味着,如果在部署模式更改时遇到问题,应在继续进行代码更改之前先解决这些问题。

最后,满足模式更改的向后兼容性条件,有时对于某些模式更改来说是无法完成的。如果是这种情况,可以将更改拆分为两个部分更改,两个部分共同完成相同的最终结果,并且都满足向后兼容性的条件。例如,重命名一个属性,或者将存储距离的单位从英尺改为米,可以按以下方式执行:

  1. 生成一个迁移,向数据库表中添加一个新列,存储以米为单位的距离。

  2. 添加一个应用程序代码,从旧列中读取数据,但写入两个列。

  3. 将这些更改部署到生产环境。

  4. 添加一个新的迁移,将旧列中的数据迁移到新列中,对于所有尚未填充新列但旧列已填充的情况。

  5. 更新应用程序代码,使其仅读取和写入新列。

  6. 将这些更改部署到生产环境。

  7. 添加一个新的迁移,移除旧列。

使用正确的工具和适当的过程,可以执行有效且安全的模式更改部署。在下一部分中,将介绍另一种方法,即使用无模式数据库。

放弃使用模式

在前面的部分中,重点是关系型数据库,其中对每个表都应用严格的模式。另一种完全不同的数据库模式管理方法是完全放弃使用数据库模式。这可以通过使用无模式或文档型数据库来实现。一个著名的无模式数据库例子是 Azure Cosmos DB。这些数据库可以将不同形式的文档存储到同一表中。这里所说的“表”是指,因为这些类型的数据库通常不使用“表”这个术语,而是将其称为数据库、容器或集合。

由于这些数据库可以在同一集合中存储不同模式的文档,从数据库的角度来看,模式变更已经不存在。但当然,随着时间的推移,应用程序代码中相应对象的结构会发生变化。要处理这种情况,最好区分存储对象到数据库和从数据库读取对象的过程。

将对象写入数据库

存储在无模式数据库中的文档通常是应用程序代码中对象的序列化。当使用关系型数据库时,这些对象通常通过对象关系映射器ORM)进行存储,比如 Entity Framework、Dapper 或 NHibernate。当使用文档数据库时,这些对象通常被序列化并存储在数据库中。这意味着代码对象定义的变化会导致在保存对象时文档结构的变化。由于文档数据库的特点,这种方式是有效的。

举个例子,考虑以下 C# 类及其序列化到文档数据库后的 JSON 表示:

|

public class Person
{
   [JsonConstructor]
   private Person() {}

   public Person(string name) {
      Name = name ?? throw new ArgumentNullException();
   }

   [JsonProperty]
   public string Name { get; private set; }
}

|

{
   “Name”: “Mark Anderson”
}

|

在这段代码在生产环境中运行一段时间后,成千上万的人员已被保存,一个新的需求出现了。除了记录人员的姓名外,还必须记录他们所在的城市。因此,Person类扩展以包括另一个属性。进行此更改并部署新代码后,每当保存一个人员时,以下代码将被使用,结果是生成如下所示的 JSON:

|

public class Person
{
   [JsonConstructor]
   private Person() {}

   public Person(string name, string city) {
      Name = name ?? throw new ArgumentNullException();
      City = city ?? throw new ArgumentNullException();
   }

   [JsonProperty]
   public string Name { get; private set; }

   [JsonProperty]
   public string City { get; private set; }
}

|

{
   “Name”: “Mark Anderson”,
   “City”: “Amsterdam”
}

|

尽管Person类的定义发生了变化——相应的 JSON 也发生了变化——这两种文档形式仍然可以保存在同一集合中。

这表明,从将信息写入数据库的角度来看,无模式方法非常方便,因为开发人员根本不需要考虑模式变更管理。

从数据库读取对象

尽管无模式数据库使得将不同形式的文档写入同一个集合变得非常容易,但在从该集合中读取文档并进行反序列化时,可能会遇到问题。实际上,模式管理的问题并没有消除,而是被推迟到了稍后的时间点。

继续前面的示例,在新的 C# Person 类定义下反序列化第一个保存的人的时候,city属性将得到一个空值。这可能是意料之外的,因为 C# 代码保证不会构造没有城市的人员。这清楚地展示了无模式数据库带来的挑战。

在这个示例中,可以通过将Person类更新为以下内容来规避该问题:

public class Person
{
   [JsonConstructor]
   private Person() {}

   public Person(string name, string city) {
      Name = name ?? throw new ArgumentNullException();
      City = city ?? throw new ArgumentNullException();
   }

   [JsonProperty]
   public string Name { get; private set; }

   [JsonIgnore]
   private string _city;

   [JsonProperty]
   public string City { 
      get { return _city; }
      private set { _city = value ?? _city = string.Empty}
   }
}

除了这个添加属性的场景外,还有许多其他场景会要求对 C# 类进行适配,以处理反序列化场景。以下是一些示例:

  • 添加原始类型的属性

  • 添加一个复杂属性、另一个对象或数组。

  • 重命名属性。

  • 将原始类型的属性替换为复杂属性。

  • 将可空属性改为非空属性。

向对象添加代码以处理这些情况会增加代码库的大小和复杂性,并将处理过去情况的功能污染主代码库。尤其是在这种情况频繁发生时,这可能会导致代码库中出现不必要的复杂性。为避免这种情况,可能的解决方案是,每当对象的模式发生变化时,按照以下过程操作:

  1. 更改对象的模式,确保只添加属性。即使目标是删除一个属性,在此阶段,只有具有新名称的属性被添加。

  2. 在对象上实现逻辑,以应对旧版本对象的反序列化。

  3. 部署对象的新版本。

  4. 启动一个后台进程,从数据库中逐一加载该类型的所有对象,并将其保存回数据库。

  5. 一旦后台进程处理完所有现有实体,删除负责处理反序列化过程中模式更改的代码,以及任何不再使用的属性。

使用这种方法,所有更改将在一段时间内传播到对象的所有存储版本。该方法的缺点是,对象结构的更改被分成两个必须分别部署的更改。此外,第二次更改的部署必须等待数据库中所有对象都已转换完毕。

其他方法和关注点。

除了前面讨论的更常见的方法,以下的一些提示和方法可能有助于减少处理数据库时的工作量,或帮助降低与数据库更改相关的风险。

最小化数据库的影响。

处理数据库的第一步可以是减少需要进行数据库更改的机会。在许多数据库中,可以编写存储过程——或者其他代码或脚本——在数据库引擎内部执行。虽然存储过程有一些好处,但更改它们也可能算作数据库模式的更改,或者至少会导致难以测试的更改。

一种简单的方法是将存储过程替换为应用程序代码,从而使用功能开关进行更简单的并行更改。

完整的并行部署。

在高风险环境或脆弱的数据库中工作时,也可以采取另一种方法进行数据库模式更改。该方法基于应用功能开关和蓝绿部署模式,具体步骤如下:

  1. 以这样的方式更改应用程序代码,使其将任何更新写入不止一个,而是两个数据库。

  2. 在生产环境中,创建现有数据库的完整副本,并配置应用程序代码以同时将所有更改写入两个数据库。这些数据库将被称为数据库和数据库,之后将使用这些名称。

  3. 仅在写入新数据库的路径中,引入对新数据库架构和应用程序代码的必要更改。

  4. 在所有读取数据的代码路径中引入必要的更改,使得所有查询都在两个数据库上运行。

  5. 更新应用程序代码,检测新数据库和旧数据库之间查询结果的差异,并在发现任何不一致时记录错误。

  6. 如果更改顺利运行,请删除旧数据库,以及应用程序代码中的旧读写访问路径。

  7. 如果更改运行时出现错误,修复问题。接下来,通过恢复目标新数据库的备份来重新启动,并从第五步开始恢复。

这种方法的优点是非常轻量级。缺点是它非常复杂,需要大量的工作,且成本较高。同时,还应考虑额外的数据库成本和备份恢复操作的时间。

测试数据库更改

就像应用程序代码一样,通过测试可以获得关于数据库架构更改质量的见解。关于如何对数据库架构进行测试的链接可以在本章末尾找到。

在大多数情况下,为了全面覆盖数据库更改带来的风险,需要进行系统测试,这些测试会在完整部署的应用程序堆栈上执行。这种类型的测试可以覆盖大部分由错误的架构、无效的存储过程、数据库和应用程序代码不匹配引起的风险。

总结

在本章中,您已经学习了如何通过源代码控制管理数据库架构及其更改。您了解了基于迁移和最终状态的更改存储方法,以及如何以安全的方式将它们应用于生产数据库。

此外,您还学习了无架构数据库如何消除传统架构管理的负担。然而,这也意味着在从数据库读取对象的旧版本时,必须应对架构差异。

在下一章中,您将学习持续测试。您不仅将学习测试技术,还将了解在何时应用哪些测试技术,以及测试如何成为 DevOps 的关键组成部分,是持续向最终用户提供价值的关键驱动因素。

问题

在总结时,以下是一些问题,帮助你测试关于本章内容的知识。你可以在附录中的评估部分找到答案:

  1. 对与错:在使用 Entity Framework 时,架构管理是通过基于迁移的支持内建的。

  2. 对与错:在使用基于迁移的架构管理方法时,您不需要在数据库架构中额外的跟踪表。

  3. 对还是错:在使用基于最终状态的架构管理方法时,你的数据库架构中不需要额外的跟踪表。

  4. 完整的并行数据库架构更改方法有哪些好处?(选择多个答案):

    1. 风险几乎降到零。

    2. 你可以在类似生产环境中衡量更改对性能的实际影响。

    3. 并行迁移减少了周期时间。

  5. 对还是错:无模式数据库完全消除了对架构更改的思考需求。

  6. 你可以做出什么样的技术选择来限制对数据库模式的更改影响?

进一步阅读

第八章:持续测试

在前几章中,您了解了用于帮助提高交付变更到生产环境速度的不同技术。如果您已经在日常工作中使用了这些技术,您会很快发现,只有在工作质量足够高的情况下,这才是可能的。如果您的工作质量不够高,您将面临许多停机或问题,最终用户也不会满意。为了取得成功,提升变更的速度和提高工作质量必须同步进行。要识别和提高工作质量,您首先需要了解什么是质量。这就是测试的重要性。测试是报告软件质量的学科。

本章将通过研究如何衡量软件开发的质量来介绍测试主题。接下来,将探讨功能测试的主题。首先,将介绍测试漏斗和金字塔模型。这些模型可用于确定需要哪些类型的测试,以及每种测试需要多少。之后,将逐一讨论不同类型的测试。您将了解它们如何工作、测试内容以及不同类型测试的优缺点。最后一节将集中讨论如何通过流水线生成并收集的所有指标和测试结果,持续报告团队工作的质量,甚至防止低质量的变更传播到用户。所有这些都将帮助您保持软件的高质量,并使您能够自信地快速且频繁地交付该软件。

本章将涵盖以下主题:

  • 定义质量

  • 了解测试类型

  • 执行功能测试

  • 执行非功能性测试

  • 维护质量

技术要求

为了实验本章中描述的技术,您可能需要以下一种或多种工具:

  • 一个可以访问构建和发布流水线以及仪表板的 Azure DevOps 项目

  • Visual Studio 2019

  • Azure DevOps 的 Basic + Test Plans 许可证

  • 一个 SonarCloud 订阅

所有这些工具都是免费的,或者可以在有限的试用期内免费获得。

定义质量

第一章中讨论的 DevOps 思维模式的一个主要目标是增加向最终用户传递价值的流动。为了实现这一目标,软件必须频繁部署,甚至可能每天多次部署。要使频繁部署成为可能,两个因素至关重要:自动化和质量。自动化在前几章中已被广泛讨论,因此现在是时候转向质量主题了。

一旦自动化构建和发布管道就绪,且更改开始以越来越快的速度流向生产环境,就该开始衡量这些更改的质量了。更重要的是,这让我们能够终止质量不合格的更改。什么算是质量足够的标准因项目而异。例如,在开发游戏时,一些 bug 可能会让用户感到烦恼,但不会造成太大问题;然而,在开发飞机或医疗软件时,一个 bug 可能会造成生命损失。在软件开发中,更高的质量通常意味着更高的成本和/或更多的时间。因此,我们可以交付的特性数量和可以保证的质量之间存在权衡。每个项目都有一个不同的最佳权衡点。

在衡量质量之前,首先要确定如何衡量软件的质量。监控软件质量的一种常见方法是收集一个或多个度量指标。例如,可以决定每周收集五个测量值。随着时间的推移,将这些度量指标绘制成图表,可以洞察软件质量的演变情况。其示例如下图所示:

接下来的章节讨论了几个度量指标的示例。

质量度量指标

度量指标是用数字表示某些事物的手段。在软件开发中,度量指标通常用于表示某个质量方面,这一方面本身可能难以量化。例如,软件质量本身可能很难描述,质量变化的情况就更难表述。因此,我们通常会捕捉一些数字,组合起来可以揭示软件质量的情况。

重要的是要意识到,度量指标是一个很好的工具,但使用时必须谨慎。一方面,可能有比正在测量的度量指标更多的因素影响着(感知的)软件质量。此外,一旦人们知道某个特定的度量指标被记录下来,他们就可以优化自己的工作,以提高或降低该指标。虽然这可能在报告中显示出期望的数字,但这不一定意味着软件质量真的在改善。为了应对这个问题,通常会记录多个度量指标。

一个著名的例子是在敏捷工作环境中使用故事点速度。记录团队的冲刺速度,以查看团队是否随着时间的推移变得更高效,听起来很有效;然而,如果团队的规模在每个冲刺中都有所不同,那么该度量指标可能会变得无用,因为人员出席情况也会影响速度。此外,团队也可以轻易地通过在每次冲刺时同意将所有估算值乘以一个随机数来伪造该度量指标。虽然这会让每个冲刺的数字都增大,但这并不代表团队的产出能力在增加。

说到衡量软件质量的指标时,客观地衡量编写代码的质量可能是困难的。开发人员通常对什么构成良好代码有不同的看法,讨论越多,团队越难达成共识;然而,当将注意力转向使用这些代码的结果时,识别有助于提供代码质量洞察的指标就变得更容易了。

例如,以下是一些示例:

  • 集成构建失败的百分比:如果代码无法编译或未通过自动化测试,这表明代码质量不足。由于每当推送新更改时,构建管道可以自动执行测试,因此它们是评估代码质量的绝佳工具。此外,由于测试可以在我们将更改部署到生产环境之前执行并收集结果,测试结果可以用于在将更改部署到发布管道的下一个阶段之前取消该更改。通过这种方式,只有足够质量的更改才能传播到下一阶段。

  • 通过自动化测试覆盖的代码百分比:如果更多的代码通过单元测试进行了测试,这将提高软件的质量。

  • 变更失败率:这是新版本代码部署后导致问题的百分比。例如,在部署新版本应用程序后,Web 服务器出现内存不足的情况。

  • 未计划工作的量:在任何一段时间内必须执行的未计划工作量可以是质量的一个重要指标。如果团队正在开发并且运营一款 SaaS 产品,那么就会有时间用于运营工作。这通常被称为未计划工作。未计划工作的量可以反映计划工作质量。如果未计划工作的量增加,那么这可能表明质量下降了。未计划工作的例子包括现场事故、处理警报、紧急修复和补丁。

  • 用户报告的缺陷数量:如果用户报告的缺陷数量增加,这可能是质量下降的一个信号。通常,这是一个滞后指标,所以一旦这个数字开始增加,质量可能已经下降了一段时间。当然,导致这个数字增加的原因有很多:新的操作系统,用户数量的增加,或者用户期望的变化。

  • 已知问题的数量:即使新发现或报告的缺陷非常少,如果缺陷从未被修复,而已知问题的数量持续缓慢增加,那么软件的质量将随着时间的推移逐渐下降。

  • 技术债务的量:技术债务是一个术语,用于描述为了短期利益(如快速交付代码)而牺牲代码质量的后果。技术债务将在下一节中详细讨论。

测试是一项旨在发现和报告软件质量的活动。测试结果(对质量的洞察)可用于决定是否允许或取消某个变更进入下一个发布阶段。

在下一节中,将探讨质量的另一个维度:代码库中的技术债务量。

技术债务

技术债务是一个术语,用于描述为实现其他目标而牺牲代码质量所带来的未来成本。例如,为了加速新特性的交付,开发人员可能会选择迅速扩展现有类,添加一些新方法来实现该特性。如果结果类不符合面向对象设计原则或变得过于庞大,这将导致该类难以理解、维护或后续修改。术语“债务”意味着有某种东西(时间、质量、关注或工作)欠缺于解决方案。只要这笔债务没有还清,你就需要支付利息,这种利息表现为所有其他工作都会稍微变慢。

技术债务可以采取多种形式,以下是一些示例:

  • 没有任何单元测试覆盖的代码,无法使用原来创建该代码时的测试来验证该代码的实现变更

  • 没有使用有意义的变量名和方法名以自解释的方式编写的代码

  • 不符合编码原则的代码,例如 KISS、YAGNI、DRY 和/或 SOLID

  • 由于有过多的变量和方法而导致过于复杂的类

  • 由于包含过多语句(尤其是流程控制语句)而过于复杂的方法

  • 通过应用程序不同部分存在循环依赖的类或命名空间

  • 不符合应用程序架构设计的类

技术债务有许多形式,要全面管理所有这些形式可能会令人生畏。因此,有许多工具可以自动测量代码库中的技术债务并进行报告。关于此类工具的讨论将在维护质量部分进行。

虽然技术债务通常被认为是坏事,但有时故意产生技术债务也是有其合理原因的。就像普通债务一样,管理债务的规模以及确保能够支付利息和偿还债务是非常重要的。

公司通常在初创阶段承担技术债务,在这一阶段,快速创建一个可用的解决方案往往是一个有意识的决策。虽然这个初始版本用于验证商业方案并吸引资金,但开发人员可以通过重新实现或重构(部分)应用程序来偿还这笔债务。

另一个原因可能是市场机会或一个已经提前几个月计划的重大业务事件。为了按时交付并达成最后期限,承担一些技术债务可能是值得的。

然而,永远不偿还债务并且只是在时间上不断增加债务,最终会增加每次开发人员需要进行更改时所支付的隐性“利息”。结果是任何更改都会比上一次更花时间。如果这种情况开始发生,那么在某个时刻任何更改都将变得不再值得,因为成本总是超过收益。此时,一个项目或产品就会失败。

在谈论测试时,理解存在哪些测试类型非常重要。下一部分将深入讨论这一主题。

理解测试类型

在传统的软件开发中,测试通常是在开发完成应用程序被声明为开发完成功能集被冻结或类似声明之后进行的。宣布开发完成后,开始执行测试,通常会经历一段长时间的反复测试和修复 bug 的过程。结果往往是在上线后仍然发现许多 bug。

向左移动(Shifting left)是一种测试原则,表示自动化测试应当在开发过程中尽早进行。如果将与软件开发相关的所有活动画成一条从开始到发布的时间线,那么向左移动意味着将自动化测试活动移得更靠近开始阶段。

为此,识别出了许多不同类型的测试,例如单元测试、集成测试和系统测试。不同的来源可能建议不同类型的测试,但这些是一些更为知名的类型。不论测试的具体名称是什么,当我们从高层次抽象来看待这些测试时,它们通常被分为以下两类:

  • 功能测试:功能测试的目的是测试应用程序是否实际实现了所需的功能。

  • 非功能性测试:非功能性测试用于验证应用程序的其他所需属性是否得到实现,并且不包含不希望出现的属性。

这些类型进一步细分为更小的子类别,如下图所示:

以下三个部分简要回顾了不同类型的功能性和非功能性测试。这是为了便于后续讨论在不同情境下选择哪种测试类型以及你的项目可能需要多少种类型的测试。

自动化功能测试的类型

在谈论自动化功能测试时,最常用的三种类型是单元测试、集成测试和系统测试。这些测试类型可以通过多个维度进行比较:创建测试所需的时间、执行测试所需的时间以及它们所测试的范围:

  • 单元测试:单元测试是编写最快的,而且执行速度非常快,通常在不到一毫秒的时间内完成。它们测试的是应用程序中最小的范围,通常是单个类或方法。这意味着一旦编写完成,几乎永远不需要更改单元测试。对于许多系统而言,更可能的是删除一个测试,而不是修改它。

  • 集成测试:集成测试的编写时间较长,因为它们涉及多个单元,这些单元需要共同设置以便协作。尽管如此,这些测试的执行速度仍然应该很快,平均从不到一秒到十几秒不等。集成测试的测试范围较大,这意味着它们能覆盖更多的代码,并且更有可能发现由于更改引入的缺陷。

  • 系统测试:系统测试测试的是一个完整的、正在运行的应用程序。根据应用程序的类型,这些通常是 API 测试或自动化 UI 测试。这些测试的创建需要很长时间,因为它们依赖于已部署的系统运行,并且通常需要在数据库或其他持久存储中设置初始状态。测试的执行时间也很长,有时每个测试需要几分钟。它们也不如单元测试和集成测试可靠,并且比单元测试和集成测试更脆弱。即使是接口的小改动也可能导致一系列测试失败。另一方面,系统测试可以发现单元测试和集成测试无法发现的错误,因为它们实际上是在测试运行中的系统。

请注意,测试中拥有大的测试范围既有优点也有缺点。优点是它能够发现很多错误。缺点是,当一个具有非常大测试范围的测试失败时,它只能提供有限的信息,帮助判断出了什么问题。这样的测试失败通常需要比范围较小的测试失败更多的调查。

以下各节将更详细地探讨每种类型的测试。

单元测试

单元测试用于测试一个单独的单元。对于面向对象编程语言来说,这意味着每个应用程序类都会有一个对应的测试类。为了实现全面的测试覆盖,测试类将为每个公共方法提供一个或多个测试。

单元测试应该运行得非常快——平均来说,在几毫秒以内。为了实现这一点,每个类在没有依赖项的情况下被实例化。这是通过使用接口实现的,其中类依赖于接口,而不是直接依赖于其他类。对于测试,依赖项会被模拟类替换,如下图所示。左侧显示的是运行时配置,右侧显示的是测试期间的配置:

一个模拟类实现了相同的接口,但默认没有关联任何行为。可以在每个测试中为其设置特定行为。模拟还可以用来验证某个依赖项上的特定操作或函数是否被调用。例如,考虑以下 C#类:

public class WorkDivider
{
    private readonly IMessageSender _messageSender;

    public WorkDivider(IMessageSender messageSender)
    {
        _messageSender = messageSender;
    }

    public void DivideWork(IEnumerable<WorkOrder> workOrders)
    {
        foreach(var workOrder in workOrders)
        {
            _messageSender.SendMessage(workOrder.GetMessage());
        }
    }
}

为了在自动化测试中实例化这个类,需要提供一个IMessageSender接口的实现。为了绕过这个依赖关系,可以使用像 Moq 这样的模拟框架来测试WorkDivider,如下所示。在这些示例中,使用NUnit作为测试框架:

[TestFixture]
public class WorkDividerTest
{
    private Mock<IMessageSender> _messageSender;
    private WorkDivider _subject;

    [SetUp]
    public void SetUp()
    {
        _messageSender = new Mock<IMessageSender>();
        _subject = new WorkDivider(_messageSender.Object);
    }

    [Test]
    public void WhenSendingAnEnumerableOfWorkingOrders_EverOrderIsSendToTheMessageSender()
    {
        var workOrder = new WorkOrder();

        _subject.DivideWork(new[] { workOrder });

        _messageSender.Verify(x => x.SendMessage(workOrder), Times.Once);
    }
}

这意味着,无法为与其他系统(例如数据库、缓存或服务总线)交互的类编写单元测试。为了确保不会导致无法使用测试覆盖应用程序的大片部分,常见的做法是将与其他系统的集成隔离到单独的类中。这些类包含与远程系统的交互,但没有业务逻辑且代码尽可能少。然后接受这些类不需要单元测试覆盖。为此常用的设计模式有外观模式、适配器模式和仓库模式。

本章末尾包含了更详细的单元测试编写指南以及如何模拟类的链接。

单元测试应准备好在每个开发者克隆应用程序代码库时在其计算机上运行。它们不应要求在本地计算机上进行任何特殊配置或设置,应该可以直接使用。这样,所有与代码库一起工作的人都可以在本地计算机上运行单元测试。因此,开发者在推送更改到中央代码库之前,最好在自己的计算机上运行所有单元测试。

除了本地验证步骤,单元测试还应作为持续集成构建的一部分。稍后你将在流水线中执行测试部分学习如何操作。只要拉取请求中有失败的单元测试,最好不要将更改合并到主分支。这甚至可以通过使用 Git 仓库分支策略来强制执行,在第二章中已经讨论过,一切从源代码控制开始

在下一节中,关于自动化功能测试的讨论将继续涉及集成测试。

集成测试

集成测试用于测试一组组件是否能够正确协同工作。这些测试有两个目的:

  • 增加那些未被单元测试覆盖的应用程序部分的测试覆盖率——例如,与其他系统交互的类

  • 解决单元测试中没有涉及的风险,并处理与类交互的情况

理解集成风险可能很困难,因为一旦所有部分都按预期工作,整个系统似乎就能正常工作。为了更好地理解这一风险,假设两个组件一起工作,负责气候控制。其中一个组件测量温度,单位是摄氏度,另一个组件根据这个温度做出反应,期望输入的单位是华氏度。很快就会发现,虽然两个组件都按预期工作,互相交换数据并采取相应的行动,但它们的结合不会产生预期的结果。

集成测试,尤其是那些与其他系统交互的测试,不仅运行时间比单元测试长,而且通常需要更多的配置或设置。这甚至可能包括一些敏感信息,如用户名、密码或证书。为了处理这种配置,可以在测试文件旁边创建一个设置文件,测试执行前从该文件加载设置。每个开发人员都可以创建自己的一份文件副本,并使用自己的配置运行测试。

继续上一节的例子,假设实现了IMessageSender接口的MessageSender类需要一个连接字符串来完成工作。MessageSender的测试类可能如下所示:

[TestFixture]
public class MessageSenderTest
{
    private MessageSender _messageSender;

    [SetUp]
    public void SetUp()
    {
        var connectionString = TestContext.Parameters["MessageSenderConnectionString"];
        _messageSender = new MessageSender(connectionString);
    }
}

构建MessageSender类所需的connectionString是从TestContext中的Parameters对象中获取的。这是NUnit框架通过.runsettings文件提供设置的方式。具体实现方式可能因测试框架不同而有所不同。一个示例.runsettings文件如下所示:

<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
 <TestRunParameters>
 <Parameter name="MessageSenderConnectionString" value="secret-value" />
 </TestRunParameters>
</RunSettings>

将设置移到单独的文件中,确保敏感信息不会被提交到源代码管理系统中。在在流水线中执行测试部分,你将学习如何为在流水线中运行测试构建一个.runsettings文件。

这是因为集成测试应该尽可能也作为持续集成构建的一部分。然而,这样做有可能使得持续集成构建变得过于缓慢。为了解决这个问题,可以实施以下解决方案之一:

  • 集成测试在一个单独的构建中执行,这个构建是与持续集成构建并行触发的。这样,持续集成构建的时间保持较短,同时集成测试仍然会持续执行,开发人员能迅速获得反馈。

  • 集成测试通常在流水线的后期执行,接近软件发布阶段——例如,在部署到测试环境之前或之后。

第一种方法的缺点是,以这种方式执行集成测试意味着测试将不再作为代码合并到master之前的质量门控。它们当然仍然会作为质量报告机制继续工作。这意味着,尽管错误可能会被合并,但它们会被构建过程检测并报告。

第二种方法没有这种风险,因为执行测试仍然是从源代码管理到生产的管道的一部分;然而,在这种方法中,如果不是每个构建都至少进入发布管道的某一部分,测试的执行可能会被推迟到稍后的时刻。这意味着缺陷可能会在稍后才暴露,延长了检测和修复问题之间的时间。

无论采用哪种方法,集成测试失败将不再阻止合并更改,因此你必须找到另一种方法,确保开发人员负责修复导致测试失败的缺陷。

随着系统测试的出现,这些权衡变得更加明显,系统测试通常需要很长时间,因此不可能将其纳入持续集成构建中。

系统测试

第三种也是最后一种类型的自动化功能测试是系统测试。这些测试旨在针对一个完整组装并运行的应用程序进行。系统测试根据应用程序的类型有两种形式:API 测试或 UI 测试。系统测试的执行时间可能很长,尤其是有复杂测试数据设置的长时间测试,往往需要超过一分钟。

你可能会遇到一种叫做编码 UI 测试的东西。这是微软现已弃用的用于编写 UI 测试的解决方案。这些测试可以从 Azure Pipelines 中执行。幸运的是,有许多替代方案,微软在其弃用消息中提到,详细内容请参见devblogs.microsoft.com/devops/changes-to-coded-ui-test-in-visual-studio-2019

系统测试是在运行的应用程序上执行的,这意味着在执行之前需要进行配置和设置。应用程序需要在受控环境中运行,所有与数据存储的集成需要完全正常工作。与其他系统的集成要么需要处于运行状态,要么用替代的模拟替换,以确保所有与这些系统集成的操作能正常进行。

这些条件使得开发人员在修改应用程序时执行这些测试的可能性较小。通常只有在创建新测试或更改现有测试时,他们才可能这样做。然而,即便如此,他们也许并不是在本地运行的应用程序版本上执行这些测试,而是在已经部署到测试环境中的版本上执行。这不一定是好事,但往往是大多数团队中的现实情况。

可惜的是,如何创建 API 或 UI 测试的介绍超出了本书的范围。市场上有许多产品可供选择,哪个最好使用会根据项目不同而有所不同。

在执行系统测试作为流水线的一部分时,它们通常是在代码部署到至少一个环境之后进行的。这通常是测试环境。这意味着系统测试是从源代码变更到生产环境部署的关键路径的一部分。如果这个路径变得太长,它们也可以从流水线中移除,改为按计划执行——例如,每晚执行一次。与集成测试一样,这样可以加速流水线,但也就失去了将系统测试作为质量门控的机会。

系统测试,尤其是 UI 测试,通常比较脆弱,在发生微小变化后可能会意外停止工作。由于这个原因,建议尽量减少此类测试的数量;但是,请记住,这些测试能够捕捉到特定的错误,例如配置错误、其他运行时错误、数据库与应用程序的不匹配,或者一系列导致错误状态的操作。

除了自动化功能测试,还有许多 DevOps 项目中也有价值的手动功能测试。接下来将讨论这些测试。

手动功能测试的类型

虽然自动化测试是快速且频繁获得开发反馈的好工具,但仍然有一些内容需要手动测试。虽然自动化重复性测试是持续监控质量的最佳方式,但有些事情仍然需要人工检查。

手动测试是向左 shift 的关键点 每当任何类型的测试或验证被向左 shift 时,这意味着它是在手动测试执行之前进行的。这样做的好处是,所有这些自动化活动都会增加我们对正在测试的应用程序版本的信心,从而提高该版本通过手动测试的可能性。换句话说,当手动测试开始时,任何新问题被发现的可能性应该非常低。

手动测试有两种类型:

  • 脚本化测试

  • 探索性测试

以下部分将讨论这两种类型的测试。

脚本化测试

脚本化测试是一种技术,用于在确保全面覆盖所有相关测试用例的同时,最大限度地减少测试执行的时间。这是通过将测试分为两个不同的阶段来完成的:测试准备和测试执行。测试准备与待测试功能的开发并行进行,甚至在开发开始之前就可以进行。在测试准备阶段,功能被分析并识别出正式的测试用例。

一旦确定了必须执行的测试用例,就会编写手动测试脚本,描述测试执行阶段需要执行的每一步。这些脚本的设计方式使得它们易于遵循,并且没有任何疑问或不明确的地方。它们也被编写成尽可能减少执行步骤的数量。虽然这可能需要更多的准备时间,但所有这些都是为了确保在测试执行时花费尽可能少的时间。

对于测试分析的更深入讨论,以及如何识别测试用例,超出了本书的范围。虽然你负责创建测试用例,Azure DevOps 会在这方面为你提供支持。使用测试计划服务,你可以创建测试计划并记录其中的测试用例,以便稍后快速执行。

要创建一个新的测试计划,请执行以下步骤:

  1. 打开 Azure 测试计划菜单:

  1. 在这个菜单中,点击测试计划。这里你将看到当前拥有的所有测试计划的概览。

  2. 点击“新建测试计划”按钮以开始创建新的测试计划。这将打开一个新的对话框,如下图所示:

  1. 给测试计划起一个有意义的名称,例如,能够说明测试计划用途的名称。

  2. 将测试计划链接到正确的产品区域路径。

  3. 选择与此测试相关的正确迭代或冲刺。

  4. 点击“创建”以完成测试计划的创建。这将自动打开该测试计划,如下所示:

一个测试计划可以分为多个测试套件,而每个测试套件又可以再次分割。实际上,测试套件对测试的作用就像文件夹对文件的作用一样。通过点击在测试套件上悬停时出现的省略号按钮,可以管理这些套件。这在之前的截图中有所展示。

创建测试计划后,就该向计划中添加一个或多个测试用例了。为此,确保打开测试套件的定义标签页,并点击“新建测试用例”按钮。会弹出一个新的窗口:

在这里,可以定义测试步骤和预期结果。要定义一个新的测试用例,请执行以下步骤:

  1. 输入测试用例的标题。

  2. 在对话框中,输入一个或多个动作及预期结果,详细描述测试用例。

  3. 一旦测试用例完全描述完成,点击保存并关闭按钮以保存测试用例,并返回到之前的屏幕,在那里可以管理测试套件。

一旦准备工作完成并且功能准备好进行测试,所有的测试就会执行。由于所有测试都已经详细脚本化,因此这可以快速且有效地完成。甚至可能会有开发人员、业务分析师或公司其他部门的人协助执行测试。这意味着测试执行本身将非常迅速。

要开始执行测试套件或计划,请执行以下步骤:

  1. 导航到执行标签:

  1. 选择一个或多个测试用例。

  2. 在右上角选择一个运行选项。

当选择对网页应用程序运行测试时,将打开一个带有测试执行器的新浏览器窗口。可以使用此测试执行器逐个测试用例地执行所有步骤,并跟踪所有的成功和错误,如下所示:

每个测试步骤后的勾选框或叉号可以用来跟踪单独步骤的结果。如果某个步骤被标记为错误,可以添加带有缺陷的评论。要将测试用例标记为通过或标记,右上角的蓝色下拉菜单可以用来标记结果。一旦选择了测试结果,测试执行器会自动跳到下一个测试。一旦所有测试都执行完毕,可以通过左上角的保存并关闭按钮保存结果。

要查看测试运行的结果,导航到测试计划,然后选择运行,查看如下仪表板:

在这里,您可以选择要查看其结果的运行,从而快速概览测试结果。在第二个标签页“测试结果”中,可以查看所有测试用例的列表,以及它们是否通过。

拥有详细脚本的一个主要好处是相同的测试可以执行多次,从而减少每次执行的成本。如果一个测试计划被执行多次,所有的执行历史都会被保存,并且可以通过前面截图中显示的视图访问。这在手动测试作为回归测试的一部分时非常有用;然而,一旦成为这种情况,通常将测试自动化为系统测试会更加有益,如果可能的话。

可以多次执行相同的测试,但对于不同的配置。在开发网页应用时,通常会通过不同的浏览器进行测试。对于桌面应用程序,这可能用于测试不同的操作系统。有关配置的详细信息,请参考微软文档:docs.microsoft.com/en-us/azure/devops/test/mtm/test-configurations-specifying-test-platforms?view=azure-devops

下一部分将讨论功能测试的最终形式,即探索性测试。

探索性测试

编写和执行详细的测试脚本可能会占用测试工程师和测试执行者大量的时间,因此这些测试通常会被自动化。一旦它们被自动化,它们将属于系统测试和自动化 UI 测试的范畴。

这并不一定意味着手动测试没有价值或没有好的投资回报。只是有些事情是人眼能发现的,而计算机无法察觉的,比如那些不够用户友好的界面、错位的界面元素,或者那些没有完全显示而被其他元素遮住的文本行或图片。

为了在不花费大量时间编写详细测试脚本的情况下捕捉这些错误,探索性测试可能是一个解决方案。在这种方法中,测试人员打开应用程序并开始调查他们认为在即将发布的版本中包含最多风险的部分。在探索应用程序的过程中,测试人员会记录他们访问过的部分以及执行过的测试用例。同时,测试人员还会记录他们发现的新风险或尚未执行的测试用例。通过这种方式,他们在工作过程中创建了一个已覆盖和未覆盖的测试用例列表。这还允许测试人员始终专注于最重要的风险和测试用例。一旦探索性测试结束,测试人员可以报告哪些应用程序区域和测试用例已覆盖,哪些未覆盖,哪些风险仍然没有被探索到。这个报告对于产品经理来说是非常有价值的输入,帮助他们决定是否继续进行发布。

一个常见的误解是,探索性测试意味着测试人员只是随便点击看应用程序是否正常工作。事实并非如此,前面的段落已经表明,探索性测试是一项高度结构化的活动,需要实践。如果执行得当,测试准备和测试执行在探索性测试过程中是交织在一起的。

探索性测试是当时间有限或无法提前知道可用测试时间时的一个非常好的工具。探索性测试可能会产生需要记录为缺陷的发现。接下来将介绍如何进行此操作。

报告手动测试结果

测试的一个重要活动是报告发现的任何缺陷或其他问题。这通常是一项繁琐且耗时的工作。你必须再次尝试重现问题,尽量记住问题是如何表现出来的,并将所有步骤写下来。然后,必须描述期望的结果和不期望的结果,拍摄截图,并将一切内容插入到缺陷跟踪器或工作管理工具中,例如 Azure DevOps。

为了简化这个过程,Azure DevOps 提供了一个Test & Feedback扩展。这个扩展提供了简单的按钮,用于录制截图或视频,并通过文本或图形进行标注。一旦发现并通过录制或截图记录了问题,它可以自动提交到 Azure DevOps 看板中。

这个扩展可以从 Azure DevOps 市场免费获得,并且支持在 Firefox 和 Chrome 浏览器中运行。当前正在开发对 Edge 浏览器的支持。扩展的链接将在本章末尾提供。

Test & Feedback 扩展可以在执行脚本化测试和进行探索性测试时使用。

这部分结束了对不同类型功能测试的讨论。下一部分将帮助你决定在项目中使用哪种类型的测试。

决定需要哪些类型功能测试的策略

面对如此多不同类型的测试,哪种测试最适合你的项目?考虑到测试种类繁多且性质各异,答案正如你所料:混合使用它们,因为它们各有不同的特点。

下图显示了不同类型的测试执行时间与它们提供的质量信心之间的关系。图中表明,尽管成功完成的手动测试具有识别缺陷的最高概率,但它们执行的时间最长。对于自动化测试,成千上万的单元测试通常可以在几分钟内完成,而十到一百个系统测试可能需要超过 30 分钟:

从这种权衡来看,通常更倾向于优先使用单元测试而非集成测试,优先使用集成测试而非系统测试,任何类型的手动测试都优先于自动化测试。

如果单元测试和集成测试的质量提高,那么这条线将进一步向左上方攀升。高质量的软件架构还将有助于减少对系统测试和集成测试的需求,并增强单元测试带来的保证。这两者都能使快速且频繁执行的自动化测试的积极效果更加显著。

理解这种权衡关系也有助于理解两种可以用于决定测试策略的模型:测试金字塔和测试奖杯,接下来的两部分将讨论这两种模型。

测试金字塔

在许多较旧的项目中,自动化功能测试并不多。通常,这些测试执行较慢,测试范围大,难以维护,且经常失败,且没有明确的原因。这些测试所提供的价值通常非常有限。为了弥补缺乏良好的自动化测试,通常会进行大量的手动测试,在新版本部署之前进行完整的回归测试。这些自动化测试非常耗时且很少执行。开发人员没有快速反馈,缺陷通常在晚些时候才被发现。在这种情况下,很难实践 DevOps,因为 DevOps 的重点是快速且频繁地创建新版本。

这样的应用程序测试组通常被称为冰淇淋锥形测试:许多手动测试和少量自动化测试,其中只有少数是单元测试。冰淇淋锥形测试是一种反模式,但常常出现在较旧或长期运行的项目中:

为了应对这一点,提出了另一种相对立的模型:测试金字塔。该模型主张拥有大量单元测试,可以在几分钟内反馈应用程序的质量,快速指出大部分错误。在此之上,其他类型的较慢测试被分层,用于捕捉前面层次无法捕捉到的错误。使用这种方法,测试覆盖率和测试时长之间有一个很好的权衡。

请注意,测试金字塔并不倡导分层方法。不要先构建一层单元测试,只有当所有单元测试完成时才开始进行集成测试。相反,它提倡比例:你应该在单元测试、集成测试和系统测试之间保持健康的比例。

关于不同类型测试之间最佳比例的一般建议很难给出。但在大多数项目中,金字塔中每一层的比例为 1:5-15 是合理的。

测试奖杯

虽然测试金字塔是一个广为人知且常用的分类测试方法,用于决定创建哪种类型的测试,但这种方法也受到了批评。虽然在 DevOps 团队中,远离手动测试和系统测试被普遍认为是必要的,但对单元测试的重视并非普遍接受。有些人反对测试金字塔暗示要创建更多的单元测试而非集成测试这一事实。

反对这种做法的原因如下:

  • 单元测试往往与其测试的实现紧密相关。 回顾在“单元测试”部分中对WorkDivider的测试,可以看出它依赖于了解DivideWork方法的实现。这个测试实际上验证的是实际实现:对SendMessage()的调用。许多单元测试都有这种特征,因此,增加大量单元测试会增加更改类级设计实现的难度。

  • 单元测试的变化率通常比集成测试更高。 单元测试类与它们测试的类紧密关联。这意味着,如果被测试的类被替换,这些单元测试也将失去所有价值。因此,有人认为,集成测试可能具有更高的投资回报率。

  • 真正的价值来自于集成组件,而不是单个组件。 即使所有单元在独立运行时都能正常工作,系统可能并不会提供任何价值。软件的真正价值只有在集成并准备运行后才会体现出来。由于测试应该验证价值交付,因此有人认为,重点应该放在编写集成测试而非单元测试上。

为了应对这些反对意见,Kent C. Dodds提出了测试奖杯模型。这个模型采纳了测试金字塔的理念,提倡尽量减少手动和系统测试,但与金字塔不同的是,它并不强调单元测试的重要性,而是更注重集成测试。测试奖杯这一名字来源于,如果将其绘制出来,会形成一个类似奖杯的形状。

不幸的是,并没有“银弹”解决方案,最好的建议是了解三种模型及其背后的推理,并将适当的推理应用到当前情况中。对于测试而言,并没有适用于所有情况的最佳解决方案。

非功能性测试的类型

功能测试主要关注验证应用程序展示的行为是否是预期的行为;然而,在应用程序开发中存在更多的风险:应用程序是否足够快速地执行操作,随着更多用户同时使用系统时,性能是否会下降,以及系统是否易于终端用户使用。验证这些系统属性的测试称为非功能性测试。

有许多类型的非功能性测试,但在 DevOps 场景中,以下三种尤为重要:

  • 性能测试

  • 负载测试

  • 可用性测试

让我们一一回顾它们。

性能测试

性能测试是用来确定在给定资源的情况下,应用程序执行某个操作的速度。性能测试通常使用专门的工具,并在完整的系统上执行。如果用于自动化 API 或 UI 测试的工具记录了测试的持续时间,那么这些测试的持续时间也可以作为性能结果。

为了比较多次测试的结果,必须确保影响性能的所有因素在测试之间保持一致。测试主体和测试执行者的虚拟机设置应保持相同。应用程序配置应保持不变,集成点应尽可能保持在相同状态——例如,应该在每次性能测试前从备份恢复相同的数据库,而不是重复使用同一个数据库。这样可以确保结果具有可比性。

尽管性能测试和负载测试常常被混淆,但它们是两种不同的测试类型。

负载测试

负载测试用于衡量系统在崩溃之前能承受多少负载。这类测试有时也被称为压力测试。与性能测试不同,负载测试会并行执行多个请求。衡量的指标是所有请求的平均性能,同时逐渐增加请求的数量。在大多数情况下,这将确定一个临界点,即每秒请求的特定数量,超出此数量时性能会突然下降。这是系统能够最大承载的每秒请求数。执行负载测试时,收集在最大请求数增加过程中所有请求的平均性能,通常会得到如下图表:

这张图表展示了为何了解应用程序的崩溃点很重要:过高的负载可能会意外地让系统崩溃,因为响应时间变化的突发性质。了解这个临界点可以让操作人员在生产环境中到达此点之前采取行动。

本章末尾有一个链接,指向一个微软在线实验室,供开发人员练习负载测试。

可用性测试

另一种重要的测试类型是可用性测试。虽然其他类型的测试侧重于验证实现是否符合产品团队的预期行为,但可用性测试则侧重于验证用户的期望是否得到了满足。这意味着测试范围更广,这些测试可以识别笨拙的用户界面,并帮助发现不清晰的文本或误解的用户请求。

可用性测试是通过让用户在一个或多个任务上使用最终的应用程序,并观察或询问他们与应用程序互动的方式来进行的。结果通常比“通过”或“未通过”更为详细,并且结果通常会反馈给产品负责人,以便编写新的用户故事或更改需求。

一个很好的可用性测试技巧是使用功能标志。功能标志使我们能够逐步将新功能暴露给更多用户。这一功能也可以用来首先仅将新功能暴露给一小部分、参与可用性研究的用户。这使得研究人员或产品负责人能够密切观察这些用户使用新功能的情况,而其他用户则无法访问该功能。

在第四章《持续部署》中曾讨论过功能标志作为渐进式曝光的一种策略。新功能的渐进式曝光本身就是一种可用性或用户接受度测试。

这种方法可以扩展到执行 A/B 测试。在这类测试中,一半的用户会接触到新功能,而另一半则不会。然后会收集关于所有用户的度量数据,看看新功能是否带来了预测的好处——例如,用户是否每天使用应用程序的时间增加了。这个话题将在第十一章《收集用户反馈》中进一步讨论,介绍如何收集用户反馈。

这样做会将可用性测试推向发布过程的后期。也可以通过在最终应用程序之前,使用原型来执行可用性测试,从而将测试提前到左侧。

这部分内容结束了对不同类型测试的讨论。在接下来的章节中,将使用度量标准和测试来自动衡量质量并实现质量控制。

在管道中执行测试

开发人员在提交代码合并请求之前,应在本地机器上执行测试。这样,他们可以确保自己所做的更改没有破坏代码中任何先前的行为。理论上,这可以保证所有合并到主分支的代码都能成功编译并通过所有测试。但在实践中,存在许多原因导致这种情况并不总是成立。一些原因如下:

  • 有些测试可能无法在本地运行。它们依赖于机密的配置值,或者配置为在一个完全配置的系统上运行。系统测试通常会遇到这两种情况中的一种或两种。在许多情况下,无法从本地系统运行系统测试。这些情况并不全是不可取的或无法克服的,但通常确实如此。

  • 开发人员毕竟是人类。他们可能会忘记在做最后一个小调整后,在本地机器上运行测试,或者他们可能确信自己的更改并没有破坏现有的行为。尤其是在紧急修复 bug 时,可能会因为追求速度而跳过测试。

为了防止这些情况导致未完全测试的代码通过流水线传播,建议所有测试也在流水线内执行。接下来的部分将展示如何为单元测试、集成测试以及通过其他系统运行的测试执行此操作。首先是单元测试。

运行单元测试

对于许多语言,Azure DevOps 内置了从流水线运行单元测试的支持。可以为 C#、TypeScript、Python、Maven、C++、Go 等多种语言执行单元测试。

对于其中一些语言,提供了一个现成的任务。例如,用 C# 编写的测试。在执行 .NET 测试(例如 C#)时,测试结果会自动以构建代理能够理解的 XML 格式存储。

这允许流水线代理解释测试结果并在构建结果中可视化它们,如下所示:

对于某些语言,可能需要执行多个任务。例如,用 TypeScript 编写的测试通常通过 NPM 命令执行。以下 YAML 可用于执行此操作:

- task: Npm@0
  displayName: 'Run unit tests - npm run tests'
  inputs:
    cwd: src
    command: run
    arguments: test

这将执行在 package.json 中指定的自定义 NPM 命令。不幸的是,这不会以流水线代理可以理解的格式存储测试结果。为了将结果转换为正确的格式,需要另一个任务:

- task: PublishTestResults@2
  displayName: 'Publish Test Results'
  inputs:
    testResultsFiles: '**\reportTests\TEST-*.xml'
    mergeTestResults: true
  condition: succeededOrFailed()

测试结果是否直接可用或需要转换,因编程语言而异。除了发布测试结果外,建议还要收集测试覆盖率结果。

记录单元测试代码覆盖率

最佳实践是,不仅在构建过程中运行所有单元测试,还要确定在任何这些测试中执行的代码基的百分比。这被称为 单元测试代码覆盖率,它是衡量测试全面性的一项指标。构建还可以配置为发布单元测试所实现的代码覆盖率。

要配置构建以发布 .NET Core 单元测试的测试覆盖率,必须执行以下步骤:

  1. 将 NuGet 包 coverlet.msbuild 安装到单元测试项目中。

  2. 使用 .NET Core 任务执行测试,并添加两个参数以生成覆盖报告,/p:CollectCoverage=true/p:CoverletOutputFormat=cobertura

  1. 添加发布代码覆盖任务:

    1. 将代码覆盖工具设置为 cobertura

    2. 配置 $(System.DefaultWorkingDirectory)/**/coverage.cobertura.xml 作为汇总文件:

  1. 构建的运行详细信息现在将包含代码覆盖报告。

这是生成详细代码覆盖报告所需的所有配置。生成的报告包含已覆盖和未覆盖的代码块数量以及计算出的覆盖率百分比。这些报告是构建结果页面的一部分。

除了单元测试,集成测试也可以作为管道的一部分运行,并且它们通常伴随着管理配置设置的挑战。

运行集成测试

集成测试通常与单元测试使用相同的框架编写。然而,它们有自己独特的挑战。通常,它们需要一个或多个设置,指定如何与测试中涉及的一个或多个其他组件进行集成。回顾之前讨论过的MessageSender类的集成测试,这是一个典型的例子。

记住,这个测试有一个.runsettings文件,应该指定它应使用的队列的connectionString吗?这个connectionString设置不能被检查到源代码控制中。相反,可以将占位符检查到源代码控制中,然后在管道执行过程中将其替换为实际的密钥。

在这种情况下,这意味着以下pipeline.runsettings文件将被检查到源代码控制中:

<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
    <TestRunParameters>
        <Parameter name="MessageSenderConnectionString" value="#{MessageSenderConnectionString}#" />
 </TestRunParameters>
</RunSettings>

在开始实际的测试执行之前,将运行另一个任务以用实际值替换占位符。正如在第三章《迁移到持续集成》中讨论的那样,这些值可以从变量组、密钥库或管道变量中安全地检索。Azure DevOps 有多个扩展可以用来将占位符替换为实际值。以下是如何执行此操作的 YAML 示例:

  - task: qetza.replacetokens.replacetokens-task.replacetokens@3
    displayName: 'Replace tokens in pipeline.runsettings'
    inputs:
      targetFiles: $(System.DefaultWorkingDirectory)/integrationtests-location/pipeline.runsettings

在执行替换令牌任务后,可以像单元测试一样调用测试运行器。

运行外部测试

除了单元测试和集成测试,你可能还需要使用其他系统执行测试。例如,Azure DevOps 没有内置支持执行负载测试或自动化 UI 测试。对于这些类型的测试,必须从管道中调用其他系统。许多系统可以通过这种方式进行集成。

如何执行此操作因系统而异,但大多数情况下,以下步骤适用:

  1. 配置外部系统中的测试。

  2. 为 Azure DevOps 安装一个扩展,使得可以从管道中调用外部系统的任务。

  3. 创建一个到外部系统的服务连接。

  4. 将任务添加到管道中。

有关配置集成的详细信息,一个好的起点通常是第三方产品供应商的网站。

维护质量

前面的部分详细介绍了用于描述应用程序质量的各种测试和度量标准。考虑到这些内容,现在是时候开始思考可以用来维持高质量甚至提高质量的工具了。

代码审查

保护代码质量的最强大工具之一就是代码评审。当使用 Git 时,开发者需要提交一个 pull request,将自己的更改合并回主分支。Pull request 允许一个或多个其他开发者对所有更改进行审查并发表评论。打开 pull request 的开发者可以查看评论并根据反馈进行相应修改,从而在继续工作时提高更改的质量。

为了让代码评审发挥最大效果,重要的是不要把它看作必须以尽可能少的努力通过的一个门槛。以一种开放的心态,假设每个人都在努力编写高质量的代码,将代码评审视为关于代码质量讨论的起点,这要比把它看作一个不受欢迎的仪式更有成效。重要的是改变视角,不再将代码评审视为软件开发中的一个烦人的仪式,别人会抱怨你的代码,而是将它视为欢迎他人对你的代码提出意见并帮助你写出更高质量代码的机会。

一旦建立了这样的心态,代码评审将成为学习的来源。它们将引发同行之间的讨论,关于如何以最佳方式解决问题:不仅是现在的最佳方式,还有未来的最佳方式,避免技术债务,并确保与待合并代码一起有足够的单元测试和集成测试。代码评审也是一个很好的辅导工具,能让初级开发者获得关于自己工作的反馈。让初级开发者评审高级开发者的代码,甚至可能更有价值。这样,他们可以提出自己尚未了解的问题,这通常会促使他们指出可能随着时间推移成为技术债务的过于复杂的解决方案。

自动收集质量指标

除了手动评审,还有许多工具可以用来自动确定代码库的质量。部分工具内建于 Azure Pipelines 中,但更复杂的功能则来自于独立的代码扫描工具。衡量技术债务的方法有多种数学方法,使用工具来进行这些衡量,不仅能提供应用程序质量的深刻洞察,还能反映出随时间变化的质量变化。

衡量应用程序质量的一个可能工具是 SonarCloud。SonarCloud 是基于 SonarCube 的 SaaS 服务。该工具可以自动扫描代码库,查找可能的 bug、安全风险、技术债务和其他质量指标。这是一个收费的独立服务,能够与 Azure DevOps pipelines 集成。使用 SonarCloud 时,必须创建一个账户并获取项目密钥,以便通过 Azure DevOps 启动 SonarCloud 扫描。

为了调用 SonarCloud,使用的是三个任务的集合,它们是 Azure DevOps 扩展的一部分。安装扩展并配置 SonarCloud 服务连接后,三个任务将被添加到管道中,以设置分析、执行分析,并(可选)在质量下降时使构建失败。第一个任务是唯一需要配置的任务,配置如以下截图所示:

每次执行的构建都会自动由 SonarCloud 扫描其代码,届时将提供关于质量的详细报告。在这些报告的基础上,会生成一个仪表板,提供一些关键质量指标的快速概览:

这是另一个展示质量指标的仪表板视图:

代码扫描工具可以用于报告代码的质量,但也可以充当质量门,如果检测到质量不足,将阻止更改合并或部署到特定环境。

可视化质量

持续衡量应用程序的质量没有意义,除非采取了相应的行动。仪表板可以作为一个强大的工具,持续洞察当前质量水平以及质量随时间的变化。

大多数代码质量工具都具有内置报告选项,它们对于质量保证工程师非常有价值。它们提供了关于应用程序哪些部分质量较高、哪些类型的问题最近发生得更频繁的详细信息。

这种类型的仪表板的缺点是它们可能难以阅读,并且它们不在开发人员执行大部分工作的工具中。因此,创建 Azure DevOps 中的仪表板来报告质量也可能是有益的。以下截图显示了这样一个仪表板的示例:

此仪表板显示当前质量和应用程序代码的概览,以及一些最近的历史记录。在这里,您可以找到以下信息:

  • 最近的更改数量显示在顶部,并显示了最新 SonarCloud 质量门的结果,目前显示为“通过”。

  • 本项目中两个不同构建的结果显示在第二行。

  • 第三行和第四行显示了项目中所有构建和发布的聚合结果。使用符号表示构建和发布的状态:成功、失败或仍在运行。

  • 右侧使用了两个小部件来显示过去 10 个构建中失败的测试百分比及相应的失败测试数量。

  • 最新发布运行的结果按环境显示在下方。

像这样的仪表板可以通过内置的小部件或扩展为每个团队或每个项目创建。在 Azure DevOps 市场中有许多可用的扩展。例如,在前面的仪表板中,使用了团队项目健康状况扩展。

Azure DevOps 仪表板可以配置为每五分钟自动刷新,使其也可以用作墙面板。

质量 gates

衡量、报告甚至可视化质量非常重要且有价值;然而,如果没有人根据所有这些指标采取行动,那对开发团队来说是没有价值的。为了防止这种情况,可以引入自动化质量 gates 或检查。

实现质量 gates 的一种方式是,在每次测试失败、测试覆盖率过低或设置的代码扫描工具阈值不再满足时,失败持续集成构建。这些都是之前讨论过的内容。另一种强制执行标准的选项是向管道添加 gates 或检查。这样,必须满足特定条件,管道才能继续。

这在经典发布和 YAML 多阶段管道之间有所不同。

经典发布

另一种选择是使用 Azure 发布管道中的 gates。在这里,可以指定必须满足的一个或多个条件,才能允许发布部署到特定环境。gates 也可以是扩展的一部分,例如之前讨论过的 SonarCloud 扩展。

可以通过选择发布管道中的任何阶段并编辑预部署条件来添加 gates。启用 gates 后,可以添加一个或多个 gates。以下是发布管道的截图,显示了如何阻止任何质量不足的构建部署到环境中:

使用部署批准和 gates 不是互斥的,因此可以混合使用两者。

多阶段管道

Gates(如同经典发布中一样)出现在多阶段 YAML 管道中。在 YAML 管道中,还有另一种机制:检查(checks)。检查被配置为在允许管道继续之前,自动验证是否满足一个或多个条件。检查可以添加到在某个阶段中使用的资源上。如果在某个阶段的一个或多个资源上找到检查,则必须通过所有检查,管道才能继续该阶段。检查也可以添加到环境和服务连接中。

要将检查添加到环境中,请导航到该环境:

现在执行以下步骤:

  1. 在右上角,展开菜单并选择“批准和检查”:

  2. 在打开的新视图中,选择“查看所有”以查看所有可用的检查类型。选择“调用 Azure 功能”:

  1. 在打开的弹出窗口中,配置要调用的 Azure 函数。至少需要提供函数的 URL 和密钥。

  2. 选择创建。

创建检查后,每个目标环境的部署任务(见第四章,持续部署)都必须通过此检查。当调用的函数返回成功的响应码时,检查通过。

支持以下类型的检查:

  • 评估工件:验证类型为容器镜像的工件是否通过自定义策略。这些策略是用一种叫做Rego的语言定义的。

  • 调用 REST API:将流水线的详细信息发送到 Azure 函数以执行自定义逻辑。如果 API 返回成功的 HTTP 状态码,则允许流水线继续。

  • 调用 Azure 函数:与调用 REST API 检查相同,但对于 Azure 函数有一些默认设置。

  • 查询 Azure Monitor 警报:仅当指定的警报不处于活动状态时才继续。

  • 所需模板:仅当当前 YAML 流水线扩展一个或多个已配置的基本 YAML 流水线时,才能继续流水线。

检查可以成为一个强大的机制,确保在允许流水线继续之前,满足一个或多个条件。

总结

在这一章中,你学习了如何衡量和验证软件开发过程的质量。快速且频繁的发布要求所写的软件具有高质量。测试是确保你编写高质量软件、减少技术债务的必要步骤。你了解了不同类型的测试以及各种自动化和手动测试的优缺点。最后,你学习了代码审查和工具如何通过报告质量和充当质量门控来帮助保持项目的高质量。

有了这些知识,你现在可以讨论测试和测试类型,帮助你决定应用程序需要哪些测试,哪些风险可以通过哪些类型的测试来解决,以及你是否需要大量的测试还是可以省略它们。你现在也能设置和配置代码扫描工具,以确保不合格的更改不会合并到主线中。

在下一章,你将学习关于安全性和合规性这两个主题,它们在实践 DevOps 时同样重要。

问题

在我们总结时,这里有一些问题供你测试你对本章内容的理解。你可以在附录的评估部分找到答案:

  1. 判断对错:单元测试验证的是单个单元在孤立环境中的工作情况。

  2. 判断对错:集成测试验证的是一个完全组装的系统是否能正常工作。

  3. 关于测试金字塔的原则,以下哪个说法是正确的?

    1. 进行更多的集成测试,少做单元测试,甚至更少做系统测试。

    2. 应该有许多单元测试,较少的集成测试,甚至更少的系统测试。

    3. 应该有很多单元测试,较少的集成测试,以及更少的系统测试。

  4. 以下哪项不是非功能性测试类型?

    1. 负载测试

    2. 可用性测试

    3. 适用性测试

    4. 性能测试

  5. 测试是为了获取关于工作质量的洞察。可以采用哪些技术来防止不合格的工作传递到生产环境?

进一步阅读

第九章:安全性与合规性

就像确保你的应用程序执行所需功能一样重要,你还需要确保它不会做不该做的事。在上一章中,你学习了质量和测试,以便持续衡量应用程序是否按预期工作。在本章中,你将学习如何防止任何不希望发生的行为。这就是安全性和合规性的主题。在增加价值流向终端用户——通过更快地部署和缩短交付周期——的同时,你仍然需要确保交付的是安全且符合规定的软件。在本章中,你将学习如何在 DevOps 流程中解决这些问题。

为此,本章将首先讨论速度与安全性之间的权衡,并解释如何在拥抱 DevOps 时安全性并不会降低,甚至可能增加。接下来,将讨论安全性的一个具体维度:如何安全地处理管道和应用程序所需的机密信息,如密钥和密码。随后,将讨论代码扫描工具,用于自动识别应用程序代码和依赖项中可能存在的安全风险。本章最后将讨论如何保持基础设施和配置部署的合规性,以及如何使用 Azure 策略和安全中心检测运行时安全风险与威胁。

本章将涵盖以下主题:

  • 将 DevOps 原则应用于安全性和合规性

  • 处理机密信息

  • 检测应用程序代码漏洞

  • 处理依赖项

  • 确保基础设施合规性

  • 监控和检测运行时安全风险与威胁

  • 你可以使用的其他工具

技术要求

要尝试本章中描述的技术,你将需要以下一项或多项资源:

  • 一个具有构建和发布流水线访问权限并且有权安装扩展的 Azure DevOps 项目

  • 一个 Azure 订阅。 (如果你还没有账户,可以访问portal.azure.com,并按照指南进行注册)

  • 安装了 PowerShell Azure 模块的 PowerShell。 (有关如何安装 PowerShell Azure 模块的说明,请参考docs.microsoft.com/en-us/powershell/azure/install-az-ps?view=azps-4.1.0

  • 可选的 WhiteSource Bolt、SonarCloud 或类似产品的订阅

前述所有内容都可以免费或作为试用版使用,供学习或评估之用。

将 DevOps 原则应用于安全性和合规性

对安全性和合规性的担忧可能是公司不愿意接受完整 DevOps 思维方式的原因之一,以便能够更频繁、更快速地发布软件。过去,他们通常有较少的发布,每个版本发布前都会交由安全团队或进行渗透测试,然后再部署到生产环境。这种做法使他们确信不会发布包含安全漏洞的软件。

这种较少发布并在最终发布前进行一次大的安全测试的做法,与 DevOps 思维方式相冲突,这也是一些公司面临的挑战所在。他们希望确保将业务价值交付给用户,但又不愿为此牺牲安全性。问题在于,这种权衡是否公平。是否可以在保证速度的同时确保安全性?难道更快、更频繁的发布,并结合严格的自动化,反而可以提高软件开发中的安全性吗?为了回答这个问题,我们首先可以探索一下在非 DevOps 环境中如何进行安全性实践,以及采用 DevOps 后这种实践需要如何改变。

将开发人员和安全工程师聚集在一起

在许多公司中,安全工程师与开发人员通常属于不同的部门。这种分离的背后理念是,保持一定的距离是有益的,即将编写代码的人(即开发人员)与检查代码的人分开。

过去,软件开发人员和软件测试人员之间也常常存在这种分离。然而,最近的研究表明,将开发人员和测试人员放得更近,并不会导致像群体思维、仅测试已知可行的部分或通过仅开发已知测试用例来作弊等不良行为。经验和研究都表明,情况恰恰相反。将开发人员和测试人员放在一起,能产生更高质量的产品。正因如此,像敏捷开发(Agile)这样的运动建议开发团队将测试等学科纳入其中。

正是基于这一思路,将安全工程整合到 DevOps 开发团队中的呼声变得越来越高。这一运动通常被称为“DevSecOps”或“强健的 DevOps”。这两个运动主张,采用 DevOps 原则,如将测试提前(shifting left)和尽可能多地自动化,能够帮助提高安全性。他们主张不再手动进行渗透测试或漏洞审查,而是将其作为交付管道的一部分,完全自动化。这样可以实现自动化、更快的反馈循环和持续交付与部署实践。

也有观点认为,软件发布频率增加还可以进一步提高安全性,原因如下:

  • 当可以使用可靠的机制自动交付软件时,任何解决安全风险的更改都可以在几分钟或几天内部署。能够迅速响应新的发现是一个极大的安全提升。

  • 速度本身也可以是一个安全措施。如果一个系统每天的工作内容发生多次变化,那么在任何时刻要想搞清楚它的内部工作原理并加以滥用将变得极其困难。

  • 应用不可变部署原则,并使用基础设施即代码(Infrastructure as Code)可确保运行应用程序的基础设施得到定期刷新。这是缓解高级持续性威胁(APT)的一个有效方法。

本章将探讨的一个内容是如何配置交付流水线以加入安全扫描。请注意,从流水线运行这些工具是一种不同的实践,它确保这些工具被正确配置,并应用正确的策略和要求。在这些活动中,安全背景和与安全工程师的紧密合作仍然至关重要。这只是另一个可以通过紧密合作产生影响的领域。尤其是在安全问题上,与其他学科的合作将是必需的;并非为了引入手动检查,而是为了共同实现自动化。

安全问题

本章将介绍一些安全问题,但需要认识到,前面的一些章节已经涉及了安全问题。正如你在软件开发中已经知道的,安全并不是仅仅在某个地方添加的东西。安全应当应用于每一个环节。以下图表展示了与软件创建和交付相关的不同活动。在每个活动旁边,列出了相关的安全问题:

让我们快速回顾一下每个阶段的安全问题:

  • 主分支合并:在这个阶段,通过拉取请求应用了四眼原则。拉取请求允许另一位工程师在代码合并到主分支之前进行审查。分支策略用于强制使用拉取请求,以确保代码能够编译且单元测试能够通过。这在第二章《一切从源代码管理开始》和第三章《迁移到持续集成》中有讨论,一切从源代码管理开始,以及迁移到持续集成

  • 构建:在这个阶段,通过向构建流水线中添加额外的任务,执行对所有源代码和第三方依赖项的安全扫描。这可以防止安全风险在未受控的情况下扩散。本章的与秘密相关的工作部分将讨论如何进行此操作。

  • 发布:在发布过程中,可以配置审批者。审批者是必须在部署到特定阶段之前给出批准的用户。此外,还使用自动化发布门控,确保(并进一步强制)在发布继续之前满足某些标准。我们将在 第四章,持续部署 中讨论如何做到这一点。

  • 部署环境目标系统):所有应用程序都将在目标环境中运行。它可以是本地的;然而,在本书中,我们的重点是 Azure。对于运行时的安全性和合规性问题,本章将介绍 Azure Policy 和 Azure Security Center。

  • 交叉切割:所有前述的要点只有在 Azure DevOps 环境中有足够的访问控制时才有用。虽然这不在本书的范围内,但它是一个重要的角度需要涵盖。用户应该拥有足够的权限来完成他们的工作,但不应能够对策略、构建和部署过程进行未经授权的更改。此外,适当的秘密管理对于确保在交付过程的各个阶段,诸如证书、密钥和密码等机密信息的安全是必要的。本章还将涵盖我们如何做到这一点。

现在,在理解了软件和安全工程师如何合作开发应用程序之后,接下来是处理这项工作不同方面的内容。本节将讨论如何处理机密。

处理机密

一个重要的安全元素是机密的处理。在部署应用程序时,始终涉及到机密。尤其是在将应用程序部署到云端,即通过互联网部署时,以安全的方式处理这些访问密钥非常重要。除了部署所需的机密外,还有一些机密需要插入到应用程序的运行时配置中。一个常见的例子是访问数据库的机密。

在 第六章,基础设施和配置即代码 中,讨论了多种交付应用程序配置的机制,包括 Azure 资源管理器 (ARM) 模板。然而,模板需要输入外部机密,因为它们不能存储在源代码控制的参数文件中。

机密不应存储在源代码控制中。

如果机密不能存储在源代码控制中,那么应该将它们存储在哪里呢?常见的选择包括将机密存储在服务连接中或在变量组中存储。

将机密存储在服务连接中

部署任何应用程序所需的第一组机密是那些用于连接目标系统的机密。没有任何个人应访问这些机密,因为它们只在部署过程中使用。这就是为什么 Azure Pipelines 允许你将它们安全地存储在服务连接中的原因。

服务连接是一个抽象,表示可以从 Azure DevOps 连接的另一个系统。服务连接有一个特定的类型,即指定它们可以连接的系统系列。例如,有用于连接到 Azure、GitHub、Jira、NPM、NuGet 及其他十多个系统的服务连接类型。也可以通过 Azure DevOps 扩展机制添加新的服务连接类型。

服务连接可以包含对另一个系统位置的引用——通常是一个 URL。在位置旁边,它们可以包含授权令牌、用户名和/或密码,具体取决于服务连接的类型。存储在服务连接中的秘密不能再被取回,甚至管理员也不能访问。此外,每当服务连接的任何详细信息发生更改时,必须重新输入秘密。这是为了防止之前输入的秘密被滥用来访问另一个位置。这些细节表明,服务连接设计用于为存储连接凭据提供安全位置。

服务连接可以在每个 Azure DevOps 项目的中央位置进行管理。您可以创建新的连接,编辑现有的连接,修改用户权限等。通过以下步骤进行操作:

  1. 要打开此视图,请导航到“项目设置”。一个垂直的设置选项列表将会打开。

  2. 从列表中点击“服务连接”。您将能够查看各种连接,如下图所示:

  1. 现在,如果您希望创建新的服务连接,请点击屏幕右上方的“新建服务连接”按钮。

  2. 要修改现有条目,只需点击它。这将带您到一个与以下截图类似的屏幕:

从此视图,您现在可以执行以下操作:

  1. 编辑服务连接详细信息。

  2. 修改用户权限。

  3. 限制权限。

  4. 添加更多用户或组,并指定每个用户是否可以使用或管理该端点。

  5. 指定哪些管道可以使用此服务连接。

在当前视图中,项目中的每个管道都可以使用该服务连接。这不推荐使用,并且可以通过“限制权限”按钮(3)来加固。在加固管道后,每个希望使用该服务连接的管道必须首先由服务连接管理员授权。

将秘密存储在变量组中

应用程序开发中涉及的秘密比连接其他系统所需的要多。举例来说,包括在应用程序编译期间需要的许可证密钥,或者在部署后需要传递给应用程序的数据库用户名和密码,或者作为 ARM 模板部署的一部分。

这些密钥可以存储在管道变量或变量组中,我们在第三章的在 Azure DevOps 中创建构建定义部分中已涵盖。微软将安全地存储所有标记为密钥的变量,并使其通过用户界面不可检索。

然而,可能有一些原因不希望将密钥存储在 Azure DevOps 中,而是希望将其存储在专用的密钥存储库中,如 Azure 密钥保管库。这样做将提供密钥保管库附带的额外保障,并且能够通过Azure 基于角色的访问控制 (Azure ****RBAC) 和密钥保管库访问策略进一步控制访问权限。

将密钥存储在 Azure 密钥保管库中时,它们仍然可以作为变量组使用,通过通过服务连接将空变量组连接到密钥保管库,如下图所示:

要将密钥保管库用作变量组的存储,请执行以下操作:

  1. 启用第二个滑动开关,以从密钥保管库加载密钥。

  2. 从下拉菜单中选择一个已存在的 ARM 服务连接,或通过从列表中选择 Azure 订阅,动态创建一个新的带有托管身份的 Azure 服务连接。

  3. 输入应加载密钥的密钥保管库名称。你也可以从下拉菜单中选择一个。在这种情况下,只有所选服务连接可访问的密钥保管库才会显示。

  4. 建议禁用允许访问所有管道的滑动开关。通常,开放授权被认为是一个风险,尤其是包含密钥的变量组应仅对明确授权的用户可用。

  5. 可以通过“安全”标签页为特定用户配置访问权限。

还可以自动创建服务连接到 Azure 和密钥保管库所需的正确授权。请注意,这两个操作都会更改 Azure 安全设置,因此请确保这些设置(仍然)正确。

检测不安全的密钥

如前所述,不应将密钥存储在源代码控制中,这就是为什么之前讨论的功能可用的原因。然而,开发人员可能会因意外或进行本地测试而将密钥写入应用程序源代码中。

为了确保这些秘密不会被提交到源代码管理中,可以使用本地插件来检测秘密并发出警告,提醒开发人员这个风险。一个可以为 Visual Studio 实现这一功能的工具是Visual Studio 的持续交付工具扩展。此扩展会扫描任何打开的文件中的秘密,并在检测到可能的秘密时发出编译器警告。有关此扩展的链接已添加到本章末尾的参考资料中。安装程序运行后,任何在 Visual Studio 中检测到的秘密都会导致编译器警告。遗憾的是,在编写本文时,该扩展尚不支持 Visual Studio 2019。

除此之外,建议将类似的工具作为交付流水线的一部分运行,以识别任何被意外提交的秘密。尽管这时候保护秘密已经太晚,但它确实提供了一个明确的信号,表明秘密已经泄露,需要更换。一个可以做到这一点的工具是CredScan。CredScan 是 Microsoft Security Code Analysis Extension 构建任务的一部分。

Microsoft 安全代码分析扩展不仅仅包含 CredScan。它还包括 Microsoft 提供的其他安全工具。

该扩展的详细信息链接可在本章末尾找到;其中还包括所有安装细节。请注意,该扩展仅在特定条件下可用,并且并非免费。

扩展安装完成后,可以将 CredScan 添加到你的流水线中,如下所示:

在参考截图中的注解时,执行这些步骤:

  1. 运行凭证扫描器任务添加到流水线中。

  2. 将工具的主版本更新为 V2。对于所有其他选项,默认设置对于首次扫描已经足够。

  3. 如果之前的扫描导致一个或多个误报,可以通过指向抑制文件将其从结果中移除。

  4. 将创建安全分析报告的任务添加到流水线中。

  5. 将发布安全分析日志的任务添加到流水线中。

  6. 将后分析任务添加到流水线中。

  7. 保存并排队构建定义。

虽然某些任务会在检测到错误时失败并取消构建,但 CredScan 任务则不会。即使检测到密码,它也总是会成功完成。只有在构建结束时的后分析任务会对发现的问题进行处理,并在发现问题时使构建失败。这样做的好处是,所有问题都会被识别,而不仅仅是第一个问题。这也允许任何其他任务在完成后继续执行。

安全分析报告任务(第 4 步)用于收集属于工具套件的一些扫描工具的日志,并将输出汇总成 CSV 和 HTML 文件。发布任务(第 5 步)将所有生成的文件作为构建产物发布。如果检测到可能的密码,以下 HTML 将被生成并作为构建产物发布:

本节内容到此结束,介绍了如何在 DevOps 流水线中保持秘密的安全。下一节将讨论应用程序漏洞的检测。

检测应用程序代码漏洞

在 DevOps 文化转型之前,通常定期进行的安全评估不能完全被忽视。这意味着,不能把它们省略,而是必须以其他方式进行。对此有两种方法。

第一种方法是继续定期进行渗透测试、安全评审和其他安全检查,就像以前一样。然而,代码不是等到通过测试后才进入生产环境,而是与安全评估分开直接部署到生产环境。这意味着存在一个接受的风险,即可能会有漏洞被部署到生产环境,直到下一次安全扫描时才会发现,并将在下一个版本中解决。采用这种方法,可以提高速度,但也需要接受一些漏洞可能会存在一段时间。

第二种方法是将应用程序安全扫描纳入提交代码到源代码仓库的常规工作流程中。例如,安全代码评审不需要每次增量更新时都做,也不需要每两个月做一次。它们也可以在每次拉取请求时进行——在代码合并之前。这样一来,你就不再是检测漏洞,而是在防止漏洞的产生。安全漏洞扫描也可以采用同样的方式,它们可以成为交付流水线的一部分,或者成为一个完整的夜间 QA 构建,每天早上报告开发质量。

当然,这种方式往往不是非黑即白,许多公司会采用这些方法的组合。他们使用自动化反馈机制来检测能发现的漏洞,将安全代码评审纳入拉取请求工作流,然后结合定期的人工渗透测试。通过这种方式,交付速度得以提高,同时安全风险并没有增加,甚至因为能够快速缓解漏洞而有所减少。

OWASP 前十

在谈到网页应用的安全性时,有几种类型的安全问题是常见的,并且负责大多数所有安全问题。这些问题类型被称为 OWASP Top 10。这是由开放网页应用安全平台OWASP)发布的十大常见安全问题列表。该列表每隔几年会进行审查,但在过去几年里保持了相当的稳定性。

OWASP Top 10 中的大多数错误可以通过实施自动化安全测试来预防;无论是通过使用静态代码分析来发现安全漏洞,还是通过使用OWASP Zed Attack ProxyOWASP ZAP)进行动态测试。

实施自动化漏洞扫描

在前一章节中,我们讨论了持续测试,并已经介绍了 SonarCloud 作为技术债务和代码质量的代码扫描器。除了评估应用代码的质量外,SonarCloud 还可以用来扫描安全漏洞。在第八章《持续测试》中,你已经学会了如何将 SonarCloud 扫描添加到你的流水线中。除此之外,还有其他更专业的工具可用,我们将在本章的最后一节讨论这些工具。

这些工具基于静态测试评估应用程序。它们扫描代码以识别任何有风险的代码。这被称为白盒方法,因为它们可以查看、检查和扫描所有代码。换句话说,所有内容都是可见的。这与黑盒方法相反,在黑盒方法中,运行中的应用程序被视为一个封闭的整体,只通过调用它并观察响应来进行测试。一个可以实现这一点的工具是 OWASP ZAP。

OWASP Zed Attack Proxy

OWASP ZAP是一个可以执行应用自动化渗透测试的工具。该工具可以以两种模式运行:

  • 基线扫描:基线扫描只需几分钟,并且在这几分钟内优化为尽可能多地扫描安全风险。这使得基线扫描足够快速,可以在部署流水线的早期运行。甚至可以在每次部署到第一个测试环境后运行安全扫描,从而为开发人员提供快速反馈。

  • 完整主动扫描:完整主动扫描需要更多时间。在这种类型的扫描中,代理将检查应用的每个响应,以识别属于该应用的其他 URL,并对其进行扫描。通过这种方式,完整的应用程序会在运行时被发现,采用蜘蛛爬行的方法。这种扫描方式更加全面,但也需要更多的时间。因此,完整扫描通常是定期运行的,例如每晚一次。

OWASP ZAP代理尝试识别任何可能的安全风险。一些最显著的风险包括 SQL 注入、JavaScript 反射和路径遍历。

OWASP ZAP 是一个可以安装在任何虚拟机上的应用程序。它的缺点是虚拟机始终在运行,即使没有正在进行的扫描。这会增加成本,当然,虚拟机本身也需要进行补丁更新和安全防护。最近,代理的容器化版本也已推出。这个容器可以在 Azure 容器实例中运行,仅在需要时启动代理,并在执行完毕后立即关闭。

这完成了我们对代码扫描工具及其实施的介绍。借助这些工具,您可以检测应用程序中的漏洞并防止安全问题。接下来的部分将探讨如何扫描应用程序的依赖项。

使用依赖项

除了应用程序代码开发中的安全风险外,还有与重用组件相关的风险。现代应用程序代码中,50% 到 80% 不是在内部开发的,而是通过包或依赖项从其他方获得的。其中一些可能是开源的,但并不一定是。也有一些组件是从其他开发公司购买的,或者是从如 NuGet 这样的库中获取的二进制文件。

依赖不仅会带来安全风险,还可能带来许可风险。如果一个团队开始使用一个根据 GPL 许可证发布的组件来构建闭源组件会怎样?如果有人发现了,团队可能被迫将其产品开源,或者至少因为未按照许可证使用他人的作品而遭遇公众羞耻。

为了减轻这些风险,可以使用多种工具来检测和扫描在构建应用程序时使用的所有依赖项。WhiteSource Bolt 是其中一个可用的工具,它作为扩展从 Azure DevOps 市场提供。

使用 WhiteSource Bolt

要开始使用 WhiteSource Bolt 执行扫描,请执行以下操作:

  1. 从 Azure DevOps 市场安装 WhiteSource Bolt 扩展。

  2. 在管道下导航至 WhiteSource Bolt 菜单。

  3. 注册并接受许可条款。

  4. 将 WhiteSource Bolt 扫描任务添加到构建或发布定义中,如下图所示:

  1. 一旦安装了 WhiteSource Bolt 任务的管道运行完毕,包含构建结果的页面将会显示一个额外的标签,名为 WhiteSource Bolt Build Report,其中展示了扫描结果,如下图所示:

该报告提供了关于扫描的应用程序构建的整体安全性和许可风险的若干见解:

  • 顶行的四个小部件提供了漏洞评分的概览,并且有三种不同的细分方式,展示了该评分是如何计算的。

  • 在此下方,所有脆弱的包按名称列出,提供依赖项的参考和推荐的缓解措施。

  • 底部的部分提供了所有依赖项使用的许可证列表。该列表按风险高低排序。

  • 在此概述下方,WhiteSource Bolt 还生成了一个依赖项列表,列出了可用新版本的依赖项(在前面的截图中未显示)。

该报告中显示的结果也可以通过 WhiteSource Bolt 菜单中的 Pipelines 菜单访问。在此视图中,可以访问所有构建的所有报告。此视图非常适合负责跨项目或组织访问安全性或许可标准的人。

这完成了我们对依赖扫描的讨论。如前所述,您可以利用这些工具来检测和扫描构建应用程序时使用的所有依赖项。在下一节中,将介绍基础设施合规性。

确保基础设施合规性

另一个重要的话题是合规性。在许多国家或市场中,在创建软件时,必须实施或遵守一套规则和政策。相当一部分这些政策与应用程序运行的基础设施相关。如果该基础设施部署和管理在 Azure 平台上,Azure Policy 可以成为确保基础设施符合规定的强大工具。

在第六章《基础设施与配置即代码》中,讨论了 ARM 模板的话题。ARM 模板可以看作是一种技术,用于将完整的 Azure 环境描述为一个包含许多对象的 JSON 数组,每个对象描述应用程序基础设施中的一个资源。

Azure Policy 允许您编写查询此文档以及通过任何 API 或 ARM 模板所做更改的策略。每当找到与查询匹配的资源时,它将被阻止创建,或者该匹配项可以添加到审计结果列表中。

除了编写自定义策略外,还有许多现成的策略可供所有 Azure 用户使用。这些策略可用于审计不符合最佳实践或一般建议的资源。还提供了名为“倡议”的策略组,描述了市场标准的适用部分。

分配 Azure 策略或倡议

策略可以在 Azure 的不同级别上进行分配,既可以在资源组级别、订阅级别或管理组级别。这可以通过门户、ARM 模板或蓝图,或 PowerShell 来完成。

使用 PowerShell 时,可以使用以下一系列命令:

  1. 要检索资源组和策略的参考,请使用以下命令:
$rg = Get-AzResourceGroup -Name myResourceGroupName
$definition = Get-AzPolicyDefinition | Where-Object { $_.Properties.DisplayName -eq 'Audit VMs that do not use managed disks' }

此处选择的策略是一个内置策略,将审计所有未使用托管磁盘但在存储帐户中有自定义磁盘的虚拟机。此策略定义将在以下任务中的命令中使用。

  1. 要将策略分配到资源组,请使用以下命令:
New-AzPolicyAssignment -Name 'audit-vm-manageddisks' -DisplayName 'Audit VMs without managed disks Assignment' -Scope $rg.ResourceId -PolicyDefinition $definition

在此任务的 30 分钟内,新策略将变为活跃状态。此时,策略评估周期开始,所有在任务范围内的资源将与策略进行评估。编写时,尚未发布有关此类评估周期所需时间的服务级别协议(SLA)。根据经验,这一过程可能需要 15 分钟到多个小时不等——具体取决于任务范围的大小。

编写 Azure 策略

虽然有许多内置的策略可用,但也有许多使用场景需要创建自定义策略。与其他 Azure 资源类似,策略也是以 JSON 文档的形式编写的。适当的 ARM 资源类型是 policyDefinitions,其结构如下:

{
   "name": "string",
   "type": "Microsoft.Authorization/policyDefinitions",
   "apiVersion": "2019-01-01",
    "properties": {
      "parameters": {
        “location”: { …}
      },
        "displayName": "…",
        "description": "…",
        "policyRule": {
            "if": {
              “field”: “location”,
              “equals”: “[parameters(‘location’)]”,
            },
            "then": {
                "effect": "<audit|deny >"
            }
        }
    }
}

parameters 对象可用于指定在稍后分配策略时需要指定的一个或多个参数。这些参数遵循与 ARM 模板参数相同的语法,并且工作方式相同。

displayNamedescription 属性可用于为策略定义提供有意义的名称和描述,供以后参考。

定义的主体包含两个元素,如下所示:

  • if 语句用于指定一个查询,以选择此策略应应用的 Azure 资源。写复杂查询的 JSON 语法有明确规定,详细信息可见本章末尾链接的 ARM 模板参考资料。

  • then 语句用于描述针对符合条件的资源所需执行的操作。这可以是 拒绝,即自动拒绝创建任何不符合要求的资源。另一种方法是,不直接拒绝不合规的部署,而是对其进行审计。虽然从理论上讲,拒绝不合规的部署非常简单,但在某些情况下,暂时允许不合规的部署是有合理原因的。在这种情况下,审计策略可以帮助跟踪这些资源。所有不合规的部署将在 Azure 活动日志中生成审计记录,并可以在 Azure 门户中的“合规性”标签下的 Azure 策略中查看,具体如下:

在编写完政策定义后,我们需要在 Azure 订阅中创建它,以便使其可用。可以通过 ARM 模板或在门户中手动创建。 从 DevOps 的角度来看,建议通过源代码管理编写政策,并通过管道将其作为 ARM 模板的一部分进行交付。 这样,Azure 政策的编写与应用程序相同,可以进行审查,并作为 DevOps 管道的一部分自动部署到 Azure。

计划

在使用 Azure Policy 时,许多公司发现他们需要创建许多政策,以定义他们希望软件开发人员遵循的所有规则。因此,将政策分组可能是有益的。这种分组称为“计划”,这些计划也在 JSON 中定义:

{
  "name": "string",
  "type": "Microsoft.Authorization/policySetDefinitions",
  "apiVersion": "2019-01-01",
  "properties": {
    "displayName": "string",
    "description": "string",
    "parameters": { … },
    "policyDefinitions": [
      {
        "policyDefinitionId": "string",
        "parameters": {}
      }
    ]
  }
}

一个计划的主体是一个对象数组。每个对象必须包含一个 policyDefinitionId 属性,并可以选择包含一个带有 parameters 的对象。 policyDefinitionId 属性必须通过 Azure 资源 ID 引用有效的 policyDefinitionsparameters 数组应指定该政策所需的所有参数。通常,这可以通过让计划指定所有政策的参数集合作为计划参数来实现。然后,个别政策的参数通过引用计划参数来指定。

获取审核结果

在分配了审核效果的政策后,一旦其生效,政策将自动评估分配范围内的所有资源。无法保证此过程需要多长时间。对于新资源,政策评估结果通常会在 15 分钟内显示,但通常这个过程会更快。

一旦结果出来,可以在门户中查看每个政策或计划的合规性状态,最终呈现出如下概览,见下图:

该报告与其他手动审核报告的区别在于,此概览会持续更新,以反映实际的、当前的合规性状态——它不是某一特定时点的合规性快照。

这种合规性类型的一个重要好处是,规则或政策会持续应用于所有现有资源和任何新进的变更。这意味着可以确保应用环境始终符合要求,并始终遵守所有适用的规则和政策。

与通常每隔几个月进行一次安全和合规性审核的方法相比,这种方式有很大不同。通常,这会导致环境只有在审核前才符合合规要求,且合规性在审核后逐渐下降。直到下次审核时,合规性才会再次接近 100%。在许多公司中,这导致了如下的合规性图表:

通过这一点,我们讨论了 DevOps 实践如何帮助提高安全性和合规性——通过确保基础设施合规性。接下来的章节将讨论本章提到的工具的几种替代方案。

监控和检测运行时安全风险与威胁

迄今为止讨论的所有安全工具都集中在防止将存在漏洞的代码推送到生产环境。然而,完整的已部署软件解决方案,包括所有支持的基础设施,远不止是代码。除此之外,解决方案中可能有许多意外或未计划的交互。在生产环境中持续监控这些内容是必要的,不仅是为了防止安全问题,还要检测任何潜在的安全问题。在 Azure 中,Azure 安全中心就是一个可以实现这一目标的工具。Azure 安全中心通过 Azure 门户提供,可以像其他服务一样从左侧菜单选择或通过顶部搜索栏搜索。

打开安全中心后,会显示类似于以下截图的内容:

此仪表板提供了三大类的洞察:

  • 策略和合规性:此部分概述了所有选定的 Azure 订阅在你配置的安全策略下的合规性状况。

  • 资源安全卫生:Azure 提供了许多安全控制,可以开启或关闭,并且有许多安全配置设置。就像其他地方一样,用户需要平衡成本、安全性、风险和易用性。此仪表板将显示有关提升资源安全性的一些建议。用户可以根据每个建议决定是否采纳。

  • 威胁防护:此部分显示已自动检测和报告的威胁或攻击数量:

所有这些概述和类别都可以进一步钻研。上面的例子显示了打开威胁防护概览的结果。在这里,它列出了它所识别的所有可能的安全威胁。在此案例中,它列出了对在订阅中托管的虚拟机的不同访问尝试。

Azure 安全中心还拥有许多其他功能,并且这些功能正在持续增加。在 Azure 部署时,这是识别和管理安全风险的地方。

这就是我们讨论如何监控运行时环境安全风险的各类技术的结束。接下来的章节将介绍一些替代工具,用于执行之前章节中提到的扫描任务。

你可以使用的其他工具

市场上有许多工具可以用于执行应用程序代码和依赖关系的安全扫描。例如,WhiteSource、Black Duck、Veracode 和 Checkmarx。

WhiteSource 是 WhiteSource Bolt 的付费版本。它提供相同的服务以及更多功能。例如,它不仅在依赖关系扫描时报告风险;它还会在新的风险出现时提醒你,特别是对于在应用程序上次扫描中存在的依赖关系。

Black Duck 是一款帮助团队管理使用开源软件所带来的风险的产品。它提供的服务与 WhiteSource 类似。

VeracodeCheckmarx 是用于识别漏洞代码的代码扫描工具。与 SonarQube 同时检查代码质量和安全风险不同,这两个产品专注于安全风险。总体来说,它们在安全扫描方面表现更好,缺点是价格更高。

总结

在本章中,你已经学到,DevOps 和安全并不是两个对立的目标,DevOps 实践可以帮助你强化安全性。首先,你学习了在处理持续部署管道时如何处理密码和其他机密。接下来,你学会了如何通过代码和依赖关系扫描工具增强你的管道,并将安全的 shift-left 原则应用其中。最后,你学会了如何使用 Azure Policy 来定义基础设施的约束和规则,以及如何自动应用这些规则或审计不合规的部署,甚至自动拒绝。

通过你所学到的知识,你现在能够与公司内的同事讨论如何解决 DevOps 团队中的安全问题。你可以与安全工程师合作,配置你所使用的工具,并获得关于你工作中安全影响的自动反馈。

在下一章中,你将学习应用程序监控。此外,你还将学习如何监控应用程序是否平稳运行,以及如何收集运行时指标。

问题

下面是一些问题,供你测试自己对本章内容的理解。你可以在附录中的评估部分找到答案:

  1. 判断题:确保软件交付的安全性只是部署管道中的一个步骤。对或错?

  2. 哪种工具可以用于安全测试,其中代理用于识别有效的应用程序 URL,然后对应用程序进行不同的攻击,如注入?

  3. 判断题:在大多数现代应用程序中,超过 50% 的代码来自开源库。对或错?

  4. 在部署或运行应用程序时,需要哪些安全位置来存储机密?(你可以选择多个答案。)

    1. 标记为机密的 Azure Pipelines 变量

    2. Azure 密钥库

    3. Azure DevOps 密钥库

    4. Azure 变量组

    5. Azure DevOps 安全变量

    6. Azure DevOps 服务连接

  5. 哪两个 Azure 产品可以用于在运行时检测安全风险?

进一步阅读

第三部分:闭环

在本节中,您将了解到,DevOps 不仅仅是为了更快地将代码推送到生产环境,或是每周交付更多的迭代。另一个重要的方面是观察和衡量已经交付的软件,以此来引导未来迭代的方向。本书的这一部分将涵盖应用监控和用户反馈。

本节包括以下章节:

  • Chapter 10,应用监控

  • 第十一章,收集用户反馈

第十章:应用程序监控

在前几章中,你学习了如何将 DevOps 原则应用到软件交付中。你学习了如何创建从源代码管理到生产的流水线。你还学会了如何确保你的交付符合合规要求和安全标准,同时不牺牲速度或忽视业务价值的交付。在本章中,你将学习如何将这个流水线转变为一个 DevOps 循环,持续交付新软件,并衡量应用程序的表现。这是一个持续的过程,在生产环境中评估应用程序的表现,并学习如何决定下一步的方向。

为了实现这一目标,本章首先介绍了一种收集应用程序崩溃报告的方法。几乎每个应用程序在某个时候都会抛出未处理的异常并崩溃。确保收集和报告应用程序崩溃,可以帮助你调查崩溃的原因并解决它们。接下来,重点转向应用程序的仪器化。仪器化是收集日志和指标的实践,它们可以帮助你了解应用程序在生产环境中的表现。你可以利用这些数据在问题发生时发出警报,或者希望能够在问题发生之前提前发现。最后,本章还探讨了与其他工具集成的几种选择。

本章涵盖以下主题:

  • 调查应用程序崩溃

  • Web 应用程序仪器化

  • 与其他工具集成

技术要求

要尝试本章中描述的技术,你将需要以下之一或更多:

  • 用于收集移动应用程序崩溃的 App Center 账户

  • 用于收集桌面应用程序崩溃的 Raygun 订阅

  • 用于对 Web 应用程序进行仪器化的 Azure 订阅

所有这些都提供免费试用选项。

调查应用程序崩溃

无论应用程序设计得多么完美,最终都会因某些意外情况而崩溃。为了从这些崩溃中学习并尽量防止未来发生类似问题,可以向应用程序添加代码来收集崩溃报告,并将其发送到一个中央位置。在这里,这些报告可以被分析并归类,以识别应用程序的改进方向。具体如何操作取决于应用程序的类型。

以下部分将讨论移动应用程序和桌面应用程序的崩溃报告收集过程。关于 Web 应用程序,崩溃报告的收集可以使用与仪器化相同的工具;我们将在后面的Web 应用程序仪器化部分讨论这个内容。

收集移动应用程序崩溃报告

许多用于收集移动应用程序崩溃报告和错误的工具之一是 Visual Studio App Center。除了分发移动应用程序外,App Center 还允许应用程序提交崩溃和错误进行分析。

要开始使用 App Center 进行崩溃报告,首先需要定义应用程序。这被称为应用程序定义,如何操作它在第四章《持续部署》中进行了讨论。通过这个应用程序定义,可以创建一个应用程序密钥,这对于配置应用程序以发送崩溃报告是必需的。要开始发送崩溃报告,需要执行以下步骤:

  1. 在项目中安装 Microsoft.AppCenter.Crashes NuGet 包。

  2. 将以下代码添加到应用程序初始化中:

AppCenter.Start("ios={appSecret};android={appSecret };uwp={appSecret}", typeof(Crashes));

除了崩溃外,还可以跟踪开发者感兴趣的其他错误。可以使用以下代码来完成此操作:

Crashes.TrackError(ex);

现在,所有未处理的异常都会自动捕获并发送回 App Center。在这里,它们可以进行分析,如下图所示:

  1. 点击任何已报告的错误或崩溃,查看详细信息,如下所示:

每个崩溃或错误都会显示一个包含最重要信息的仪表板。这包括报告的数量和受影响用户的数量。同时,还会显示受影响的设备类型和操作系统。在页面顶部,显示了堆栈跟踪,开发者可以使用这些信息来调查并且希望能够修复问题。

这部分内容涵盖了从移动应用程序收集崩溃报告和错误。接下来的部分介绍了桌面应用程序的相同概念。

收集桌面应用程序的崩溃报告

崩溃报告同样适用于桌面应用程序。同样,桌面应用程序有许多解决方案,而且它们大多数的工作原理差不多。这些解决方案之一是 Raygun。Raygun 是一个商业服务,适用于 .NET 应用程序,但也支持许多其他语言和平台。

要使用 Raygun 收集崩溃报告,请按照以下三步操作:

  1. 注册 Raygun 账户。

  2. 在解决方案中安装 Mindscape.Raygun4Net NuGet 包。

  3. 捕获未处理的异常并将其转发到 Raygun。

以下示例展示了如何捕获并将未处理的异常转发到 Raygun:

 class Program
    {
        private static readonly RaygunClient _raygunClient = new RaygunClient("myKey");

        static void Main(string[] args)
        {
            AppDomain.CurrentDomain.UnhandledException += HandleEx;
            throw new Exception("Boom!");
        }

        private static void HandleEx(object sender, UnhandledExceptionEventArgs e)
        {
            _raygunClient.Send(e.ExceptionObject as Exception);
        }
    }

完成此操作后,可以在 Raygun 网络界面中查看所有异常。在这里,如果堆栈跟踪足够相似,异常会自动分组。也可以单独查看并浏览它们,但在大多数情况下,只关注更大的异常组是有意义的。

以下截图展示了如何在 Raygun 中浏览这些组:

点击此界面中的异常消息,可以查看完整的堆栈跟踪以及该异常实例的所有共享属性。

这部分内容结束了我们对移动和桌面应用程序崩溃报告收集的讨论。通过这样的做法,你可以发现并调查客户在生产环境中遇到的问题。在接下来的部分,将介绍 web 应用程序的监控,以进一步增强我们对应用程序在生产环境中表现的理解。

网络应用的监控

网络应用程序在许多方面与移动应用和桌面应用程序不同——例如,大部分应用程序代码并不在客户端运行,而是在服务器上运行。这使得开发人员可以比其他类型的应用程序更容易地收集关于 web 应用程序运行方式的信息。这个过程称为“监控应用程序”。

日志是系统保存的文本消息,用于描述服务器执行路径。这有助于开发人员通过检查日志输出回溯并探索发生过的事情。结构化日志正迅速成为追踪日志的标准。结构化日志是一种技术,其中日志不再只是文本消息,而是带有每个参数一组值的参数化文本消息。这样有两个优势——日志可以更好地压缩,并且可以更快速地进行搜索。

指标是记录应用程序数据的值。它们包括时间戳、指标名称和数值。例如,每秒记录一次 CPU 使用百分比。

在监控应用程序时,很容易集中关注许多服务器级别的日志和指标。例如,许多操作员默认会开始收集诸如 CPU 使用率、内存压力和 I/O 操作等指标。虽然这些指标没有错,但它们并不总能反映从用户角度来看应用程序的性能。其他指标,如响应时间或队列消息处理延迟,可能会提供更好的用户体验洞察。虽然衡量系统指标是没有问题的(它们通常是未来问题的良好指示),但你也应该尝试收集以用户为中心的指标。

Azure 提供了 Application Insights 服务来监控应用程序,重点是 web 应用程序。可以通过 Azure 门户创建 Application Insights 工作区,这将打开如下截图所示的工作区。这里需要注意的一个重要内容是“Instrumentation Key”,它将在后续章节中使用。尽管该字段明确显示,但建议将其视为应用程序的密钥:

如果你想通过一个现成的应用程序来实验 Application Insights 和 Azure Monitor,本章末尾的 进一步阅读 部分提供了一个示例链接。该示例是一个简单的 URL 缩短应用,使用了多个应用组件,并且内置了日志记录和监控,可以用来在几分钟内开始实验本节介绍的概念。

以下小节将详细介绍日志记录、度量以及如何调查单个请求。

日志记录

最基本的仪器化类型之一是将日志语句添加到应用程序代码中。过去,这些日志被保存在运行应用程序的服务器的磁盘上。然后,检索和调查这些日志需要花费大量的时间和精力。

在现代托管环境中,日志不再保存在本地文件系统中,而是远程存储。随着临时基础设施和服务器的动态增减,无法再将日志保存在服务器上,并确保以后能够检索它们。因此,日志通过 HTTP 传输到专门的日志存储中,如 Application Insights。

发出日志

要将日志条目写入日志存储(例如 Application Insights)中,必须完成以下两项操作:

  1. 日志条目需要在应用程序代码中发出,适用时,使用 ILogger 接口。此接口可通过 Microsoft.Extensions.Logging.Abstractions NuGet 包获得。

  2. 需要安装 Application Insights NuGet 包(Microsoft.ApplicationInsights.AspNetCore),并且需要将 Application Insights 注册为 LoggingProvider。这样,所有发送到前述接口的日志将被转发到 Application Insights 代码中。反过来,这些代码会将所有日志转发到 Application Insights 服务。

以下示例代码展示了如何通过类中的 ILogger 接口生成结构化日志条目:

public class Example
{
    private readonly ILogger<Example> _logger;

    public Example(ILogger<Example> logger)
    {
        _logger = logger;
    }

    public void DoSomething(User user)
    {
      _logger.LogWarning(
        "Doing something for user with id '{userId}' and username '{username}'", 
        user.Id, 
        user.Username);
    }
}

日志条目开头不应有美元符号($)。这里没有使用字符串插值,但文本消息中插入了两个占位符。结构化日志条目将识别这些占位符,并在显示日志条目时插入提供的值。

在生成日志条目后,应注册日志提供程序以捕获这些日志。可以通过 .NET Core 内置的依赖注入来完成此操作。

安装 Application Insights NuGet 包后,需要在 CreateWebHostBuilder 方法中添加以下代码:

public static IWebHostBuilder CreateWebHostBuilder(string[] args) 
{
  return WebHost.CreateDefaultBuilder(args)
    .UseStartup<Startup>()
    .ConfigureLogging(builder => {
        builder.AddApplicationInsights(“myKey”);
    }
}

当使用版本 2.7.0-beta3(或更高版本)的 Application Insights NuGet 包,并且使用 Application Insights 来获取度量数据时,前述配置不再需要。

启动应用程序后,所有警告级别及以上的日志条目会自动转发到 Application Insights。要更改转发哪些条目,哪些不转发,可以配置过滤器。本章最后将提供关于如何详细配置 Application Insights 的更多链接。

搜索日志

在将日志条目发送到 Application Insights 后的几分钟内,它将出现在界面上,供查询使用。为此,打开 Application Insights 实例并导航到左侧菜单中的日志(Analytics)(1)。这将打开如下所示的视图:

在这里,可以编写查询(2),用于搜索记录的日志,使用的是Kusto 查询语言KQL)。Application Insights 已优化以处理大量数据,大多数查询的返回结果在一秒钟内,甚至在搜索数百万条日志条目时也能快速返回。

对日志进行警报

收集和搜索日志在排查特定情况或响应用户投诉时非常有用。然而,在某些情况下,当特定条件出现时,最好能够自动收到通知。这就是所谓的警报。

在 Azure 中,可以创建警报规则,当某个条件满足时,开发人员将收到通知。警报功能由 Azure Monitor 提供,且与许多 Azure 服务(包括 Application Insights)集成。

要创建新的警报规则,请按照以下步骤操作:

  1. 使用门户导航到 Azure Monitor。

  2. 现在,选择警报。这样会打开以下截图所示的视图:

如果有任何需要关注的警报,它们会在这里显示。

  1. 要添加新的警报规则,请使用屏幕左上方的按钮。点击后会打开另一个视图,如下截图所示。在这里,可以配置警报条件:

在前面的截图中,左侧显示了用于配置警报的视图。在这里,必须做出多个选择来创建一个警报:

  1. 这是作为警报主题的资源。它可以是任何类型的资源,在本示例中,警报将针对 Application Insights 工作区。

  2. 这是设置警报的条件。要选择这些条件,右侧会弹出窗口。在这里,可以在不同类型的警报之间进行选择。选择“日志搜索”警报类型,将打开如下所示的详细视图。在这里,必须做出以下选择:

    • 对追踪日志的查询(参考前面截图中的 2a):在此示例中,查询追踪日志中的条目,这些条目的严重性为 4 或更高,意味着它们是通过 LogWarningLogCritical 方法发出的。

    • 触发警报的条件和操作符(2b):在此情况下,只要有一个或多个匹配,警报就会触发。

    • 评估警报条件的间隔(2c):当指定一个匹配特定数字的查询时,这决定了必须满足该数量的时间间隔。

    • 评估警报条件的频率(2d):过于频繁地评估警报条件可能导致警报以快速的系列频繁开关。过于不频繁地评估警报条件可能会导致警报来得太晚。实验将帮助你了解如何配置这一点。

  3. 这是在警报条件满足时执行的操作。由于可能有很多警报需要调用相同的操作组,因此可以将操作分组,并在此处引用这些操作组。操作的一些示例包括调用 Webhook 或发送短信或电子邮件。

  4. 警报配置通过输入名称和描述来完成。

  5. 最后,警报可以被保存。

警报创建并激活后,系统会自动完成这一过程,几分钟内,警报就准备好检查应用程序日志,并在满足警报条件时发出信号。

日志记录是深入了解请求发生了什么以及错误如何产生的好方法。另一种了解应用程序行为的技术是使用指标。

指标

除了日志记录外,应用程序还可以发出一个或多个指标。指标是随着时间变化的一系列值,描述系统的一个或多个方面。一些指标的示例如下:

  • 当前登录的用户数量

  • 用户查看的产品数量

  • 数据库事务的数量

收集这些指标可以提供关于系统如何使用以及当前如何操作的洞察。指标通常用于创建仪表盘和警报。

发出指标

要开始使用指标,首先必须由应用程序发出指标并存储在集中位置。除了日志记录外,应用程序洞察还可以用于获取指标。

要使用应用程序洞察来获取指标,需要执行以下步骤:

  1. 指标需要从应用程序代码中发出(在适用的情况下),使用TelemetryClient类。此接口可以从Microsoft.Extensions.Logging.Abstractions NuGet 包中获得。

  2. 安装Microsoft.ApplicationInsights.AspNetCore应用程序洞察 NuGet 包。

  3. 使用TelemetryClient注册到Dependency容器。通过在容器构建器上使用扩展方法来完成,如以下代码片段所示:

builder.RegisterType<TelemetryClient>().SingleInstance();
  1. 完成此操作后,应用程序准备开始发出指标。可以使用TelemetryClient类来完成:
public class Example
{
 private readonly TelemetryClient _telemetryClient;

 public Example(TelemetryClient telemetryClient)
 {
     _telemetryClient = telemetryClient;
 }

 public void DoSomething()
 {
     _telemetryClient.GetMetric(“doSomethingCalledCounter”).TrackValue(1.0);
 }
}

发出一个指标涉及两个步骤。首先,使用GetMetric()方法获取该指标的引用。接下来,使用TrackValue方法提交一个值。提交的值应为双精度数或允许隐式转换为双精度数。

一旦指标发出,就可以用来创建图表和指标。然而,在继续讨论这些主题之前,首先要讨论另一种类型的指标——即 Azure 平台指标。

除了应用程序发出的指标之外,还有许多可以从运行系统的 Azure 平台中记录的指标。以下是一些示例:

  • CPU 使用百分比

  • 服务总线上的消息数量

  • 每秒数据库事务数

  • 可用的磁盘空间

这些指标通常与应用程序的性能密切相关,甚至可能是领先指标。例如,当可用磁盘空间达到 0 时,大多数 Web 服务器会停止工作。

在 Azure 中,每个服务默认会发出一系列的指标,这些称为平台指标。不同的服务发出的指标各不相同,且用户无法影响这些指标的生成。Azure Monitor 也会自动收集这些指标,这些指标可以像应用程序发出的指标一样用于绘图和警报。第一个要探讨的内容——绘图——将在下一节中介绍。

平台指标是内置的并且免费提供,且保留 93 天。

绘制指标

所有收集的指标,无论是在 Application Insights 中还是在 Azure Monitor 中,都可以用于构建可视化指标的图表和仪表板。可以使用每个 Azure 资源中可用的“指标”选项卡来创建图表。也可以使用 Azure Monitor 提供的工具来创建图表,这样,可以在一个画布上合并多个资源的图表。操作步骤如下:

  1. 打开 Azure Monitor,可以从左侧菜单访问。

  2. 导航到“指标”菜单。这将打开如下所示的视图:

  1. 一旦画布打开,可以将一个或多个图表添加到其中。图表是通过顶部的图表构建器构建的。这里需要做四个选择:

    • 需要绘制图表的资源。

    • 需要绘制图表的资源所属的指标命名空间:对于每种 Azure 资源类型,只有一个命名空间。唯一的例外是 Application Insights,它有两个命名空间——一个是默认指标,另一个是通过 TelemetryClient 发出的应用程序指标。

    • 绘制指标:对于自定义指标,这指的是前一节中在 GetMetric() 方法中选择的名称。

    • 用于将多个测量值合并为图表中一个点的数学运算:这可以是最小值、最大值、平均值或总和。

    • 要向同一图表添加多个图表线条,请选择顶部的“添加指标”。重复之前的四个选择以配置新的图表。

  2. 若要将此图表作为仪表板的一部分以便于重复使用,请点击顶部的“固定到仪表板”按钮。

  3. 然后可以通过右侧的菜单直接访问仪表板。

拥有一个度量的图表,甚至是在仪表板中显示多个图表,对于调查问题非常有帮助。然而,没有人愿意持续关注仪表板以查看进展情况。因此,还可以为度量配置警报。

对度量进行警报

与日志条目类似,当一个度量值超过或低于某个阈值时,Azure Monitor 也可以发出警报。需要跟进的日志条目可能与某个单一用户或客户有关,他们遇到问题。而度量值则有助于检测所有用户受到影响的情况,或基础设施无法正常工作或即将停止工作的情况。

为度量创建警报的过程与从日志创建警报的过程非常相似。要创建新的警报规则,请使用门户导航到 Azure Monitor,然后选择“警报”(Alerts)。接下来,点击“新建警报规则”按钮,打开如下所示的视图:

在此截图中,必须进行以下选择才能对度量创建警报:

  1. 选择作为警报主体的资源。这可以是任何类型的输出度量值的资源——在此实例中,已选择一个 Application Insights 工作区。

  2. 配置触发警报的条件。为此,会在右侧打开一个新窗口。要进入前面截图中显示的视图,选择度量警报类型,并从填充的列表中选择正确的警报。接下来,视图将更改为前面截图中显示的内容。在这里,必须进行以下选择:

    • 选择触发警报的阈值(参见前面截图中的 2a)。静态阈值是默认设置,需要配置操作符、聚合类型和值。

    • 选择警报应评估的粒度间隔(2b)。

    • 选择评估频率(2c)。警报评估得越频繁,发生事件与发送警报之间的延迟就越短。

    • 保存警报条件(2d)。

  3. 选择一个或多个在满足警报条件时需要触发的操作组。

  4. 配置警报规则名称和描述。

  5. 保存警报。

警报在保存后几分钟内变为活动。现在,每当满足警报条件时,开发人员将通过配置的警报组方法收到通知。

调查请求

在使用 Application Insights 进行日志记录和度量时,Application Insights 还提供了许多内置的功能可供使用。其功能之一是可以通过一个视图——称为“搜索”(Search)——对所有由 Application Insights 收集的数据执行搜索查询。

在这里,可以搜索所有由 Application Insights 收集的信息,包括以下内容:

  • 应用程序代码生成的日志,包括 NuGet 包和 .NET 框架。

  • 所有依赖调用:这些是对数据库和其他系统的调用,Application Insights 会自动检测到。Application Insights 会记录目标系统和持续时间。

  • 所有异常:应用程序中发生的所有异常都会被 Application Insights 记录,即使它们已经被应用程序代码正确处理。

  • 请求:所有通过 HTTP 进入的用户请求都会被记录。重要的属性,如 URL、持续时间和 HTTP 方法,也会被包括在内。

要打开搜索视图,请导航到正确的 Application Insights 实例,然后转到搜索标签(1),以获得以下截图所示的视图:

在搜索视图中,可以配置多个搜索参数(2):

  • 搜索的时间间隔:默认为过去 24 小时。

  • 搜索的事件类型:可以是请求、日志条目、页面视图、异常、依赖调用等。

  • 搜索的文本内容。

几秒钟内,所有匹配的结果会以条形图形式显示。每个条形代表一个时间段,并显示在该时间段内的匹配次数。此图表下方会显示所有单独的匹配项。这些是所有可用事件类型的混合。

点击任何一个结果会打开一个新视图,显示选定记录与所有其他类型的关系,按请求分组。这使你能够快速导航到所有由 Application Insights 在单个用户请求执行期间收集的日志、依赖调用和异常。结果可以以列表和时间轴的形式显示。这使你能够非常快速地查看在执行用户请求时,服务器所做的操作:

通过这些调查应用程序和接收事件通知的方式,决定创建哪些警报、哪些不创建非常重要,这不仅是为了创建一个健康的工作环境,还为了在监控和新工作的平衡中找到合适的点。这是下节的主题。

优化警报

一旦团队开始为其应用程序添加指标和警报,并且关注他们认为重要的指标,警报很快就会开始出现。在此时,重要的不是仅仅响应警报,而是要调查警报并关闭它们。警报还应该被视为学习的机会。

优化警报设置

创建一系列警报后,重要的步骤是定期重新评估它们。通过这样的评估,可能得出以下两种结论:

  • 警报阈值的变化:定期评估警报涉及查看一段时间内的指标,并检查警报阈值目前的位置。这可能导致结论认为阈值过低或过高。

  • 去除重复:查看一个月内已触发的警报,很可能会发现一组或多组警报总是同时触发。例如,某个特定 Web 服务器上设置的一组警报可能是如此相关,它们总是在同一时间触发。一个常见的例子是 CPU 使用率和 HTTP 请求的平均响应时间;这两个指标通常会同时上升。如果是这种情况,值得考虑去除其中一个警报,或者将其中一个警报降级为仅为警告。重复的警报增加了需要立即响应的事项数量,导致团队在没有明确好处的情况下承受更大的压力。

不断优化警报集不仅有助于减少浪费,还能防止所谓的警报疲劳。

警报疲劳

如果警报规则没有持续进行审查和更新,它们可能会对团队产生负面影响,特别是当警报规则触发过于容易或过于频繁时,人员将不再正确响应这些警报。如果警报数量过多,会使人感到疲惫,变得对警报麻木。无论这些警报是虚假的还是实际的,警报的数量足以让人们进入一种不再关心的状态。

如果在团队中观察到这种情况,就该彻底改变警报的生成和响应方式。如果不这样做,团队成员可能会生病或完全离开公司。

防止这种情况的一种方法是实施一个健康的值班计划。

要捕获哪些指标

在讨论指标时,一个常见的问题是应该发出和监控哪些指标。这里有许多可能的指标,以及更多关于此主题的不同看法。作为一个好的起点,通常会收集以下 Web 应用程序的指标:

  • 每分钟请求数、每分钟事务数或类似指标:这是用来捕捉 Web 应用程序当前负载或吞吐量的度量标准。

  • 平均响应时间:这个度量标准捕捉的是在某一时间窗口内所有请求的响应时间。

  • 错误率:这个度量标准捕捉的是所有请求中导致错误的百分比。作为错误的指导标准,通常将所有400及以上的 HTTP 响应码视为错误。

当这三个指标一起被捕获并绘制在同一图表中时,它们为理解应用程序行为提供了第一步。让我们看几个例子:

  • 当平均响应时间上升,但吞吐量(每分钟请求数)保持不变时,这可能表明承载应用程序的基础设施出现了问题。

  • 当吞吐量和平均响应时间同时上升时,这可能意味着流量正在增加,当前的基础设施无法在相同的响应时间下支持这种吞吐量。

  • 当错误率上升,但其他指标保持不变时,这可能意味着部署出现问题,或者某个特定的代码路径开始生成(更多)错误。

当然,这些只是一些示例,还有许多可能的场景。其他指标可以帮助排除特定的场景或尽量避免它们。例如,开始监控数据库负载百分比也可以帮助检测第二种场景的特定实例。如果数据库负载接近 100%,可能是时候将数据库升级到更高性能的层级,以维持相同响应时间下的更高吞吐量。

总结这一部分内容时,最后有一个建议——在开始监控时,往往会倾向于关注托管应用程序的系统。作为替代方案,还应考虑监控那些对业务产生直接影响的指标,或者是能够反映用户在应用程序可用性方面满意度的指标。这比仅仅关注系统要更接近衡量业务价值。

以下是一些示例:

  • 在在线商店中,每分钟售出的书籍数量可以是一个非常有价值的商业指标。试想一下,如果能够通过 Azure Monitor 和应用程序代码中的自定义指标实时获取这个指标,将会对业务产生什么样的影响。

  • 对于在线阅读平台,虚拟翻页的数量可以是一个有价值的指标,表明用户是否愉快地使用该服务。只要这个数字出现急剧下降或迅速增加,可能就表明出现了问题。

要找出在特定场景下哪些指标有意义,可能有助于与业务或领域专家进行沟通。

制定值班计划

一旦配置了警报并开始触发,就没有必要设置警报在早上 8 点之前或下午 5 点之后不触发。换句话说,必须确保某些严重性的警报即使在办公时间之外也能得到跟进。

在许多公司中,警报机制还是新鲜事物,因此隐性地期望某些人在办公时间外(除了他们的常规职责)可以处理这些警报。有时,当警报每年只触发一两次,且没有关于响应时间的约定时,这可能根本不会成为问题。

然而,在许多组织中——尤其是随着时间的推移——人们通常期望在一定的时间范围内对这些警报作出响应。除此之外,随着系统的规模变大、复杂性增加,或者系统数量的增长,警报的数量也可能会增加。

应对这一问题的方式是制定值班计划,并与工程师达成正式协议,明确他们的职责以及组织将如何奖励他们的付出。这使得工程师能够设定清晰的期望,并根据这些协议保护自己的自由时间。足够的休息时间有助于工程师在高压期间得到放松,这样他们才能在值班时保持警觉,随时准备应对预期的任务。

有很多关于什么构成健康值班计划的资料,关键字是健康。一些基本建议如下:

  • 在非工作时间值班的员工,不应在工作时间继续值班。

  • 为值班工程师提供合理的补偿,考虑他们保持电话畅通、避免受影响等情况。什么是合理的补偿因情况而异,但值班的要求越高,补偿应该越高。

  • 为值班人员提供合适的工具。例如,当响应时间要求为 30 分钟或更短时,给值班人员配备一个背包,里面放有笔记本电脑、电话和连接互联网的设备。

  • 确保每位员工的值班时间不超过至少 75%。

  • 允许员工以调休的形式休假,这样如果他们需要在夜间响应警报,可以晚点上班。

每当系统的正常运行受到干扰时,无论是工作时间还是非工作时间,都可以进行实时站点事件回顾,以了解发生了什么以及如何减少再次发生的可能性。

实时站点回顾

在警报触发并且团队已响应并解决问题后,是时候评估发生了什么了。这就是所谓的实时站点事件回顾。在这里,整个团队会聚集在一起,讨论以下内容:

  1. 发生了什么——首先,应该构建一个时间线,从发现事件的时刻到恢复正常操作为止。接着,时间线会扩展,加入导致触发事件的前因后果。

  2. 接下来,评估一系列事件,了解响应过程中哪些做得好。如果团队的某个成员使用了一个新工具快速诊断问题,这也能对团队其他成员产生帮助。

  3. 只有在此之后,才是审视可能改进的地方,并将这些改进点转化为团队的高优先级工作。识别出可能的安全保障措施,并安排实施,或者识别新的警报机制,在类似问题发生之前就能发出警报。

  4. 触发初始响应的警报或一组警报被评估,以确定它们是否足够充分,或者是否可能包含重复内容。

实时站点事件回顾的最佳时间是尽可能接近事件发生的时间。实际上,这意味着要给每个人足够的时间休息和恢复,并计划安排在下一个工作日进行会议。

这完成了我们对 Application Insights 和 Azure Monitor 在监控网络应用程序时功能的概述。下一节将介绍几种将 Application Insights 和 Azure Monitor 与其他工具集成的方法。

与其他工具的集成

Azure Monitor 和 Application Insights 是收集应用程序日志和指标的优秀工具,同时还可以存储这些数据并进行搜索。然而,开发团队或企业可能有一些原因偏好使用其他工具来可视化应用程序性能或响应警报。一个重要的整合驱动因素通常是一个人或团队使用的主要工具。如果一个团队主要在 ServiceNow 或 Grafana 中操作,那么将这些工具与 Azure Monitor 集成通常更为有用,而不是强迫这些团队使用多个工具。

存在许多可能的集成,以下小节中详细介绍了一些例子。

IT 服务管理应用程序

在前一节中我们介绍了操作组,并讨论了如何在网络应用程序中进行监控。操作组是响应警报时执行的一组操作。

除了丰富的内置功能外,还可以在现有的IT 服务管理ITSM)解决方案中自动触发警报。如果公司内已经有 ITSM 解决方案,那么通过 Azure Monitor 创建一个单独的警报通道并不合适。相反,使用 Azure Monitor 的 ITSM 连接器可以让你从一个解决方案中管理公司范围内的所有警报。

当前,ServiceNow、Provance、System Center Service Manager 等工具已有可用的集成。这些连接通过 ITSM 连接器创建。

Azure Boards

在许多开发团队中,Azure DevOps 是开发人员花费大部分时间使用的工具。这也是他们通过 Azure Boards 执行待办事项管理的地方。

同时,操作员(希望开发人员也能参与)在 Application Insights 中进行调查工作,以确定用户错误的原因,并深入分析失败的原因。这项调查工作可能会导致需要在 Azure DevOps 中回溯的任务。

为了简化操作,可以通过以下步骤从 Application Insights 配置与 Azure DevOps 的集成:

  1. 导航到左侧菜单中的工作项选项(1)。这将打开下图所示的视图。在这里,可以配置与 Azure Boards 的连接:

要配置连接,必须填写以下详细信息:

  1. 输入 Azure DevOps 链接。此时需要附加上组织的名称。

  2. 选择要使用的 Azure DevOps 项目。可以从下拉菜单中选择。

  3. 选择一个产品区域,在该区域中将创建新的项目项。默认情况下,这是与项目名称相同,除非你更改它。

  4. 为新的工作项提供一个用户名称,作为默认所有者。

配置此连接后,在 Application Insights 的相关页面上会显示一个新的“创建工作项”按钮。此按钮允许您直接在待办事项列表中创建一个包含所有相关信息的缺陷。

Grafana

Azure Monitor 允许您构建简单、易用的仪表板。使用 Azure Monitor 仪表板的优势在于,它们与所有其他 Azure 实践(例如基于角色的访问控制RBAC)和 Azure 资源管理器模板)完美集成。

然而,团队可能已经采用了其他可视化工具,如 Grafana。Grafana 是一个广受欢迎的平台,非常适合用于操作仪表板。Grafana 可以配置为通过 Azure Monitor 进行连接,并查询指标以生成图表。Grafana 还具有告警功能,但不支持查询日志。

要将 Grafana 连接到 Azure Monitor,需要执行以下步骤:

  1. 在您的 Azure 订阅使用的 Azure Active Directory 帐户中创建一个新的应用注册。记下该应用注册的Tenant IdClient IdSubscription IdClient Secret属性。

  2. 为应用注册创建新的 RBAC 角色分配,并为要监控的资源至少设置Reader权限。

  3. 在 Grafana 中配置一个新的 Azure Monitor 类型的数据源。插入在第 1 步中收集的用于 Azure 身份验证的属性。

  4. 向仪表板添加一个新的图表,选择 Azure Monitor 作为数据源。

通过执行上述步骤,可以在几分钟内设置 Grafana 与 Azure Monitor 的连接。

总结

在本章中,您学习了如何开始完成 DevOps 循环。您还学会了如何处理崩溃报告,并从各种应用程序中收集它们,以及如何为 Web 应用程序添加监控代码。您现在知道如何使用 Application Insights 集中日志和指标,并深入了解请求和依赖调用。您还学会了如何将 Azure Monitor 与其他工具集成,以进一步简化您的开发过程。

通过这些知识,您现在可以开始了解您的应用程序如何在生产环境中运行。通过这样做,您不仅可以更快地交付软件,还可以从使用中学习并从中进行改进。

在下一章中,您将学习如何收集用户反馈,以补充从系统日志和指标中学到的内容。您还将学习如何衡量应用程序和新功能的最终用户满意度。

问题

在我们总结时,这里有一系列问题供您测试对本章内容的理解。您可以在附录的评估部分找到答案:

  1. 判断正误——是否可以使用 Application Insights 捕获 Azure 平台提供的自定义指标?

  2. Azure Monitor 中平台指标的保留时间是多少?

  3. 判断题 – 使用 Application Insights 可以从您自己的应用程序代码中捕获自定义指标。

  4. 你怎么称呼工程师因为警报过多而开始忽视警报的情况?

  5. 判断题 – 在 Azure 中,触发警报时可以调用 webhook。

进一步阅读

第十一章:收集用户反馈

在上一章中,你学习了如何衡量应用程序在生产环境中的表现。你学习了如何收集崩溃报告和日志,以及如何对应用程序进行仪器化。然而,软件的目的不仅仅是交付运行完美的应用程序,更重要的是创造业务价值。收集用户反馈对于判断你的应用程序是否也在实现这一更高的目标是必要的。在本章中,你将学习如何衡量用户的满意度,了解他们使用哪些功能以及哪些功能未被使用,进而如何利用这些信息来指导未来的开发。

为此,本章首先介绍持续反馈的概念。接下来,它将介绍不同的方式来请求用户反馈并记录他们的回应。这可以是应用内的,也可以通过其他渠道。此外,除了直接收集反馈外,你还可以利用其他间接渠道。例如,Twitter 上对你的软件的反应,以及应用程序中各功能的使用情况。最后,本章将介绍假设驱动开发,这是微软实践的一种软件开发方法。

本章将涵盖以下主题:

  • 理解持续反馈

  • 请求反馈

  • 收集间接反馈

  • 实施假设驱动开发

技术要求

本章没有技术要求。

理解持续反馈

正如在第一章中所解释的,DevOps 简介,DevOps 是一个文化运动,旨在将开发人员和运维人员更加紧密地联系在一起,帮助他们更快、更可靠地交付业务价值。反馈循环是实现这一目标的重要元素。在上一章中,我们看到了许多反馈循环:

  • 开发人员可以在本地机器上运行单元测试,以验证他们的更改没有破坏现有行为。

  • 源代码提交后,所有单元测试将重新运行,并启动一个包含更多测试的流水线。

  • 除了功能测试,还可以进行安全测试和依赖性扫描。

  • 发布后,收集日志和度量指标,以确定应用程序是否平稳运行。

所有这些都提供了关于工作技术质量的反馈,现在是时候增加一个反馈环节——一个用于验证应用程序是否真正满足用户需求的环节。

尽管听起来显而易见,但实际上,这个问题经常被开发者忽视,远超过他们愿意承认的程度。在许多公司中,人们信任产品负责人或业务分析师,并相信他们能够预测用户需要哪些功能,并按优先级排序。

我们知道,软件开发是一项复杂的活动,其中某些更改的结果往往无法预先预测。在这种情况下,持续寻找用户反馈非常重要,以确定功能是否在提供应有的价值。

持续寻求反馈有助于做出以下决策:

  • 移除大多数用户未使用的功能;这将减少对它们的维护需求,从而降低成本并释放开发时间。

  • 扩展用户最常使用的功能,使其在界面中更加突出

  • 根据用户对应用程序感知的质量来增加或减少测试工作量

更进一步思考,我们可能会得出结论:无法预测一个功能是否能够真正提供足够的商业价值以证明其存在的合理性。那些进行此类开发的公司通常采用假设驱动开发的方法,稍后会讨论此方法。

首先,接下来的章节将介绍请求应用用户反馈的不同方法。

请求直接反馈

收集用户反馈的一种非常直接的方式就是直接请求反馈。在过去的几年里,越来越多的应用程序内置了反馈机制。其他常见的方法包括发布公开的产品路线图和与客户直接互动。

应用内反馈的优点

在应用内收集反馈是获取直接用户反馈的一个好方法。应用内反馈的例子包括对特定视图或操作进行评分,给出点赞或点踩,或者发送一个开心或难过的笑脸。

收集应用内反馈有以下优点:

  • 这是客户提供反馈的最简单方式,几乎不占用他们的时间。

  • 由于这种方法的非侵入性,更多的用户可能会选择响应。

  • 记录的反馈可以是上下文感知的。

  • 在记录反馈时,应用程序还可以记录当前状态和最近的用户活动,并将这些信息与用户反馈一起发送。这使得用户的单击操作比看起来更有价值,它可以快速揭示应用程序中最受欢迎和最不受欢迎的部分。

  • 最终,允许应用内反馈可以让用户感受到被倾听和重视。

当然,记录用户及其使用应用程序的方式的数据需要获得他们的同意。必须完全透明地告知你打算如何使用收集到的用户信息。此外,通常还需要明确的内容同意选项,并提供撤回先前同意的选项。具体要求因国家而异,并且是一个法律考虑因素。

这种反馈方式的缺点是,分析这些反馈可能会过于复杂。而且,由于结果通常是匿名的,无法进行后续跟进,这使得很难了解用户为什么对某个界面感到满意或不满意。有时会通过在反馈框下添加一个复选框来缓解这个问题,内容可能是:“我同意被联系并提供一次性反馈。”

为了理解用户反馈的原因,其他反馈机制,如访谈或焦点小组,可能更为合适。

拥有一个公开的路线图

另一种收集用户反馈的方法是公开分享当前的待办事项列表,以及哪些功能尚未包含其中。有一个团队公开分享他们正在开发的功能,那就是 Azure DevOps 团队。当然,这个列表并不包含产品组计划中的所有功能。这样做的原因可能是为了保持竞争优势,或者在重大公告之前保持某些新功能的保密。然而,他们的待办事项列表确实为人们提供了当前正在开发的功能的良好视角。

采用这种做法可以让产品用户接触并评论这个公开列表。用户可以请求将某些功能上移或下移优先级列表,他们还可以分享自己希望添加哪些功能。

这种方法可以为公司带来以下优势:当用户参与对功能列表的反馈时,他们被鼓励说明为何提出某些请求。这可能会提供关于客户需求的新见解,并可能导致优先级的调整。

这种方法也有一些缺点:

  • 不是所有用户都会参与并对公开的待办事项列表提供反馈。这可能会导致在提供反馈的用户群体中出现偏向于声音更大或需求更高的客户。虽然这不一定是个问题,但值得牢记这一点。

  • 与用户互动,处理他们的功能请求或他们希望被上移或下移的功能,可能会非常耗时。尤其是与产品内反馈相比,这种方法需要更多时间。

除了拥有公开的功能开发路线图外,还有其他方式可以让用户了解公司当前正在做的事情以及未来的规划。以下是一些示例:

  • UserVoice:UserVoice 是一个平台,允许用户提出新功能并投票支持其他人提议的功能。它允许收集用户的想法,而无需向用户开放实际的待办事项列表。

  • Bugtrackers:如果客户对报告应用程序中的错误和问题非常积极,开放一个 Bugtracker 可能会有所帮助。这可以让用户看到哪些问题已经被发现,并了解这些问题是否以及何时可能被修复。

公共待办事项列表和类似 UserVoice 的平台比开放的待办事项列表更常见。公开的错误或问题列表通常出现在开源开发中。

使用访谈或焦点小组

请求用户反馈的其他方式包括一对一访谈和焦点小组。虽然这些方法比开放的待办事项和公共讨论更加耗时,但它们的优势在于能够更平衡地选择用户。

例如,如果一个应用程序明显针对四个不同的市场细分,那么拥有五个焦点小组——每个市场细分一个,再加一个混合的焦点小组——会是有益的。前四个小组将有助于聚焦每个群体的具体需求,而第五个小组则会激发大量讨论,并提供关于不同群体间不同需求对比的见解。

访谈和焦点小组不仅更适合获取反馈,还能帮助理解用户的思维过程。与用户面对面交流可以深入探讨他们的思维方式,以及他们如何看待应用程序。

这部分结束了关于直接用户反馈的讨论。在下一节中,将讨论间接用户反馈。

收集间接反馈

在软件开发中有一句著名的话:“用户不知道他们想要什么”。虽然这听起来很严厉,但有几个原因说明为什么直接从讨论、访谈和焦点小组获得的用户反馈不一定能得到好的产品反馈:

  • 其中一个原因是每个人都希望被喜欢。当进行访谈或与一组用户交谈时,他们有可能只会说他们认为访谈者想听到的话。

  • 它有较高的周转时间。安排访谈和焦点小组需要时间,找到一个所有人都能参加的时间可能需要几天甚至几周。

  • 每隔几周就要求同一组用户提供反馈是很困难的。尤其是在尝试确定某个功能是否随着最新更新得到改善时,这一点尤为重要。

基于这些原因,减少请求反馈可能是值得的,转而衡量用户如何在功能层面与应用程序互动,以及他们是否对应用程序所提供的价值感到满意。

一种方法是通过衡量应用程序中的用户行为,并基于此发出指标。在第十章《应用监控》中,介绍了应用程序洞察(Application Insights)来收集应用级指标。虽然指标通常用于发布关于应用性能的度量,但指标也可以用于发布关于应用使用情况的度量。以下是一些例子:

  • 每个页面的访问频率是多少?

  • 有多少次特定操作被执行?

  • 完成某个视图需要多长时间?

  • 有多少用户打开了某个特定的表单,却从未完成它?

收集这些指标可以提供关于用户如何与应用程序互动,以及他们使用或未使用哪些部分的重要见解。

除了使用情况,另一个衡量用户满意度的指标可能是 Twitter 情绪或支持请求的数量。

情绪分析

除了收集产品内的指标外,还有一些可以在产品外收集的指标。例如,Twitter 就是一个信息来源。通过 Azure 云和机器学习算法,现在可以持续分析所有指向特定 Twitter 账户或话题标签的推文,并自动检测突发变化。

甚至有一个 Azure Pipelines 扩展,它可以持续测量 Twitter 情绪,并在情绪过于消极时取消发布进度,阻止其进入下一个阶段。这个扩展作为一个管道门控实现,并且可以在 Azure DevOps 市场中找到。

支持请求

就像 Twitter 情绪一样,可能还有其他可以自动收集的用户满意度指标。持续收集每分钟的支持电话或邮件数量,并检测到某个峰值,可以清晰地指示出用户问题。利用机器学习和系统集成,可以将这些数据用于自动响应或提示用户查看结果。

采用这样的实践可以节省检测生产问题的时间,可能是几分钟甚至几小时。根据用户反馈做出决策可以更进一步。这就是所谓的假设驱动开发,接下来会讨论这个话题。

实施假设驱动开发

软件开发中的风险之一是,团队忙于创建越来越多的功能,以至于忘记反思它们的商业价值,而每个人都知道并非每个功能都是成功的。有些功能可能根本不会被使用,甚至可能被用户讨厌。作为一个行业,我们已经学会了产品负责人很难预测哪些功能会真正被用户喜欢,哪些不会。即使使用之前讨论的所有反馈机制,预测用户需求仍然是困难的。

另一个需要认识的重要点是,每个产品中的功能也会带来未来的成本。每个功能都需要文档支持、维护和更新。这意味着不必要的功能也在推动成本上升。从这个角度看,删除非价值功能而不仅仅是保留它们,甚至尽早从产品中移除它们是有道理的。

假设驱动的开发是一种实践,它首先承认无法预测一个功能是否会带来价值、没有价值,甚至更糟,是否会降低商业价值。接下来,它建议将待办事项中的功能转化为快速、轻量的实验,并在产品中运行这些实验,以确定一个新功能是否增加了价值。

这样的实验可以以类似用户故事的形式写出,例如:我们相信用户希望有一个新的单字段弹出窗口来快速创建预约,而不是使用完整的对话框。当我们看到超过 25%的预约是通过这个新对话框创建的,而且预约的平均批准率提高了 2 个百分点以上时,我们确信这是真的。第一部分是假设,第二部分是验证该假设的确认标准。

一旦这些内容被写下,就会创建一个最小化实现的单字段弹出窗口,并通过度量标准监控其使用情况及原始表单的使用情况。根据测量结果,可能会发生以下情况之一:

  • 假设中陈述的信念被确认是真实的,并且新功能增加了价值。围绕该功能的更多故事可以添加到待办事项列表中,以提升产品所带来的业务价值。

  • 假设中陈述的信念未得到确认,且进一步的实验预计不会得出不同的结果。该功能被从待办事项列表中移除,当前的最小实现甚至可能被从产品中移除。

  • 假设中陈述的信念尚未得到确认,但实验仍在继续。这种情况可能发生在用户对某个功能有大量投诉,而团队决定修复该功能时。如果一种方法不起作用,他们可能会尝试另一种方法。

使用前面概述的方法,团队可以通过最小化在实验后没有增值的功能上花费的时间,来增加他们在业务价值上的影响,甚至将这些功能从产品中移除。

通常,假设驱动开发与分阶段发布机制(如功能标志或部署环)相结合。实验只在少部分用户中进行,这样如果功能没有带来足够的价值,可以更容易地撤回该功能。

这部分完成了关于收集和使用用户反馈以及用户反馈如何与 DevOps 目标——为最终用户交付业务价值——相结合的讨论。

总结

在本章中,你学习了如何衡量软件开发活动的业务成果。首先,你了解了反馈的重要性,以及反馈如何帮助理解客户需求,并判断这些需求是否得到了满足。接着,介绍了多种获取反馈的方法,包括直接和间接的方式。最后,你学习了假设驱动开发,并了解了实验心态如何帮助减少浪费。

有了这些知识后,你现在可以选择并实施反馈机制,以了解用户对你应用的情感态度。你现在能够实施基于实验的方法来创建软件,专注于增值功能,忽略或甚至移除那些没有增值的功能。

在下一章中,你将学习关于容器的所有内容。容器正在迅速改变软件交付的方式,并且通常用于将 DevOps 原则应用于现有和新应用程序。

问题

在我们总结时,以下是一些问题供你测试你对本章内容的理解。你可以在附录的评估部分找到答案:

  1. 对错:公开分享路线图没有任何缺点。

  2. 在评估公开路线图上的用户反馈时,应该牢记哪些重要问题?

  3. 哪两个用户满意度的间接指标相对容易捕捉?

  4. 以下哪项不是假设驱动开发中使用的假设的一部分?

    1. 一个假设

    2. 一个确认阈值

    3. 一个结论

  5. 面试或焦点小组相比于其他反馈收集方式有哪些两个好处?

进一步阅读

第四部分:高级主题

在本节中,您将学习容器以及如何设计您的 Azure DevOps 组织。

容器已经改变,并将继续改变软件的创建和交付方式。此外,您还需要考虑如何创建自己的 Azure DevOps 组织。您如何组织项目和团队决定了人们如何协同工作。

最后一章将帮助您为参加 AZ-400 考试做好准备,并测试您的准备情况。

本节包括以下章节:

  • 第十二章,容器

  • 第十三章,规划您的 Azure DevOps 组织

  • 第十四章,AZ-400 模拟考试

第十二章:容器

在过去的几年里,容器已成为热门话题。它们允许你将任何应用程序、任何工具(无论是用任何语言编写)打包,并将其部署到基本主机或集群上。在实施 DevOps 时,容器可以带来巨大的价值。这就是为什么 DevOps 和容器常常被同时提及的原因。然而,它们并不是同一回事。虽然 DevOps 更像是一种文化现象,容器则是一种技术,是托管应用程序的替代方式。

在本章中,你将了解更多关于容器以及它们是如何工作的。通过练习,创建自定义容器镜像并在不同的托管平台上运行,例如 Azure 容器实例和 Kubernetes。

本章将涵盖以下主题:

  • 容器简介

  • 构建容器镜像

  • 在 Azure DevOps 中构建镜像并在 Azure 中运行

  • Kubernetes 介绍

  • Kubernetes 实战

  • 升级容器

  • 容器和 Kubernetes 的扩展

  • 使用 Azure DevOps 部署到 Kubernetes

技术要求

要实验本章描述的技术,你需要以下一项或多项:

  • Docker Desktop

  • Visual Studio 2019

  • Azure 订阅

  • Azure CLI

所有这些都可以免费获得,或者在有限的时间内免费用于评估目的。

容器简介

容器是虚拟化的演进。通过虚拟化,物理机器的资源在多个虚拟机之间共享。共享这些资源也意味着每个虚拟机都有自己的操作系统。而使用容器时则不同。容器不仅共享资源,还共享操作系统内核,使其与虚拟机镜像相比非常小。

由于操作系统内核是共享的,容器也非常便携。镜像可以部署到任何支持运行容器的主机环境中。这是因为应用程序的所有二进制文件和配置都存储在容器内。因此,容器外部的环境变量不会影响应用程序。当然,也有一些注意事项:容器共享操作系统内核;Linux 容器只能在 Linux 操作系统上运行,Windows 容器也同样如此。

容器提供了虚拟化操作系统的能力,以便在单一操作系统上运行多个工作负载。以下图表展示了常规托管、虚拟机托管和容器之间的区别:

如果你曾经听说过容器,那么几乎可以肯定你也听说过 Docker。这是因为 Docker 是最著名的容器引擎之一,用于运行容器。下一节将深入探讨 DevOps 和容器,剩下的章节将详细介绍有关容器的更多技术细节。

DevOps 和容器

如在介绍中提到的,DevOps 和容器并不是同一个概念。容器是让 DevOps 更加容易的技术。这是因为容器具有一些优点,使其成为 DevOps 的完美工具:

  • 一致性:因为你构建了容器镜像,所以"它在我的机器上运行"这一问题被消除了。

  • 关注点分离:使用容器时,您的应用程序将分布在不同的容器之间,这使得维护和分离进程变得更加容易。

  • 平台:该解决方案可以在不同的平台上运行。无论是在 Azure、Amazon Web Services 还是本地环境中运行,都没有关系。

扩展来说,DevOps 更多的是一种文化,而非技术。如在第一章《DevOps 介绍》中提到的,技术组件 用于支持 DevOps。在本章的其余部分,我们将专注于技术方面的内容。

托管选项

如前所述,容器的一个好处是它们非常便携。这也意味着容器可以托管在许多平台和技术上。

要运行容器,有许多选项会根据您的使用案例有所不同。以下是其中一些选项:

  • Azure 应用服务

  • Azure 服务框架

  • Docker Swarm

  • Docker 桌面版

  • Kubernetes

根据应用程序/容器的需求,它可以运行在前面提到的所有选项中。

用于运行容器的镜像(容器镜像)也需要托管。这些镜像托管在一个所谓的容器注册表中。在容器注册表中,它们可以是私有或公开发布的。两个最著名的注册表是 Docker 注册表和 Azure 平台中的 Azure 容器注册表。

现在我们已经了解了关于容器的一些背景信息,接下来我们将深入探讨容器背后的技术,并了解创建自定义容器镜像所需的内容。

构建容器镜像

本节将带领您了解构建容器镜像并在本地系统上执行的过程。为此,我们首先需要创建一个应用程序,然后为其添加 Docker 支持,接着创建镜像并最终进行测试。让我们开始吧!

创建应用程序

要能够测试和检查容器中运行的内容,必须有一个应用程序。为此,可以创建一个新应用程序,也可以使用现有的应用程序。

创建新应用程序时,最简单的选项是在 Visual Studio 2019 中使用默认的 ASP.NET Core 网站模板。可以通过几次点击添加容器支持。只需在创建项目时勾选“启用 Docker 支持”框即可。

保持新应用程序处于打开状态,或打开现有应用程序。在下一节中,我们将探讨如何向现有应用程序添加 Docker 支持。

向现有应用程序添加 Docker 支持

向现有应用程序添加 Docker 支持需要几个简单的步骤:

  1. 打开 Visual Studio 2019 中的项目/解决方案,右键点击项目。

  2. 选择“添加”并选择“Docker 支持”:

根据你的客户端工具和 Visual Studio 配置,也可能会有一个“容器编排器支持”选项。通过该选项,可以选择你选择的云编排器。在此示例中,我们使用 Docker,因为这一格式被主要的容器编排器所支持。然而,也存在其他云编排器选项:

  • Docker Swarm

  • Kubernetes

  • Mesos Marathon

根据所使用的云编排器,会向项目中添加一个特定格式的文件。

通过添加 Docker 支持,项目中会新增一个名为 Docker 的文件。Dockerfile 是容器镜像的规范。该文件可以被 Docker 读取,并视为指令。该文件是一个文本文件,包含一系列独立的命令,也可以在命令行工具中调用这些命令来组装镜像:

FROM mcr.microsoft.com/dotnet/core/aspnet:3.0-buster-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
EXPOSE 555
FROM mcr.microsoft.com/dotnet/core/sdk:3.0-buster AS build
WORKDIR /src
COPY ["ExistingDevOpsProject/ExistingDevOpsProject.csproj",
"ExistingDevOpsProject/"]
RUN dotnet restore "ExistingDevOpsProject/ExistingDevOpsProject.csproj"
COPY . .
WORKDIR "/src/ExistingDevOpsProject"
RUN dotnet build "ExistingDevOpsProject.csproj" -c Release -o
/app/build
FROM build AS publish
RUN dotnet publish "ExistingDevOpsProject.csproj" -c Release -o
/app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ExistingDevOpsProject.dll"]

本示例使用了一种名为多阶段构建文件的技术。这是因为该文件使用了多个 FROM 语句,每个语句都引用了特定的镜像。

在多阶段构建之前,无法使用多个 FROM 语句。在那时,构建高效的容器镜像非常困难。文件中的每个语句代表镜像上的一个额外层,导致镜像越来越大。

在构建过程中,还需要移除任何在该过程中需要的组件。因此,开发和生产通常会有单独的 Dockerfile。

如前所述,Dockerfile 由一系列指令组成,其中最常用的指令如下:

  • FROMFROM 命令用于指定镜像基于的操作系统或基础镜像。在本例中,mcr.microsoft.com/dotnet/core/aspnet:3.0-buster-slim 镜像用于应用程序的生产版本,mcr.microsoft.com/dotnet/core/sdk:3.0-buster 镜像用于构建镜像。

  • RUNRUN 命令用于在构建容器镜像的过程中安装组件或执行操作。

  • ENTRYPOINTENTRYPOINT 命令指定容器镜像的入口点需要是什么。在此示例中,入口点指定为一个 .NET 应用程序,它引用了编译过程中构建的库。

到目前为止,我们已经创建了应用程序并添加了 Docker 支持。接下来,我们将看看如何用这个应用程序创建镜像。

使用应用程序创建镜像

要能够创建 Docker 镜像,需要安装 Docker Desktop,因为 Visual Studio 会使用它来构建镜像。通过完整的 Dockerfile,可以使用以下步骤来构建镜像:

  1. 在 Visual Studio 中右键单击 Dockerfile 并选择构建 Docker 镜像:

  1. 在镜像的编译和构建过程中,请查看输出窗口。查看它可以提供有关容器镜像分层方法的更多见解。此分层方法可以通过输出窗口中显示的步骤来查看:

  1. Docker Desktop 还使得在本地运行和存储镜像成为可能。构建镜像后,打开终端并运行以下命令:
docker images

该命令会显示当前机器上所有的镜像。在这个列表中,创建镜像时下载的基础镜像也会列出。

运行容器镜像

容器镜像可以通过在 Docker 中运行来本地启动。现在我们已经有了一个容器镜像,可以创建一个容器:

  1. 运行以下 docker container run 命令:
docker container run --publish 8123:80 --detach --name [container name] [image name]

上述命令将启动命令末尾指定的容器镜像。此外,还指定了不同的参数:

    • 发布publish 参数会将主机的端口打开到容器中。如示例所述,这将打开端口 8123 并将流量路由到容器内的端口 80

    • 分离detach 参数会在后台运行容器,并打印出其特定 ID。

    • 名称:Docker 中容器的名称。

  1. 要列出所有正在运行的容器,可以在终端中使用 docker ps 命令。

  2. 容器运行时,打开浏览器并导航到 http://localhost:8123。如果一切正常,这将显示默认的 ASP.NET Core 网页:

由于在本地构建并在自己的机器上运行并不是 DevOps 思维方式的体现,我们将在接下来的章节中转向一个不同的托管平台。

在 Azure DevOps 中构建镜像并在 Azure 中运行

为了支持持续集成和持续交付,源文件需要存储在一个代码库中。因此,让我们在 Azure Repos 中共享资源,并尝试使用 Azure Pipelines 构建我们的容器。构建完容器镜像后,还需要一个存储镜像并运行容器的地方。在 Azure 平台中,有两个非常适合此场景的服务:

  • Azure 容器注册表:此服务是基于开源 Docker 注册表的托管私有 Docker 注册表。在这里,你可以维护和注册容器镜像。

  • Azure 容器实例:Azure 容器实例,也称为 ACI,是一种运行隔离容器的解决方案,无需过多的管理。

为了简化本指南,文件已预先添加到仓库中,且 Azure 资源已经创建。

创建服务端点

正如书中所讨论的,Azure DevOps 中与 Azure 和容器注册表等外部服务的连接是通过服务端点进行配置的。由于镜像需要可用,以便 Azure 容器实例能检索到它,因此需要将其发布到容器注册表。Azure DevOps 到注册表的连接是在服务连接中配置的。

执行以下步骤来配置服务连接:

  1. 在 Azure DevOps 项目中,打开项目设置。

  2. 在项目设置中,点击服务连接。

  3. 在服务连接概览中,点击创建服务连接并选择 Docker 注册表。

  4. 在弹出的窗口中,填写正确的信息并保存连接:

保存连接将为项目添加一个服务连接,供我们创建的管道或你未来创建的管道使用。

创建新管道

为了能够开始构建容器镜像并将其发布到注册表,我们将创建一个新的管道。对于这个示例,我们将使用 YAML 管道体验。

执行以下步骤以开始使用管道:

  1. 打开你的 Azure DevOps 项目并点击管道。

  2. 在管道概览中,点击新建管道。

  3. 选择 Azure Repos Git,选择正确的仓库,然后选择启动管道:

  1. 从启动管道中删除两个虚拟脚本任务,并打开助手。

  2. 在助手中,搜索 Docker 任务并将任务添加到管道中。

  3. 选择为容器注册表创建的服务连接,并保持其他信息为默认设置。

确保将任务的 buildContext 属性更改为指向正确的目录。这对于 Docker 能够在构建镜像时引用正确的路径是必要的。

添加后,YAML 文件应如下所示:

- task: Docker@2
  inputs:
    containerRegistry: 'MSFT Container Registry'
    repository: 'azuredevops'
    command: 'buildAndPush'
    Dockerfile:'**/Dockerfile'
    buildContext:
'$(System.DefaultWorkingDirectory)/ExistingDevOpsProject'
  1. 保存并运行管道。第一次运行后,容器镜像将被创建并发布到容器注册表。

容器注册表中的镜像可以通过使用预定义的 URL 进行检索。该 URL 包含几个特定的组件:

    • [container registry]/[repository]:[tag]

      • 容器注册表:容器注册表的基础 URL。

      • 仓库:在发布镜像过程中指定的仓库。

      • 标签:镜像特定版本的标签。默认情况下,使用的 Docker 标签是BuildId

  1. 现在我们已经有了容器镜像的引用,Azure 容器实例应该能够提取该容器并运行它。所需的只是一个 Azure CLI 命令:
az container create --resource-group [resource group] --name [ACI name] –location westeurope –image [Image reference] --dns-name-label [dns reference] –ports 80 --registry-username [username of the registry] --registry-password [password of the registry]

由于每个构建的镜像引用不同(标签值为BuildId),因此BuildId将在 Azure CLI 命令中通过$(Build.BuildId)变量获取:

az container create --resource-group aci-rg-devops --name aci-demo-app –location westeurope –image msftazuredevops.azurecr.io/azuredevops:$(Build.BuildId) --dns-name-label aci-msft-demo –ports 80 --registry-username $(username) --registry-password $(password)

要执行上述脚本,必须将 Azure CLI 任务添加到管道中。在此任务中,我们通过服务端点配置正确的订阅,并设置内联脚本。

脚本将在aci-rg-devops资源组中创建一个名为aci-demo-app的容器实例,并从msftazuredevops.azurecr.io仓库中提取azuredevops容器镜像。

该任务的完整 YAML 如下所示:

- task: AzureCLI@2
  inputs:
  azureSubscription: 'Subscription MPN'
  scriptType: 'bash'
  scriptLocation: 'inlineScript'
  inlineScript: 'az container create --resource-group aci-rg-devops -
name aci-demo-app --location westeurope --image msftazuredevops.azurecr.io/azuredevops:$(Build.BuildId) --dns-name-label aci-msft-demo --ports 80 --registry-username $(username) --registry-password $(password)'

运行此管道将导致在 Azure 中创建一个 Azure 容器实例。该容器将运行与本地运行的应用程序完全相同的内容:

打开 Azure 门户中的 Azure 容器实例时,您将看到它是一个正在运行的实例,并且根据 Azure CLI 命令中提供的值dns-name-label,会附带一个 FQDN(完全限定域名),即aci-msft-demo.westeurope.azurecontainer.io。在浏览器中打开此 URL,查看我们已推送到容器的应用程序:

它显示的内容与在本地启动的容器相同。这是因为在两个地方,启动的是相同的容器镜像。

在这一部分,我们启动了 Azure 容器实例中的容器,但当容器出现问题时,我们如何管理正在运行的容器并重新启动它们呢?这就是 Kubernetes 的作用所在。

Kubernetes 简介

Kubernetes 是一个用于运行容器的服务。它是由 Google 最初开发的集群编排技术,现在是一个开源平台,用于自动化应用容器在主机集群上的部署、扩展和操作,从而提供以容器为中心的基础设施。

Kubernetes 的功能

如前所述,容器为您提供了一个很好的打包应用程序的方式。在运行应用程序时,您需要确保应用程序继续运行,这正是 Kubernetes 的核心功能之一:

  • 服务发现与负载均衡:容器的暴露方式在 Kubernetes 中进行控制,此外,它还能够在编排过程中平衡流量。

  • 存储编排:能够将不同类型的存储提供商挂载到平台上。

  • 发布与回滚:Kubernetes 可以自动为指定的部署创建和重启容器。

  • 自愈:Kubernetes 可以在容器失败时进行自愈。

  • 密钥和配置管理:Kubernetes 内建功能来管理机密信息,如令牌、密码和密钥。

为了提供这些功能,Kubernetes 由多个组件组成。

Kubernetes 核心组件和服务

Kubernetes 由一些核心组件组成,这些组件共同工作,提供一个出色且稳定的产品来运行和管理容器。接下来的几个小节将逐一介绍这些组件。

主节点

Kubernetes 中一个重要的组件是主节点。主节点负责管理集群,它包含所有 Kubernetes 核心组件,以便管理集群:

  • kube-apiserver:一个用于暴露 Kubernetes API 的组件。该 API 被 Kubernetes 的管理工具使用,例如 kubectl 和 Kubernetes 仪表盘。

  • etcd:用于保持 Kubernetes 集群的状态。

  • kube-scheduler:一个组件,用于选择运行 Pods 的节点。

  • kube-controller-manager:控制器管理器监督多个较小的控制器,执行诸如复制 Pods 和管理节点操作等任务。

通过使用这些组件,主节点可以保持集群的期望状态。需要知道的是,当你与 Kubernetes 交互时,你实际上是在与主节点进行通信。主节点随后会与集群内的其他组件进行通信。

常规节点

这些节点是运行容器的节点。它们可以是虚拟机,甚至是物理机器。在这些机器上,安装了所谓的 kubeletkubelet 是一个代理,用于在节点内运行 Pods/容器。

正如你在前面的章节中可能已经注意到的,Kubernetes 内部还有其他核心服务,我们接下来将讨论这些服务。

Pod

在 Kubernetes 中,Pods 用于运行应用程序。在 Pods 内部,指定了运行应用程序所需的资源。Kubernetes 中的调度器(kube-scheduler)会根据需求和与集群关联的节点来检查在哪里运行应用程序。

Pod 本身有一个有限的生命周期,在部署新版本时会被删除,或者例如当节点失败时,Pod 可以被同一节点或其他节点上的 Pod 替代。

服务

该服务有时也被称为负载均衡器,用于提供 Pods 的逻辑分组并为它们提供连接性(即连接方式)。

三个主要服务如下:

  • 集群 IP:为一个 Pod 集群添加一个内部 IP 地址。

  • 节点端口:将端口映射到底层节点目录,以便通过节点的 IP 地址连接到应用程序/Pod。

  • 负载均衡器:此服务添加一个负载均衡器资源,并在负载均衡器上配置一个外部 IP 地址。在外部,负载均衡器会根据在负载均衡器中配置的规则将流量路由到特定节点,并在内部将流量路由到正确的 Pod。

通过这些服务,Pods 的内部和外部连接被安排好了。服务和 Pods 都在部署中指定。

部署

部署描述了应用程序的期望状态。它描述了副本的数量,还包括更新策略。Kubernetes 会跟踪 Pod 的健康状况,并在需要时移除或添加 Pod,以符合部署中描述的期望状态。

这些部署在 YAML 文件中指定。例如,在 Kubernetes 中运行容器时,必须指定副本集。副本集确保在任何给定时间都有指定数量的 Pod 副本在运行。

Kubernetes 操作

当你刚接触容器,尤其是 Kubernetes 时,可能很难立刻弄明白。不过,为了帮助你理解这个概念,请看以下图示:

容器部署到 Kubernetes 集群中是在所谓的部署文件 (1) 中定义的。在这些部署文件中,描述了应用程序的期望状态。这个期望状态被描述为一个 YAML 文件。

在这个例子中,期望状态是一个负载均衡器服务和三个 Pod (2)。这些 Pod 被 Kubernetes API 在运行容器的节点上划分 (3)。在部署文件中定义的服务确保流量被路由到特定的 Pod。可以通过更新部署来更改部署。

调度程序还可以在配置了自动扩展应用程序的情况下更改部署。例如,在这种情况下,可能会向集群中添加第四个 Pod。在服务中,也可以有一个外部负载均衡器,将流量路由到 Kubernetes 的内部负载均衡器 (4)。

Azure Kubernetes 服务

Azure Kubernetes 服务,或称 AKS,是 Microsoft 实现的 Kubernetes。设置一个常规的 Kubernetes 集群需要很多工作,但使用 AKS 后,它变得更加简单。这是因为 Kubernetes 是一个托管平台,几乎所有的操作任务都由平台本身处理。

AKS 的一些关键功能如下:

  • Azure 管理关键任务,如健康监控和维护,包括 Kubernetes 版本升级和修补。

  • Azure 执行简单的集群扩展。

  • Kubernetes 的主节点是完全托管的。

  • 主节点是免费的,你只需为运行中的代理节点付费。

通过使用 AKS,Kubernetes 集群可以在几分钟内投入使用。此外,重点将放在应用程序上,因为主节点是完全托管的。现在,让我们尝试使用自定义镜像运行一个 Kubernetes 集群。

Kubernetes 实战

在本章的前几节中,我们创建了一个容器并将其部署到 Azure 容器实例中。现在让我们将该容器部署到 Kubernetes 集群中。

创建集群可以通过 Azure CLI 或 ARM 模板完成。为了方便演示,本示例使用 Azure CLI。

首先,需要创建一个新的资源组来托管 Azure Kubernetes 集群:

az group create --name mpn-rg-kubernetes --location westeurope

现在,我们可以创建我们的 Kubernetes 集群。

创建 Kubernetes 集群

创建资源组后,可以向该组添加新的 Kubernetes 集群:

az aks create --resource-group mpn-rg-kubernetes --name mykubernetescluster --node-count 1 --enable-addons monitoring --generate-ssh-keys

此命令将创建一个名为 mykubernetescluster 的新 Kubernetes 集群,并且只有一个节点。这意味着在 Azure 门户中将创建一个虚拟机,配置为 Kubernetes 集群的节点。此外,集群上将启用监控附加组件。

创建该集群将需要几分钟时间。在 Azure 中,将在指定的资源组中创建 mykubernetescluster 服务。同时,Azure 平台本身还会创建另一个资源组。

Kubernetes 基础设施

在此资源组中,创建了运行集群所需的所有虚拟化基础设施。这也意味着未来可以根据应用程序的需求向该资源组添加新组件:

在创建的资源组中,您将找到运行集群所需的所有资源:

现在,Kubernetes 基础设施已启动并运行,可以开始资源的管理和部署。

管理 Kubernetes

要管理 Kubernetes,使用 kubectl 命令行工具并在本地安装(或在 Azure 云控制台中使用)。这是一个命令行界面工具,将与 Kubernetes API 通信。让我们看看如何使用这个命令行工具与 Kubernetes 配合工作:

  1. 如果您尚未安装 Azure CLI,请运行以下命令在您的机器上安装 Azure CLI:
az aks install-cli
  1. 要连接到集群,需要检索凭据并将其保存到本地系统。这可以通过使用 az aks get-credentials 命令并指定资源组和集群名称来完成:
az aks get-credentials --resource-group mpn-rg-kubernetes --name mykubernetescluster
  1. 配置好所有前提条件后,许多基本功能可以针对 Kubernetes 集群运行。例如,可以参考以下两个命令:
    • 检索集群中的节点:
kubectl get nodes
    • 获取集群中的 Pod:
kubectl get pods
  1. 除了前面的命令,您还可以尝试以下 Azure CLI 命令来打开 Kubernetes 仪表板。此仪表板是建立在 Kubernetes API 之上的管理界面,可以与 kubectl 命令行一起使用:
az aks browse --resource-group mpn-rg-kubernetes --name mykubernetescluster

仪表板显示在以下截图中:

需要创建一个部署文件才能在集群中运行容器。现在我们来看看如何做这件事。

部署容器镜像

我们将创建一个部署文件并将其部署到 Kubernetes。为此,请执行以下步骤:

  1. 在你喜欢的文本编辑器中创建一个新文件,并命名为deploy.yaml。将以下信息添加到deploy.yaml文件中:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubernetes-deployment
  labels:
    app: customapplication
spec:
  replicas: 3
  selector:
    matchLabels:
      app: customapplication
  template:
    metadata:
      labels:
        app: customapplication
    spec:
      containers:
      - name: azuredevops
        image: msftazuredevops.azurecr.io/azuredevops:586
        ports:
        - containerPort: 80

在这个示例中,指定了以下内容:

    • 部署将使用kubernetes-deployment作为名称创建(metadata.name)。

    • 部署将创建指定容器的三个副本(spec.replicas)。

    • 选择器结合标签标签,用于指定该部署文件将在 Kubernetes 中管理哪些组件。

    • 部署文件将为msftazuredevops.azurecr.io/azuredevops:586镜像文件创建一个容器。

  1. 要将此文件部署到 Kubernetes,我们将再次使用kubectl命令行并使用apply命令:
kubectl apply -f deploy.yaml

-f参数用于指定使用本地路径作为部署文件的引用。执行命令后,你可以打开 Kubernetes 仪表盘查看状态,甚至可能观察到错误。

可能会遇到一个错误,提示从你的位置拉取镜像失败。这可能是一个安全问题。在后台,AKS 使用一个服务主体。你在创建新的 Kubernetes 集群时应该也会看到这个问题。确保为该服务主体授予 Azure 注册表的访问权限。

  1. 在执行成功后,尝试使用get pods命令查看系统中是否有三个 pod。如果一切顺利,Kubernetes 中应该运行着三个 pod,但应用程序仍然无法对外部访问。

为了使其可用,我们需要向部署文件中添加一个服务。

如果你想将其添加到同一个文件中,在不同的部署之间添加一行---字符。这在你为部署定义单独文件时不是必需的。

deploy.yaml文件中,添加以下部分:

---
apiVersion: v1
kind: Service
metadata:
    name: customapplication-service
spec:
    type: LoadBalancer
    ports:
    - port: 80
    selector:
        app: customapplication

此 YAML 部分创建一个负载均衡器,并将其附加到指定的选择器(spec.selector.app),意味着它将用于我们之前指定的 pod。

在后台,Kubernetes 将创建一个 Azure 负载均衡器和一个公共 IP,以便与 pod 连接。

  1. 要获取服务的外部 IP 地址,请使用以下命令,直到显示外部 IP 地址:
kubectl get service

这将返回所有服务及其外部 IP 地址(如果存在)。还可以快速查看 Kubernetes 的附加资源组,看看哪些 Azure 资源已被创建。

做得好!在这一部分,你学会了如何创建一个 Kubernetes 集群,并通过kubectl和部署文件在其上部署容器镜像。在下一部分,我们将继续学习如何升级这些容器。

升级容器

在 Kubernetes 中,应用程序更新非常简便。为此,Kubernetes 使用滚动更新,这意味着在替换容器之前,流量会首先被排空。在应用程序升级过程中,Kubernetes 会部署一个额外的 Pod 并通过一些指定的探针进行检测。

探针是一种定期对 Pod 执行的诊断操作,用来检查其状态。在升级或创建 Pod 时,Kubernetes 会启动额外的 Pod,并确保它通过存活和就绪探针。

如果新创建的 Pod 成功通过了两个探针的检查,那么对旧 Pod 的流量将被终止,流量将转向新 Pod。为了终止旧 Pod 的流量,Kubernetes 使用一个终止宽限期。在这个期间,连接到负载均衡器的连接会停止,活动连接将被成功处理,新的流量将被路由到一个正在运行的 Pod。默认的宽限期是 30 秒,在此期间,Pod 将处于终止状态,所有流向该 Pod 的旧流量将被重定向到其他 Pods。

这个过程会持续进行,直到所有 Pods 都被替换为新版本。这一切都是 Azure Kubernetes 中的默认行为。通过调整部署文件并使用与之前相同的命令应用部署,便可触发一次部署:

Kubectl apply -f [file]

默认情况下,httpGet 探针会添加到正在暴露的 Pods 中,但也可以通过向部署中添加就绪探针或存活探针来进行自定义:

readinessProbe:
          httpGet:
             path: /
             port: 80
             initialDelaySeconds: 5
             periodSeconds: 5
             successThreshold: 1

此就绪探针对 Pod 执行一个 httpGet 请求,并具有以下选项:

  • path:它应该为 httpGet 请求调用的路径。

  • port:调用时应使用的端口号。这个端口号也在我们的部署文件中进行配置。

  • initialDelaySeconds:容器启动后,探针第一次运行前等待的秒数。

  • periodSeconds:探针等待超时之前的秒数。

  • successThreshold:探针所需的成功次数,最小值为 1

如前所述,部署有一个默认的滚动升级场景配置。可以使用以下命令来获取滚动部署的配置:

kubectl describe deployment kubernetes-deployment

如果你有兴趣这样做,构建一个新版本的容器并在 Kubernetes 中进行升级。在运行升级之前,确保打开仪表板,并在更新期间刷新页面,你将看到额外的 Pods 启动并且旧的 Pods 被终止。

在本节中,我们学习了如何升级容器,这将帮助你保持最新版本。接下来,在下一节中,我们将进一步探讨容器和 Kubernetes 的扩展。

扩展容器和 Kubernetes

随着应用程序需求的增长,你需要扩展应用程序。应用程序的扩展可以通过多种方式完成,不同的组件也可以进行扩展:

上图展示了不同的扩展应用程序或集群的方法,接下来我们将会在以下小节中讨论这些方法。

手动扩展 pods

可以通过更新副本数量轻松扩展 pods。尝试使用 kubectl get pods 命令获取您的 pods,然后使用以下命令增加副本数量:

kubectl scale --replicas=[number of pods] deployment/[deploymentname]

使用此命令,pods 的数量将根据副本的数量进行扩展或收缩。扩展或收缩将根据部署中的设置进行。

自动扩展 pods

Azure Kubernetes 也支持自动扩展。调度程序将根据 CPU 使用率或其他可用指标来更新 pods 的数量。

Kubernetes 使用指标服务器来实现这一功能。指标服务器从在集群中运行的节点上的 kubelet 代理的总结 API 中收集指标。

如果您使用的是 Kubernetes 1.10 或更高版本,默认情况下指标服务是可用的。如果您使用的是较旧版本,您将需要手动安装指标服务器。

自动扩展功能还需要在 Kubernetes 的部署端进行一些配置。对于部署,您需要指定正在运行的容器的请求和限制。这些值是针对特定指标指定的,例如 CPU。

在以下示例中,已为 CPU 指标指定了请求和限制。CPU 指标是以 CPU 单位来衡量的。在 Azure 中,一个单位代表一个核心。不同的平台上可能有不同的含义:

resources:
  requests:
     cpu: 0.25
  limits:
     cpu: 0.5

这部分可以添加到部署文件中的容器中,以确保当需要处理大量请求时,pods 可以自动扩展。

使用更新的部署文件进行部署,并在 Kubernetes 集群中创建一个自动扩展规则:

kubectl autoscale deployment [deployment name] --cpu-percent=60 --min=1 --max=10

该规则将使用自动扩展功能更新部署。如果所有 pods 的平均 CPU 使用率超过其请求使用量的 60%,自动扩展器将把 pods 扩展至最多 10 个实例。然后为部署定义最小 1 个实例:

创建自动扩展器后,您可以通过运行以下命令检查它:

kubectl get hpa

HPA 代表 水平 Pod 自动扩展器

尝试在应用程序中创建一个 CPU 密集型操作,并检查执行期间的自动 pod 创建。Kubernetes 集群将注意到大量 CPU 使用情况,并会通过创建多个 pods 自动扩展集群。

一旦密集操作完成,Kubernetes 将把 pods 的数量缩减到最小值。

节点扩展

除了扩展 pods,Kubernetes 还可以扩展在 Kubernetes 集群中运行的节点数量。可以使用以下命令来扩展节点数量:

  1. 首先,获取有关当前环境的信息,方法是请求节点数量:
az aks show --resource-group mpn-rg-kubernetes --name mykubernetescluster  --query agentPoolProfiles
  1. 然后,使用此命令更新 nodepool。从上一个命令的结果中提取 nodepool 的名称:
az aks scale --resource-group mpn-rg-kubernetes --name mykubernetescluster --node-count 2 --nodepool-name nodepool1

扩展节点数可以大幅提高性能。这也会使集群变得更昂贵。通过缩减集群节点数,可以降低成本,并且只使用应用程序实际需要的资源。为了跟踪这一点,节点还可以自动扩展。

自动扩展节点

除了手动扩展节点外,节点还可以通过更新 Kubernetes 集群自动扩展。这可以通过使用 az aks update 命令来完成。使用此命令,你可以设置最小和最大节点数量。然后,自动扩展器会确保在需要时创建节点:

az aks update --resource-group mmpn-rg-kubernetes --name mykubernetescluster   --update-cluster-autoscaler --min-count 1 --max-count 5

Azure Kubernetes 还可以通过 Azure Container Instances 实现扩展。要使用此选项,在创建 AKS 集群时需要应用特定配置。主要是因为 Azure Container Instances 需要在虚拟网络中指定一个子网。

在本节中,我们学习了如何扩展容器和集群,以大幅提升性能。接下来是从 Azure DevOps 进行部署,以便实现持续部署。

使用 Azure DevOps 部署到 Kubernetes

我们已经看到了许多通过命令行部署和配置 Kubernetes 集群的选项。然而,在使用 DevOps 时,变化需要以持续的方式进行应用。

为此,Azure DevOps 中有 Kubernetes 清单任务,包含许多管理 Kubernetes 集群的功能:

task: KubernetesManifest@0
  inputs:
  action: 'deploy'
  kubernetesServiceConnection: '[service connection name]'
  manifests: '[path to your deployment file]'
  containers: 'msftazuredevops.azurecr.io/azuredevops:$(Build.BuildID)'

在前面的示例中,配置了以下内容:

  • action:我们希望执行的操作类型。在此示例中,使用 deploy 操作,因为我们想要部署/应用一个部署文件。

  • kubernetesServiceConnection:与 Kubernetes 集群的服务连接。

  • manifests:清单文件的路径。由于我们使用的是 deploy 动作,因此这应该是指向部署文件的引用。

  • containers:一个特殊字段,你可以在这里覆盖要部署的容器的版本。通过指定上述内容,每个镜像都在部署清单中指定了 msftazuredevops.azurecr.io 引用,并且 azuredevops 仓库会被替换为此字段中配置的新值。

在 Azure DevOps 管道中使用 Kubernetes 目标环境的另一个优点是能够看到在 Azure DevOps 中运行的环境。这将显示集群中正在运行的 pods 数量。

尝试使用以下阶段配置进行构建,该配置将把部署文件发布到 Azure DevOps 的工件位置:

stages:
  - stage : Build
    displayName : Build
    jobs:
     - job:
       pool:
           vmImage: 'ubuntu-latest'
       continueOnError: false
       steps:
       - task: Docker@2
         inputs:
           containerRegistry: '[Container Registry service connection]'
           repository: 'azuredevops'
           command: 'buildAndPush'
           Dockerfile: '**/Dockerfile'
           buildContext: '$(System.DefaultWorkingDirectory)/[folder path
 for docker]'
       - task: CopyFiles@2
         inputs:
           SourceFolder: '$(system.defaultworkingdirectory)/[path to the
 deployment manifest files]'
           Contents: '*'
           TargetFolder: '$(build.artifactstagingdirectory)'
           flattenFolders: true
       - task: PublishBuildArtifacts@1
         inputs:
           PathtoPublish: '$(Build.ArtifactStagingDirectory)'
           ArtifactName: 'drop'
           publishLocation: 'Container'

在构建阶段旁边,添加以下发布阶段。管道初次执行后,Azure DevOps 中将提供一个新环境。在发布创建的环境中,附加 Kubernetes 集群,以查看正在运行的 pods 信息:

- stage : Release
    displayName : Release
    jobs:
     - deployment: KubernetesDeploy
       displayName: Deploy Kubernetes
       pool:
         vmImage: 'ubuntu-latest'
       environment: 'Kubernetes'
       strategy:
         runOnce:
           deploy:
             steps:
             - task: DownloadPipelineArtifact@2
               displayName: 'Download pipeline artifacts'
               inputs:
                 buildType: 'current'
                 targetPath: '$(Pipeline.Workspace)'
             - task: KubernetesManifest@0
               inputs:
                 action: 'deploy'
                 kubernetesServiceConnection: '[Kubernetes service
 connection]'
                 manifests: '$(Pipeline.Workspace)[deployment manifest]’
                 containers: '[container registry]:$(Build.BuildID)'

在这个示例中,为多阶段管道指定了两个阶段。第一阶段将通过 Docker 任务构建容器镜像,并将其发布到容器注册表。发布镜像后,它还会发布一些构建产物,在本例中是 Kubernetes 清单。

第二阶段将部署到一个名为 Kubernetes 的特定环境。如果该环境尚未添加,它也将在 Azure DevOps 中创建。在剩余的过程中,它会检索构建阶段发布的产物,并使用 Kubernetes 清单任务来部署 Kubernetes 资源。

总结

在本章中,你了解了容器是什么,以及它们如何与 DevOps 相关联。DevOps 更像是一种文化,而容器则是技术上支持 DevOps 的方式。你还学习了如何通过 Dockerfile 创建容器镜像,特别是使用多阶段构建文件。最后,我们深入了解了 Kubernetes,学习了如何托管容器,并通过使用 kubectl 命令管理正在运行的容器。

运用本章所学的知识,你现在能够将应用程序部署到 Kubernetes,并确保它随着接收的请求数量进行扩展。

在下一章中,你将学习如何通过使用 Azure DevOps 来促进 DevOps 流程。你将了解哪些方法适合你的组织和团队,哪些不适合,并学习如何通过 Azure DevOps 实现这些结构和方法。

问题

在本章结束时,以下是一些问题,帮助你测试自己对本章内容的理解。你可以在附录的评估部分找到答案:

  1. 容器对 DevOps 的好处有哪些?

  2. 判断对错:特定容器可以托管在不同的平台上(Azure/AWS)。

  3. 是否可以为现有应用程序添加容器支持?

  4. RUN 命令在 Dockerfile 中的作用是什么?

  5. Kubernetes 可以在不同的组件上进行扩展。这些组件有哪些?

进一步阅读

第十三章:规划你的 Azure DevOps 组织

在之前的章节中,你学习了许多关于 DevOps 的技术和实践。在本章中,我们将退后一步,看看如何构建一个强大的 DevOps 组织,以及在此过程中需要考虑的事项。接下来,你将了解这能为你带来关于安全性和追踪性的好处。从此以后,你将学习如何整合你的工具链,并在 Azure DevOps 上进行标准化。

我们将从创建一个 Azure DevOps 组织开始,在这里你将学习如何在工具中布局你的产品和团队,并了解有哪些构造可以使用。你还将了解许可和不同方法的安全性影响。接下来,你将学习追踪性及其如何帮助创建可验证的软件开发过程。接下来是工具的整合。随着你在 DevOps 旅程中的进展,你可能会发现每个团队使用的是它们熟悉并喜欢使用的不同工具。虽然 DevOps 强调赋能人员,但某种程度的标准化可能是可取的,你将学习如何进行标准化。最后,你将了解到,可能你永远无法完全完成 DevOps 的采用。

本章将涵盖以下主题:

  • 设置 Azure DevOps 组织

  • 确保追踪性

  • 工具整合

  • 接受没有结束状态的现实

技术要求

要跟随本章的实际操作部分,以下任一项是必需的:

  • 一个 Microsoft Live 账户,也叫个人账户

  • 一个工作或学校账户

设置 Azure DevOps 组织

为了实践之前章节中描述的一个或多个技术和技巧,可能已经为此目的创建了一个专门的 Azure DevOps 组织,或者也许已经有一个现成的可以用来做这个的组织。然而,为公司从零开始创建组织需要更多的考虑。花时间合理规划组织的布局,可以节省很多后续时间。

本节描述了构建 Azure DevOps 所需的组件,如何使用这些组件来组织适合的安全模型,以及许可选项和成本。

Azure DevOps 的组织方式

Azure DevOps 中的顶层构造称为组织。对于大多数公司来说,通常只需要一个组织,但也允许创建多个组织。

每个 Azure DevOps 组织都托管在特定的区域。组织的大多数数据(源代码和工作项)都保证位于该组织所在的区域,但由于服务的全球覆盖,一些信息始终存储在其他数据中心。拥有分布式组织,且团队和产品分布在不同地理位置,可能是使用多个组织的原因。例如,如果某些团队位于澳大利亚,另一些位于西欧,那么创建两个独立的组织并将所有团队托管在离他们最近的地理位置是有意义的。这将使大部分服务物理上接近他们,从而在使用 Azure DevOps 时大大减少延迟。

一个组织可以与 Azure Active DirectoryAAD)相关联。如果启用此链接,只有该特定 AAD 内的用户才允许访问该组织。使用公司 AAD 并非强制性的;也可以使用 Microsoft 帐户创建一个组织。

在每个组织中,可以创建一个或多个项目。项目是一个独立的工作项、源代码库、管道定义以及所有其他 Azure DevOps 工件的容器。项目之间的共享和链接可能性是有限的。写作时,只有工作项可以跨项目相关联,其他的则不能。通过这种方式,项目可以作为强大的隔离边界,在需要时强制执行产品或团队之间的严格安全性。通常建议尽量减少项目的数量,目标是尽可能只有一个项目。

以下图表展示了 Azure DevOps 组织和项目的可能组织结构。它显示了两个组织与 Azure Active Directory 相连接,一个位于西欧,另一个位于澳大利亚。在西欧的组织中有两个项目,而在澳大利亚只有一个项目:

如前所述,建议尽量使用较少的组织和项目。但在这个例子中,澳大利亚到西欧的延迟是将其拆分为两个组织的一个合理理由,以便将 Azure DevOps 托管在靠近团队的位置。在西欧将其拆分为两个项目,可能是由于第 4 个团队在处理第 3 个产品时需要较高的隔离级别。

将团队 1 到 3 和产品 1 与 2 合并为一个项目是故意为之。原因在于,在一个单独的项目中,可以定义多个产品领域和多个团队。将所有这些内容集中在一个项目中,可以方便地将工作项链接起来进行组合管理。这样,一个团队的工作项也可以与另一个团队在另一个产品上的提交或拉取请求相关联。如果功能跨多个产品或应用(组件)分布,这将非常有帮助。

为了使在单个项目中定义所有产品和所有团队成为可能,了解 Azure DevOps 安全模型及其访问控制实现方式非常重要。

创建 Azure DevOps 组织和项目

创建新的 Azure DevOps 组织和一个或多个项目通常是由管理员完成的任务,这些管理员稍后还将负责管理这些环境。这些组织很可能连接到 Active Directory。如果是为了私人使用或培训和学习目的创建组织,建议使用个人账户。

使用个人账户创建新组织,请执行以下操作:

  1. 访问dev.azure.com

  2. 选择“开始免费”以开始创建新的 Azure DevOps 组织。

  3. 在弹出的登录对话框中,使用个人账户登录。

  4. 登录后,选择正确的居住国家,并通过以下对话框选择是否接收提示和其他服务更新:

  1. 按下“继续”按钮以创建新组织。

创建组织后,将自动启动向导以创建第一个项目。要创建项目,请执行以下操作:

  1. 提供项目名称。

  2. 选择将项目设为公开或私有。私有项目是默认设置,旨在组织内部创建软件并不允许匿名访问。公开项目则用于开源开发。

创建新组织和新项目后,可以使用管理界面对这些选项进行更改。

请记住,重命名组织或项目将更改 URL,因此所有现有的集成和链接可能会中断。

甚至可以稍后更改组织的位置。这需要提交请求,并不像更改其他设置那样简单。本章末尾包含了如何执行此操作的文档链接。

一旦组织和项目可用,就可以开始设置安全性。

Azure DevOps 安全模型

在 Azure DevOps 中,可以将授权分配给单个用户或安全组。安全组可以是现有 AAD 组的逻辑封装,也可以在 Azure DevOps 中定义。一般来说,建议尽可能将授权分配给组,限制单个用户的授权。

要配置用户或安全组的授权,有两种互补的方式可供选择:

  • 组织级和项目级授权

  • 对象级授权

在使用本地产品 Azure DevOps Server 时,还可以使用服务器级安全组和设置。

在 Azure DevOps 服务中,组织被称为项目集合,项目被称为团队项目。有时,这些名称也会在 Azure DevOps 中出现。

组织级和项目级授权:为了允许用户在某一类型的所有对象上执行特定操作,可以设置组织级或项目级授权。例如,查看内置组 Project Collection Build Administrators,以及 [ProjectName]\Build Administrators,这些组默认有权限查看、管理和编辑构建定义和构建资源。可以在组织和项目级别设置的权限会自动应用于组织或项目中的所有单个资源。

对象级授权:在 Azure DevOps 的大多数对象上,可以为用户或组分配个别权限。这些权限通过在对象本身上设置访问控制列表ACL)来配置。以下示例展示了一个经典的构建定义:

对于每个组、每个操作,可以配置“允许”(Allow)、“拒绝”(Deny)、“未设置”(Not set)或继承(inherited)。当一个操作配置为“拒绝”(Deny)时,访问永远不被允许,即使用户是某个组的成员,而该组的授权被指定为“允许”(Allow)。换句话说,当有两个冲突的授权(允许和拒绝)时,“拒绝”优先于“允许”。“未设置”(Not set)应被解释为一种隐式拒绝,它不具有优先权。换句话说,当有两个冲突的授权(未设置和允许)时,用户将被允许访问。

在 Azure DevOps 中,一些工件是层级结构的一部分。例如,管道可以位于文件夹中。每当启用继承时,来自更高层级的权限将传播到该工件。这意味着,当用户有权访问一个管道文件夹时,所有的权限将传播到所有底层文件夹和管道,前提是没有更具体的授权设置。

虽然安全模型决定了用户的授权,但用户的操作也受到其分配的访问级别的限制,而访问级别是基于其许可证的。

Azure DevOps 许可证

创建 Azure DevOps 组织的另一个方面是管理许可证。在 Azure DevOps 中,每个用户在登录产品之前需要分配一个访问级别。定义了三种访问级别:

  • 利益相关者:利益相关者是免费的用户,可以登录产品,但对其功能的访问权限有限。利益相关者可以管理工作项、管理管道和查看仪表板。他们无法访问其他任何产品区域,因此此许可证级别仅适用于非开发角色。

  • 基础版:基础版用户拥有付费许可证,允许他们访问产品的所有部分,但不包括测试管理和高级测试执行功能。在撰写本文时,基础版用户许可证的费用为每月 5.06 欧元。

  • 基础版和测试计划:使用基础版和测试计划许可证选项的用户可以访问 Azure DevOps 的所有部分。他们拥有与基础用户相同的访问权限,同时还提供了测试管理工具以及用户验收测试、测试执行和测试结果报告工具。截至目前,基础版和测试计划许可证选项的费用是每月 43.86 欧元。

每个组织的前五个基础许可证是免费的。这意味着,用户可以在不产生任何费用的情况下尝试和学习该产品。此外,Visual Studio 订阅者还可以获得免费的许可证。专业版订阅者可获得免费的基础许可证,企业版订阅者则可获得免费的基础版和测试计划许可证。

授权可以随时分配和重新分配,因此对于一个有许多加入和离开的公司或团队,在任何时刻都不需要购买比实际活跃人员更多的授权。

授权费用并不是使用 Azure DevOps 时唯一的费用;了解按使用量计费的费用同样非常重要。

基于消费的费用

授权使用户能够访问产品,之后他们可以以固定费用使用产品中的所有服务,除了以下两项:

  • Azure Pipelines 并行执行

  • Azure Artifacts 存储

并行执行:默认情况下,每个 Azure DevOps 组织都提供一个由 Microsoft 托管的并行执行作业。这意味着,虽然可以定义任意数量的流水线,但同一时间只能有一个作业在执行。当然,这个数量是可以增加的,但需要额外购买更多的 Microsoft 托管的并行执行作业,每个作业当前的费用是每月 33.74 欧元。

作为一种替代方案,还可以购买自托管作业。对于这些作业,执行代理不是由 Microsoft 提供,而是由组织自行提供。这提供了完全控制硬件的机会(和责任)。目前,自托管流水线的费用为每月 12.65 欧元。

工件存储:在使用 Azure Artifact 提要时,前 2 GB 的存储是免费的。额外使用的存储将按每月 1.69 欧元的费用收取。

一旦团队中的更多用户拥有 Azure DevOps 的许可证并在其中进行工作,就可以用来提高软件开发的可追溯性。

确保可追溯性

Azure DevOps 相对于本书中讨论的其他一些工具的一个优势是,它是一个完全集成的工具套件,每个工具支持特定的 DevOps 流程。这种端到端的集成允许从工作板上描述的任务到相关二进制文件部署到环境中的详细且长期的可追溯性。

当使用一组仅支持 DevOps 流程一部分的其他工具时,通常可以将它们整合在一起,当然,这将带来一定的可追溯性。例如,在使用 Jira 和 GitHub 时,可以将 GitHub 中的提交、拉取请求和其他更改与 Jira 中描述的工作相关联。当在 Jenkins 中拾取合并的更改来构建和部署产品时,Jenkins 也会有从 Jenkins 到 GitHub 的可追溯性。然而,无法直接看到哪个工作项与哪个 Jenkins 部署完成。

这样做的缺点是,使用 Jira 工具的产品负责人无法看到一个已完成的用户故事是否已经与发布关联。他们需要访问多个工具来找到这个问题的答案:在 GitHub 中,他们需要找到所有与故事相关的提交,然后查看这些提交是否已经通过 Jenkins 发布:

当使用 Azure Boards、Repos 和 Pipelines 时,情况是不同的。在使用所有 Azure DevOps 服务时,您可以实现从故事到部署的可追溯性,反之亦然。以下是一个示例,突出显示如何查看哪些提交第一次被部署到具有特定部署的环境中:

拥有这种端到端的可追溯性能够迅速回答一系列问题,包括以下问题:

  • 这个工作项的工作是否已经开始?(分支和拉取请求可以与工作项关联。)

  • 这项工作已经是我们夜间构建的一部分了吗?(提交和构建管道可以与工作项关联。)

  • 这个 bug 已经修复了吗?解决方案是否已经可以在环二的客户中使用?(发布和环境展示了哪些新的提交和工作项是最新部署的一部分。)

在讨论可追溯性时,尤其需要强化的一个观点是,它不是为了归咎。可追溯性并不是用来找出谁犯了哪个错误,而是用来了解事物的状态以及对象之间的关系。一旦可追溯性成为归咎的基础,工程师很快会找到隐藏自己工作的办法,这将导致更多错误和更少的可见性,只会使问题变得更糟。

在可追溯性优势显而易见的情况下,让我们探索一下工具整合如何帮助实现这些优势。

整合工具

市场上可以观察到的一个趋势是,越来越多的可追溯性和 DevOps 产品将其产品扩展到不仅仅是源代码控制、管道或仅仅是部署的领域。比如 GitHub 和 GitLab,它们正在添加新的服务。更多集成的应用生命周期管理ALM)或 DevOps 套件出现,而 Azure DevOps 已经提供了这些服务多年。

然而,许多公司并未配备这些集成套件。团队在不同的生态系统中运营,导致不同的工具选择。或者也许团队仅仅有不同的偏好,或者在不同时间点开始采用 DevOps 实践,选择了其他工具。无论原因如何,许多公司都有多个工具用于相同的工作。

不幸的是,使用不连接的工具或为相同任务使用多个工具会带来许多缺点:

  • 如果团队使用不同的工具,团队之间的协作将受到阻碍。不管开发者的个人偏好如何,当组织的一半使用 Jenkins 而另一半使用 Azure Pipelines 时,这可能会成为生产力的障碍。进一步来说,当一个团队使用不同的工具时,切换团队或帮助其他团队将受到严重影响。

  • 当工具更多时,成本也更多。即使所有工具都是开源且免费的,仍然会涉及成本。这些成本可能包括例如支持合同或请求、培训,或者解决特定问题所需的时间。升级和维护也是如此。当工具更多时,总成本增加。

为了克服这些挑战,许多大型公司决定标准化使用的工具,或者完全标准化,或者至少在一定程度上进行标准化。作为一种替代或过渡解决方案,还可以通过工具之间的集成来开始整合。

工具标准化

为了应对这些缺点,大多数公司接受以下两种策略之一:

  • 通过集中决策选择一个工具(针对每个 DevOps 领域)供整个公司使用

  • 集中采用有限的工具集,团队可以从中选择要采用的工具

完全集中化:当完全集中化时,一个中央团队或部门代表所有人决定在组织内使用哪些 DevOps 工具。一旦做出并实施了这样的决定,这将降低成本并使工程师更容易协助其他团队。

当然,缺点是,一个单一的工具不一定是每个人的最佳选择,而对于整个组织来说,所选择的工具可能是最好的——这种标准化在某些边缘情况下可能会造成损害。

有限集中化:为了避免这种情况,其他公司选择有限集中化。不是只选择一个工具,而是选择一组工具作为公司的标准。团队现在可以根据他们的具体需求,从两到三个工具中选择一个。这在不牺牲具有特定需求的团队生产力的同时,减少了完全去中心化的许多缺点。

采用这两种策略之一可能意味着一些现有工具将被弃用或完全停用。这可能是一个缓慢且痛苦的过程,特别是在大型组织中,通常存在冲突的利益。虽然有很多方法可以进行迁移,但也有策略可以使迁移过程变得不那么痛苦。

迁移策略

减少使用的 DevOps 工具数量通常意味着必须废弃一个或多个工具。这可能是困难的,因为这些工具通常用于实施治理和合规性,以满足法律和法规的要求。实际上,这意味着可以做以下两件事之一:

  • 旧工具并未完全废弃,而是仅不再使用,以保持变更历史记录。

  • 在废弃旧工具之前,必须将历史记录迁移到新工具中。

在选择进行迁移时,有四种方式可以选择:

  • 从 Azure DevOps Server 迁移到 Azure DevOps Services

  • 一次性迁移

  • 同步

  • 重建

Azure DevOps Server 曾被称为Team Foundation ServerTFS)。TFS 的旧版本需要升级到最新版本的 Azure DevOps Server,然后才能导入到 Azure DevOps Services 中。导入服务始终支持最新的两个版本的 Azure DevOps Server。

以下各节详细介绍了这三项内容。

从 Azure DevOps Server 迁移到 Azure DevOps Services

对于希望从 Azure DevOps Server 迁移到 Azure DevOps Services 的组织,有一个高保真度的迁移服务。当前存在于本地 Azure DevOps Server 环境中的每个项目集合,都可以使用 Azure DevOps Server 导入服务迁移到 Azure DevOps 组织。所有当前存在于本地项目集合中的资产都将迁移到 Azure DevOps:工作项、源代码控制库以及构建和发布定义。

项目集合的迁移包含以下高层步骤:

  1. 验证项目集合是否准备好迁移:此步骤不会进行任何更改,而只是检查迁移的所有前提条件是否已满足。

  2. 准备迁移:在此步骤中,将生成一个描述如何进行迁移的 JSON 文件。还可以提供第二个文件,用于将本地身份与 AAD 身份关联,以确保迁移后所有历史记录仍然正确地与更改者关联。

  3. 进行迁移的演练,以验证导入过程将产生预期的结果。

  4. 实际迁移:在此步骤中,将把集合下线,从项目集合数据库生成 DACPAC,上传 DACPAC 和第二步的文件,并启动迁移。

  5. 导入后,所有资产需要验证,并且在特定场景下,可能需要进行一些导入后的操作。

关于使用迁移服务的详细指南,其中包括检查清单和逐步操作说明,已在本章末尾提供链接。

大爆炸式迁移

第二种可能的策略是“大爆炸式迁移”。在某个时刻,旧工具被关闭,所有数据迁移到新工具中,新工具开始投入使用。如果有任何不符合 DevOps 精神的事情,那就是这种迁移。它具有较高的风险,且往往一旦实施无法回退。通常,这不是一个好的迁移策略。

然而,一种适合这种方法的情况是源代码管理的迁移。现在有一些工具可以实现从不同来源迁移到任何类型的托管 Git 解决方案,包括 Azure DevOps。源代码管理也有一个好处,那就是变更历史深深嵌入系统中,因此带着历史迁移往往比其他类型的数据更容易。

同步

另一种迁移策略是允许在一段时间内同时使用两个工具。

一种方法是使用一种工具,用于在旧工具和新工具之间进行同步。这可以是单向的,从旧到新,或者是双向的。通过这种方式,创建了一个过渡的情况,两个工具可以同时使用。每个团队可以在一定的时间窗口内选择自己的迁移时间。这避免了强制性的迁移窗口。团队也可以选择将两个工具并行使用一段时间。这使得他们可以学习新工具,同时如果遇到压力时,仍能切换回他们熟悉的工具。经过一段过渡期后,旧工具可以设置为只读,或者完全停用。这种方法通常在工作跟踪系统中效果良好。在这些系统之间,概念通常非常相似(如史诗、特性、故事和冲刺),这使得同步成为一种可行的方法。

重新构建

一种稍有不同的方法是要求团队在新工具中重新构建。这种方法也会创建一个并行的工作情境,但没有自动化的迁移或同步。团队必须在新工具中重新做他们的工作流程或工作方式。由于这可能需要一段时间,旧工具在团队进行这项工作时将继续使用。一个通常适合这种方法的情况是构建和/或发布管道。

无论选择哪种策略,在所有情况下,确保新工具或工具集对团队比现有工具更具优势都非常重要。这应该提升性能、可追溯性、易用性,或与其他公司工具的集成。围绕迁移的积极氛围能够显著改善迁移的结果。

作为迁移到单一工具的替代方案,可以通过工具间的集成来将现有工具整合在一起。

集成工具

作为替代不再使用的工具,可以将它们与首选工具集成。尤其是当已决定迁移到 Azure DevOps 时,这将是一个强有力的策略。在前面的多个章节中,每个主题下都列出了与 Azure DevOps 集成的不同工具。

在追求端到端可追溯性时,这些集成可以作为将工具紧密结合的手段。考虑以下示例。

一个组织正在使用 Azure DevOps 来管理工作项、托管 Git 仓库以及执行构建管道。由于历史原因,部署是通过 Octopus Deploy 完成的。由于完全迁移成本过高,因此选择了集成策略。通过 Azure DevOps 自动触发 Octopus Deploy,而非手动触发,达成了多个目标:

  • 已实现端到端自动化。

  • 发布管理现在也可以在 Azure DevOps 中完成,即使在 Azure DevOps 中的每次部署不过是触发 Octopus Deploy。

  • 在 Azure DevOps 中进行发布管理,现在可以实现端到端的可追溯性。

在整合工具时,实际上涉及 DevOps 的所有事宜,你必须准备好接受一个事实,那就是你永远无法完成所有工作。

接受没有终极状态这一事实。

可以合理预期,在任何时刻,团队都会有一个或多个改进想要应用于他们的应用程序、工具链或工作方式。为应对这一点,建议不要总是不停地修改一切。

相反,尽量将变更实施分批进行,或分为一系列定义明确的步骤。同时,要注意事物的自然顺序。没有先建立适当的持续集成过程,是无法实施持续部署的。另外,采用基础设施即代码(Infrastructure as Code)将在应用代码已经实施持续部署过程的情况下,最大程度地发挥价值。接下来,当基础设施和配置代码已成为常规做法时,自动化治理和安全措施将发挥最佳效果。而一旦这些实践得以实现,新的改进将会出现在雷达上——未来可能需要的改进。

除了这一系列的改进外,还需要意识到并非每个团队都处于同一阶段,也并非每个团队都能以相同的速度前进,开发过程也并非总是线性的。

但这并不意味着无法跟踪和规划未来的变更,并将一个团队的经验教训应用于其他团队。追踪这些的一个过于简化的方法可以是一个表格,如下所示。

在这里,我们看到五个团队中不同 DevOps 实践或理念的采用情况。所有团队都在实践持续集成CI)。其中一些团队正在实践持续部署CD),而团队 3 仍在努力进行这一工作(进行中WIP),第五个团队尚未开始。最后,团队 2 已经在尝试基础设施即代码IaC)。最后,由于没有“结束状态”,只是时间问题,下一项实践或理念将出现,某个团队将开始进行实验:

如果像前面所展示的那样的表格,能够经常更新、评估和扩展,这有助于促进持续学习,并改善软件的创建和交付方式。学习和改进将成为标准,这将有助于提高向最终用户交付价值的效率。它还表明,采用 DevOps 是一个持续的过程,永远没有“完成”这一时刻。

它还提供了一种方式,可以看到哪些团队处于前沿,哪些团队在跟随。通过为领先的团队提供更多实验和与其他团队共享知识的空间,组织可以鼓励这些领先团队进一步提升,同时也能加速其他团队的发展。

带着关于持续学习、实验和自我推动的这一提醒,是时候结束本书了。让我们在下一部分回顾这一章的内容。

总结

在本章中,你学习了如何配置你的 Azure DevOps 组织,以及如何为你的产品和团队创建布局。你了解了不同配置选项的影响,并学会了如何应用这些选项。接下来,你学习了如何利用 Azure DevOps 为你的开发过程添加可追溯性。你了解了如何在故事和任务中捕捉想法和任务,以及如何将它们追溯到部署,并反向追溯。你还学会了如何在组织中整合工具,并了解了何时应停止尝试整合工具。最后,你学会了持续改进的重要性。

通过本章中学到的内容,你现在能够为你的团队、多个团队或整个组织设置和配置 Azure DevOps。你可以创建适合组织的结构,并开始使用它,无论是单个团队还是多个团队。你还能够逐步标准化你的工作方式,并将团队整合到相同的工具集上。

本书的最后一章已完成。你可以参考本书为 AZ-400 考试做准备,因为大多数主题都是相似的。然而,为了更好地备考,我建议你在其他来源中阅读更多相关内容,并尽量获得更多的实操经验。作为练习工具,本章结尾提供了一个模拟考试,帮助你为最终考试做好准备。

祝你好运!

问题

在我们总结之前,这里有一系列问题,帮助你测试对本章内容的理解。你可以在附录的 评估 部分找到答案:

  1. 判断对错:用户在 Azure DevOps 中存储的所有数据都保证在一个区域内吗?

  2. 按照以下顺序重新排列 Azure DevOps 的概念,使得每个下一个元素都作为下一个概念的容器:

    • 工作项

    • 组织

    • 区域

    • 项目

  3. 判断对错:通常建议为组织开发的每个应用程序创建一个新项目。

  4. 哪两个元素限制了用户在 Azure DevOps 中可以执行的操作?

  5. 使用单一工具进行应用生命周期管理/DevOps 相较于使用一套工具的主要优势是什么?

进一步阅读

第十四章:AZ-400 模拟考试

设计 DevOps 策略

  1. 你被要求将 Azure DevOps 引入你的组织。目前,部署使用了许多其他工具。你被问到以下哪些工具可以与 Azure DevOps 集成。[可以有多个答案。]

    1. Octopus Deploy

    2. Jira

    3. Jenkins

    4. 应用中心

  2. 你被要求为团队创建一个仪表盘,显示团队工作情况的信息。你应专注于展示鼓励敏捷和 DevOps 工作方式的度量标准和图表。你会选择哪些度量标准?[选择三项。]

    1. 一个显示工作项平均周期时间的小部件

    2. 一个显示最近部署结果(成功或失败)的小部件

    3. 一个显示每天新增代码行数的小部件

    4. 一个显示团队当前打开的拉取请求数量的小部件

  3. 你被要求在项目中实施静态代码分析。做这件事的最佳时机是什么?

    1. 在构建阶段,发布工件之前

    2. 在部署阶段,实际部署到目标环境之前

  4. 你在本地运行 Team Foundation Server 2015。你被要求使用高保真度迁移将此服务器上的所有资产迁移到 Azure DevOps。请将以下任务按正确顺序排列。

    1. 运行迁移验证工具。

    2. 执行导入过程的完整端到端演练。

    3. 创建 TFS 服务器项目集合的便携式备份。

    4. 将你的 TFS 服务器升级到 Azure DevOps 服务器的最近两个版本之一。

    5. 为每个项目集合运行导入服务。

  5. DevOps 的核心原则之一是“持续向最终用户交付价值”。以下哪项不是用于实现这一点的?

    1. 人员

    2. 实践

    3. 流程

    4. 产品

  6. 你在一个大型企业工作,需要自动为新员工分配许可证。为此,你创建了一个与 HR 应用程序连接的小型应用程序。在你的应用程序和 Azure DevOps 之间,你使用了哪种类型的授权?

    1. 用户帐户

    2. PAT 令牌

    3. OAuth 令牌

    4. 一次性密钥

  7. 你需要将现有的 Git 仓库迁移到 Azure DevOps。你需要做哪些事情?[选择两项。]

    1. 创建一个初始化的 Git 仓库。

    2. 创建一个未初始化的 Git 仓库。

    3. 执行git remote rm origin; git remote add origin <new-repository-url>; git push

    4. 执行git remote redirect origin <new-repository-url>; git push

  8. 你在一个异构环境中工作,团队通过公司使用不同的 DevOps 工具。你的一个构建正在 Azure DevOps 中运行,但另一个团队想从另一个工具中使用你的管道工件。你可以使用以下哪项来暂存构建工件,以便其他工具进行连接?

    1. 管道工件

    2. Artifactory

    3. Octopus Deploy 二进制服务器

    4. 工件源/通用包

  9. 你负责为团队在 Azure DevOps 中配置源代码控制。以下哪些要求只能通过使用 TFVC 来实现?

    1. 你需要强制执行四眼原则。

    2. 你需要为一个用户配置特定文件夹的访问权限。

    3. 你需要为一个用户配置特定文件的访问权限。

    4. 你需要连接到经典构建管道。

  10. 你需要执行并记录探索性测试会话。除了执行测试外,你还应该能够自动在你的 Azure Boards 待办事项中报告错误。你计划使用 Test & Feedback 扩展来完成此任务,并为所有测试人员分配测试许可证。这是否完成了目标?

  11. 你需要执行并记录探索性测试会话。除了执行测试外,你还应该能够自动在你的 Azure Boards 待办事项中报告错误。你计划使用 Test & Feedback 扩展来完成此任务,并为所有测试人员分配基础+测试许可证。这是否完成了目标?

  12. 你需要执行并记录探索性测试会话。除了执行测试外,你还应该能够自动在你的 Azure Boards 待办事项中报告错误。你计划使用 Test & Feedback 扩展来完成此任务,并为所有测试人员分配基础许可证。这是否完成了目标?

  13. 你负责识别可以用来衡量采用 DevOps 的影响的度量指标。以下哪些你建议使用?[选择两个。]

    1. 同时进行的工作量

    2. 速度

    3. 循环时间

    4. 冲刺持续时间

  14. 以下哪项不是 DevOps 习惯?

    1. 团队自治与企业对齐

    2. 最大化未做的工作量

    3. 假设驱动开发

    4. 现场文化

  15. 你负责为与团队一起创建的新应用程序制定测试策略。你应该提出以下哪些建议?

    1. 为了验证最关键的用户场景是否仍然正常工作,应编写一个或多个系统测试。

    2. 为了验证最关键的用户场景是否仍然正常工作,在每次部署前应执行压力测试。

    3. 每十个单元测试中,应该至少有一个集成测试。

    4. 在生产环境中启用新功能之前,应对生产环境执行最终的冒烟测试。

  16. 源代码是你公司最有价值的资产之一。当用户不通过公司网络连接时,你希望在访问 Azure DevOps 时实施多因素身份验证。以下哪些你可以用来实现这一点?

    1. Azure Active Directory 条件访问

    2. Azure Active Directory 网络允许

    3. Azure DevOps 网络控制

    4. Azure Active Directory 帐户组

  17. 你在一个为其他团队提供 Azure 订阅和资源组的团队工作。作为你工作的部分,你希望监控所有团队是否实施了推荐的 Azure 安全最佳实践。以下哪些你使用?

    1. Azure 策略

    2. Azure 安全中心

    3. Azure Key Vault

    4. Azure 安全监控

  18. 你被要求提供从 Azure DevOps 内部启动 SonarCloud 扫描的方法。以下哪些步骤能创建一个完整的解决方案?[选择两个]

    1. 更新 Azure DevOps 项目中的 Sonar Cloud 配置。

    2. 创建一个新的 Sonar Cloud 服务连接。

    3. 激活 Sonar Cloud 集成包。

    4. 安装 Sonar Cloud 扩展。

  19. 你需要在 Kubernetes 中应用更新到一个部署——你应该使用哪个命令?

    1. kubectl apply

    2. kubectl deployments

    3. kubectl get services

    4. kubectl deploy

  20. 管理 Azure Kubernetes 集群时,你需要使用哪些基本工具?[选择所有适用的工具]

    1. Azure CLI/PowerShell

    2. kubectl

    3. Azure DevOps

实施 DevOps 开发流程

  1. 你正在开发一个 Microsoft .NET Core 应用,并想分析该应用,检查是否使用了任何存在已知安全漏洞的开源库。你可以使用以下哪些产品进行此类分析?[选择两个]

    1. Jenkins

    2. Whitesource Bolt

    3. Snyk

    4. App Center

  2. 你当前使用(i)JIRA,(ii)GitLab 和(iii)Octopus Deploy 来处理部分 DevOps 流程。你希望将 DevOps 工具整合,并选择使用 Azure DevOps。你应该使用哪些 Azure DevOps 服务来替代这些工具?选择正确的服务来替换这三个服务。[匹配三对]

    1. Azure Pipelines

    2. Azure Repos

    3. Azure Boards

    4. Azure 工件

  3. 你正在评估不同的构建代理选项。选择私有代理而非 Microsoft 托管代理的有效理由有哪些?[选择两个]

    1. 你需要确保在执行任何任务之前,代理上已经安装好自定义软件。

    2. 你需要确保在销毁之前,仅使用同一环境执行一个管道任务。

    3. 你需要从构建代理直接连接到你的本地网络。

    4. 你需要确保始终运行在最新的镜像上。

  4. 你负责管理团队部署到 Azure 应用服务的应用程序设置。以下哪些服务不能用来实现这一目标?

    1. Azure 应用配置

    2. ARM 模板

    3. Azure 策略

    4. Azure Key Vault

  5. 你负责为你的团队创建大量的构建管道。几乎所有管道都需要相同的结构。哪个 Azure DevOps Pipelines 构造能帮你?

    1. 分支策略

    2. 任务组

    3. Azure 工件

    4. 部署组

  6. 你正在使用 Entity Framework 作为应用程序的数据库访问层。你负责管理数据库升级,并希望使用 Entity Framework 来管理数据库架构。你应该使用哪种类型的架构迁移?

    1. 基于迁移

    2. 基于最终状态

  7. 你需要将本地更改保存到 Git 仓库中。你需要使用哪些命令?

    1. git clonegit push

    2. git commitgit push

    3. git addgit commitgit push

    4. git addgit commit

  8. 你需要防止任何人在更改无法编译或任何单元测试失败的情况下将更改合并到主分支。你可以使用以下哪个工具来实现这一目标?

    1. 分支保护中心

    2. Azure Repos 分支策略。

    3. Azure Repos 分支安全。

    4. 在 Azure DevOps 中这是不可行的;你需要使用其他产品,例如 GitHub。

  9. 你的公司在本地使用 GitHub Enterprise 托管源代码。为了实现持续集成和持续部署,你希望使用 Azure DevOps。以下哪些组件形成了一个完整的解决方案,使其成为可能?[选择两个。]

    1. 外部 Git 服务连接

    2. 打开防火墙以允许来自 Azure Pipelines 到 GitHub Enterprise 的 HTTPS 连接

    3. Git 源代理(HTTP)

    4. 本地代理

  10. 一个新团队加入公司,他们需要开始开发一个新应用程序。他们要求你推荐一种分支策略,允许他们并行开发多个功能并随时部署新版本,同时最小化后期合并更改的需求。你推荐以下哪个方案?

    1. 为每个团队成员创建一个独立的分支,并选择性地合并提交。

    2. 为每个功能创建一个分支,并在功能完成后合并该分支。

    3. 为每个任务创建一个分支,并在任务完成后合并该分支。

    4. 在每次完成可交付的工作后尽可能频繁地创建并合并分支。

  11. 你负责为团队在 Azure DevOps 中配置源代码管理。你优先选择以下哪种源代码管理系统?

    1. TFVC

    2. Git

  12. 以下哪个选项是正确的?

    1. 在 Azure DevOps 项目中,你可以拥有任意数量的 Git 和 TFVC 仓库。

    2. 在 Azure DevOps 项目中,你最多只能有一个 Git 仓库和一个 TFVC 仓库。

    3. 在 Azure DevOps 项目中,你最多只能拥有一个 TFVC 仓库,并可以拥有任意数量的 Git 仓库。

    4. 你可以在 Azure DevOps 项目中拥有 Git 仓库或 TFVC 仓库,但不能同时拥有两者。

  13. 你的团队正在开发一个移动应用程序,并希望使用 App Center 将该应用程序分发到应用商店以及团队内的测试人员。你应该使用以下哪些选项?[选择两个。]

    1. 仅限邀请的预发布组

    2. 推送到存储集成

    3. 存储连接

    4. 分发组

  14. 你正在为一个新项目创建一系列微服务。你希望从一个集中的位置管理配置。许多配置设置在微服务之间共享。以下哪种解决方案最适合这个用例?

    1. Azure 密钥保管库

    2. Azure 应用配置

    3. Azure 配置中心

    4. ARM 模板

  15. 你必须确保当代码未经过至少两个人查看时,不能将代码提交到主分支。以下哪些选项提供了一个完整的解决方案?[选择三个。]

    1. 强制使用拉取请求来合并更改到主分支。

    2. 在推送新提交到分支时重置该分支的拉取请求上的审批投票。

    3. 至少需要有两个审核者,但允许每个人合并自己的更改。

    4. 至少需要有一个审核者,该审核者不能是打开拉取请求的人。

  16. 你必须对团队生成的二进制文件(DLL)进行签名,以便其他使用这些文件的团队可以验证这些文件没有被修改,并且确实来自你的团队。你必须安全地存储用于签名的证书。你可以在哪里存储证书并且仍然能在管道中使用该文件?如果有多个答案符合要求,选择最简单的解决方案。

    1. Azure Pipelines 库

    2. Azure Key Vault

    3. 在源代码管理中加密

    4. Azure DevOps 证书存储

  17. 你必须确保每个构建管道都包含一个由团队预先共享的任务组。以下哪个 Azure DevOps 构造可以用来实现这一点?

    1. 管道装饰器。

    2. 管道验证器。

    3. 管道执行前的任务。

    4. 这不可能——你必须实施手动审计过程。

  18. 你的源代码存储在一个 Subversion 源代码管理系统中。你想迁移到 Azure DevOps Pipelines 进行持续集成。你不想迁移源代码,也不想从 Pipelines 连接到 Subversion。这可能吗?

  19. 开发团队正在创建一个容器化应用程序。该解决方案需要部署到 Azure 的 Kubernetes 集群中。你需要创建该集群并确保应用程序按预期运行。请选择你应该执行的命令,并按正确的执行顺序排列它们。

    1. kubectl apply

    2. az group create

    3. az aks create

    4. az appservice plan create

    5. kubectl get deployments

    6. az aks get-credentials

    7. kubectl get hpa

    8. az create group

    9. kubectl get services

  20. 运行容器而非虚拟机的一个巨大优势是容器共享操作系统内核。这使得容器镜像比虚拟机镜像更小。这个说法正确吗?

实现持续集成

  1. 按照范围的大小顺序排列以下测试类型。从范围最小的测试类型开始。

    1. 集成测试

    2. 单元测试

    3. 系统测试

  2. 以下哪些是正确的?[选择多个选项。]

    1. 压力测试是通过不断增加负载到系统上,来识别系统的崩溃点。

    2. 集成测试始终包括数据库。

    3. 性能测试用于衡量系统执行特定任务的速度。

    4. 可用性测试用于识别系统响应过慢的使用场景。

  3. 你正在创建一个 Azure DevOps 仪表板,以向团队提供有关所写代码质量的见解。以下哪个小部件不适合放在这样的仪表板上?

    1. 显示最近部署情况及其成功与否的小部件

    2. 显示每日提交次数的小部件

    3. 显示单元测试代码覆盖率变化的小部件

    4. 显示最新构建是否失败的小部件

  4. 以下哪项不是有效的合并策略?

    1. 合并提交

    2. 变基

    3. 交错

    4. 合并提交

  5. 以下哪项不属于 OWASP Top 10?

    1. 注入

    2. 敏感数据暴露

    3. 最小权限原则违规

    4. 使用已知漏洞的依赖项

  6. 你的团队正在创建应该稍后部署到 Azure 的容器镜像。以下哪些你可以用来存储你的镜像?[选择两个]

    1. Azure 容器实例

    2. Azure 容器注册表

    3. Azure Kubernetes 服务

    4. Docker Hub

  7. 你希望在任何集成构建失败时都能收到通知。你在 Azure DevOps 项目中配置了电子邮件订阅。这能完成这个目标吗?

  8. 你希望每当 Azure Artifact feed 中的一个新版本的工件可用时触发 YAML 管道。这可能吗?

  9. 你想在多阶段 YAML 管道的同一阶段中使用托管代理、云中的私人代理和本地代理的混合。这可能吗?

  10. 你希望创建一个发布管道,该管道每天在同一时间触发。你还希望排除星期日。这可能吗?

  11. 开发团队正在创建一个容器托管的应用程序,并希望将镜像共享到互联网上。团队通过 Docker 构建镜像,并尝试通过 Kubernetes 托管它。这是正确的做法吗?

  12. 以下哪些地方可以存储容器镜像?[选择所有适用项]

    1. Azure 容器实例

    2. Docker Hub

    3. Azure 容器注册表

    4. Azure 容器存储

实现持续交付

  1. 你所在的公司正在使用 ServiceNow 作为变更管理系统。公司有一项规定,要求使用 ServiceNow 跟踪每次生产环境的部署。你负责确保在变更管理系统中没有有效变更的情况下,应用程序不会部署到生产环境。以下哪些方法可以实现这一目标?[选择两个]

    1. 你实现了一个部署回调,检查 ServiceNow 中是否有有效的变更。

    2. 你为部署到生产阶段添加了一个部署门作为前置条件。

    3. 你为 QA 阶段完成部署添加了一个部署门作为后置条件。

    4. 你创建一个环境并命名为 Production-ServiceNow check。

  2. 你需要将一个应用程序部署到十二台本地虚拟机上,这些虚拟机分布在三个子网中。为了实现完整的工作解决方案,你应该执行哪些操作?[选择三个]

    1. 创建一个新的部署组,并将正确的代理添加到该组中。

    2. 在你需要部署的所有虚拟机上下载并安装私人代理。

    3. 向你的发布管道添加一个选择作业,以选择要使用的部署组。

    4. 在每个子网中的一台虚拟机上下载并安装私有代理。

    5. 在发布管道中添加一个部署组作业来执行部署应用所需的任务。

    6. 在发布管道中配置用户名和密码,用于配置如何连接到代理。

  3. 你需要配置一个发布管道,以满足多个条件才能启动部署到生产环境。这些条件是(i)审批委员会中的四个成员中至少两个需要批准该部署,(ii)在发布后的第一小时内,应该检查 Azure Monitor 是否有任何警报。可以使用 Azure DevOps Pipelines 完成此操作吗?

  4. 你正在使用 SQL Server 数据工具SSDT)来将数据库模式描述为代码。你还希望使用 SSDT 来进行模式升级。应该使用哪种类型的模式迁移?

    1. 基于迁移

    2. 基于最终状态

  5. 你正在使用无模式数据库。这是否完全消除了模式管理的问题?

  6. 你的团队必须遵循规定,要求每个新版本必须在部署到生产环境之前由测试经理手动批准。以下哪个更改能够最有意义地满足这一要求?

    1. 你在生产阶段添加了一个前部署检查,验证在一个本地构建的系统中是否有所有应用版本的签字记录。

    2. 你在 QA 阶段添加了一个后部署检查,如果所有自动化测试通过,系统会在指定的系统中设置测试经理的审批。

    3. 你在 QA 阶段添加了一个后部署审批,必须由测试经理进行审批。

    4. 你禁用了对生产阶段的自动审批,并指示所有人在启动部署前咨询包含所有测试经理签字的系统。

  7. 你正在为团队创建多个发布管道。许多管道将使用相同的配置值来执行某些任务。以下哪个选项可以帮助你将这些值作为一个完整的解决方案进行复用?

    1. 变量组

    2. 任务组

    3. 变量容器

    4. Azure Key Vault

  8. 你想要自动生成发布说明,内容来自于部署中完成的故事。你想要在不使用任何扩展或附加组件的情况下完成这项任务——只使用 Azure DevOps 的内建功能。这样做可能吗?

  9. 你想从 Azure DevOps 部署一个应用到 Azure Service Fabric。你需要安装一个扩展来完成这项任务吗?

  10. 你需要对 Kubernetes 中的一个部署应用更新。应该使用什么命令?

    1. kubectl apply

    2. kubectl deployments

    3. kubectl get services

    4. kubectl deploy

  11. 在 Azure DevOps 中使用什么类型的任务来将容器部署到 Azure Kubernetes?

    1. Kubernetes 清单

    2. Kubernetes

    3. Kubernetes 通用任务

    4. Kubectl

  12. 什么类型的文件最适合部署到 Kubernetes 集群中?

    1. ARM 模板

    2. Terraform 文档

    3. PowerShell 脚本

    4. YAML 部署文件

实施依赖管理

  1. 你正在使用 Azure Artifacts 来托管你的团队创建的 NuGet 包。你有一个新需求,要将你创建的其中一个(且仅有一个)包提供给组织中的所有其他团队。以下哪些是有效的解决方案?[选择多个。]

    1. 你创建一个新的源,并允许你 Azure Active Directory 中的任何用户使用该源中的包。你将要共享的包移动到此源中。

    2. 你允许组织内的所有用户使用你的现有源。

    3. 你创建一个新的源并允许组织内的任何用户使用该源中的包。你将要共享的包移动到此视图中。

    4. 你在现有的源中创建一个新视图,并将要共享的包发布到此视图。接下来,你配置使得组织中的所有成员都可以从该视图中读取包。

    5. 你将现有的源添加为上游源,以便其他团队也可以拉取你的包。

  2. 以下哪些是将你的解决方案拆分成多个较小的解决方案,并使用共享包或库来组装完整应用程序的有效理由?[选择两个。]

    1. 你在一个解决方案中有超过 25 个 C# 项目。

    2. 你的代码库变得非常庞大,以至于编译和/或运行单元测试开始变得过慢。

    3. 你的团队变得太大,已经被拆分为两个小组。将解决方案拆分开也将明确各自的所有权:每个团队一个解决方案。

    4. 你正在接近 Git 仓库中 10,000 个文件的限制。

  3. 以下哪些是 Azure Artifacts 支持的上游源?[选择两个。]

    1. Composer

    2. Python

    3. Gems

    4. Maven

  4. 你正在使用 Azure Artifact 源来进行依赖管理。你正在依赖一个通过 NuGet 公共提供的库。以下哪些可以用来通过现有的源使用此包?

    1. 上游源

    2. 外部视图

    3. 上游视图

    4. 依赖视图

  5. 你有一个库,它在两个应用程序中使用,但仅限于你自己的团队。以下哪种策略是共享此库的最佳方式?

    1. 将共享库作为共享项目链接到两个消费该库的解决方案中。

    2. 将库放在一个单独的代码库中,并使用构建管道来构建库,并将其作为 NuGet 包上传到 Azure Artifacts。在你的两个应用程序中使用这个包。

  6. 你想使用通用包将应用程序组件从 Azure DevOps 分发到不同的部署协调器。这样可以吗?

实施应用程序基础架构

  1. 你正在开发一个将在两个不同的 Azure 区域中部署的应用程序,以支持故障转移场景。以下哪些解决方案可以一起构成一个有效的解决方案?[选择两个。]

    1. 你创建了一个 ARM 模板和两个参数文件。第一个参数文件对应第一个 Azure 区域,第二个参数文件对应第二个 Azure 区域。你使用 ARM 模板更新基础设施。

    2. 你创建了一个 ARM 模板和参数文件,仅在一个区域更新基础设施。在另一个区域,你手动更新基础设施,以防止配置漂移。

    3. 你首先更新两个区域的基础设施。只有在基础设施更新成功后,你才会将应用程序部署到这两个区域。

    4. 你首先在一个区域更新基础设施,然后部署应用程序。只有在此成功后,才会更新另一区域的基础设施并将应用程序部署到该区域。

  2. 你需要使用 Azure DevOps 管道将 Azure 资源管理器模板部署到 Azure 资源组。一些你需要使用的参数存储在 Azure Key Vault 中。以下哪些选项组合不是完整解决方案的必要部分?

    1. 创建一个新的变量组,并使用服务连接将其链接到正确的 Key Vault。

    2. 给 Azure Active Directory 服务主体在正确的 Azure Key Vault 上分配读取者 RBAC 角色。

    3. 在你的 Azure DevOps 项目中配置一个新的 Azure 资源管理器服务连接,并通过这种方式创建一个新的 Azure Active Directory 服务主体。

    4. 为 Azure Active Directory 服务主体授予以下访问策略:列出和获取权限。

  3. 你负责在 Azure 中创建和配置多个虚拟机。你应该使用哪些工具组合?[选择两个。]

    1. Azure 自动化 DSC

    2. Azure 运行时 Runbooks

    3. ARM 模板

  4. 你正在配置应用程序将在其上部署的 Azure 资源组。你被提供了一个预创建的服务主体,以便从 Azure DevOps 管道进行部署。你应该给这个服务主体分配哪个 RBAC 角色来部署资源?

    1. 读取者

    2. 贡献者

    3. 部署者

    4. 拥有者

  5. 你需要为你的团队设置 RBAC 角色分配。你想遵循最小权限原则。同时,你还需要确保团队成员能访问他们所需要的资源。以下哪种解决方案最为合适?

    1. 你将用于部署的主体和所有团队成员添加到一个 Azure Active Directory 组。你将该 Azure Active Directory 组分配为资源组的贡献者角色。

    2. 你将用于部署的主体在资源组上授予贡献者权限,并将所有团队成员赋予读取者角色。

    3. 你创建了两个新的 Azure Active Directory 组:读取者和写入者。你将用于部署的服务主体添加到写入者组,将所有团队成员添加到读取者组。你将读取者角色分配给读取者组,写入者角色分配给写入者组。

    4. 你创建了一个新的 Azure Active Directory 组。你将用于部署的服务主体添加到此组,并为该组分配了贡献者角色。你为团队成员创建了一个升级程序,允许他们临时加入此 Azure Active Directory 组。

  6. 以下哪个工具不能用于管理 Azure 资源?

    1. Terraform

    2. Azure DevOps CLI

    3. Azure PowerShell

    4. CloudFormation

  7. 你正在实践基础设施即代码,并希望从 Azure DevOps 部署一个 ARM 模板,作为部署过程的一部分。以下哪个解决方案是以最简单的方式完成此操作的?

    1. 你在部署管道的 Cmd 任务中执行一个 Azure CLI 脚本。

    2. 你在部署管道中执行一个 PowerShell 脚本。

    3. 你使用内置任务来部署 ARM 模板。

    4. 你将模板上传到 Azure 存储账户,并使用 HTTP REST 调用来启动 ARM 模板的部署。

  8. 你正在通过实践 一切皆代码 来转变你团队交付应用程序的方式。以下哪项不能通过 Azure Blueprints 或 ARM 模板创建?

    1. Azure 订阅

    2. Azure Active Directory 安全组

    3. Azure RBAC 自定义角色

    4. Azure RBAC 角色分配

  9. 你所在的团队为其他团队提供 Azure 订阅和资源组。作为工作的一部分,你希望限制一个团队可以创建的 Azure 资源类型。你应该使用以下哪种方法?

    1. Azure RBAC 角色和角色分配

    2. Azure 策略

    3. OWASP Zed 攻击代理

    4. Azure 安全中心

  10. 使用基础设施和配置作为代码的方式有哪些不是其优势?

    1. 最小化配置漂移

    2. 同行审查支持

    3. 配置更改的响应时间更短

    4. 配置更改的源代码历史

实施持续反馈

  1. 你必须收集团队创建的应用程序的崩溃报告。你可以使用哪些工具来完成这项任务?[选择两个。]

    1. Snyk

    2. Raygun

    3. App Center

    4. Azure 自动化

  2. 你正在配置许多警报。有些警报需要通过电子邮件发送警告,而其他的则是关键错误,需要发送短信。无论警报是警告还是错误,你还需要更新一个本地构建系统,触发警报时要通知该系统。

你创建了以下解决方案:一个用于警告的操作组既发送电子邮件又调用本地构建系统的 WebHook。一个用于错误的操作组既发送短信又调用本地构建系统的 WebHook。对于警告的警报,你配置了操作组一;对于错误的警报,你配置了操作组二。

这是一个完整且正确的解决方案吗?

  1. 你正在配置许多警报。有些警报需要通过电子邮件发送警告,而其他的则是关键错误,需要发送短信。无论警报是警告还是错误,你还需要更新一个本地构建系统,触发警报时要通知该系统。

您创建了以下解决方案:一个动作组同时发送电子邮件、短信并调用主构建系统的 WebHook。您将在所有警报上配置该动作组,并添加一个警报条件配置,仅对警告发送电子邮件,仅对错误发送短信。

这是一个完整且正确的解决方案吗?

  1. 您正在配置多个警报。一些警报需要通过电子邮件发送警告,而其他警报则是严重错误,需要通过短信发送。无论警报是警告还是错误,您还需要更新一个主构建系统,以便触发警报时更新该系统。

您创建了以下解决方案:一个警告动作组发送电子邮件,一个错误动作组发送短信,第三个动作组调用主构建系统的 WebHook。对于警告,您配置了动作组一和二;对于错误,您配置了动作组二和三。

这是一个完整且正确的解决方案吗?

  1. 您希望邀请用户提供对产品的想法和建议。这应该在一个公共地方进行,以便其他用户可以对这些建议发表评论并投票。以下哪种工具可以用最简单的方式来做到这一点?[选择两个。]

    1. Azure Blob 存储静态站点

    2. GitHub 问题

    3. Uservoice

    4. Azure Boards 公共视图扩展

答案

1: 1, 2, 3, 4 2: 1, 2, 4 3: 1 4: 4, 1, 3, 2, 5 5: 2
6: 2 7: 2, 3 8: 4 9: 3 10: 2
11: 1 12: 1 13: 1, 3 14: 2 15: 1, 4
16: 1 17: 2 18: 2, 4 19: 1 20: 1, 2
21: 2, 3 22: 1(3), 2(2), 3(1) 23: 1, 3 24: 3 25: 2
26: 1 27: 2 28: 2 29: 1, 2 30: 4
31: 2 32: 3 33: 3, 4 34: 2 35: 1, 2, 3
36: 1 37: 1 38: 1 39: 2, 3, 6, 1, 8 40: 1
41: 2, 1, 3 42: 1, 3 43: 2 44: 3 45: 3
46: 2, 4 47: 1 48: 1 49: 1 50: 1
51: 2 52: 3 53: 2, 3 54: 1, 2, 5 55: 1
56: 2 57: 2 58: 3 59: 1 60: 2
61: 2 62: 1 63: 1 64: 4 65: 3, 4
66: 2, 3 67: 2, 4 68: 1 69: 2 70: 1
71: 1, 4 72: 2 73: 1, 3 74: 2 75: 4
76: 4 77: 3 78: 2 79: 2 80: 3
81: 2, 3 82: 1 83: 2 84: 1 85: 2, 3

第十五章:评估

第一章

  1. 正确。在传统组织中,开发通常负责创建软件变更,而运维则负责维护目标系统的稳定性。由于变更本身带有风险,可能破坏稳定性,因此运维通常对变更持抵触态度。

  2. 错误。理论上,虽然可以单独实践不同的 DevOps 实践,但真正的价值在于将它们结合起来。例如,没有持续集成和测试自动化的持续部署不仅几乎没有意义,甚至会很危险,因为没有持续集成和测试自动化提供的质量保证,持续部署变更是极具风险的。

  3. 错误答案是第 4 项。DevOps 不是一个职位名称,而是一种文化运动。实际上,在开发和运维之间创建一个新的 DevOps 团队,通常与 DevOps 哲学相悖。与其说是两个目标各自独立的团队或部门,不如说现在有了三个。

  4. Fastlaning 是一种加速处理未计划的高优先级工作而非计划工作的方法,同时保持整个团队的单一冲刺看板。

  5. DevOps 有很多定义。一些常见的主要元素包括商业价值、最终用户、持续部署、自动化和协作。

第二章

  1. 集中式和去中心化版本控制之间的主要区别在于,在去中心化版本控制系统中,每个用户都拥有源代码的完整历史。而在集中式系统中,只有服务器拥有完整历史。去中心化系统在与服务器断开连接时表现最佳,而集中式系统通常允许更详细的访问控制。

  2. 正确。Git 是最著名的去中心化版本控制系统。

  3. 正确答案是第 3 项。Rebasing 不是分支策略,而是合并策略。

  4. 在使用 Git 时,拉取请求用于请求将一个分支的更改合并到另一个分支。拉取请求可以被审查、批准或拒绝。为了强制使用拉取请求,可以使用 Git 策略。

  5. 正确答案是第 2 项。Trunk-based 开发不是合并策略,而是分支策略。

第三章

  1. 错误。持续集成是指将每个开发人员的工作与他们同事的工作至少每天集成一次,并对集成后的源代码进行构建和测试。仅仅运行每日构建并不等同于持续集成。

  2. 正确。经典的构建流水线总是与源代码库连接的。可能在构建流水线中并未使用这些源代码,但连接始终存在。

  3. 错误。可以创建一个直接从某个阶段开始的 YAML 流水线。链接到源代码仓库不再是强制性的。

  4. 正确答案是 1:服务连接。服务连接在包含需要调用外部工具的管道的组织或项目中进行配置。一旦配置了服务连接,它可以从一个或多个管道中使用。

  5. 正确答案是 1 和 3:对封闭网络的访问以及安装额外软件的能力。自托管代理部署在由你拥有的基础设施上。这意味着你可以将它们部署在你控制的网络上,从而使它们能够访问该网络。由于代理部署在你的基础设施上,你还可以决定安装哪些软件(以及不安装哪些软件)。任务和扩展任务会在代理执行作业之前自动下载到代理上。你可以拥有任意数量的并行管道而无需使用自托管代理。然而,为此你需要向微软购买额外的并行执行。

第四章

  1. 错误。也可以通过定时触发或手动触发新发布。

  2. 所有答案都是正确的。

  3. 答案 2 和 3 是正确的。基于环的部署和金丝雀部署都只将应用程序的新版本暴露给有限的用户群体。功能开关也用于逐步曝光,但它们并不是用来限制部署的风险,而是用来限制新功能发布的风险。

  4. 正确。部署组用于执行来自发布管道的任务,不是在组中的一个代理上执行,而是在所有代理上执行。部署组的目的是在运行代理的机器上部署软件。

  5. 一个可能的优势是,Azure DevOps 保留了所有步骤的端到端可追溯性。如果你还在 Azure DevOps 中管理工作项和源代码,你将保持从工作项到发布的端到端可追溯性,而且这一切仍然可以使用 App Center 进行实际的部署。

第五章

  1. 错误。一个包的版本可以在多个视图中可见。

  2. 错误。管道工件只能从 Azure DevOps 内的其他管道中使用。

  3. 正确。Azure Artifact feeds 可以用于将通用包共享给其他产品。这使得你可以在 Azure DevOps 中编译应用程序,将二进制文件作为通用包上传,然后在另一个产品中重新下载它。这在使用其他部署工具时非常有用,例如 Octopus Deploy。

  4. 正确答案是 2 和 4。答案 1 是错误的,因为包引用(无论是在.csproj文件中还是在nuget.config文件中)应该仅按名称和版本引用包。答案 3 是错误的,因为consumer不是 Azure Artifact feeds 中的有效访问级别。正确的访问级别是 reader(或更高),因此答案 2 是正确的。答案 4 也是正确的。你需要将包的位置添加到你的 NuGet 配置中。

  5. 一个动机可能是解决方案的大小。如果编译和测试解决方案需要的时间过长,以至于开发人员需要等待反馈,那么将解决方案拆分成较小的部分可能更好。这可以缩短开发人员的反馈周期,从而提高开发速度。另一个动机可能是多个团队正在开发一个应用程序,并且他们希望增加团队之间的隔离度。

第六章

  1. 正确。ARM 模板允许你为 Azure 资源组中的所有资源指定最终状态。应用 ARM 模板将始终导致创建缺失的资源并更新现有的资源。如果指定了 complete 部署模式,即使模板中没有的资源也会被删除。

  2. 正确答案是第 2 点。模块、Run As 帐户和变量都是在第六章,基础设施与配置即代码中讨论的构件。

  3. 错误。ARM 模板参数允许引用 Azure Key Vault 中的值,从而避免用户在源代码管理中输入密钥或其他敏感信息。在部署时,这些机密会被检索并在 Azure 中使用,前提是启动操作的身份具有对该 Key Vault 的访问权限。

  4. 正确。你可以在 Azure 自动化帐户中定义一个或多个计划,并将这些计划与 Runbook 关联起来。

  5. 实践基础设施即代码可以带来许多好处。两个常被提及的例子是防止配置漂移和能够按需创建新环境。配置漂移通过在预定计划内重新应用相同的基础设施规范来防止。按需创建环境可以用来快速创建新的测试环境,执行测试后再将其删除。这可以带来更可重复的测试结果,并可能节省测试基础设施的成本。

第七章

  1. 正确。Entity Framework 和 Entity Framework Core 都内置支持在对架构定义进行更改后生成迁移。

  2. 错误。大多数基于迁移的方法使用一个额外的表格来跟踪哪些迁移已经应用到数据库中。

  3. 正确。基于最终状态的方法通过将当前架构与目标架构进行比较来工作。这会生成一个一次性的 SQL 脚本,执行该脚本将数据库架构更新到目标状态。每次运行之间没有存储状态。

  4. 正确答案是 1 和 2。如果正确地并行运行,可以显著减少变更风险。如果出现问题,你可以随时移除所有新的代码和数据库副本,从一个正常运行的状态重新开始。确保两个环境都正常工作,还可以对生产工作负载进行非常精确的性能测量。然而,缺点之一是,周期时间实际上很可能会增加。你必须将多个较小的变更逐一推送到生产环境中,这样会增加总的时间消耗。

  5. 错误。你的模式(无论是隐式的还是以数据对象形式捕获的)仍然会发生变化。然而,只有当你从数据库中读取以前版本的对象时,变化才会显现。实际上,你只是在延迟处理模式变化的问题。

  6. 你可以选择不使用数据库级的编码技术,如存储过程和触发器。你将逻辑从数据库中提取出来,能减少必须做的数据库变更的总次数。

第八章

  1. 正确。在单元测试中,单个组件是被隔离测试的。在面向对象语言中,这通常是一个单独的类。

  2. 错误。在集成测试中,验证的是一组组件的正确工作,而不是整个组装好的系统。如果测试的是整个组装并部署的系统,这被称为系统测试。

  3. 答案 2 是正确的。测试金字塔建议进行大量单元测试,以验证尽可能多的需求。只有那些无法通过单元测试覆盖的风险,才会添加集成测试,从而减少集成测试的数量。系统测试的数量更少,仅用于覆盖那些单元测试或集成测试未能覆盖的风险。

  4. 答案 3 是正确的。所有其他类型的测试都在本章中介绍。

  5. 这里可以提到的两种技术是代码审查和管道门控。代码审查让开发者检查同事的工作,帮助彼此保持高质量。管道门控则可以用来阻止应用的构建或版本在某些条件不满足时进一步传播。例如,条件可以包括某些质量指标,或最低的测试覆盖率或测试结果标准。

第九章

  1. 错误。为了安全地创建和交付软件,整个过程,尤其是管道,必须得到保障。仅在最后添加安全措施是行不通的,因为安全必须贯穿交付过程的每一个步骤。

  2. OWASP Zed Attack ProxyZAP)可以用于这类测试。

  3. 正确。在现代应用中,多达 80%的代码可能来自开源库或框架。

  4. 正确答案是 1、2、4 和 6。没有 Azure DevOps 安全变量或 Azure DevOps 密钥库这种东西。

  5. Azure Policy 可以用于禁止或列出不希望出现的 Azure 配置,通常与基础设施配置或资源配置相关。Azure Security Center 可以用于识别和修复运行时的安全风险。

第十章

  1. 错误。Azure 发出的平台指标由每个独立服务定义,无法更改。

  2. 93 天。这个数字保证了至少有三个月的历史数据。

  3. 正确。自定义指标可以通过 SDK 或 REST API 在你自己的应用程序代码中计算并发送到 Application Insights。

  4. 警报疲劳。

  5. 正确。Azure 允许创建包含 Webhooks 的操作组,在触发警报时调用这些 Webhooks。

第十一章

  1. 错误。一个可能的缺点是失去市场竞争优势。如果竞争对手知道你接下来要开发什么,他们可能会在这一方面有所准备。

  2. 可能的担忧是某些用户或用户群体比其他人更为活跃,这可能导致公众意见与实际听到的意见之间存在差异。此外,关于公开路线图的反馈很可能只来自现有用户,而潜在用户可能不会就他们缺失的功能对你的路线图发表评论。

  3. 本章讨论的两个示例是社交媒体渠道上的情感分析和支持请求的数量与严重性。

  4. 答案 3 是正确的。假设陈述了一种信念,即某个功能是必要的——这是假设的部分。第二部分是需要观察的可衡量的用户反馈,直到这个信念得到确认,这被称为确认阈值。假设还没有得出结论。

  5. 用户访谈或焦点小组的潜在好处是,它们通常在较小规模下进行,不仅可以衡量反馈,还可以理解背后的原因。另一个好处是,参与者可以被精心选择,以代表所有用户或特定的用户群体。

第十二章

  1. 容器对 DevOps 的好处包括一致性、关注点的分离和平台的可移植性。

  2. 正确:根据主机操作系统的不同,容器的托管位置并不重要。

  3. 是的,这是可能的。可以通过添加 Docker 支持和项目级别来实现。

  4. RUN命令用于安装组件或在构建容器镜像的过程中执行操作。

  5. 节点和 Pod 可以在 Azure Kubernetes Service 中调度。它们都可以手动或自动扩展。

第十三章

  1. 错误。某些信息可以传递到其他地区,或在全球范围内可用。例如,有时当选择的区域的容量不足时,代理会在其他区域运行。

  2. 工作项 | 项目 | 组织 | 区域。Azure DevOps 组织是用户可以创建的顶级构建。每个组织位于一个特定区域,由微软维护。在一个组织内,可以创建一个或多个项目。反过来,一个项目可以包含许多工作项,例如用户故事、功能或史诗。

  3. 错误。一般建议是保持项目数量适中:项目越少越好。隔离性和非常严格的授权边界可能是选择使用多个项目的原因。

  4. 授权和许可。可以设置授权,以便每个单独用户或一组用户可以访问的权限。分配给用户的许可还可以限制某些功能的使用。例如,拥有利益相关者许可的用户不能使用源代码控制。

  5. 端到端可追溯性。在执行工作管理、源代码控制、构建、工件和部署时,如果使用单一工具,就可以追踪到某个用户故事是如何交付给用户的。

posted @ 2025-06-27 17:07  绝不原创的飞龙  阅读(23)  评论(0)    收藏  举报