IaC - Understand ARM Template
With the introduction of Azure Resource Manager model life become much easier. New model not only provide much more granule control over design, deployment and management but it also provide the JSON base template deployment to support Infrastructure-as-Code. Which is one of important pillar of DevOps.
This post is part of learn Azure RM Automation series. In this first post I will walk you through the structure and basics.
This post is part of learn Azure RM Automation series. In this first post I will walk you through the structure and basics.
Understand the solution/ code structure
You can use any authoring tool like VisualStudio, VisualStudio code, notepad++ etc. to start writing IaC code. Please make sure you install Azure SDK/ Azure RM plugin (to work with VS code).
Create template using VisualStudio
File - New Project - Cloud - Azure Resource Group
Select template
Solution Structure
Azure RM have 3 main files as a part of IaC solution.
-
Azuredeploy.json: this is first file where we write actual code (better to call orchestration).
-
Azuredeploy.parameters.json: this parameter JSON file is used to store parameter values provided by the users at the time of deployment. You can either hardcode these values, or you can provide these values on run time.
-
Deploy.ps1: this PowerShell is use to run some validations/ checks, also if define it create a stagining artifacts storage to upload and later download files securely using SAS token.
Will explain more as we progress.
I am using VS Code ( lightweight, free and powerful tool).
Expressions and functions
The basic syntax of the template is JSON. However, expressions and functions extend the JSON values available within the template. Expressions are written within JSON string literals whose first and last characters are the brackets: [ and ], respectively. The value of the expression is evaluated when the template is deployed. While written as a string literal, the result of evaluating the expression can be of a different JSON type, such as an array or integer, depending on the actual expression. To have a literal string start with a bracket [, but not have it interpreted as an expression, add an extra bracket to start the string with [[. Typically, you use expressions with functions to perform operations for configuring the deployment. Just like in JavaScript, function calls are formatted as functionName(arg1,arg2,arg3). You reference properties by using the dot and [index] operators.
Understand azuredeploy.json
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"resources": [],
"outputs": {}
}
- schema: Location of the JSON schema file that describes the version of the template language. You should use the URL shown above.
- contentVersion: Version of the template (such as 1.0.0.0). You can provide any value for this element. When deploying resources using the template, this value can be used to make sure that the right template is being used.
- parameters: Values that are provided when deployment is executed to customize resource deployment.
- variables: Values that are used as JSON fragments in the template to simplify template language expressions.
- resources: Resource types that are deployed or updated in a resource group.
- outputs: Values that are returned after deployment.
azuredeploy.parameters.json
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
}
}
azuredeploy.json
parameters
This section is used to recive input from the user. You need to provide parameter name and it’s type.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"<<paramName>>":{
"type": "string"
}
}
}
You can also add input validation by using various property.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"vmName": {
"type": "string",
"defaultValue": "workVM",
"minLength": 4,
"maxLength": 10,
"metadata": { "value": "VM name" }
},
"vmAdminName": {
"type": "string",
"defaultValue": "localadmin",
"minLength": 4,
"maxLength": 10,
"metadata": { "value": "VM local admin name" }
},
"vmAdminPassword": {
"type": "securestring",
"minLength": 4,
"maxLength": 20,
"metadata": { "value": "VM local admin password" }
},
"dnsNamePrefix": {
"type": "string",
"metadata": { "value": "DNS Name" }
},
"vmSize": {
"type": "string",
"defaultValue": "Standard_D2s_v3",
"allowedValues": [
"Standard_D2s_v3",
"Standard_D4s_v3",
"Standard_D8s_v3"
],
"metadata": { "value": "VM Size" }
}
},
"variables": {},
"resources": [],
"outputs": {}
}
Parameter properties
“type”: type of input parameter. (string, array, bool, int, object, secureObject, secureString)
“minLength”: minimum length for the string or array type.
“maxLength”: maximum length for the string or array type.
“minValue”: minimum value for the int type parameter.
“maxValue”: maximum value for the int type parameter.
“allowedValues”: values can be only one of listed values.
“defaultValues”: value to be used if one is not provided.
variables
In the section, you construct values that can be used throughout your template. You do not need to define variables, but they often simplify your template by reducing complex expressions.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {
"<variable-name>": "<variable-value>",
"<variable-name>": {
<variable-complex-type-value>
}
},
"resources": [],
"outputs": {}
}
}
You can simple define the variables, but I like to group variables into complex objects. Use the variable.subentry format to reference a value from a complex object. Grouping variables can help you track related variables. It also improves readability of the template. Here’s an example:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {
"storageProperty":{
"namePrefix": "[concat(uniquestring(resourceGroup().id),'storage')]",
"accountType": "Standard_LRS"
},
"vmProperty": {
"imagePublisher": "MicrosoftWindowsServer",
"imageOffer": "WindowsServer",
"imageSKU": "2012-R2-Datacenter",
"imageVersion": "latest",
"osDiskName": "[concat(parameters('vmName'),'_osdisk')]",
"vmStorageAccountContainerName": "vhds"
},
"networkProperty":{
"nicName": "[concat(parameters('vmName'),'_nic')]",
"addressPrefix": "10.0.0.0/16",
"subnetName": "[concat(parameters('vmName'),'_subnet')]",
"subnetPrefix": "10.0.0.0/24",
"virtualNetworkName": "[concat(parameters('vmName'),'_vnet')]"
},
"publicIPProperty":{
"publicIPAddressName": "[concat(parameters('vmName'),'_pubip')]",
"publicIPAddressType": "Dynamic"
},
"vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('networkProperty').virtualNetworkName)]",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('networkProperty').subnetName)]"
},
"resources": [],
"outputs": {}
}
}
resources
Here we define the resources that are deployed or updated. This section can get complicated because you must understand the types you are deploying to provide the right values. For the resource-specific values (apiVersion, type, and properties) that you need to set.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"resources": [
{
"condition": "<boolean-value-whether-to-deploy>",
"apiVersion": "<api-version-of-resource>",
"type": "<resource-provider-namespace/resource-type-name>",
"name": "<name-of-the-resource>",
"location": "<location-of-resource>",
"tags": {
"<tag-name1>": "<tag-value1>",
"<tag-name2>": "<tag-value2>"
},
"comments": "<your-reference-notes>",
"copy": {
"name": "<name-of-copy-loop>",
"count": "<number-of-iterations>",
"mode": "<serial-or-parallel>",
"batchSize": "<number-to-deploy-serially>"
},
"dependsOn": [
"<array-of-related-resource-names>"
],
"properties": {
"<settings-for-the-resource>",
"copy": [
{
"name": ,
"count": ,
"input": {}
}
]
},
"resources": [
"<array-of-child-resources>"
]
}
],
"outputs": {}
}
Element name | Required | Description |
condition | No | Boolean value that indicates whether the resource is deployed. |
apiVersion | Yes | Version of the REST API to use for creating the resource. |
type | Yes | Type of the resource. This value is a combination of the namespace of the resource provider and the resource type (such as Microsoft.Storage/storageAccounts). |
name | Yes | Name of the resource. The name must follow URI component restrictions defined in RFC3986. In addition, Azure services that expose the resource name to outside parties validate the name to make sure it is not an attempt to spoof another identity. |
location | Varies | supported geo-locations of the provided resource. You can select any of the available locations, but typically it makes sense to pick one that is close to your users. Usually, it also makes sense to place resources that interact with each other in the same region. Most resource types require a location, but some types (such as a role assignment) do not require a location. |
tags | No | Tags that are associated with the resource. |
comments | No | Your notes for documenting the resources in your template |
copy | No | If more than one instance is needed, the number of resources to create. The default mode is parallel. Specify serial mode when you do not want all or the resources to deploy at the same time. |
dependsOn | No | Resources that must be deployed before this resource is deployed. Resource Manager evaluates the dependencies between resources and deploys them in the correct order. When resources are not dependent on each other, they are deployed in parallel. The value can be a comma-separated list of a resource names or resource unique identifiers. Only list resources that are deployed in this template. Resources that are not defined in this template must already exist. Avoid adding unnecessary dependencies as they can slow your deployment and create circular dependencies. |
properties | No | Resource-specific configuration settings. The values for the properties are the same as the values you provide in the request body for the REST API operation (PUT method) to create the resource. You can also specify a copy array to create multiple instances of a property. |
resources | No | Child resources that depend on the resource being defined. Only provide resource types that are permitted by the schema of the parent resource. The fully qualified type of the child resource includes the parent resource type, such as Microsoft.Web/sites/extensions. Dependency on the parent resource is not implied. You must explicitly define that dependency. |
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
},
"variables": {
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"name": "[variables('storageProperty').namePrefix]",
"apiVersion": "2016-01-01",
"location": "[resourceGroup().location]",
"tags": {
"displayName": "StorageAccount"
},
"sku": {
"name": "[variables('storageProperty').accountType]"
},
"kind": "Storage"
},
{
"apiVersion": "2016-03-30",
"type": "Microsoft.Network/publicIPAddresses",
"name": "[variables('publicIPProperty').publicIPAddressName]",
"location": "[resourceGroup().location]",
"tags": {
"displayName": "PublicIPAddress"
},
"properties": {
"publicIPAllocationMethod": "[variables('publicIPProperty').publicIPAddressType]",
"dnsSettings": {
"domainNameLabel": "[parameters('dnsNamePrefix')]"
}
}
},
{
"apiVersion": "2016-03-30",
"type": "Microsoft.Network/virtualNetworks",
"name": "[variables('networkProperty').virtualNetworkName]",
"location": "[resourceGroup().location]",
"tags": {
"displayName": "VirtualNetwork"
},
"properties": {
"addressSpace": {
"addressPrefixes": [
"[variables('networkProperty').addressPrefix]"
]
},
"subnets": [
{
"name": "[variables('networkProperty').subnetName]",
"properties": {
"addressPrefix": "[variables('networkProperty').subnetPrefix]"
}
}
]
}
},
{
"apiVersion": "2016-03-30",
"type": "Microsoft.Network/networkInterfaces",
"name": "[variables('networkProperty').nicName]",
"location": "[resourceGroup().location]",
"tags": {
"displayName": "NetworkInterface"
},
"dependsOn": [
"[resourceId('Microsoft.Network/publicIPAddresses/', variables('publicIPProperty').publicIPAddressName)]",
"[resourceId('Microsoft.Network/virtualNetworks/', variables('networkProperty').virtualNetworkName)]"
],
"properties": {
"ipConfigurations": [
{
"name": "ipconfig1",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPProperty').publicIPAddressName)]"
},
"subnet": {
"id": "[variables('subnetRef')]"
}
}
}
]
}
},
{
"apiVersion": "2015-06-15",
"type": "Microsoft.Compute/virtualMachines",
"name": "[parameters('vmName')]",
"location": "[resourceGroup().location]",
"tags": {
"displayName": "VirtualMachine"
},
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts/', variables('storageProperty').namePrefix)]",
"[resourceId('Microsoft.Network/networkInterfaces/', variables('networkProperty').nicName)]"
],
"properties": {
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"osProfile": {
"computerName": "[parameters('vmName')]",
"adminUsername": "[parameters('vmAdminName')]",
"adminPassword": "[parameters('vmAdminPassword')]"
},
"storageProfile": {
"imageReference": {
"publisher": "[variables('vmProperty').imagePublisher]",
"offer": "[variables('vmProperty').imageOffer]",
"sku": "[variables('vmProperty').imageSKU]",
"version": "latest"
},
"osDisk": {
"name": "osdisk",
"vhd": {
"uri": "[concat(reference(resourceId('Microsoft.Storage/storageAccounts', variables('storageProperty').namePrefix), '2016-01-01').primaryEndpoints.blob, variables('vmProperty').vmStorageAccountContainerName, '/', variables('vmProperty').osDiskName, '.vhd')]"
},
"caching": "ReadWrite",
"createOption": "FromImage"
}
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('networkProperty').nicName)]"
}
]
},
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": true,
"storageUri": "[reference(resourceId('Microsoft.Storage/storageAccounts', variables('storageProperty').namePrefix), '2016-01-01').primaryEndpoints.blob]"
}
}
}
}
],
"outputs": {}
}
outputs
In this section have values that are returned after deployment. We can refer these values in other deployments.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
},
"variables": {
},
"resources": [
],
"outputs": {
"<<outputkey>>":{
"type": <<keyType>>,
"value": <<KeyValue>>
}
}
}
example
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
},
"variables": {
},
"resources": [
],
"outputs": {
"dnsName":{
"type": "string",
"value": "variables('publicIPProperty').dnsName"
}
}
}
Learn Azure RM Automation
This post is part of learn Azure RM automation series. Assuming that you have atleast some basic knowledge in Azure. Please refer related post for more details.
Please refer
Best practices for creating Azure Resource Manager templates
Please do let me know your thoughts/ suggestions/ question in disqus section.
Related Posts
- Azure Bastion
- Secure secretes in Azure DevOps Pipelines
- Integration of Azure Backup into VM create experience
- Azure Archive Storage
- Auto-shutdown RM VM