使用 DSC 生成持续集成和连续部署管道

Azure DevOps Services | Azure DevOps Server 2020 | Azure DevOps Server 2019 | TFS 2018

注意

在 Microsoft Team Foundation Server (TFS) 2018 和更低版本中,生成和发布管道被称为“定义”,运行被称为“生成”,服务连接被称为“服务终结点”,阶段被称为“环境”,而作业被称为“阶段” 。

此示例演示如何使用 PowerShell、DSC 和 Pester 生成持续集成/持续部署 (CI/CD) 管道。

生成和配置管道后,可以用它来完全部署、配置和测试 DNS 服务器及关联的主机记录。 此过程模拟了将用于开发环境的管道第一部分。

自动 CI/CD 管道有助于更快、更可靠地更新软件,确保所有代码都经过测试,并确保最新版代码始终可用。

先决条件

若要使用此示例,应熟悉以下内容:

将需要

若要生成并运行此示例,需要一个包含多台计算机和/或虚拟机的环境。

客户端

将在这台计算机上执行生成和运行此示例所需的全部工作。 客户端计算机必须是安装以下项的 Windows 计算机:

Azure DevOps订阅

Azure DevOps 组织。 如果你没有此类帐户,可免费创建一个。 (Azure DevOps 组织不同于你的GitHub组织。如果要在它们之间对齐,请为它们指定相同的名称。)

TFSSrv

托管 TFS 服务器的计算机,将在其中定义生成和发布。 必须在此计算机上安装 Team Foundation Server 2017

BuildAgent

运行用于生成项目的 Windows 生成代理的计算机。 必须在此计算机上安装并运行 Windows 生成代理。 若要了解如何安装和运行 Windows 生成代理,请参阅在 Windows 上部署代理

还需要在此计算机上安装 xDnsServerxNetworking DSC 模块。

TestAgent1

在此示例中,DSC 配置将这台计算机配置为 DNS 服务器。 此计算机必须运行 Windows Server 2016

TestAgent2

这是托管此示例配置的网站的计算机。 此计算机必须运行 Windows Server 2016

将代码添加到存储库

首先创建 Git 存储库,并从客户端计算机上的本地存储库导入代码。 如果尚未将 Demo_CI 存储库克隆到客户端计算机,请立即运行以下 git 命令:

git clone https://github.com/PowerShell/Demo_CI
  1. 在客户端计算机上的 Web 浏览器中,转到 TFS 服务器。

  2. 创建名为Demo_CI的新团队项目

    请务必将“版本控制”设置为“Git”

  3. 在客户端计算机上运行以下命令,添加对刚刚在 TFS 中创建的存储库的远程控制:

    git remote add tfs <YourTFSRepoURL>

    其中,<YourTFSRepoURL> 是在上一步中创建的 TFS 存储库的克隆 URL。

    如果不知道在何处找到此 URL,请参阅克隆现有 Git 存储库

  4. 运行以下命令,将代码从本地存储库推送到 TFS 存储库:

    git push tfs --all

  5. 此时,TFS 存储库将填充有 Demo_CI 代码。

  1. 在 Web 浏览器中导航到Azure DevOps订阅。

  2. 创建名为Demo_CI的新团队项目。 请务必将“版本控制”设置为“Git”

  3. 在客户端计算机上,使用以下命令将远程添加到刚刚创建的存储库:

    git remote add devops <YourDevOpsRepoURL>

    在上一步中创建的Azure DevOps存储库的克隆 URL 在哪里<YourDevOpsRepoURL>

    如果不知道在何处找到此 URL,请参阅克隆现有 Git 存储库

  4. 运行以下命令,将代码从本地存储库推送到 TFS 存储库:

    git push devops --all

  5. Azure DevOps存储库将填充Demo_CI代码。

注意

此示例使用 Git 存储库 ci-cd-example 分支中的代码。 请务必将此分支指定为项目中的默认分支,以及创建的 CI/CD 触发器。

了解代码

创建生成和部署管道前,我们先来分析一些代码,以便了解具体情况。 在客户端计算机上,打开常用文本编辑器,再转到 Demo_CI Git 存储库根。

DSC 配置

从本地 Demo_CI 存储库根 ./InfraDNS/Configs/DNSServer.ps1 打开文件 DNSServer.ps1

此文件包含用于设置 DNS 服务器的 DSC 配置。 完整内容如下:

configuration DNSServer
{
    Import-DscResource -module 'xDnsServer','xNetworking', 'PSDesiredStateConfiguration'

    Node $AllNodes.Where{$_.Role -eq 'DNSServer'}.NodeName
    {
        WindowsFeature DNS
        {
            Ensure  = 'Present'
            Name    = 'DNS'
        }

        xDnsServerPrimaryZone $Node.zone
        {
            Ensure    = 'Present'
            Name      = $Node.Zone
            DependsOn = '[WindowsFeature]DNS'
        }

        foreach ($ARec in $Node.ARecords.keys) {
            xDnsRecord $ARec
            {
                Ensure    = 'Present'
                Name      = $ARec
                Zone      = $Node.Zone
                Type      = 'ARecord'
                Target    = $Node.ARecords[$ARec]
                DependsOn = '[WindowsFeature]DNS'
            }
        }

        foreach ($CName in $Node.CNameRecords.keys) {
            xDnsRecord $CName
            {
                Ensure    = 'Present'
                Name      = $CName
                Zone      = $Node.Zone
                Type      = 'CName'
                Target    = $Node.CNameRecords[$CName]
                DependsOn = '[WindowsFeature]DNS'
            }
        }
    }
}

请注意 Node 语句:

Node $AllNodes.Where{$_.Role -eq 'DNSServer'}.NodeName

此语句可查找所有定义为在 DevEnv.ps1 脚本创建的配置数据中担任 DNSServer 角色的节点。

可阅读 about_arrays 了解有关 Where 方法的详细信息

请务必在执行 CI 时使用配置数据定义节点,因为节点信息可能会在不同环境中进行切换,而使用配置数据则可以轻松更改节点信息,无需更改配置代码。

在第一个资源块中,配置调用 WindowsFeature,以确保启用 DNS 功能。 后面的资源块调用 xDnsServer 模块中的资源,以配置主要区域和 DNS 记录。

请注意,这两个 xDnsRecord 块包装在循环访问配置数据数组的 foreach 循环中。 再强调一遍,配置数据是由 DevEnv.ps1 脚本创建,接下来我们将对此进行分析。

配置数据

DevEnv.ps1 文件位于本地 Demo_CI 存储库的根目录 ./InfraDNS/DevEnv.ps1,在哈希表中指定环境专属配置数据,并将哈希表传递给 DscPipelineTools.psm (./Assets/DscPipelineTools/DscPipelineTools.psm1) 中定义的 New-DscConfigurationDataDocument 函数调用。

DevEnv.ps1 文件:

param(
    [parameter(Mandatory=$true)]
    [string]
    $OutputPath
)

Import-Module $PSScriptRoot\..\Assets\DscPipelineTools\DscPipelineTools.psd1 -Force

# Define Unit Test Environment
$DevEnvironment = @{
    Name                        = 'DevEnv';
    Roles = @(
        @{  Role                = 'DNSServer';
            VMName              = 'TestAgent1';
            Zone                = 'Contoso.com';
            ARecords            = @{'TFSSrv1'= '10.0.0.10';'Client'='10.0.0.15';'BuildAgent'='10.0.0.30';'TestAgent1'='10.0.0.40';'TestAgent2'='10.0.0.50'};
            CNameRecords        = @{'DNS' = 'TestAgent1.contoso.com'};
        }
    )
}

return New-DscConfigurationDataDocument -RawEnvData $DevEnvironment -OutputPath $OutputPath

New-DscConfigurationDataDocument 函数是在 \Assets\DscPipelineTools\DscPipelineTools.psm1 中进行定义,以编程方式通过作为 RawEnvDataOtherEnvData 参数传递的哈希表(节点数据)和数组(非节点数据)创建配置数据文档。

此示例只使用了 RawEnvData 参数。

psake 生成脚本

Build.ps1(位于 Demo_CI 存储库的根目录 ./InfraDNS/Build.ps1)中定义的 psake 生成脚本定义了生成任务。 它还定义了每个任务依赖的其他任务。 调用时,psake 脚本可确保指定的任务(或名为“Default”的任务,如果未指定的话)能够正常运行,并确保所有依赖项也能正常运行(这具有递归性,以便依赖项的依赖项也能正常运行,依此类推)。

在此示例中,Default 任务被定义为:

Task Default -depends UnitTests

虽然 Default 任务本身并无实现代码,但却依赖 CompileConfigs 任务。 生成的任务依赖关系链可确保生成脚本中的所有任务都能正常运行。

在此示例中,通过调用 Initiate.ps1 文件(位于 Demo_CI 存储库的根目录)中的 Invoke-PSake 来调用 psake 脚本:

param(
    [parameter()]
    [ValidateSet('Build','Deploy')]
    [string]
    $fileName
)

#$Error.Clear()

Invoke-PSake $PSScriptRoot\InfraDNS\$fileName.ps1

<#if($Error.count)
{
    Throw "$fileName script failed. Check logs for failure details."
}
#>

为示例创建生成定义时,我们将提供 psake 脚本文件作为 fileName 此脚本的参数。

生成脚本定义以下任务:

GenerateEnvironmentFiles

运行用于生成配置数据文件的 DevEnv.ps1

InstallModules

安装配置 DNSServer.ps1 所需的模块。

ScriptAnalysis

调用 PSScriptAnalyzer

UnitTests

运行 Pester 单元测试。

CompileConfigs

使用 GenerateEnvironmentFiles 任务生成的配置数据,将配置 (DNSServer.ps1) 编译到 MOF 文件中。

clean

创建用于此示例的文件夹,并删除在之前运行中生成的所有测试结果、配置数据文件和模块。

psake 部署脚本

Deploy.ps1(位于 Demo_CI 存储库的根目录 ./InfraDNS/Deploy.ps1)中定义的 psake 部署脚本定义了部署和运行配置的任务。

Deploy.ps1 定义以下任务:

DeployModules

TestAgent1 上启动 PowerShell 会话,并安装包含配置所需 DSC 资源的模块。

DeployConfigs

调用 Start-DscConfiguration cmdlet,以在 TestAgent1 上运行配置。

IntegrationTests

运行 Pester 集成测试。

AcceptanceTests

运行 Pester 验收测试。

clean

删除在之前运行中安装的所有模块,并确保有测试结果文件夹。

测试脚本

验收测试、集成测试和单元测试在 Tests 文件夹(位于 Demo_CI 存储库的根目录 ./InfraDNS/Tests)的脚本中进行定义,每个脚本位于各自文件夹内名为“DNSServer.tests.ps1”的文件中。

测试脚本使用 PesterPoshSpec 语法。

单元测试

单元测试用于测试 DSC 配置本身,以确保配置能够按预期运行。 单元测试脚本使用 Pester

集成测试

集成测试用于测试系统配置,以确保在与其他组件集成时,能够按预期配置系统。 使用 DSC 配置的此类测试在目标节点上运行。 集成测试脚本混用 PesterPoshSpec 语法。

验收测试

验收测试用于测试系统,以确保其行为符合预期。 例如,它执行测试,以确保网页在查询时返回正确的信息。 此类测试是从目标节点远程运行,以测试实际方案。 集成测试脚本混用 PesterPoshSpec 语法。

定义生成

现在,我们已经将代码上传到存储库并查看了它的作用,接下来让我们定义生成。

本文将只介绍要添加到生成定义的生成步骤。 有关如何在Azure DevOps中创建生成定义的说明,请参阅创建和排队生成定义

创建新的生成定义 (选择名为"InfraDNS"的 初学者管道 模板) 。 向生成定义添加以下步骤:

  • PowerShell
  • 发布测试结果
  • 复制文件
  • 发布项目

在添加这些生成步骤之后,编辑每个步骤的属性,如下所述:

PowerShell

  1. targetType 属性设置为 File Path.
  2. filePath 属性设置为 initiate.ps1.
  3. 向“参数”属性添加“-fileName build”。

此生成步骤运行用于调用 psake 生成脚本的 initiate.ps1 文件。

发布测试结果

  1. TestResultsFormat 设置为 NUnit
  2. TestResultsFiles 设置为 InfraDNS/Tests/Results/*.xml
  3. TestRunTitle 设置为 Unit.
  4. 请务必选中“控制选项已启用”和“始终运行”

此生成步骤在我们前面分析的 Pester 脚本中运行单元测试,并将结果存储在 InfraDNS/Tests/Results/*.xml 文件夹中。

复制文件

  1. 将以下代码行添加到“内容”

    initiate.ps1
    **\deploy.ps1
    **\Acceptance\**
    **\Integration\**
    
  2. 将“目标文件夹”设置为“$(Build.ArtifactStagingDirectory)\

此步骤将生成和测试脚本复制到临时目录,以便它们可以在下一步中作为生成项目发布。

发布项目

  1. TargetPath 设置为 $(Build.ArtifactStagingDirectory)\
  2. ArtifactName 设置为 Deploy
  3. 设置为 "已启用true"。

启用持续集成

现在,我们将设置一个触发器,这样只要有更改签入 git 存储库的 ci-cd-example 分支,便会触发项目生成。

  1. 在 TFS 中,单击" 生成 & 发布 "选项卡
  2. 选择“DNS Infra”生成定义,再单击“编辑”
  3. 单击 "触发器 "选项卡
  4. 依次选择“持续集成(CI)”和分支下拉列表中的“refs/heads/ci-cd-example
  5. 依次单击“保存”和“确定”

现在 git 存储库中的任何更改都触发了自动生成。

创建发布定义

让我们来创建发布定义,以便将项目部署到包含每个代码签入信息的开发环境。

为此,添加与之前创建的 InfraDNS 生成定义相关联的新发布定义。 请务必选择“连续部署”,以便只要有新生成完成,都会触发新发布。 (请参阅发布管道是什么?),然后按如下进行配置:

向发布定义添加以下步骤:

  • PowerShell
  • 发布测试结果
  • 发布测试结果

按如下所述编辑步骤:

PowerShell

  1. TargetPath 字段设置为 $(Build.DefinitionName)\Deploy\initiate.ps1"
  2. 将“参数”字段设置为“-fileName Deploy

第一次发布测试结果

  1. NUnit选择 TestResultsFormat 字段
  2. TestResultsFiles 字段设置为 $(Build.DefinitionName)\Deploy\InfraDNS\Tests\Results\Integration*.xml
  3. TestRunTitle 设置为 Integration
  4. 条件 设置为 succeededOrFailed()

第二次发布测试结果

  1. NUnit选择 TestResultsFormat 字段
  2. TestResultsFiles 字段设置为 $(Build.DefinitionName)\Deploy\InfraDNS\Tests\Results\Acceptance*.xml
  3. TestRunTitle 设置为 Acceptance
  4. 条件 设置为 succeededOrFailed()

验证结果

现在,每当在分支中 ci-cd-example 推送更改时,都会启动新的生成。 如果生成成功完成,便会触发新部署。

可以在客户端计算机上打开浏览器并转到 www.contoso.com,从而检查部署结果。

后续步骤

此示例配置 DNS 服务器 TestAgent1,以便 URL www.contoso.com 解析为 TestAgent2,但实际并不部署网站。 存储库中的 WebApp 文件夹下提供了执行此操作的相关框架。 可以使用所提供的存根来创建 psake 脚本、Pester 测试和 DSC 配置,从而部署自己的网站。