Azure Resource Manager Templates for Global Azure Web App Deployments

23/06/2015

I have been slow at picking up the Azure Resource Manager templates, mostly because I don’t deploy that many identical solutions to Azure, but also because I for a long time didn’t find the json template language very intuitive. Attending Build 2015 and seeing it in use during several sessions have changed that, it also helps that Visual Studio 2015 now have support for authoring the templates.

So for one of my latest project I decided to create an Azure Resource Manager template, or in short ARM template, that can provision a storage account, multiple azure websites in different regions with an appsetting pointing to the storage account and with application insights. This can all be achieved with the template language, and in fact it is not as difficult as I thought.

Lets look at some of the most interesting aspects of the template. If you want to use the template in its entirety, just grab it from github: https://github.com/sjkp/azure-arm-multiregion-website-template

First the parameter part of the template. Besides some obvious parameters for choosing type of SKU and naming, I added the following two parameters.

The siteLocations is an array of all the azure data center locations where you want a web site to be created. The numberOfSites parameters is really an unwanted parameter, but as far as I can figure out you can’t do a simple length of the siteLocations array from the template language, and I needed a way to tell the copy operation how many times it should loop. So obviously numberOfSites should match the length of the siteLocations array.

Update 14. Dec 2015Since October 2015, it has been possible to use length to get the length of an array. I have updated the template in github to reflect that.

[js]
"siteLocations": {
"type": "array",
"defaultValue": [
"West Europe",
"East Asia",
"Southeast Asia",
"East US",
"West US"
],
"metadata": {
"description": "Locations"
}
},
"numberOfSites": {
"type": "int",
"defaultValue": 5,
"metadata": {
"description": "Number of locations"
}
},
[/js]

The next interesting part of the template are the resources definition. First I define a storage account, which is pretty basic, in my specific application I only wanted one storage account, but you can of course make a storage account for each location if you need that.

The hosting plan on the other hand you have to create for each location, because it holds the machine actually hosting your website in that location. The way to create multiple hosting plan is similar to how I create multiple websites so I wont go into detail with that.

Creating the sites is a little interesting, firstly in order to create mulitple sites, we use the copy operation where the count is set to the parameter numberOfSites.
In order to get the right location we index into the parameter siteLocations, like this [parameters('siteLocations')[copyIndex()]], so copyIndex() basically returns the current index we are at in our copy loop, so you will see it scattered all over my template to ensure unique naming etc.
[js]
{
"apiVersion": "2014-06-01",
"name": "[concat(parameters(‘siteName’),copyIndex())]",
"type": "Microsoft.Web/sites",
"location": "[parameters(‘siteLocations’)[copyIndex()]]",
"tags": {
"[concat(‘hidden-related:’, resourceGroup().id, ‘/providers/Microsoft.Web/serverfarms/’, parameters(‘hostingPlanName’))]": "Resource",
"displayName": "Website"

},
"dependsOn": [
"[concat(‘Microsoft.Web/serverfarms/’, concat(parameters(‘hostingPlanName’),copyIndex()))]",
"[concat(‘Microsoft.Storage/storageAccounts/’, parameters(‘newStorageAccountName’))]"
],
"properties": {
"name": "[concat(parameters(‘siteName’),copyIndex())]",
"serverFarm": "[concat(parameters(‘hostingPlanName’),copyIndex())]",
"siteConfig": {
"appSettings": [
{
"name": "AzureStorageAccount",
"value": "[concat(‘DefaultEndpointsProtocol=https;AccountName=’,parameters(‘newStorageAccountName’),’;AccountKey=’,listKeys(variables(‘storageid’),’2015-05-01-preview’).key1)]"
}
]
}
},
"copy": {
"name": "siteCopy",
"count": "[parameters(‘numberOfSites’)]"
}
}
[/js]
Another interesting thing about the web/sites resource deployment is how to set an appSetting. That part is handled by:
[js]
"siteConfig": {
"appSettings": [
{
"name": "AzureStorageAccount",
"value": "[concat(‘DefaultEndpointsProtocol=https;AccountName=’,parameters(‘newStorageAccountName’),’;AccountKey=’,listKeys(variables(‘storageid’),’2015-05-01-preview’).key1)]"
}
]
}
[/js]
Here we can see that I create an appsetting key with the name AzureStorageAccount and by the use of concat and various string parts and parameters I construct an Azure storage connection string, with the primary key of the storage account my template created earlier. In order to get that key you can use the command listKeys. A similar approach can be used to get keys for e.g. Azure SQL databases.

The rest of the template is just a lot of similar stuff in order to add different application insight metrics.

The best part of the VS2015 support for creating this type of deployment projects is that it provides you with a PowerShell script Deploy-AzureResourceGroup.ps1, that makes it super easy to use your template to create a new resource group with all the artefacts defined in the template. A nice benefit of having everything in a resource group is that removing it is as simple as running the PowerShell command: Remove-AzureResourceGroup and boom all the websites, web hosting plans, application insights, and azure storage account is gone.

Also I like to point out that this is really really fast at provisioning the azure websites and all the artefacts. It takes around a minute to setup everything for the five regions in my template – try to beat that by clicking through the portal!
deployAzureResourceGroup