CICD Pipeline with Azure Stack – Part 4
This is part 4 in a series. You can find part 1 here , part 2 here, and part 3 here.
First off, let me quell your anticipation. I got it working! It was not as straightforward as I might like, but it will work. If you haven’t already read post three, I would recommend doing so. The long and short of it is that the build task Azure Resource Group deployment in TFS doesn’t understand the Azure Stack environment. It doesn’t know how to talk to it, so any build task is going to fail. One of the engineers at Microsoft suggested I use a PowerShell task to deploy instead, which I did. That was not as simple as I would have liked, but here is what I had to do.
First the server running the build agent, in my case the TFS server, will need to have the Microsoft Azure PowerShell for Azure Stack TP2 – September 2016 installed. If you have a newer version installed, you will receive API version errors. Next, I created a very basic template for deployment in Visual Studio. I literally just used the single Windows VM Azure quickstart template from GitHub. When you create the solution in Visual Studio, it also creates a Deploy-AzureResourceGroup.ps1 PowerShell script, which can be used to deploy the template from Visual Studio directly. I created a copy of the script called Deploy-AzureResourceGroupTFS.ps1. In that script I added the following parameters:
[string] $AzureStackARMEndpoint = 'https://api.azurestack.local',
[string] $AzureStackName = "AzureStack",
[string] $AzureStackSubscriptionName = "Default Provider Subscription"
Yes, I know there’s a password field. In order to store the string securely, I am using the variables in the Build configuration to store the password. I have also included the AzureStack-Tools\Connect\AzureStack.Connect.psm1 PowerShell module from this repo on GitHub. That module has a function I want to use to add the AzureStack environment from within the script. Now in the script I have added the following lines:
$AzureStackSecPwd = ConvertTo-SecureString $AzureStackPwd -AsPlainText -Force
$AzureStackCredentials = New-Object System.Management.Automation.PSCredential($AzureStackUser,$AzureStackSecPwd)
$ConnectModulePath = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, "AzureStack.Connect.psm1"))
Write-Verbose "Add Azure Stack Environment"
Add-AzureStackAzureRmEnvironment -AadTenant $AzureStackTenant -ArmEndpoint $AzureStackARMEndpoint -Name $AzureStackName
Write-Verbose "Add Azure Stack Account"
Add-AzureRmAccount -EnvironmentName $AzureStackName -TenantId $AzureStackTenant -Credential $AzureStackCredentials
Write-Verbose "Login Azure Stack"
Login-AzureRmAccount -EnvironmentName $AzureStackName -Credential $AzureStackCredentials
Write-Verbose "Select Azure Stack Sub"
Select-AzureRmSubscription -SubscriptionName $AzureStackSubscriptionName
Note that I am using the newest version of the AzureStack-Tools. The Add-AzureStackRmEnvironment parameters have changed, adding ArmEndpoint and removing Domain. The PowerShell commands above are the process by which an Azure Stack environment is added and referenced by regular Azure PowerShell commands. This is the same process that is used by most of the PowerShell scripts I have seen when they are trying to perform work in Azure Stack, unless they are strictly using REST calls with an authentication header.
Now my deployment script has a connection to Azure Stack and it can start the deployment process. I check my project into TFS and create a Build sequence that includes a PowerShell Task.
In the Build definition, I will define some of the input parameters using the Variables tab. Specifically, I am defining the AzureStackUser, AzureStackPwd, and AzureStackTenant information.
Within the PowerShell task, the variables are referenced by adding a dollar sign and parentheses, like this $(AzureStackUser).
This allows the AzureStackPwd to stay encrypted on the Build server, rather than storing it in plain text. The parameters for the ARM template reside in the same project, and currently the administrator password for the VM I am creating is stored in plain text. I plan to refactor that to use a Key Vault secret. However, since the vault may be in Azure Cloud or Azure Stack, I need to alter the template to decide which key vault to access.
After running the Build definition, I can see that the template deploys successfully to my Azure Stack environment. Hooray!
Of course it failed more than a few times, but that’s the nature of running builds.
Now I will add a second task to the Build definition to deploy an Azure Resource Group deployment in Azure Cloud.
I will use the same templates and parameters.
If the first build task is successful in Azure Stack, then this task will begin and run in Azure. In a real world situation, I might have some tests that run after the first build task to verify the environment, then release it to Azure Cloud. My Azure Stack install might be a development environment and Azure Cloud is for production. I might also have slightly different parameters for the production deployment, like using larger VMs or a bigger VM Scale Set.
After adding the second task, I run the build process again. Voila, it is successful for both tasks, and now I have an identical build running in both Azure Stack and Azure Cloud.
Next I go into the properties of the Build definition and tell it to fire off a build each time I check in code.
Then I go back into the template and change the VM size from Standard_A1 to Standard_A2. I commit the change and check it into source control. Sure enough, a build process kicks off and both my Azure Stack and my Azure Cloud VMs are brought up as Standard_A2 size VMs. I actually had to make a minor change in the Deploy-AzureResourceGroupTFS.ps1 script to get the continuous integration piece working with Azure Stack. For the New-AzureRmResourceDeployment command I added the Mode parameter and passed the value Complete. I tried Incremental, but for whatever reason Azure Stack didn’t like that. It will probably be fixed with the next release. Azure Cloud was fine with the implied Incremental setting.
Is this a perfect CICD process? No, not by a long shot. I want to write tests to validate that the Azure Stack environment is running properly, and I can do that by adding a PowerShell task to run after the Azure Stack build. What I would like to do it try and leverage the Pester testing framework to validate my infrastructure. The inestimable Adam Bertram has some posts on this very topic, so I plan to read through those and come back to my pipeline to try adding tests.
Also, in case you didn’t hear, Azure Stack TP3 is out! All this development has been on Azure Stack TP2. I now have TP3 running on a separate server in the lab, and I plan to recreate the environment and see what’s different about TP3. Expect some posts on that topic very soon!
I’ve checked the whole project into my GitHub repo. You can check out the templates and deployment scripts there.