Endjin - Home

Advanced Azure Resource Manager template patterns – t-shirt sizing and optional resources

by Richard Kerslake

header-advanced-arm-p1-1024px

One very useful but little used pattern when working with Resource Manager templates, is the ability to use parameters to optionally deploy resources, constrain certain resource configurations based on other user defined parameters, or to toggle parameters based on other values.

To give a couple of concrete examples, imagine we have a highly reusable template that can be used for deploying a web app.

1. We want to deploy web apps with different tiers e.g. some on Free/Shared as they are just test sites, and some on Basic/Standard as they are going to be higher load and public facing. We have a requirement to set the web app to ‘AlwaysOn’ if possible so the site stays warm and users don’t get a penalty if it has stopped. This is only possible on the Basic/Standard tier and not on Free/Shared.

2. We want to be able to optionally choose to deploy a storage account along with the web app. Some of our web apps need it and some don’t, but we just want to maintain one simple template, where this can be included if desired.

3. We want to allow the user to specify “t-shirt” sizes (i.e small, medium, large) and deploy an appropriate set of resources for that particular predefined configuration.

All of these can be achieved using the same fundamental pattern.

1. Toggling resource behaviour based on other parameters

This scenario could be achieved by simply setting two different parameters, one for Tier and one for AlwaysOn. However this requires the user of the template to know which tiers support AlwaysOn! There is a better way, allowing the user to specify just the Tier with the value for AlwaysOn being automatically determined.

First we need to have a parameter to allow the user to define the tier:

"appServicePlanSku": {
      "type": "string",
      "allowedValues": [
        "Free",
        "Shared",
        "Basic",
        "Standard"
      ],
      "defaultValue": "Free"
    }

Next we need a variable that maps the allowed values for the tier parameter to possible values for the AlwaysOn configuration parameter:

"alwaysOn": {
      "Free": "false", 
      "Shared": "false", 
      "Basic": "true", 
      "Standard": "true"
    }

Finally, in the web app configuration resources, we can index into the AlwaysOn variable to get a value appropriate to the web app tier:

{
      "name": "[parameters('webAppName')]",
      "type": "Microsoft.Web/sites",
      "location": "[resourceGroup().location]",
      "apiVersion": "2015-08-01",
      "dependsOn": [ "[concat('Microsoft.Web/serverfarms/', parameters('appServicePlanName'))]" ],
      "properties": {
        "name": "[parameters('webAppName')]",
        "serverFarmId": "[resourceId('Microsoft.Web/serverfarms/', parameters('appServicePlanName'))]"
      },
      "resources": [
        {
          "apiVersion": "2015-08-01",
          "name": "web",
          "type": "config",
          "dependsOn": [
            "[concat('Microsoft.Web/Sites/', parameters('webAppName'))]"
          ],
          "properties": {
            "alwaysOn": "[variables('alwaysOn')[parameters('appServicePlanSku')]]"
          }
        }
      ]
    }

So, if we deploy a web app to the Free tier, AlwaysOn will be set to False, and if we deploy a web app to the Standard tier, AlwaysOn will be set to True. One less problem for the template user to think about!

2. Optionally deploying a nested template

In this scenario we will use nested templates to deploy additional resources. What nested templates are deployed can be controlled via parameters set by the user, in a similar way to the previous scenario.

First we need a parameter to allow the user to chose to deploy the storage resource, or not e.g. by setting ‘yes’ or ‘no’ values:

"includeStorage": {
      "type": "string",
      "allowedValues": [
        "yes",
        "no"
      ],
      "defaultValue": "no"
    }

Next we need a variable that maps the parameter to the possible storage templates, where ‘yes’ equates to one particular template and ‘no’ equates to an entirely different template. The ‘no’ template can be a completely empty template with no resources i.e. deploying this deploys nothing, or is a no-op.

"storageTemplate": {
      "yes": "[concat(parameters('_artifactLocation'), 'storage-resources.json', parameters('_artifactLocationSasToken'))]",
      "no": "[concat(parameters('_artifactLocation'), 'empty-template.json', parameters('_artifactLocationSasToken'))]"
    }

Finally we run the nested template from the main template, by getting the chosen template name from the variable defined above, based on the user preference:

{
      "apiVersion": "2015-01-01",
      "name": "nestedStorageTemplate",
      "type": "Microsoft.Resources/deployments",
      "properties": {
        "mode": "Incremental",
        "templateLink": {
          "uri": "[variables('storageTemplate')[parameters('includeStorage')]]",
          "contentVersion": "1.0.0.0"
        }
      }
    }

This could be applied to any number of different scenarios. For example when deploying an elasticsearch cluster you could allow the user to choose whether to also deploy another VM hosting Kibana, or when deploying a VM with varying disk size or performance requirements, you could allow the user to pick how many data disks should be attached, with different nested templates set up for the different scenarios supported.

3. T-shirt sizing

This approach can be used for providing ‘T-shirt sizing’ in your templates. For example, when deploying an application that requires a number of VMs, you allow the user to set a parameter defining the size of the deployment as ‘Small’, ‘Medium’ or ‘Large’. This size parameter can then be used as above to control many different aspects of the template to deploy as required e.g. different VM SKUs, different number of VMs, different number of VM data disks and so on.

Parameter to define a t-shirt size:

"tshirtSize": {
      "type": "string",
      "allowedValues": [
        "small",
        "medium",
        "large"
      ],
      "defaultValue": "small"
    }

Variable to define how many VMs to deploy for that particular size option:

"numberOfVms": {
      "small": 1,
      "medium": 4,
      "large": 12   
    }

VM resource that deploys the desired number of VMs:

{
      "apiVersion": "2015-06-15",
      "type": "Microsoft.Compute/virtualMachines",
      "name": "[concat(variables('vmName'), copyindex())]",
      "location": "[parameters('location')]",
      "copy": {
        "name": "virtualMachineLoop",
        "count": "[variables('numberOfVms')[parameters('tshirtSize')]]"
      },
      "dependsOn": [
        ...
      ],
      "properties": {
        ...
      }
    }

Hopefully these examples have made it very clear how useful this pattern is when using Azure Resource Manager templates. It can allow you to design and build templates that are much more flexible and reusable.

About the author

Endjin Alumni, Richard was a Software Engineer and certified Microsoft Cloud Platform developer, providing strategy, insight and engineering services. He has a background in financial services, working on large scale distributed trading systems. Richard has a passion for delivering real business value to endjin’s clients, who are seeking to take advantage of Microsoft Azure and the Cloud. You can follow Richard on Twitter.