Deconstructing JSON: Counting On

So here I am, in JSON-land, working on my deployment for VMs. But so far, I’ve only deployed one VM in my network. But one VM is boring. I want to be able to rack and stack more than one machine at a time! 🙂  Enter the copy object and the copyIndex function!  You can read the official documentation about it, but let see how this works within my existing template. Things to note:

  • You can only reference the copyIndex function in RESOURCES. Don’t try to build variables with them, you’ll just be sad.
  • You can’t use a copy loop in a nested resource (this blog series hasn’t encountered this yet).
  • You can’t use them to loop items that are defined as properties within a resource, like multiple data disks.

So in my example, let’s extend the template to deploy 3 VMs in the same subnet and put them all in the same NSG (network security group). First, you must declare in the parameters that you’d like count something:

   "count": { 
      "type": "int", 
      "defaultValue": 3 
    },

Now, whenever you want to reference the “current iteration” of the loop elsewhere in your template, you’ll use copyIndex(). Another thing to note – computers like to count starting at zero, humans like to count starting at 1. If we stick with copyIndex(), we’ll get VMs numbered 0, 1, 2. If you’d like them to be numbered 1, 2, 3 use copyIndex(1), which will offset the numbering by 1.

Since this template is going to create 3 VMs, we will need to make sure to account for other resources that VMs have a one to one relationship with and be sure to loop those as well. The only items that we are required to loop are the network interfaces and the public IP addresses (which have individual DNS names).  We don’t have to loop things like the storage account (which can be used by multiple VMs) or the network, since we only need one in this case.

In the resource for the publicIPAddresses, you’ll see the reference to the copyIndex() in the name, and in the DNS domain label. In each case, the incremented number will be tacked on at the end of each. Then finally, you’ll see the specific reference to looping this resource added, with the name of “pubipcopy”, pulling the count from the parameter you set early.

     {
      "type": "Microsoft.Network/publicIPAddresses",
      "apiVersion": "2015-06-15",
      "name": "[concat(parameters('publicIPAddressName'), parameters('dnsNameForPublicIP'),copyIndex())]", 
      "location": "[parameters('location')]",
      "properties": {
        "publicIPAllocationMethod": "[variables('publicIPAddressType')]",
        "idleTimeoutInMinutes": 4,
        "dnsSettings": {
          "domainNameLabel": "[concat(parameters('dnsNameForPublicIP'),copyIndex())]"
        }
      },
       "copy": {
        "name": "pubipcopy",
        "count": "[parameters('count')]"
      }
     },

One of the reasons you need to name each resource copy object is so they can be used in places where dependencies are required.

For the network interface, you’ll see I’ve made similar references to the copyIndex() in the name. Also a network interface is dependent on the existence of a public IP address, so you’ll see “pubipcopy” referenced under “dependsOn” as well as in the IP configuration properties.

   {
      "apiVersion": "2015-06-15",
      "type": "Microsoft.Network/networkInterfaces",
      "name": "[concat(parameters('nicName'), copyIndex())]",
      "location": "[parameters('location')]",
    
      "dependsOn": [
        "pubipcopy",
        "[concat('Microsoft.Network/virtualNetworks/', parameters('vnetName'))]",
        "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]"
      ],
      "properties": {
        "ipConfigurations": [
          {
            "name": "ipconfig1",
            "properties": {
             "privateIPAllocationMethod": "Dynamic",
             "publicIPAddress": {
                "id": "[resourceId('Microsoft.Network/publicIPAddresses', concat(parameters('publicIPAddressName'),parameters('dnsNameForPublicIP'),copyIndex()))]"
              },
              "subnet": {
                "id": "[variables('subnetRef')]"
              }
              }
          }
        ],
       "dnsSettings": {
         "dnsServers": []
       },
       "enableIPForwarding": false,
       "networkSecurityGroup": {
               "id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]"
            }
      },
       "copy": {
        "name": "niccopy",
        "count": "[parameters('count')]"
      }
    },

This all comes together when you get to the VM resource where it refers to the NIC in the network profile and using the copyIndex() when creating the VM name, computer name and OS disk.

    {
      "apiVersion": "2015-06-15",
      "type": "Microsoft.Compute/virtualMachines",
      "name": "[concat('NanoVM-', copyIndex())]", 
      "location": "[parameters('location')]",
      "copy": {
        "name": "vmcopy",
        "count": "[parameters('count')]"
        },
      "dependsOn": [
        
        "[concat('Microsoft.Storage/storageAccounts/', parameters('newStorageAccountName'))]",
        "niccopy"
      ],
      "properties": {
        "hardwareProfile": {
          "vmSize": "[parameters('vmSize')]"
        },
        "osProfile": {
          "computerName": "[concat('NanoVM-', copyIndex())]",
          "adminUsername": "[parameters('adminUsername')]",
          "adminPassword": "[parameters('adminPassword')]"
        },
        "storageProfile": {
          "imageReference": {
            "publisher": "[variables('imagePublisher')]",
            "offer": "[variables('imageOffer')]",
            "sku": "[parameters('windowsOSVersion')]",
            "version": "latest"
          },
          "osDisk": {
            "name": "[concat('osdisk_', concat('NanoVM-', copyIndex()))]" ,
            "vhd": {
              "uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/NanoVM-',copyIndex(),uniqueString(resourceGroup().id),'-osdisk.vhd')]"
            },
            "caching": "ReadWrite",
            "createOption": "FromImage"
          }
          },
            
          "networkProfile": {
          "networkInterfaces": [             
        {
              "id": "[resourceId('Microsoft.Network/networkInterfaces',concat(parameters('nicName'), copyIndex()))]"
            }
          ]
        },
          "diagnosticsProfile": {
          "bootDiagnostics": {
             "enabled": "true",
             "storageUri": "[concat('https://',parameters('newStorageAccountName'),'.blob.core.windows.net')]"
          }
        }
        }

      }

You can find my full template here: https://github.com/techbunny/Templates/tree/master/multiple_nano_server

ARM Templates: What Error?!?

There is more than one way to deploy an ARM template once you’ve created or edited on to your liking. The first way is to copy the code into a blank template deployment in the Azure portal. This has a few benefits, primarily that it gives you the option to edit any parameters in the template to your liking before deployment and saves you from having to write a separate “azuredeploy.parameters.json” file.

However the deployment blade in the portal doesn’t really give you much in the way of error messages when the template fails. Often, you are give something along the lines of “error exists on line 1: position 10,374” or some such information. But your template is clearly more than one line in length and I have no intention of counting out thousands of characters to find the error location. The full text of the message may or may not be helpful depending on what point in the deployment the error occurred.

This is where deploying the template via PowerShell shines. As much as the red text is heartbreaking, it will give you the proper line number and position count to line up with your code. Also included is some often useable error text to point you on your way.

This single line of PowerShell will deploy a template that is hosted online (like in GitHub) and the -Verbose switch will report on the status of each of your resources as they start and complete deployment.

New-AzureRmResourceGroupDeployment -ResourceGroupName "<YourResourceGroupName>" -TemplateURI $templateFileURI -TemplateParameterURI $parameterFileURI -Verbose

If that doesn’t solve your problem or point you in the right direction, it’s time to dig deeper in the Azure Audit Logs from within the portal. They are chronological, so after a few clicks, you can drill down into the details of the most recent error. You can also access those logs via PowerShell:

Get-AzureRmLog -Status Failed -ResourceGroup "YourResourceGroupName" -DetailedOutput

You can also access these logs by digging down into the deployment in the portal. Just to “Audit Logs” and you’ll find all the activity in your subscriptions. Looked for the ones related to failed deployments or validations of deployments and as you drill into the details you’ll find what’s going wrong. Usually it’s an incorrect location of a parameters file, maybe a storage account name that’s already in use, or a missing resource group.

Happy Hunting!

How Microsoft IT does the Cloud

One of the things that is fascinating about working at Microsoft is watching how we function internally as our own “customer”. This is very apparent when it comes to integrating with cloud solutions like Azure. We have the same challenges as any other external customer, with the added bonus of developing, testing and implementing our own solutions. Sometimes people will ask me something like “You work for Microsoft, isn’t Azure just free for you?”

Alas, it is not. Every bit of Azure I use to learn, test and demo is accounted for somewhere, and I’m just lucky my manager hasn’t come after me holding an embarrassing bill to explain!

The MSIT Showcase has a host of documents, case studies and webinars to peel back the curtain and give you an idea of what it’s like to move many critical workloads into the cloud. Here are three links to get you started:

The cloud security mindset (Article) –
https://www.microsoft.com/itshowcase/Article/Content/703/The-cloud-security-mindset

Managing resource efficiency in a hybrid cloud (Webinar) –
https://www.microsoft.com/itshowcase/Article/Content/702/How-Microsoft-IT-manages-resource-efficiency-in-Azure-and-onpremises

Architecting your network for successful Azure adoption (Case study) –
https://www.microsoft.com/itshowcase/Article/Content/648/Architecting-your-network-for-successful-Azure-adoption

Enjoy!