What do you do when you want to continuously test your code in environments that match your production environment in a way that is cost-effective? Or, what do you do when you need to deploy the same type of resource multiple times, such as front-end web servers? The answer to those questions is that you use Azure Resource Manager (ARM) Templates.
ARM Templates are JSON files that allow you to declare what you want your resources and environment to look like. These templates are deployed using several PowerShell commands that are part of the Azure PowerShell Module. Deploying your Azure resources this way allows you to declare your environment and then make it so.
There are two deployment modes for ARM Templates, Incremental and Complete. In the Complete mode, Resource Manager deletes resources that exist in the resource group but are not specified in the template. In Incremental mode, Resource Manager leaves unchanged resources that exist in the resource group but are not specified in the template. By default, ARM Templates are deployed in Incremental mode.
There are also many extremely powerful functions available for use in ARM templates such as CopyIndex, which increments resources for you. This function allows you to declare a Virtual Machine (VM) once in a template, and then Azure will deploy the VM as many times as you specify and increment the name each time (MyVM1, MyVM2, MyVM3, etc). This saves a lot of time and lines of code in your ARM template because you only need to declare the resource and its dependencies one time, instead of for every single VM you want to provision. You can also use PowerShell Desired State Configuration to configure your VM post-deployment, saving even more time!
Using the common scenario of deploying a new virtual machine (VM) in Azure, let’s take a look at how we would do that with an ARM template. When you deploy a new VM in Azure you aren’t deploying just a VM. You also need a storage account (for diagnostics or disks), a virtual network, a NIC and a public IP address. An ARM Template for this scenario would look like the example below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
{ “$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”: “[toLower(‘celedonblogstorage’)]”, “apiVersion”: “2015-06-15”, “location”: “[resourceGroup().location]”, “tags”: { “displayName”: “celedonblog Storage Account” }, “properties”: { “accountType”: “Standard_LRS” } }, { “apiVersion”: “2015-06-15”, “type”: “Microsoft.Network/publicIPAddresses”, “name”: “celedonblog-PublicIP”, “location”: “[resourceGroup().location]”, “tags”: { “displayName”: “PublicIPAddress” }, “properties”: { “publicIPAllocationMethod”: “Dynamic”, “dnsSettings”: { “domainNameLabel”: “[toLower(‘celedonblog’)]” } } }, { “apiVersion”: “2015-06-15”, “type”: “Microsoft.Network/virtualNetworks”, “name”: “celedonblog-VirtualNetwork”, “location”: “[resourceGroup().location]”, “tags”: { “displayName”: “VirtualNetwork” }, “properties”: { “addressSpace”: { “addressPrefixes”: [ “10.0.0.0/16” ] }, “subnets”: [ { “name”: “celedonblog-VirtualNetwork-Subnet”, “properties”: { “addressPrefix”: “10.0.0.0/24” } } ] } }, { “apiVersion”: “2015-06-15”, “type”: “Microsoft.Network/networkInterfaces”, “name”: “celedonblog-NetworkInterface”, “location”: “[resourceGroup().location]”, “dependsOn”: [ “Microsoft.Network/publicIPAddresses/celedonblog-PublicIP”, “Microsoft.Network/virtualNetworks/celedonblog-VirtualNetwork” ], “tags”: { “displayName”: “celedonblog Network Interface” }, “properties”: { “ipConfigurations”: [ { “name”: “ipconfig1”, “properties”: { “privateIPAllocationMethod”: “Dynamic”, “publicIPAddress”: { “id”: “[resourceId(‘Microsoft.Network/publicIPAddresses’, ‘celedonblog-PublicIP’)]” }, “subnet”: { “id”: “[concat(resourceId(‘Microsoft.Network/virtualNetworks’, ‘celedonblog-VirtualNetwork’), ‘/subnets/celedonblog-VirtualNetwork-Subnet’)]” } } } ] } }, { “apiVersion”: “2017-03-30”, “type”: “Microsoft.Compute/virtualMachines”, “name”: “celedonblog”, “location”: “[resourceGroup().location]”, “dependsOn”: [ “[concat(‘Microsoft.Storage/storageAccounts/’, toLower(‘celedonblogstorage’))]”, “Microsoft.Network/networkInterfaces/celedonblog-NetworkInterface” ], “tags”: { “displayName”: “celedonblog” }, “properties”: { “hardwareProfile”: { “vmSize”: “Standard_A2” }, “osProfile”: { “computerName”: “celedonblog”, “adminUsername”: “sysadmin”, “adminPassword”: “MySup3rS3cur3pa$$w0rd” }, “storageProfile”: { “imageReference”: { “publisher”: “MicrosoftWindowsServer”, “offer”: “WindowsServer”, “sku”: “2012-R2-Datacenter”, “version”: “latest” }, “osDisk”: { “name”: “celedonblogOSDisk”, “caching”: “ReadWrite”, “createOption”: “FromImage” } }, “networkProfile”: { “networkInterfaces”: [ { “id”: “[resourceId(‘Microsoft.Network/networkInterfaces’, ‘celedonblog-NetworkInterface’)]” } ] }, “diagnosticsProfile”: { “bootDiagnostics”: { “enabled”: true, “storageUri”: “[concat(‘http://’, toLower(‘celedonblogstorage’), ‘.blob.core.windows.net’)]” } } }, “resources”: [ { “type”: “extensions”, “name”: “celedonblogAzureDiagnostics”, “apiVersion”: “2015-06-15”, “location”: “[resourceGroup().location]”, “tags”: { “displayName”: “celedonblogAzureDiagnostics” }, “dependsOn”: [ “Microsoft.Compute/virtualMachines/celedonblog” ], “properties”: { “publisher”: “Microsoft.Azure.Diagnostics”, “type”: “IaaSDiagnostics”, “typeHandlerVersion”: “1.5”, “autoUpgradeMinorVersion”: true, “settings”: { “xmlCfg”: “[base64(‘<WadCfg> <DiagnosticMonitorConfiguration overallQuotaInMB=”4096″ xmlns=”http: //schemas.microsoft.com/ServiceHosting/2010/10/DiagnosticsConfiguration”> <DiagnosticInfrastructureLogs scheduledTransferLogLevelFilter=”Error”/> <Logs scheduledTransferPeriod=”PT1M” scheduledTransferLogLevelFilter=”Error” /> <Directories scheduledTransferPeriod=”PT1M”> <IISLogs containerName =”wad-iis-logfiles” /> <FailedRequestLogs containerName =”wad-failedrequestlogs” /> </Directories> <WindowsEventLog scheduledTransferPeriod=”PT1M” > <DataSource name=”Application!*” /> </WindowsEventLog> <CrashDumps containerName=”wad-crashdumps” dumpType=”Mini”> <CrashDumpConfiguration processName=”WaIISHost.exe”/> <CrashDumpConfiguration processName=”WaWorkerHost.exe”/> <CrashDumpConfiguration processName=”w3wp.exe”/> </CrashDumps> <PerformanceCounters scheduledTransferPeriod=”PT1M”> <PerformanceCounterConfiguration counterSpecifier=”\Memory\Available MBytes” sampleRate=”PT3M” /> <PerformanceCounterConfiguration counterSpecifier=”\Web Service(_Total)\ISAPI Extension Requests/sec” sampleRate=”PT3M” /> <PerformanceCounterConfiguration counterSpecifier=”\Web Service(_Total)\Bytes Total/Sec” sampleRate=”PT3M” /> <PerformanceCounterConfiguration counterSpecifier=”\ASP.NET Applications(__Total__)\Requests/Sec” sampleRate=”PT3M” /> <PerformanceCounterConfiguration counterSpecifier=”\ASP.NET Applications(__Total__)\Errors Total/Sec” sampleRate=”PT3M” /> <PerformanceCounterConfiguration counterSpecifier=”\ASP.NET\Requests Queued” sampleRate=”PT3M” /> <PerformanceCounterConfiguration counterSpecifier=”\ASP.NET\Requests Rejected” sampleRate=”PT3M” /> <PerformanceCounterConfiguration counterSpecifier=”\Processor(_Total)\% Processor Time” sampleRate=”PT3M” /> </PerformanceCounters> </DiagnosticMonitorConfiguration> </WadCfg>’)]”, “storageAccount”: “[toLower(‘celedonblogstorage’)]” }, “protectedSettings”: { “storageAccountName”: “[toLower(‘celedonblogstorage’)]”, “storageAccountKey”: “[listkeys(concat(‘/subscriptions/’, subscription().subscriptionId, ‘/resourceGroups/’, resourceGroup().name, ‘/providers/Microsoft.Storage/storageAccounts/’, toLower(‘celedonblogstorage’)), ‘2015-06-15’).key1]”, “storageAccountEndPoint”: “https://core.windows.net” } } } ] } ], “outputs”: {} } |
At first glance ARM Templates may look complicated, however, they are easy to read and once you start working with ARM templates you will come to appreciate how powerful they are. Using a tool like Visual Studio Code along with its extensions for Authoring ARM Templates makes it a breeze to read and configure ARM templates.
Additional Resources:
- https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-authoring-templates
- https://docs.microsoft.com/en-us/azure/templates
- https://azurecitadel.github.io/workshops/arm/