What are Azure ARM templates
- ARM templates are an Infrastructure as Code (IaC) methodology of deploying resources in the Azure cloud.
- They use declarative JSON to define resources and configurations.
- The Azure Resource Manager could consume ARM templates to the via the Azure portal, Azure CLI, Azure PowerShell or some other API end point that we’d like to use.
- Azure Resource Manager will take note of the resources and configurations defined in ARM templates and then communicate with Azure resource providers to deploy those resources.
In this article we will explain the benefits of using ARM templates, describe the different components of ARM templates and finally deploy a virtual machine using an ARM template.
Advantages of ARM templates
Here are some advantages of using ARM templates:
- Templates improve consistency: Resource Manager templates provide a common language for you and your peers to describe your deployments. Regardless of the tool you use to deploy the template, the structure, format, and expressions inside the template remain the same.
- Templates allow hierarchical resource deployments: Templates enable you to deploy multiple resources in the correct order. For example, you wouldn't want to deploy a virtual machine prior to creating an operating system (OS) disk or network interface. Resource Manager maps out each resource and its dependent resources, and creates dependent resources first ensuring that the correct order for resource deployment is preserved.
- Templates reduce scope of human error: Manually creating and connecting resources can be time consuming, and it's easy to make mistakes. Resource Manager ensures that the deployment happens the same way every time.
- Templates are code: Templates express your requirements through code. You can think of a template as a type of Infrastructure as Code that can be shared, tested, and versioned similar to any other piece of software. Also, because templates are code, you can create a “paper trail” that you can follow. The template code documents the deployment. Most users maintain their templates under some kind of revision control, such as GIT. Using this type of versioning allows us to asses how our template has evolved over time.
- Templates promote reuse: Your template can contain parameters that are filled in when the template runs. A parameter can define a username or password, a domain name, and so on. Template parameters enable you to create multiple versions of your infrastructure, such as staging and production, while still utilizing the exact same template.
- Templates can be linked: You can link Resource Manager templates together to make the templates themselves modular. You can write small templates that each define a piece of a solution, and then combine them to create a complete system.
- Templates enable simplified orchestration: You only need to deploy the template to deploy all of your resources. Without templates, this would require multiple operations.
Anatomy of an ARM template
ARM templates are written in JSON but are not related to the JSON language itself. JSON allows us to express data stored as an object (such as a virtual machine or load balancer) as text. A JSON file is essentially a collection of key-value pairs. Here each key is a string whose value could be a string, a number of a collection of key-value pairs. A typical ARM template will comprise the following sections:
{
"$schema": "http://schema.management. azure.com/schemas/2019-04- 01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"resources": [],
"outputs": {}
}
Let’s now describe these sections one by one.
- Schema: Location of the JSON schema file that describes the version of the template language.
- ContentVersion: Use this value to document significant changes in your template. When deploying resources using the template, this value can be used to make sure that the right template is being used.
- Parameters: These are values that are provided during resource deployment execution to customize the deployment. For example, the virtual machine name during a VM deployment. Parameters can have default values in case we chose not to provide a value during resource deployment.
- Variables: They are used to express information that is likely to remain static across resource deployments such as an admin user name in case of VM deployments.
- Resources: These define the resource types that are created or updated during the resource deployment. For example, VM network, disk or public IP addresses.
- Outputs: This section comprises values that are returned or presented as output upon successful completion of the ARM template deployment.
Sample ARM template to deploy virtual machine:
{
"$schema": "https://schema.management.demo.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"vmName": {
"type": "string",
"defaultValue": "azure-demo-01",
"metadata": {
"description": "The name of you Virtual Machine."
}
},
"adminUsername": {
"type": "string",
"metadata": {
"description": "Username for the Virtual Machine."
}
},
"authenticationType": {
"type": "string",
"defaultValue": "password",
"allowedValues": [
"sshPublicKey",
"password"
],
"metadata": {
"description": "Type of authentication to use on the Virtual Machine. SSH Key is recommended."
}
},
"adminPasswordOrKey": {
"type": "securestring",
"metadata": {
"description": "SSH Key or password for the Virtual Machine. SSH key is recommended."
}
},
"dnsLabelPrefix": {
"type": "string",
"defaultValue": "[toLower(concat('azure-demo-01-', uniqueString(resourceGroup().id)))]",
"metadata": {
"description": "Unique DNS Name for the Public IP used to access the Virtual Machine."
}
},
"ubuntuOSVersion": {
"type": "string",
"defaultValue": "18.04-LTS",
"allowedValues": [
"12.04.5-LTS",
"14.04.5-LTS",
"16.04.0-LTS",
"18.04-LTS"
],
"metadata": {
"description": "The Ubuntu version for the VM. This will pick a fully patched image of this given Ubuntu version."
}
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Location for all resources."
}
},
"VmSize": {
"type": "string",
"defaultValue": "Standard_B2s",
"metadata": {
"description": "The size of the VM"
}
},
"virtualNetworkName": {
"type": "string",
"defaultValue": "azure-demo-01-vnet",
"metadata": {
"description": "Name of the VNET"
}
},
"subnetName": {
"type": "string",
"defaultValue": "default",
"metadata": {
"description": "Name of the subnet in the virtual network"
}
},
"networkSecurityGroupName": {
"type": "string",
"defaultValue": "azure-demo-01-nsg",
"metadata": {
"description": "Name of the Network Security Group"
}
}
},
"variables": {
"publicIpAddressName": "[concat(parameters('vmName'), 'PublicIP' )]",
"networkInterfaceName": "[concat(parameters('vmName'),'NetInt')]",
"subnetRef": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('subnetName'))]",
"osDiskType": "Standard_LRS",
"subnetAddressPrefix": "10.1.0.0/24",
"addressPrefix": "10.1.0.0/16",
"linuxConfiguration": {
"disablePasswordAuthentication": true,
"ssh": {
"publicKeys": [
{
"path": "[concat('/home/', parameters('adminUsername'), '/.ssh/authorized_keys')]",
"keyData": "[parameters('adminPasswordOrKey')]"
}
]
}
}
},
"resources": [
{
"type": "Microsoft.Network/networkInterfaces",
"apiVersion": "2020-06-01",
"name": "[variables('networkInterfaceName')]",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.Network/networkSecurityGroups/', parameters('networkSecurityGroupName'))]",
"[resourceId('Microsoft.Network/virtualNetworks/', parameters('virtualNetworkName'))]",
"[resourceId('Microsoft.Network/publicIpAddresses/', variables('publicIpAddressName'))]"
],
"properties": {
"ipConfigurations": [
{
"name": "ipconfig1",
"properties": {
"subnet": {
"id": "[variables('subnetRef')]"
},
"privateIPAllocationMethod": "Dynamic",
"publicIpAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]"
}
}
}
],
"networkSecurityGroup": {
"id": "[resourceId('Microsoft.Network/networkSecurityGroups',parameters('networkSecurityGroupName'))]"
}
}
},
{
"type": "Microsoft.Network/networkSecurityGroups",
"apiVersion": "2020-06-01",
"name": "[parameters('networkSecurityGroupName')]",
"location": "[parameters('location')]",
"properties": {
"securityRules": [
{
"name": "SSH",
"properties": {
"priority": 1000,
"protocol": "TCP",
"access": "Allow",
"direction": "Inbound",
"sourceAddressPrefix": "*",
"sourcePortRange": "*",
"destinationAddressPrefix": "*",
"destinationPortRange": "22"
}
}
]
}
},
{
"type": "Microsoft.Network/virtualNetworks",
"apiVersion": "2020-06-01",
"name": "[parameters('virtualNetworkName')]",
"location": "[parameters('location')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"[variables('addressPrefix')]"
]
},
"subnets": [
{
"name": "[parameters('subnetName')]",
"properties": {
"addressPrefix": "[variables('subnetAddressPrefix')]",
"privateEndpointNetworkPolicies": "Enabled",
"privateLinkServiceNetworkPolicies": "Enabled"
}
}
]
}
},
{
"type": "Microsoft.Network/publicIpAddresses",
"apiVersion": "2020-06-01",
"name": "[variables('publicIpAddressName')]",
"location": "[parameters('location')]",
"sku": {
"name": "Basic",
"tier": "Regional"
},
"properties": {
"publicIpAllocationMethod": "Dynamic",
"publicIPAddressVersion": "IPv4",
"dnsSettings": {
"domainNameLabel": "[parameters('dnsLabelPrefix')]"
},
"idleTimeoutInMinutes": 4
}
},
{
"type": "Microsoft.Compute/virtualMachines",
"apiVersion": "2020-06-01",
"name": "[parameters('vmName')]",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.Network/networkInterfaces/', variables('networkInterfaceName'))]"
],
"properties": {
"hardwareProfile": {
"vmSize": "[parameters('VmSize')]"
},
"storageProfile": {
"osDisk": {
"createOption": "fromImage",
"managedDisk": {
"storageAccountType": "[variables('osDiskType')]"
}
},
"imageReference": {
"publisher": "Canonical",
"offer": "UbuntuServer",
"sku": "[parameters('ubuntuOSVersion')]",
"version": "latest"
}
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('networkInterfaceName'))]"
}
]
},
"osProfile": {
"computerName": "[parameters('vmName')]",
"adminUsername": "[parameters('adminUsername')]",
"adminPassword": "[parameters('adminPasswordOrKey')]",
"linuxConfiguration": "[if(equals(parameters('authenticationType'), 'password'), json('null'), variables('linuxConfiguration'))]"
}
}
}
],
"outputs": {
"adminUsername": {
"type": "string",
"value": "[parameters('adminUsername')]"
},
"hostname": {
"type": "string",
"value": "[reference(variables('publicIPAddressName')).dnsSettings.fqdn]"
},
"sshCommand": {
"type": "string",
"value": "[concat('ssh ', parameters('adminUsername'), '@', reference(variables('publicIPAddressName')).dnsSettings.fqdn)]"
}
}
}
We will not go into the details of each and every JSON expression described in the template as the objective of this article is not an ARM template deep dive but will instead broadly elucidate the components of the template.
Parameters section
As mentioned earlier, the parameters section is where we describe values that are subject to change during our resource deployment. Here we have mentioned the VM name, admin user name, authentication type, DNS label, OS version, location, size and network information. We have provided default values for most of the components but you don’t need to do that necessarily. You may omit the default values in case you would like to populate these parameters at deployment execution time.
Variables section
Here we mention attributes that are not likely to change across resource deployments. We may move fields from the parameters section to the variables section if we do not intend to modify them across resource deployment operations.
Resources section
The components defined in the parameters and variables sections are referenced in the resources section. In this section we define the resources that we are deploying along with their configuration details. Within our example ARM template, we’ve defined network interfaces, network security groups, public IP addresses and virtual machine resources. There are sections where we’ve used the depends on attribute in the resource definition section. This ensures that the required resource has been successfully deployed before attempted to deploy the dependent resource. This allows us to have an ordered creation of resources.
The outputs section
This section allows us to return some output to the user upon the successful execution of this ARM template. In our example we are displaying the admin user name to use to connect to the VM, the public IP address of the VM and the ssh connection string i.e., username@IPaddress
.
We realize that understanding every field to use in an ARM template and writing an ARM template from scratch may appear to be a daunting task. But the good news here is that we don’t have to. We do not need to pressure ourselves in mastering JSON syntax and mastering the various fields used in defining resource parameters. There is an abundance of community-based templates available that we can use right out of the box just with a little tweaking. Once we get accustomed to manipulating and deploying ARM templates, we may have the confidence and will of deploying our own.
Deploy VM using an Azure ARM template
Now that we have a basic understanding of the different logical components that make up an ARM template, let’s use our template to deploy a virtual machine.
Step 1: Open the templates menu in Azure portal
Open the Azure portal and within the search bar type templates and press enter. This will open the templates services menu.
Step 2: Provide general information
In the template services window, click on the create button. This opens up a page where we need to provide a name and description for our template. Once you have typed in the details, click on the ok button on the bottom left of the screen.
Step 3: Add template
In this window, we need to copy and paste the contents of the template file that we shared earlier in the article.
Once you’ve entered in the file contents click on ok at the bottom left of the screen. Once done, click on Add button that will be displayed on the next page. This will now add our template to Azure template services. It may take a couple of seconds to appear in the template services window. In case it does not appear automatically, just click refresh a couple of times.
Step 4: Deploy resources from template
Once the Azure ARM template is available in the template services window, click on it and the below page will appear.
Click on deploy and this will open the template settings window where we could modify the parameters specified in the template in case. we wish to override the defaults. We did not specify default values for the admin user name, password and resource group fields. Therefore, we will need to fill in those values now.
Once we are satisfied with the values we have set, check mark the terms and conditions and click on purchase. This will now deploy the resources defined in the ARM template in the resource group that we specified during deployment. The resource deployment operation will take a couple of seconds. Once it completes, we could open the Virtual Machine services section to verify that our VM has been deployed.
The above output confirms that our VM has indeed been deployed successfully.
Summary
In this article, we explained the benefits of using ARM templates along with the different components that make up an ARM template. We also shared and described a sample ARM template to deploy a virtual machine and went on to use that template to create a virtual machine in the Azure cloud.
References
For writing this article, we referred to ARM template documentation available at https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/syntax