An Intricate Look at ARM Templates - Part 4

Adding Resources to First Template

Greetings! For the sake of navigating through these series of blog posts, please refer to the following links that take you through what I’ve covered thus far:

Part 1 – Background and History
Part 2 – Prep Work and ARM Template Structure Breakdown
Part 3 – Building Your First Template with Visual Studio

From here on out, I will make sure to include all blog links at the top, so you can navigate through each post with ease! I figure that may even be better than bookmarking each post.

Picking up from where we left off with my part 3 post, please open up the “arm-vm-1” solution within Visual Studio Community and let’s add some more resources into the template!

Right click on “Resources” again and select “Add New Resource.”

addnewresource.png

Type in “Windows” and you should see several different options. Make sure you select “Windows Virtual Machine.”

addresources.png

In the right-hand side of the dialogue box, note that you are asked to input a few different pieces (Name, Storage account, and Virtual network/subnet). Also note the Windows server version. For some reason, the engineering within Visual Studio does not account for newer Windows VMs, with a newer OS and managed disks. We will work on tweaking that code after you add the resource in. If a 2012 Datacenter OS is fine for you and your deployment, you can simply adjust the API version to use managed disks (which I will cover, so hang tight).

Type in “arm-vm-1” for the name and click on the downward triangle on the right of the “Storage account” box:

addresource2.png
addresrouce3.png

Select “Create New.” Ultimately this will not be relevant as we continue building the template, but for ease of plugging in necessary code, please go through these steps.

Type in “armvms” and click “Add.” You should see the following dialogue prompt:

dialogue.png

Select the virtual network and subnet from the previous blog. Then click “Add.”

add.png

Examine the JSON Outline:

jsonoutline2.png

You should see parameters filled in, along with more resources and variables. Take a step back and think about the amount of code you have added yourself. Nothing. Visual Studio and Visual Studio Community have some native engineering components that make ARM Template adoption within IaC workflows easy for operations personnel to get up to speed quickly. The best part about ARM Templates? You NEVER start from scratch!

Now comes the hard part – we need to do a little bit of editing, so this works as anticipated. As I wrote earlier, building a VM using Visual Studio Community or regular Visual Studio means you are building a VM that’s slightly outdated. These days, the recommendation is to use Managed Disks versus building out a storage account to house VHD files. Rather than spend time talking about Managed Disks here, please review the FAQ, overview, and a blog post that details out some of the background to help with understanding. The quick high level surrounds the fact that customers now just need to specify standard or premium disks when building an IaaS VM. Microsoft handles the rest related to placement to alleviate the need of moving VHDs around to eliminate any possible resource contention. Unmanaged disks (VHDs in storage accounts) were trickier to manage and maintain.

So, let’s take the VM resource itself and examine what we need to change. To start, you should have this show up as the VM resource:

{
      "name": "[parameters('arm-vm-1Name')]",
      "type": "Microsoft.Compute/virtualMachines",
      "location": "[resourceGroup().location]",
      "apiVersion": "2015-06-15",
      "dependsOn": [
        "[resourceId('Microsoft.Storage/storageAccounts', variables('armvmsName'))]",
        "[resourceId('Microsoft.Network/networkInterfaces', variables('arm-vm-1NicName'))]"
      ],
      "tags": {
        "displayName": "arm-vm-1"
      },
      "properties": {
        "hardwareProfile": {
          "vmSize": "[variables('arm-vm-1VmSize')]"
        },
        "osProfile": {
          "computerName": "[parameters('arm-vm-1Name')]",
          "adminUsername": "[parameters('arm-vm-1AdminUsername')]",
          "adminPassword": "[parameters('arm-vm-1AdminPassword')]"
        },
        "storageProfile": {
          "imageReference": {
            "publisher": "[variables('arm-vm-1ImagePublisher')]",
            "offer": "[variables('arm-vm-1ImageOffer')]",
            "sku": "[parameters('arm-vm-1WindowsOSVersion')]",
            "version": "latest"
          },
          "osDisk": {
            "name": "arm-vm-1OSDisk",
            "vhd": {
              "uri": "[concat(reference(resourceId('Microsoft.Storage/storageAccounts', variables('armvmsName')), '2016-01-01').primaryEndpoints.blob, variables('arm-vm-1StorageAccountContainerName'), '/', variables('arm-vm-1OSDiskName'), '.vhd')]"
            },
            "caching": "ReadWrite",
            "createOption": "FromImage"
          }
        },
        "networkProfile": {
          "networkInterfaces": [
            {
              "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('arm-vm-1NicName'))]"
            }
          ]
        }
      }
    }

Before we alter anything, ensure that’s what shows up for your VM resource. If it doesn’t, do not be afraid to start over at the beginning of this blog.

Within Azure, there is a concept of a Resource Provider. We had covered this briefly within my first blog post. Basically, all configuration operations occur over an HTTPS RESTful API contract to a trusted Azure endpoint. Dependent upon the request, the trusted Azure endpoint can provision, delete, and manage services on a user’s behalf. Azure uses the response from the Resource Provider to manipulate resources based upon a configuration request. Every service in Azure has a Resource Provider. Additionally, there are a number of different API versions that you can use as well. The rule of thumb is to use a more recent API as you will get
all the brand-new whistles and bells. If you look at the API we used for the VNet example, we used an API with the following date: 2016-03-30. The way I tend to think of this is think about what was around with VNets in 2016. Using the older API is fine to provision resources, but you may lose out on newer services that were released after March 30, 2016 (i.e. Service Endpoints, Private Link, etc.). So as you configure ARM Templates, adjusting the API will be something you need to do as services grow and mature within Azure. For purposes of familiarizing yourself, we will keep using the VNet we deployed within the first blog post with the 2016-03-03 API.

We do want to grab the newer API for Microsoft.Compute that allows for a Managed Disk vs. an Unmanaged Disk. As a way of showcasing how to obtain this information, I have a PowerShell script that can be used to query the REST APIs for that information. I will not cover how to install the Azure cmdlets inside this blog article. If you need some assistance, please read of the following post: Install the Azure PowerShell Module.

Take note of the cmdlets below:

# Lists out all Resource Providers in a given region
Get-AzResourceProvider -Location eastus -ListAvailable | ft ProviderNamespace

# Lists out all resource type names
(Get-AzResourceProvider -ProviderNamespace Microsoft.Compute).ResourceTypes `
| ft ResourceTypeName

# Obtains all relevant APIs that can be used for a resource during deployment
((Get-AzResourceProvider -ProviderNamespace Microsoft.Compute).ResourceTypes `
| Where-Object ResourceTypeName -eq virtualmachines).ApiVersions 

The relevant output we really care about for purposes of continuing to build this ARM Template is from the 3rd cmdlet. I placed the other two cmdlets in so you could dig in as time unfolds with different APIs and build ARM Templates for Azure. Note, the first 2 cmdlets are exploratory cmdlets

output4.png

Another great resource is to use the ARM Template Reference. If you are ever curious about how to formulate your ARM Templates, what the syntax looks like, etc., that link will be a savior for you in your infrastructure as code configuration patterns for the environments you’re maintaining.

For purposes of building out a VM within this ARM Template, please use the 2018-10-01 API. As a frame of reference, here is the actual API documentation from the ARM Template Reference link above. When creating ARM Templates, ensure the API you select can support all the features you’re looking to deploy (i.e. services like Managed Disks, Service Endpoints, Private Link, etc.).

Another important aspect before we dig in and tweak lines of code is to properly understand JSON formatting. Going back to my first post, JSON syntax is derived from JavaScript object notation (JSON) syntax, where data is in name/value pairs, data is separated by commas, curly braces hold objects, and square brackets hold arrays.

“But what does that mean?” you may find yourself asking. Let me quickly unravel JSON in a way that makes sense.

In its simplest form, here is a name/value pair

"name": "Shannon"

In another attempt at simplifying concepts for understanding, here is a JSON object:

{
     "name": "Shannon"
}

Take note of the placement of curly braces and quotation marks. These are extremely important as the Azure Resource Manager APIs serialize your template for deployment when you create a deployment using Visual Studio.

When we have multiple objects and the possibility of an array, here is a quick example to understand what we’re going to be doing when we plug some of this into an ARM Template:

{
     "name": "Shannon"
},
{
  "favoriteColors": [
     {
    "color": "blue"
     },
           {
               "color": "purple"
           }    
  ]
},

Notice how objects live within the curly braces and the array lives within the two brackets. At the end of each object, there’s a comma. In my example above, I would continue to add additional JSON objects, as I left a comma at the end. If the comma was not there, that would be the end of the file. Commas are crucial with serializing your ARM Templates. Never in my life did I think I’d be debugging files because I missed a comma (true story). I’ll save a blog post for troubleshooting your ARM Templates for later in this series.

To understand the components we need to change, please reference the snippet of actual code below (I revised it from what Visual Studio inserts when you add a resource). To understand line by line for the VM, I’ll comment out sections to clarify and help you understand how to create a virtual machine resource:

{
// Each resource needs a name, a resource provider type (i.e.                     // Microsoft.Compute/virtualMachines, a location, and an apiVersion. As you build // more ARM Templates, you will find this to be the standard norm. Inside the first // 4 lines, you see that I’ve called upon my parameters section for the VM name.   // You can call upon parameters or variables the same way and you will use a single // quote vs. double quotes. 
      "name": "[parameters('arm-vm-1Name')]",
      "type": "Microsoft.Compute/virtualMachines",
      "location": "[resourceGroup().location]",
      "apiVersion": "2018-10-01",
      
// Using depends on allows you to specify when specific resources should be built. // For this VM example, we specify that the NIC needs to be already created before // the ARM Template deploys the VM. Depends on is an array.
      "dependsOn": [
        "[resourceId('Microsoft.Network/networkInterfaces/', variables('arm-vm-1NicName'))]"
      ],




// Tags are a way to insert metadata about your resources. Within an ARM Template,   
// you can programmatically add that information in so your automated builds     
// contain all the required metadata for things like charge back.
      "tags": {
        "displayName": "arm-vm-1"
      },


// Next comes all the details about the VM. There’s a requirement for a hardware       
// profile, an OS profile, a storage profile (which contains the OS and data disk    
// information), a network profile, and a diagnostic profile. Take note of how   // this template calls upon parameters and variables throughout the process of   // declaring the resource within the template file.
      "properties": {
        "hardwareProfile": {
          "vmSize": "[variables('arm-vm-1VmSize')]"
        },
        "osProfile": {
          "computerName": "[parameters('arm-vm-1Name')]",
          "adminUsername": "[parameters('arm-vm-1AdminUsername')]",
          "adminPassword": "[parameters('arm-vm-1AdminPassword')]"
        },
        "storageProfile": {
          "imageReference": {
            "publisher": "[variables('arm-vm-1ImagePublisher')]",
            "offer": "[variables('arm-vm-1ImageOffer')]",
            "sku": "[parameters('arm-vm-1WindowsOSVersion')]",
            "version": "latest"
          },
          "osDisk": {
            "name": "arm-vm-1OSDisk",
            "createOption": "FromImage",
            "caching": "ReadWrite"
          }
        },
        "networkProfile": {
          "networkInterfaces": [
            {
              "id": "[resourceId('Microsoft.Network/networkInterfaces/', variables('arm-vm-1NicName'))]"
            }
          ]
        },
        "diagnosticsProfile": {
          "bootDiagnostics": {
            "enabled": true,
            "storageUri": "[reference(resourceId('Microsoft.Storage/storageAccounts/', variables('arm-vm-1StorageAccountName'))).primaryEndpoints.blob]"
          }
        }
      }
    }

When you added the VM resource in, you should have seen a number of variables and parameters fill themselves in within the JSON Outline. The parameters should look like this (remember we didn’t have parameters for the last blog post where we created a virtual network):

"parameters": {
    "armvmsType": {
      "type": "string",
      "defaultValue": "Standard_LRS",
      "allowedValues": [
        "Standard_LRS",
        "Standard_ZRS",
        "Standard_GRS",
        "Standard_RAGRS",
        "Premium_LRS"
      ]
    },
    "arm-vm-1Name": {
      "type": "string",
      "minLength": 1
    },
    "arm-vm-1AdminUserName": {
      "type": "string",
      "minLength": 1
    },
    "arm-vm-1AdminPassword": {
      "type": "securestring"
    },
    "arm-vm-1WindowsOSVersion": {
      "type": "string",
      "defaultValue": "2012-R2-Datacenter",
      "allowedValues": [
        "2008-R2-SP1",
        "2012-Datacenter",
        "2012-R2-Datacenter",
        "Windows-Server-Technical-Preview"
      ]
    }
  },

The variables should look like this (keep in mind the variables now will include the variables from the earlier blog post with the virtual network configuration settings):

"variables": {
    "armvm-vnetPrefix": "10.0.0.0/16",
    "armvm-vnetSubnet1Name": "Subnet-1",
    "armvm-vnetSubnet1Prefix": "10.0.0.0/24",
    "armvm-vnetSubnet2Name": "Subnet-2",
    "armvm-vnetSubnet2Prefix": "10.0.1.0/24",
    "armvmsName": "[concat('armvms', uniqueString(resourceGroup().id))]",
    "arm-vm-1ImagePublisher": "MicrosoftWindowsServer",
    "arm-vm-1ImageOffer": "WindowsServer",
    "arm-vm-1OSDiskName": "arm-vm-1OSDisk",
    "arm-vm-1VmSize": "Standard_D2_v2",
    "arm-vm-1VnetID": "[resourceId('Microsoft.Network/virtualNetworks', 'armvm-vnet')]",
    "arm-vm-1SubnetRef": "[concat(variables('arm-vm-1VnetID'), '/subnets/', variables('armvm-vnetSubnet1Name'))]",
    "arm-vm-1StorageAccountName": "[concat(uniquestring(resourceGroup().id), 'armvm')]",
    "arm-vm-1NicName": "[concat(parameters('arm-vm-1Name'), 'NetworkInterface')]"
  },

If we were to deploy this template as is right now, we would not have a way to access the VM via RDP. We don’t have a site to site VPN set up with an extension of our private IP space, so since we’re in experimentation mode and getting ourselves comfortable with the ARM Template creation process, let’s add a public IP in so we can RDP to the VM in Azure.

Right click on “resources” and click on “Add New Resource.”

addnewresource2.png

Type in “public” and you will see the Public IP resource within Visual Studio:

public.png

Give it the name of arm-vm-1-pip and click “Add.”

addpublicip.png

Once you add the public IP in, take note at the additional pieces that were added into the template (all additional components show up as bolded white text within my version of Visual Studio):

parameters2.png

Now we are ready to deploy the template! Make sure to save your project!

Before we deploy, please refer to the GitHub repo I created. My hope is that it helps out with what shows up before and after, related to the configuration tweaks. You will soon start finding that referring to example ARM Templates is going to be the easiest way to become more and more familiar with the process of building them on your own.

With that, head back to Visual Studio!

Right click on the project inside “Solution Explorer.” Move the cursor down to “Deploy,” and when the menu expands to the left, select the deployment you used from the previous blog (“arm-templates”).

deploy4.png

You should see this dialogue box pop up again:

editparam3.png

We’ll need to edit the parameters for this deployment. Click on “Edit Parameters.” Note, we’ll cover parameters and how to configure those within the parameters file within the next blog.

Some of the ARM Template has been configured with a defaultValue within the main ARM Template. You should see some of that trickle in. Whatever is not filled in, spend some time and fill that in here.

savepassword.png

A few notes:

1) The admin user cannot be “Administrator.” Other variants are fair game (“admin”, “adm”, etc.). I tend to use my initials + admin in most of my deployments for the admin account that gets created at the time of deployment. Feel free to use a similar approach or use “admin.”

2) The VM password you use needs to adhere to specific requirements. In the event the URL ever changes, please note I’ve called out everything below (which is valid as of December 2019):

  • Portal - between 12 - 72 characters

  • PowerShell - between 8 - 123 characters

  • CLI - between 12 - 123 • Have lower characters

  • Have upper characters

  • Have a digit

  • Have a special character (Regex match [\W_])

The following passwords are not allowed:
Abc@123 iloveyou! P@$$w0rd P@ssw0rd P@ssword123 Pa$$word pass@word1 Password! Password1 Password22

3) When you add the VM in, allowed values for the OS get added in. For purposes of moving along and building your 2nd template, don’t worry about adjusting that aspect for this template. We will cover how to adjust within the next blog post.

4) The public IP DNS name is the user-friendly way of accessing a VM without needing to use an IP address. The public IP DNS name needs to be unique across all of Azure and needs to adhere to specific standards that are called out in the following document online. Within my example, I used the VM name + dns at the end and for your deployment, it will need to be adjusted. In a future blog post I will showcase some of how to generate unique names or GUIDs within the template.

5) Place a check mark in “Save passwords as plain text in the parameters file.” In real life, you would not do this often as plain text passwords compromise everything about the integrity of security within your automated deployments. Since we’re still experimenting and getting comfortable with how to deploy these templates, placing a check mark in that box will mean PowerShell won’t prompt you to input your password during template deployment.

After you hit Save, the deployment should be kicked off. You should see similar screen shots as what is shown below:

ss.png
ss2.png

Flip over to the portal and take a look at the resource group. You should see similar resources:

portal3.png

Note, when I talked about generating unique names for resources above for your public IP DNS name, the template already did that for your storage account. Again, I’ll cover that in the next blog post. If you explore the resources deployed, you should see things like the tags, the public IP DNS name, the boot diagnostics, etc. Explore what you just deployed and you will start to see how these ARM Templates translate to actual resources within Azure.

And that concludes this installment within the ARM Template series. My hope is to crank out another blog or two within the next month to cover more topics in depth. Thanks for reading and I hope you enjoyed learning more about ARM Templates!

—Shannon Kuehn

Previous
Previous

AZ- 500 – Microsoft Azure Security Technologies Exam – December 2019 Updates

Next
Next

An Intricate Look at ARM Templates - Part 3