如何测试链接的 ARM 模板?

How to test linked ARM templates?

我的 ARM 模板太大了,所以我想使用链接模板。我知道模板需要放在 ARM 可以访问的地方。但是在将它们上传到目标位置之前,我应该能够以某种方式测试它们。否则,我可能会用无效的模板覆盖以前工作的模板。那我该如何恢复?

你是怎么做到的?

以下是适合我的方法。我有一个从 Azure Devops 存储库部署模板的管道。管道将其部署到开发环境,因此破坏某些东西并不是世界末日。如果这是一个选项,您也可以在推送到回购之前在本地进行测试(在某些情况下可能真的很难做到,模板对 "real" 开发环境中的东西有很多依赖性,并在其他环境中进行测试由于对现有 resources\key vaults\etc 的引用,订阅是不可能的。我在 azure devops 中使用以下代码片段到 运行 powershell 脚本,然后是部署:

- task: AzurePowerShell@3
  displayName: UpdatePrereq
  inputs:
    azureSubscription: ${{ parameters.azureSubscription }}
    ScriptType: InlineScript
    Inline: |
        ${{ format('. $(Build.Repository.LocalPath)\scripts\_helpers.ps1
        Update-DeploymentPrereq -resourceGroup {1} -location {3}
        Update-Prereq -pathSuffix {0} -pathBase $(Build.Repository.LocalPath) -resourceGroup {1} -buildId $(Build.BuildNumber) -paramFile {2}
        Update-DeploymentConcurrency -resourceGroup {1} -buildId $(Build.BuildNumber)',
            parameters.buildDir, parameters.resourceGroupName, parameters.paramFile, parameters.location ) }}
    azurePowerShellVersion: LatestVersion

- task: AzureResourceGroupDeployment@2
  displayName: DeploySolution
  inputs:
    azureSubscription: ${{ parameters.azureSubscription }}
    resourceGroupName: ${{ parameters.resourceGroupName }}
    location: ${{ parameters.location }}
    templateLocation: 'URL of the file'
    csmFileLink: "https://xxx.blob.core.windows.net/$(containerName)/azuredeploy.json"
    csmParametersFileLink: ${{ format('https://xxx.blob.core.windows.net/$(containerName)/param.{0}.json', parameters.paramFile) }}

让我解释一下这里发生的事情:

  1. Update-DeploymentPrereq - 用于检查目标资源组是否存在以及是否设置了正确的标签。如果不是,则创建并标记它。标签是: Version - 表示最后成功部署的版本; FailedVersion - 这实际上意味着最后一次部署失败,它包含启动部署的构建 ID(这将在成功部署后删除); InProgress - 包含开始部署到资源组的构建的构建 ID;这将在部署结束后重置(无论是否失败),因此它只有在构建 运行ning.
  2. 时才有价值
  3. Update-Prereq - 此更新将所有 templates\artifacts 上传到专用存储帐户(稍后详细介绍)
  4. Update-DeploymentConcurrency - 通过检查 InProgress 标记的值检查此资源组上是否已经有部署 运行ning。如果该值匹配 not running(或您将其编码为 test- it sets theInProgress` 标记的任何字符串与作业的构建 ID 相匹配,否则 - 使用非零退出代码退出脚本,有效地破坏构建(从而防止碰撞)。
  5. 部署应该很明显

现在让我们谈谈 Update-Prereq,因为它对于所有这些工作都是必不可少的。它通过执行以下操作为每个构建生成一个随机容器名称:

Function Get-StringHash ([String]$String, $HashName = "MD5") {
    $StringBuilder = New-Object System.Text.StringBuilder
    [System.Security.Cryptography.HashAlgorithm]::Create($HashName).ComputeHash([System.Text.Encoding]::UTF8.GetBytes($String))|
        ForEach-Object { [Void]$StringBuilder.Append($_.ToString("x2"))
    }
    $StringBuilder.ToString().Substring(0, 24)
}

...

Function Update-Prereq {
    ... 
    $containerName = Get-StringHash ( $resourceGroup + $buildId )
    New-AzureStorageContainer -Name $containerName -Context $storageContext -Permission Blob
    Write-Host "##vso[task.setvariable variable=containerName]$containerName"
    ...
} 

这会产生一个 24 个字符长的字符串,它是确定性的(对于提供给 Get-StringHash 函数的相同输入,它总是相同的,这意味着当您 运行 从相同的构建提交到同一个资源组 - 它总是上传到同一个容器,但是如果你 运行 它用于另一个提交或另一个资源组 - 它会生成一个新的容器名称),从而避免你正在谈论的冲突。前面提到的标签泄露了构建 ID,它 maps 到使用 GitVersion 的特定提交(所以你总是可以弄清楚什么版本的代码是 deployed\or 未能部署到特定环境)。并输出生成的容器名称,因此 AzureResourceGroupDeployment@2 能够找出并使用该特定容器找到模板。

还有其他构建 steps,控制 failure\success 标记,清理旧的 builds\old 容器。如果您需要我的具体实现,我可能会把它变成一个博客 post

基本上这一切都归结为:

  1. 防止并行执行
  2. 每个资源 group\commit 组合都有一组单独的模板,因此它们永远不会重叠
  3. 能够判断将哪个构建部署到环境中
  4. 能够毫不费力地将它回溯到提交 ID。

这可能不是最佳方式,但这是我在需要时能想到的最佳方式。

ps。对于本地测试,你可以只使用 Update-Prereq 函数,因为你不需要所有的检查,只需要唯一的 url 为你的模板

所以我想到了这个脚本。每个开发人员都有预先创建的资源组,他将在其中部署资源和预先创建的 Blob 容器来存储当前正在开发的模板。在部署模板之前,我使用 azcopy 将我的本地文件夹与 Blob 容器同步。不幸的是,有时 Test-AzResourceGroupDeployment 不会在失败的情况下为您提供足够的详细信息,因此我无法根据它的 return 值来决定是否执行部署。现在这似乎工作正常。但它已经是第 3 或第 4 个版本,将来可能会改变。一种想法是合并 ARM-TTK 进行模板测试。

$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$resourceGroup = $currentUser.Substring($currentUser.IndexOf('\') + 1) + "-testing"
$containerName = $currentUser.Substring($currentUser.IndexOf('\') + 1) + "-testing"
$storageAccountName = "team_shared_account_here";
$containerUrl = "https://${storageAccountName}.blob.core.windows.net/${containerName}"

Write-Host "Current user: <${currentUser}>" -ForegroundColor Green
Write-Host "Deployment will use templates from <${containerName}> container" -ForegroundColor Green
Write-Host "Resources will be deployed to <${resourceGroup}> resource group" -ForegroundColor Green
Write-Host

Write-Host "Syncing templates..." -ForegroundColor Green
.\azcopy.exe sync '.' $containerUrl --include-pattern "*.json" --delete-destination true

$toDeploy = "app1", "app2"
foreach ($template in $toDeploy) {
    $templateUri = "${containerUrl}/${template}.json"
    $templateParameterUri = "${containerUrl}/${template}.parameters.DEV.json"

    Write-Host "`nDeploying:  ${templateUri}" -ForegroundColor Green
    Write-Host "Paramaters: ${templateParameterUri}" -ForegroundColor Green

    Test-AzResourceGroupDeployment -ResourceGroupName $resourceGroup `
        -TemplateUri $templateUri `
        -TemplateParameterUri $templateParameterUri `
        -Mode Incremental `
        -Verbose

    New-AzResourceGroupDeployment -ResourceGroupName $resourceGroup `
        -TemplateUri $templateUri `
        -TemplateParameterUri $templateParameterUri `
        -Mode Incremental `
        -DeploymentDebugLogLevel All `
        -Verbose
}