问题
My ARM template is getting too big so I would like to use linked templates. I understand that templates need to be somewhere where they are accessible to ARM. But I should be able to test them somehow before I upload them to target location. Otherwise there is a risk that I override previously working templates with invalid ones. How do I revert then?
How do you do it?
回答1:
Here's what works for me. I have a pipeline that deploys the template from the Azure Devops repo. The pipeline deploys it to the dev environment, so breaking something is not the end of the world. You can also test locally, before pushing to the repo if that's an option (it might be really hard to do in some cases, where the template has lots of dependencies on stuff in "real" development environment, and testing it on other subscriptions is impossible due to references to existing resources\key vaults\etc). I'm using the following snippet in azure devops to run the powershell script and then the deployment:
- 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) }}
let me explain a bit what is happening here:
- Update-DeploymentPrereq - is used to check if the target resource group exists and has proper tags set. creates and tags it if its not. tags are:
Version
- means last successfully deployed version;FailedVersion
- this effectively means last deployment failed and it contains the build id that started the deployment (this would be deleted after a successful deployment);InProgress
- contains the build id of the build that started deployment to the resource group; this would be reset after a deployment ends (whether it failed or not), so it only has a value while the build is running. - Update-Prereq - this one uploads all the templates\artifacts to a dedicated storage account (more on this later)
- Update-DeploymentConcurrency - checks if there is a deployment already running on this resource group by checking the value of the
InProgress
tag. If the value matchesnot running
(or whatever string you code it to test- it sets the
InProgress` tag to the build id of the job, else - exits the script with non-zero exit code, effectively breaking the build (thus preventing collision). - Deployment should be obvious
Now lets talk a bit about the Update-Prereq
as it is essential for all of this to work. It is generating a random container name for each build by doing something like this:
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"
...
}
This results in a 24 characters long string that is deterministic (it would always be the same for the same input provided to the Get-StringHash
function, meaning when you run the build from the same commit to the same resource group - it always uploads to the same container, but if you run it for another commit or another resource group - it would generate a new container name), thus avoiding the clash you are talking about. Aforementioned tags give away the build id, which maps to a particular commit using GitVersion (so you can always figure out what version of code is deployed\or failed to be deployed to a particular environment). And its outputting the generated container name, so the AzureResourceGroupDeployment@2
would be able to figure it out and use that specific container to find the template.
There are other build steps, that control the failure\success tagging, clean up old builds\old containers. I can probably turn this into a blog post if you need my specific implementation
Essentially it all boils down to:
- preventing parallel execution
- having a separate set of templates for each resource group\commit combination, so they never overlap
- being able to tell which build is deployed to the environment
- being able to backtrack it to the commit id without to much trouble.
This might not be the optimal way of doing that, but that's the best I could think of when I needed it.
ps. for local testing you can just use Update-Prereq
function, since you dont need all the checking, just unique url for your templates
回答2:
So I came up with this script. Each developer has precreated resource group where he will deploy resources and precreated Blob container to store templates currently in development. Before deploying templates I use azcopy
to synchronize my local folder with Blob container. Unfortunately sometimes Test-AzResourceGroupDeployment
doesn't give you enough details in case of failure so I can't make decision based on it's return value to execute deployment or not. For now this seems to work fine. But it's already 3rd or 4th version and it will probably change in future. One idea is to incorporate ARM-TTK for template testing.
$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
}
来源:https://stackoverflow.com/questions/59697821/how-to-test-linked-arm-templates