Hi everyone! I’m Chris and I’ve recently joined Coeo as a Cloud Consultant. I specialise in helping customers looking towards the cloud with all aspects of Azure cloud adoption and usage..
I’m quite a fan of automation when it comes to Azure. Automating the build of your environments in Azure has several benefits – every environment can be identical for dev, test, prod etc, which gives you confidence that your underlying infrastructure is consistent for the lifecycle of a project. It also means new environments can be spun up quickly.
ARM Templates: An Introduction
These automation benefits are can be achieved using Azure Resource Manager (ARM) templates. ARM templates are JavaScript Object Notation (JSON) formatted scripts that can be used to define what components need to be built, their dependencies on each other (you can’t build a virtual machine first without storage or networking!) and their properties. This is done in a declarative nature, effectively telling Azure what you want created than not having to worry about how it will be created and having to write scripts or code around that task. This is where I think ARM templates have a slight advantage over writing all the deployment in a PowerShell script.
ARM templates are divided into 3 main sections: parameters, variables, and resources. Parameters will prompt the user for the values unless they are hardcoded. Variables are used to have a single point where information is declared rather than repeating that value all the way through a template. Resources are the actual components required for the Azure build - such as web apps, virtual machines, virtual networking, Azure storage etc.
An Azure VM example
The following is a snippet from an example Azure ARM template showing a virtual machine resource:
{ "name": "[variables('VMName')]", "type": "Microsoft.Compute/virtualMachines", "location": "[resourceGroup().location]", "apiVersion": "2015-06-15", "dependsOn": [ "[resourceId('Microsoft.Storage/storageAccounts', variables('storeName'))]", "[resourceId('Microsoft.Network/networkInterfaces', variables('VM01NicName'))]" ], "properties": { "hardwareProfile": { "vmSize": "Standard_D1_v2" }, "osProfile": { "computerName": "[variables('VMName')]", "adminUsername": "[parameters('VM01AdminUsername')]", "adminPassword": "[parameters('VM01AdminPassword')]" }, "storageProfile": { "imageReference": { "publisher": "MicrosoftWindowsServer", "offer": "WindowsServer", "sku": "2012-R2-Datacenter", "version": "latest" }, "osDisk": { "name": "VM01OSDisk", "vhd": { "uri": "[concat(reference(resourceId('Microsoft.Storage/storageAccounts', variables('storeName')), '2016-01-01').primaryEndpoints.blob, variables('VM01StorageAccountContainerName'), '/', variables('VM01OSDiskName'), '.vhd')]" }, "caching": "ReadWrite", "createOption": "FromImage" } }, "networkProfile": { "networkInterfaces": [ { "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('VM01NicName'))]" } ] } } } |
While this may look complex, let’s explore the code to understand how the VM is being deployed. The DependsOn
states that 2 other resources are needed for the build to succeed. In this case this is a storage account and a NIC. We have also specified via the hardware profile that this is to be a Standard_D1_v2 VM in Azure, the VM name, the Windows OS version, and the location of the VHD (in the storage account first referenced in DependsOn
section). We can then deploy this to a resource group and Azure will take care of the build process for us. Good stuff, right?
Where ARM templates fall down slightly is if you want to start introducing flexibility to the deployment. If you wanted to introduce conditions, so for example if a user enters a certain parameter, a certain resource is deployed, this has currently not been possible. I would likely need more than 1 ARM template for each scenario. Until now.
New flexibilities for ARM
At the Microsoft Build conference this year Microsoft announced new capabilities to remove this limitation and have added support for conditions for ARM templates to give your deployments more flexibility.
But how do we programmatically use conditions when constructing our templates? Let’s take a basic example of a deployment of virtual machines and having some flexibility around deploying Windows or Linux.
First, a parameter PlatformSelection
is declared with only 2 values allowed: Windows or Linux.
"PlatformSelection": { "type": "string", "allowedValues": [ "Windows", "Linux" ] }, |
I then need to define both sets of OS settings for both Windows and Linux operating system types, where normally I would just have one set of these.
"WinImagePublisher": "MicrosoftWindowsServer", "WinImageOffer": "WindowsServer", "WinOSVersion": "2012-R2-Datacenter", "LinuxImageOffer": "UbuntuServer", "LinuxOSVersion": "12.04.5-LTS", "linuxPublisher": "Canonical", |
Next, under the virtual machine I can use an if statement when specifying which variables to use based on the specified platformselection
value of Windows or Linux.
"storageProfile": { "imageReference": { "publisher": "[if(equals(parameters('platformSelection'), 'Windows'), variables('WinImagePublisher'), variables('linuxPublisher'))]", "offer": "[if(equals(parameters('platformSelection'), 'Windows'), variables('WinImageOffer'), variables('LinuxImageOffer'))]", "sku": "[if(equals(parameters('platformSelection'), 'Windows'), variables('WinOSVersion'), variables('LinuxOSVersion'))]", "version": "latest" |
Publisher, offer and sku are formed of the same syntax, which is formed of 3 parts; the value of the parameter and the comparison we want to make, the TRUE value is then declared 2nd, with the FALSE value declared last. So for publisher, if the platformSelection parameter equals “Windows” then the WinImagePublisher variable will be used. If platformSelection parameter equals “Linux”, then Linuxpublisher will be used.
Another example would be specifying a condition around an entire resource for deployment. Say for example I would like to use availability sets for my virtual machines deployment, but only when the number of VMs is greater than 1. If I deploy 1 virtual machine, then an availability set shouldn’t be deployed.
Here we can define a parameter for “number of instances” of virtual machines and put a condition under the availability set resource that specifies if the parameter is greater than 1 then the availability set resource should deploy:
{ "condition": "[greater(parameters('numberOfInstances'), 1)]", "name": "[variables('availabilitySetName')]", "type": "Microsoft.Compute/availabilitySets", "location": "[resourceGroup().location]", "apiVersion": "2015-06-15", "dependsOn": [], "properties": { } } |
We also need to cover the property under the virtual machine for availability sets as the VM(s) will need to be linked to the availability set:
"properties": { "availabilitySet": { "id": "[if(greater(parameters('numberOfInstances'), 1), resourceId('Microsoft.Compute/availabilitySets', variables('availabilitySetName')), json('null'))]" }, |
Again, this if statement is divided into 3 sections like before, with json(null)
being our FALSE, which means do nothing.
While greater, equals, and if statements have been shown here, there are a range of logical and comparison functions available to allow you to create as much complexity as you’d like.
Deployment of ARM templates to Azure
Deploying new ARM templates can be done in a few different ways, but the most common ways are using Azure PowerShell, Visual Studio, or a build and release toolset like Visual Studio Team Services (VSTS).
For PowerShell, we can deploy ARM templates using the New-AzureRMResourceGroupDeployment
command. This allows us to specify an ARM template whether it’s locally saved or via a URL. If we make changes to our ARM template however we can also check it’s valid without deploying it using Test-AzureRMResourceGroupDeployment
- giving confidence that new deployments will work correctly beforehand.
Summary
Automation is useful when deploying environments and resources in Azure, but until now ARM templates have had limitations around flexibility with our deployments, meaning every deployment scenario would need to be treated independently. With the addition of conditions for ARM templates this gives us a great way to deploy our different scenarios easily, while removing the overhead of having to manage multiple deployment templates.
Recommended Reading
Azure ARM Introduction – https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-overview
Creation and deployment - https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-manager-create-first-template
Logical functions - https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-template-functions-logical
Comparison functions - https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-template-functions-comparison
If you'd like further information about ARM templates, or anything to do with Coeo's Microsoft Azure cloud services, just