构建面向 ARM 模板的实际 Azure DevOps 流水线

当您在线搜索时,您会找到关于Azure DevOps的各种博客文章、文档和教程。所有这些都是有价值的资源,但很少有人能够通过一个真实的场景来引导您。许多人只是简单地浏览安全方面,以便将密码以明文形式留下,要么是为了简单起见,要么是为了最终产品基本上什么都不做。让我们改变这一点。

在这篇文章/教程中,您将从头到尾学习如何构建一个真实的 Azure DevOps 发布流水线,该流水线自动化基础架构。更具体地说,您将学习如何使用 Azure DevOps 构建连续部署流水线以配置 Azure 虚拟机。

通过这个项目的最后,您将拥有一个完全运行的 Azure 流水线。从单个 GitHub 存储库提交开始,它将:

  • 构建一个临时的 Azure 资源组
  • 通过 ARM 模板配置 Azure VM
  • 在 CI/CD 流水线中设置该 ARM 模板
  • 在模板发生任何更改时,启动模板验证测试
  • 将 ARM 模板部署到 Azure
  • 测试已部署的基础结构
  • 拆除所有 Azure 资源

让我们立即开始!

项目概览

此项目将分为六个主要部分。它们是:

Azure 资源准备

在本节中,您将学习如何在 Azure 中设置所有先决条件资源。在这里,您将:

  • 创建 Azure 服务主体 以执行管道中的各种任务
  • 建立一个 Azure Key Vault 并为管道提供所需的秘密
  • 设置适当的访问策略以供 ARM 部署和管道使用

Azure DevOps 准备工作

一旦您设置了所有 Azure 资源,就该为您的管道准备 Azure DevOps。 在本节中,您将:

脚本/模板概述

有各种与该项目相关的工件,包括用于构建服务器的ARM模板和Pester测试。在本节中,我们将简要介绍模板正在提供的内容以及Pester在流水线中具体测试的内容。

流水线创建

在这一部分,真正的乐趣开始了。您将开始设置实际的流水线。在这里,您将学习如何通过单个YAML文件设置整个编排。

您将使用多阶段流水线UI体验构建流水线。截至撰写本文时,此功能处于预览状态。

流水线演示

一旦构建了流水线,您需要看它运行!在这一部分中,您将学习如何触发流水线并简单地观察魔法发生。

清理

最后,由于这只是一次演示,您将获得一个脚本来拆除教程期间构建的所有内容。

听起来很多吗?是的!但不用担心,您将逐步学习,一次攻克一个任务。

如果您想要一个包含用于构建此流水线的所有Azure CLI命令的脚本,您可以在ServerAutomationDemo GitHub存储库中找到,名称为demo.ps1

先决条件

你将会学到很多,但也需要做好准备。如果你计划跟着做,请确保你有以下内容:

ARM deployments allowed to access the key vault
  • Cloud Shell 或 PowerShell 6+(如果在本地运行) – 示例可能在 Windows PowerShell 中运行,但未经测试。所有示例将在本地通过 PowerShell 控制台执行,但 Cloud Shell 也可以正常工作。你将会自动构建流水线。
  • 安装 Azure CLI(如果在本地运行) – 你将学习如何使用 Azure CLI 在本文中执行任务。但是,同样的操作也可以通过 Azure 门户、PowerShell 或 Azure SDK 执行。

警告:你即将执行的操作将花费真实的货币,除非你有一些 Azure 信用额度。你将在 Azure 中启动的成本最高的资源是一个虚拟机,但仅是暂时的。

开始之前

在本教程中,你将进行大量的配置。在开始之前,请确保你准备好以下物品。

  • 将要部署到的 Azure 订阅资源的名称 – 示例将使用 亚当自动化师
  • 订阅的ID
  • Azure AD 租户的Azure AD 租户 ID
  • DevOps 组织名称 – 本示例将使用adbertram.
  • 放置资源的区域 – 本示例将使用eastus.
  • 将临时密钥库放入的 Azure 资源组的名称 – 本示例将使用ServerAutomationDemo.
  • A password to assign to the local administrator account on a deployed VM – the examples will use “I like azure.”.
  • GitHub 存储库的 URL – 本示例将使用https://github.com/adbertram/ServerAutomationDemo.

使用 Azure CLI 登录

在文章中,您需要大量使用 Azure CLI 进行操作。我喜欢 Azure PowerShell cmdlets,但是 Azure CLI 目前能够执行更多的 DevOps 任务。

您的第一个任务是进入 PowerShell 6+ 控制台。一旦在控制台中,请使用以下命令通过 Azure 进行身份验证:az login。该命令将打开一个浏览器窗口,并提示您输入帐户信息。

身份验证成功后,请确保将您的订阅设置为默认。将其设置为默认将避免您不断地指定它。

az login
az account set --subscription 'Adam the Automator'

准备 Azure 资源

一旦使用 Azure CLI 登录,就该开始处理业务了。Azure Pipeline 有许多不同的依赖项和各种各样的开关。在这个第一部分中,您将学习如何进行一些设置,并为您的流水线准备环境。

安装 Azure CLI DevOps 扩展

您需要一种使用 Azure CLI 构建各种 Azure DevOps 组件的方法。默认情况下,它不包括该功能。要从 Azure CLI 管理 Azure DevOps,您需要安装 DevOps 扩展

幸运的是,安装扩展只需要一行命令,如下所示。

az extension add --name azure-devops

安装扩展后,将组织设置为默认值,以防止反复指定它。

az devops configure --defaults organization=https://dev.azure.com/adbertram

创建资源组

尽管管道将创建一个临时资源组,但您也应该为此演示中引入的任何资源创建一个资源组。更具体地说,这个资源组是您将创建 Azure Key Vault 的地方。

az group create --location "eastus" --name "ServerAutomationDemo"

创建 Azure 服务主体

下一个任务是创建 Azure 服务主体。您需要一个 Azure 服务主体来进行 Azure 密钥保管库的身份验证。您还将使用此服务主体来验证 服务连接。按照下面的步骤为密钥保管库和最终 ARM 部署创建服务主体。

$spIdUri = "http://ServerAutomationDemo"
$sp = az ad sp create-for-rbac --name $spIdUri | ConvertFrom-Json

此时,将 $sp.appId 的值保存在某个地方是个好主意。稍后构建管道时,您会需要它!

您会在示例中看到一些 PowerShell 命令,例如 ConvertFrom-Json。由于 Azure CLI 仅返回 JSON 字符串,将其转换为 PowerShell 对象后,更容易引用属性。

构建密钥保管库

本教程中的管道需要引用一些密码。与其以明文存储密码,不如用正确的方式处理。所有敏感信息都将存储在 Azure 密钥保管库中。

要创建密钥保管库,请使用如下所示的 az keyvault create 命令。此命令在之前创建的资源组中创建密钥保管库。还请注意 enabled-for-template-deployment 开关。这会更改密钥保管库访问策略,允许未来的 ARM 部署访问密钥保管库。

az keyvault create --location $region --name "ServerAutomationDemo-KV" --resource-group "ServerAutomationDemo" --enabled-for-template-deployment true
Allowing ARM to access the keyvault

创建密钥保管库密码

一旦密钥保管库创建完成,就是创建密码的时候了。在此演示中,请创建两个名为ServerAutomationDemo-AppPwStandardVmAdminPassword的密码。AppPw密码是服务主体的密码。VM密码将被分配给在VM上部署的本地管理员账户。

az keyvault secret set --name "ServerAutomationDemo-AppPw" --value $sp.password --vault-name "ServerAutomationDemo-KV"
az keyvault secret set --name StandardVmAdminPassword --value "I like azure." --vault-name "ServerAutomationDemo-KV"

请注意,在此示例中,您正在使用先前定义的PowerShell变量。在这里,您提供服务主体密码($sp.password)。

允许流水线访问密钥保管库

接下来,流水线需要权限访问密钥保管库。放宽密钥保管库访问策略一点,为创建的服务主体提供getlist权限来管理密钥保管库的密码。

az keyvault set-policy --name "ServerAutomationDemo-KV" --spn $spIdUri --secret-permissions get list

准备Azure DevOps

现在您已经完成了所有Azure资源的准备工作。是时候在Azure DevOps中做一些准备工作了。

安装Pester扩展

首先要执行的任务是安装PesterRunner Azure DevOps扩展。流水线将运行两组Pester测试,以确保VM ARM部署成功。运行Pester测试的一种简单方法是使用PesterRunner扩展。

使用下面的命令安装扩展。

az devops extension install --extension-id PesterRunner --publisher-id Pester

创建Azure DevOps项目

现在是创建项目的时候,流水线将在其中创建。使用 Azure CLI 创建 Azure DevOps pipeline 非常简单。只需运行下面的命令来创建项目并将项目设置为默认项目。

az devops project create --name "ServerAutomationDemo"
az devops configure --defaults project=ServerAutomationDemo

创建服务连接

您的流水线需要对两个服务进行身份验证 – ARM 和您的 GitHub 存储库。为此,必须创建两个服务连接。

首先,创建 ARM 服务终结点。下面的命令将提示输入服务主体密码。确保首先在控制台上显示它并将其复制到剪贴板。

确保填入您的订阅 ID、租户 ID,并替换下面的订阅名称。

## 运行 $sp.password 并将其复制到剪贴板
$sp.Password

## 创建服务终结点
az devops service-endpoint azurerm create --azure-rm-service-principal-id $sp.appId --azure-rm-subscription-id "YOURSUBSCRIPTIONIDHERE" --azure-rm-subscription-name 'Adam the Automator' --azure-rm-tenant-id $tenantId --name 'ARM'

接下来,为 GitHub 创建一个服务连接。由于流水线将通过 Git 提交触发,它需要能够读取存储库。

此时 GitHub 个人访问令牌就派上用场了。下面您还需要再次粘贴服务主体密码。您将为两个服务连接使用相同的服务主体。

$gitHubServiceEndpoint = az devops service-endpoint github create --github-url 'https://github.com/adbertram/ServerAutomationDemo' --name 'GitHub' | ConvertFrom-Json

## 在提示时粘贴 GitHub 令牌 

创建变量组

管道将引用两个密码的关键保管库密码。要安全地执行此操作,您必须创建一个变量组并将其链接到关键保管库。

首先,按照下面的步骤创建变量组。

az pipelines variable-group create --name "ServerAutomationDemo" --authorize true --variables foo=bar

注意foo=bar变量?虽然没有使用,但是需要一个单一的变量来创建变量组。

将变量组链接到关键保管库

此时,您不幸地需要转到 Azure DevOps 门户。截至本文撰写时,Azure CLI 尚无法将关键保管库链接到变量组。

导航至 Azure DevOps 项目,然后单击。然后,您应该看到如下所示的ServerAutomationDemo变量组。单击ServerAutomationDemo变量组。

Available variable group

进入变量组后,单击将 Azure 关键保管库的秘密链接为变量。这样做后,您将收到警告,说明您将擦除所有变量,并单击确认。您将看到如何执行此操作。此操作没有问题,因为foo变量始终是临时的。

Linking variable group to pipeline

确认后,选择ARM服务连接和之前创建的ServerAutomationDemo-KV关键保管库,如下所示。然后单击添加

Setting the service connection

现在检查如下所示之前创建的两个密码,并单击确定保存以保存更改。

Selecting keyvault secrets for the pipeline to use

项目文件概览

如果你已经走到这一步,恭喜!你现在已经准备好开始构建流水线了。但等等……还有更多!

为了使Azure Pipeline在现实世界中发挥作用,本教程构建了一个包含“单元”和“验收”测试的完整流水线。这使得教程更加有趣,但也需要对正在发生的事情进行一些额外解释。

在本教程的GitHub存储库中,你会找到一些文件,如下所示。现在是时候要么克隆这个存储库,要么根据这些文件构建你自己的存储库了。

GitHub files list
  • azure-pipelines.yml – 最终的YAML流水线
  • connect-azure.ps1 – 用于对Azure订阅进行身份验证的PowerShell脚本
  • server.infrastructure.tests.ps1 – 一个简单的Pester测试,用于确认VM配置是否良好
  • server.json – 用于部署VM的Azure ARM模板
  • server.parameters.json – 提供ARM模板参数值的Azure ARM参数模板。

确保在server.parameters.json文件中替换你的订阅ID和密钥保管库名称为密钥保管库IT。

  • server.templates.tests.ps1 – 用于确认ARM模板是否有效的Pester“单元”测试

稍后你将看到这些文件如何在流水线中相互配合。

创建管道

假设您已克隆了我的 GitHub 仓库或自己建立了一个,现在是创建管道的时候了!要做到这一点,请运行az pipelines create命令。下面的命令将使用提供的 GitHub 仓库作为触发器创建一个名为ServerAutomationDemo的管道。它将查看master分支并使用之前构建的服务连接。

az pipelines create --name "ServerAutomationDemo" --repository "https://github.com/adbertram/ServerAutomationDemo" --branch master --service-connection $gitHubServiceEndpoint.id --skip-run

根据您的 GitHub 仓库中是否有azure-pipelines.yml文件,您可能会或可能不会收到类似以下的反馈。无论哪种方式,您的控制台都会看起来类似。请确保您的 GitHub 个人访问令牌已准备就绪!

Creating the Azure DevOps pipeline with the Azure CLI

YAML 管道审阅

此时,您的管道已准备就绪,但首先了解 YAML 管道非常重要。请查看azure-pipelines.yml文件。当使用多阶段 YAML 管道功能时,该文件是管道。

让我们分解组成此 YAML 管道的各个组件。

触发器

由于您正在构建自动运行的 CI 管道,因此您需要一个触发器。以下触发器指示管道在 Git 主分支中检测到提交时运行。

请注意paths部分。默认情况下,在CI构建中,如果您不明确包含或排除文件或目录,则当在任何文件上提交时,流水线将运行。由于这个项目完全围绕一个ARM模板构建,所以如果例如您对Pester测试进行了微调,您就不希望运行流水线。

trigger:
  branches:
    include:
      - master
  paths:
    include:
      - server.json
      - server.parameters.json

每个构建都需要一个代理。每个构建代理需要在虚拟机上运行。在这种情况下,虚拟机使用的是ubuntu-latest虚拟机映像。这个映像是在构建最初创建时定义的默认映像。由于这个流水线的“简单性”,它没有被更改。

pool:
  vmImage: "ubuntu-latest"  

变量

接下来,我们有所有的变量和变量组。此流水线中的各种任务需要读取诸如Azure订阅ID、租户ID和服务主体的应用程序ID等值。与在每个任务中复制静态值不同,它们被定义为变量。

还请注意group元素。此元素引用了您之前创建的变量组。请务必在此时替换subscription_idtenant_id

请记住,在创建Azure服务主体部分,您被提醒要将$sp.appId的值保存在某个地方。这就是您需要它的地方。将该服务主体应用程序ID的值分配给application_id,如下所示。

variables:
    - group: ServerAutomationDemo
    - name: azure_resource_group_name
      value: "ServerProvisionTesting-$(Build.BuildId)"
    - name: subscription_id
      value: "XXXXXXXXXXXXX"
    - name: application_id
      value: "XXXXXXXXXXXXX"
    - name: tenant_id
      value: "XXXXXXXXXXXX"

注意azure_resource_group_name变量的值。在该值中,您会看到$(Build.BuildId)。这是一个系统变量,代表当前作业的构建ID。在这个上下文中,它被用来确保创建的临时资源组是唯一的。

PowerShell 准备任务

接下来的一系列任务调用 PowerShell 代码。这个管道示例使用 PowerShell 来创建和移除一个用于测试目的的临时资源组。在这些部署任务中,您将看到两个调用 PowerShell 代码的示例。

第一个任务调用一个存在于 GitHub 仓库中的脚本connect-azure.ps1。这个任务用于认证 Azure 订阅,以便后续的 Azure PowerShell 命令能够运行。

这个 Azure PowerShell 连接任务通过调用脚本,并传递一个密钥库秘密值(ServerAutomationDemo-AppPw)以及管道变量subscription_idapplication_idtenant_id

第二个任务运行的 PowerShell 代码inline,意味着脚本并不存在。相反,PowerShell 代码在管道 YAML 本身中使用azure_resource_group_name管道变量的值定义。

- task: PowerShell@2
  inputs:
    filePath: "connect-azure.ps1"
    arguments: '-ServicePrincipalPassword "$(ServerAutomationDemo-AppPw)" -SubscriptionId $(subscription_id) -ApplicationId $(application_id) -TenantId $(tenant_id)'
- task: PowerShell@2
  inputs:
    targetType: "inline"
    script: New-AzResourceGroup -Name $(azure_resource_group_name) -Location eastus -Force

Pester 模板测试

接下来我们有第一个 Pester 测试。在这样一个 CI/CD 管道中,拥有几个不同层次的测试是非常重要的。如果您正在为一个软件项目创建一个管道,您可能会创建各种单元测试。

由于这个示例管道是围绕单个 ARM VM 部署构建的,因此首先要进行的“单元”测试是测试 JSON 模板的有效性。您可以在 server.templates.tests.ps1 文件中添加尽可能多的针对 ARM 模板文件本身的不同测试。

请注意下面,管道正在使用各种 系统变量。这些变量引用了文件一旦传送到构建代理后的文件位置。

PesterRunner 任务将测试结果发送到一个 XML 文件,稍后管道将读取该文件。

- task: Pester@0
  inputs:
    scriptFolder: "@{Path='$(System.DefaultWorkingDirectory)/server.template.tests.ps1'; Parameters=@{ResourceGroupName='$(azure_resource_group_name)'}}"
    resultsFile: "$(System.DefaultWorkingDirectory)/server.template.tests.XML"
    usePSCore: true
    run32Bit: False

ARM VM 部署

我们现在来到了 ARM 部署阶段。由于整个管道都是围绕部署 VM 的能力构建的,因此这一步非常重要!该任务部署 ARM 模板,并提供了使其发生的所有必需属性。

请注意 deploymentOutputs: arm_output 属性。在下一步中,需要一个任务连接到已部署的 VM。通过 ARM 部署返回 DNS 名称或 IP 地址是获取此 VM 的一种好方法。 deploymentOutputs 选项创建了一个可以在其他任务中引用的管道变量。

- task: AzureResourceManagerTemplateDeployment@3
  inputs:
    deploymentScope: "Resource Group"
    azureResourceManagerConnection: "ARM"
    subscriptionId: "1427e7fb-a488-4ec5-be44-30ac10ca2e95"
    action: "Create Or Update Resource Group"
    resourceGroupName: $(azure_resource_group_name)
    location: "East US"
    templateLocation: "Linked artifact"
    csmFile: "server.json"
    csmParametersFile: "server.parameters.json"
    deploymentMode: "Incremental"
    deploymentOutputs: "arm_output"

Pester “接受” 测试

VM 部署完成后,您需要确保它已经通过“集成”或“接受”测试正确部署。这个 PesterRunner 任务正在调用 Pester,并运行另一组与基础设施相关的测试,以确保 VM 已成功部署。

请注意,我们通过 ArmDeploymentJsonOutput 参数传入 ARM 部署的输出值。Pester 测试脚本文件定义了一个参数,该参数接受该值并读取 VM 的 DNS 主机名。

 - task: Pester@0
    inputs:
      scriptFolder: "@{Path='$(System.DefaultWorkingDirectory)/server.infrastructure.tests.ps1'; Parameters=@{ArmDeploymentJsonOutput='$(arm_output)'}}"
      resultsFile: "$(System.DefaultWorkingDirectory)/server.infrastructure.tests.XML"
      usePSCore: true
      run32Bit: False

您可以看到下面的 PowerShell 脚本文件server.infrastructure.tests.ps1的样子。请注意,它正在读取 VM 的 DNS 主机名,然后运行简单的开放端口检查。

$ArmDeploymentOutput = $ArmDeploymentJsonOutput | convertfrom-json

## 运行测试
describe 'Network Connnectivity' {
    it 'the VM has RDP/3389 open' {
        Test-Connection -TCPPort 3389 -TargetName $ArmDeploymentOutput.hostname.value -Quiet | should -Be $true
    }
}

“验收”测试清理

流水线部署基础结构的唯一原因是测试 ARM 模板的有效性。因为这个基础设施是暂时的,所以需要清理。在最后一个 PowerShell 任务中,流水线正在移除早期创建的资源组及其中的所有内容。

- task: PowerShell@2
  inputs:
    targetType: "inline"
    script: Get-AzResourceGroup -Name $(azure_resource_group_name) | Remove-AzResourceGroup -Force

Pester 测试发布

最后,我们来到了最后一组任务。Azure 流水线有一个名为 发布测试结果 的任务。此任务会在构建代理上读取 XML 文件,并在 Azure DevOps 中显示测试结果。这是一种方便的方式,可以轻松查看所有运行的测试结果。

- task: PublishTestResults@2
  inputs:
    testResultsFormat: "NUnit"
    testResultsFiles: "$(System.DefaultWorkingDirectory)/server.infrastructure.tests.XML"
    failTaskOnFailedTests: true

- task: PublishTestResults@2
  inputs:
    testResultsFormat: "NUnit"
    testResultsFiles: "$(System.DefaultWorkingDirectory)/server.template.tests.XML"
    failTaskOnFailedTests: true
The Tests section of a pipeline run

使用 Azure DevOps 流水线

最后,我们准备运行流水线并查看其工作原理。在 Azure DevOps Web UI 中,请确保您在 ServerAutomationDemo 项目中。一旦在这里,请点击 管道,然后您应该会看到 ServerAutomationDemo 管道。

运行流水线的一种方式是点击右侧的三个点,如下所示。然后,点击 运行管道。这将启动自动化过程。

Running a pipeline

流水线将继续运行,并按照指示执行每个任务。到最后,您应该看到作业执行的每个任务的所有绿色复选标记,如下所示。

Successful job task execution

清理

一旦您在流水线中摆弄过,并且您在这里完成了所有任务,您应该清理一下。毕竟,这只是一个教程,不是一个生产任务!

以下是一些命令,用于清理本文中构建的所有内容。此代码会移除服务主体、Azure AD 应用程序、资源组及其中的所有内容以及 Azure DevOps 项目。

$spId = ((az ad sp list --all | ConvertFrom-Json) | ? { '<https://ServerAutomationDemo>' -in $_.serviceprincipalnames }).objectId
az ad sp delete --id $spId

## 移除资源组
az group delete --name "ServerAutomationDemo" --yes --no-wait

## 移除项目
$projectId = ((az devops project list | convertfrom-json).value | where { $_.name -eq 'ServerAutomationDemo' }).id
az devops project delete --id $projectId --yes

总结

本教程旨在让您了解构建真实 Azure DevOps 基础设施自动化流水线的一瞥。尽管有无数其他构建此类流水线的方法,但您在本教程中学到的技能应该能帮助您完成许多不同的配置。

现在去吧,开始更多自动化吧!

Source:
https://adamtheautomator.com/azure-devops/