Creating Azure Environments using Azure Resource Manager Templates

Automation is cool. I love automation.

Many times I created scripts to build and rebuild my test/demo/play environments in azure. Most of the time my efforts felt a lot like the lower graph below:

It was simply too complex. Every time I wanted to add/change something, I had to remember how to do it; search the net because I didn’t remember; go over all the broken tutorials around… Waste of time. So I always went back to manually building my environments.

Last week I discovered Azure Resource Manager (ARM) templates. I think that this time, finally, I found what I was looking for.

Some background: An Azure Resource Group is a logical container of Azure resources. For example, if you have a web application with 2 web servers and one database, with a load balancer before the two web servers, it is logical to group these resources with a single name and a single entry point for management of these resources. Read more on resource groups in the Azure documentation. An ARM template is a JSON text file that provides a description of the environment that you want to exist in azure. Note that this is not only a one-time thing – you can update an ARM template and re-execute it to update the environment you have already defined.

For this tutorial I will be using Azure PowerShell to deploy my templates, which are simple JSON files. You can create these templates with the help of Visual Studio, which also provides wizards to help you in the process. But nothing beats the shell when you want to do real automation.

First, you need to set up PowerShell to work with the azure subscription where you want to deploy. This is done in two steps, first adding the Azure account where the subscription is, and then selecting the subscription:

Add-AzureAccount

This will open a popup window where you enter the credentials of the admin for your azure subscription, adding it to the PowerShell environment.

Now let’s get started with a simple example: deploy a virtual machine. This is a simple task, but requires a number of elements:

  1. A resource group where the VM will belong
  2. A storage account for the VM’s disks
  3. A virtual network (all VMs in azure configured using resource groups must be inside a vnet)
  4. A public IP if we want to access the VM from outside the virtual network
  5. A network interface that connects the VM to the virtual network

Let’s do this one by one. The resource group is created with the New-AzureResourceGroup cmdlet, which requires a name for the resource group and a location. I have decided to call my resource group “vainodemorg” and put it in “Central US”. :

New-AzureResourceGroup -Name vainodemorg -Location "Central US"

The requirement for a location is somewhat confusing because resource groups can contain resources from multiple locations… Go figure.

Now let’s create our first template! I chose to start with the storage account, which is really simple. A template is a JSON formatted string that conforms to the ARM template schema. In its most simple instance, it has 3 elements: $schema, which is used to validate the correctness of the template; contentVersion, which defines the version of the template itself, and can be used to validate that the correct template is being deployed; and resources, which define the resources deployed (or updated) in this template. Here I am creating a storage account (resource type Microsoft.Storage/storageAccounts) named “vainodemostor”, located in “Central US”, of type “Standard_LRS” (local redundancy).

{
  "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "resources": [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "name": "vainodemostor",
      "apiVersion": "2015-05-01-preview",
      "location": "Central US",
      "properties": {
        "accountType": "Standard_LRS"
      }
    }
  ]
}

To apply the template to the resource group I defined above, I use the PowerShell cmdlet code>New-AzureResourceGroupDeployment. I named my template file with the same name as the storage I am deploying, but this is not required đŸ™‚

New-AzureResourceGroupDeployment -ResourceGroupName "vainodemorg" -TemplateFile .\vainodemostor.json

Checking that the resource was deployed can be done using the Get-AzureResourceGroup cmdlet, which prints a list of all the resources in the resource group, and by now it should include the storage account.

Cool. Next, I’ll add the virtual network, public IP and VM NIC. Here you see that you can create multiple resources in one template file, and not only that, but you can also define dependencies between them. This is important because there is no predefined creation order for the resources defined in the template, so if one resource depends on another (in our case, the NIC depends on both the vnet and the public IP) this must be defined in the template. Another thing you can see in this template is the use of template functions such as resourceId and concat. A list of all functions can be found here. And now, the template:

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "resources": [
    {
      "name": "vainodemovnet",
      "type": "Microsoft.Network/virtualNetworks",
      "location": "Central US",
      "apiVersion": "2015-05-01-preview",
      "properties": {
        "addressSpace": {
          "addressPrefixes": [
            "10.0.0.0/16"
          ]
        },
        "subnets": [
          {
            "name": "vainodemovnetsubnet",
            "properties": {
              "addressPrefix": "10.0.0.0/24"
            }
          }
        ]
      }
    },
    {
      "apiVersion": "2015-05-01-preview",
      "type": "Microsoft.Network/publicIPAddresses",
      "name": "vainodemovmpublicip",
      "location": "Central US",
      "properties": {
        "publicIPAllocationMethod": "Dynamic",
        "dnsSettings": {
          "domainNameLabel": "vainodemovm"
        }
      }
    },
    {
      "apiVersion": "2015-05-01-preview",
      "type": "Microsoft.Network/networkInterfaces",
      "name": "vainodemovmnic",
      "location": "Central US",
      "dependsOn": [
        "[resourceId('Microsoft.Network/publicIPAddresses', 'vainodemovmpublicip')]",
        "[resourceId('Microsoft.Network/virtualNetworks','vainodemovnet')]"
      ],
      "properties": {
        "ipConfigurations": [
          {
            "name": "ipconfig1",
            "properties": {
              "privateIPAllocationMethod": "Dynamic",
              "publicIPAddress": {
                "id": "[resourceId('Microsoft.Network/publicIPAddresses', 'vainodemovmpublicip')]"
              },
              "subnet": {
                "id": "[concat(resourceId('Microsoft.Network/virtualNetworks','vainodemovnet'),'/subnets/vainodemovnetsubnet')]"
              }
            }
          }
        ]
      }
    }
  ]
}

And as before, we deploy it to the resource group using PowerShell:

New-AzureResourceGroupDeployment -ResourceGroupName "vainodemorg" -TemplateFile .\vainodemovnet.json

The last step in the journey today, and the whole reason it was started, is the Virtual Machine. This template is a bit more complicated but still very self-explanatory. I had to define the name of the VM, its size, disk location, OS image, and network interface:

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "resources": [
    {
      "apiVersion": "2015-05-01-preview",
      "type": "Microsoft.Compute/virtualMachines",
      "name": "vainodemovm",
      "location": "Central US",
      "properties": {
        "hardwareProfile": {
          "vmSize": "Standard_A2"
        },
        "osProfile": {
          "computerName": "vainodemovm",
          "adminUsername": "vainolo",
          "adminPassword": "Password123!@#"
        },
        "storageProfile": {
          "imageReference": {
            "publisher": "MicrosoftWindowsServer",
            "offer": "WindowsServer",
            "sku": "2012-R2-Datacenter",
            "version": "latest"
          },
          "osDisk": {
            "name": "osdisk",
            "vhd": {
              "uri": "http://vainodemostor.blob.core.windows.net/vhds/vainodemovmdisk.vhd"
            },
            "caching": "ReadWrite",
            "createOption": "FromImage"
          }
        },
        "networkProfile": {
          "networkInterfaces": [
            {
              "id": "[resourceId('Microsoft.Network/networkInterfaces','vainodemovmnic')]"
            }
          ]
        }
      }
    }
  ]
}

A final touch of PowerShell:

New-AzureResourceGroupDeployment -ResourceGroupName "vainodemorg" -TemplateFile .\vainodemovm.json

Voila! The environment is finally up :-).

While this may seem a lot of work just to create a VM, in most real world cases you are creating a more complex environment and many of the elements are repeated. Also, this is a great way to create environments that are exactly the same, just by changing the names of the parts. Here there are hard coded, but templates also support both variables and parameters, so the names don’t have to be hardcoded. I’ll talk more about this in another post, as this one is already longer than expected. Stay tuned!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.