Anatomy of an ARM Template
The Azure Resource Manager version of Microsoft's Azure Cloud came with the notable addition of using ARM template to create resources in Azure. ARM templates are JSON files that are used to declaratively provision resources in a repeatable and predictable manner. JSON's readability is one of the main drawbacks with ARM templating, but knowing some of the basics can help turn you into a pro in no time.
Before we begin creating ARM templates, we have to understand what the limitations are. Though you are not likely to reach these limitations during normal development, a developer for a large organization that utilizes larger and complex templates may find it necessary to be aware of these. ARM template syntax limitations are the following:
256 parameters
256 variables
800 resources (including copy count)
64 output values
24,576 characters in a template expression
Another thing to keep in mind is that ARM templates, as mentioned above, is written in JSON. JSON is a language that was originally meant to be created and read by machines. It is not the easiest language to develop or parse through. The majority of people that develop ARM templates will start form an existing template to save time and headache. Be sure to check out the Quickstart Templates on Github. One way to validate ARM template syntax is to use a tool such as VSCode or Visual Studio, which will help you by underlining any syntax issues (and syntax that may not be an issue, by the way). JSONLint is a popular tool for quickly validating ARM Template syntax. Simply copy and paste your entire template into the editor and click validate.
There are six main areas to an ARM template. The first you will notice right at the top is the ARM template schema version. Here is an example from an ARM template:
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#"
This rarely, if ever, changes so if you see a schema version with a prior year in it, don’t be alarmed. If you click on the link, the schema for how ARM templates are created will be downloaded. Take a peek but you won’t be editing this file so no need to save it or commit it to memory. This is a required value.
The next section is the content version of the template. This is the poor man's attempt at version control, but better than nothing if you don’t use a version control tools. This is an editable value that you can use to version your template as you make changes. Use it as a last resort and only if you really need to do so. Generally, it is set to "1.0.0.0" and there is no need to edit it. It is, however, a required value.
"contentVersion": "1.0.0.0"
The next section is the parameters section. Here you will set the inputs needed for the resources in the template. You specify the data type, any default values, specify allowed values, enter a description of the parameter, and specify the range of values for the parameter. These are static inputs that are processed just before the template runs.
"parameters": {
"location": {
"type": "string",
"defaultValue": "eastus2",
"metadata": {
"description": "The Azure Region where the resource will be deployed"
}
}
}
This section is used mainly for two reasons. The first reason is to assemble complex inputs, often times using concatenate or some other Azure ARM Template functions. The second reason is to set a static value that, for whatever reason, will never change. For example if you are creating a template to deploy a Windows VM, then you would always have certain values set to inputs that refer to a Windows VM image and these will never change. The variables section of the ARM template is not something you can input before the template is deployed, so even though the name "variables" might normally refer to a different type of value, the values in this section are fairly static. Below are examples of a complex input and a static value that might not change in the future.
"subnetRef": "[concat(variables('vnetId'), '/subnets/', parameters('subnetName'))]"
"pulisher": "MicrosoftWindowsServer"
The last section is the outputs section. This is typically set to display a value that is only accessible after the template has completed, such as a URI to a deployed resource. The ability to view this output will depend on the method of deployment.
"outputs": {
"resourceID": {
"type": "string",
"value": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddresses_name'))]"
}
}
ARM Template Schemas: https://docs.microsoft.com/en-us/azure/templates/
One of many feature of ARM templates that is helpful to keep in your back pocket is the list of functions that are available. ARM Templates by nature are not meant to be a flexible tool but functions help to introduce some dynamic capabilities that will enable your solution to adapt to different scenarios. Another helpful tool to have is the ability to introduce decision making with the use of the condition operation. While not perfect, conditional ARM templates have the ability to deploy resources in your template depending on a certain value that you specify. A caveat to this is that if you plan to deploy a different version of a certain resource based on a conditional value, you may have to have a near duplicate resource in the template to reflect the alternate resource value. Even with this caveat, I consider conditional deployment a vast improvement over linked templates, which do not always scale well and are not always easy to manage.
ARM Template Functions: https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-template-functions
Conditional ARM Templates: https://docs.microsoft.com/en-us/azure/architecture/building-blocks/extending-templates/conditional-deploy