PacITPros November Meeting Resources

This month at the PacITPros meeting, I covered several of the ways you can create virtual machines in Azure and get started doing Infrastructure as Code. For a quick summary of the various ways to do this check out this bit of Azure documentation.

If you need to get started with Azure and don’t have a subscription yet, break out your Microsoft Account (aka LiveID, Hotmail account, etc) and pick one of these options:

Since I dove deeper on doing these deployments with ARM templates and JSON, here are some of the resources you might want to revisit (or check out if you missed the meeting).

Here is an example of the PowerShell command for getting an updated list of images for Windows Server or Ubuntu Server in a particular region:

Get-AzureRmVMImageSku -Location "west us" -PublisherName Microsoftwindowsserver -Offer WindowsServer 
Get-AzureRmVMImageSku -Location "West US" -PublisherName Canonical -Offer UbuntuServer

Finally, here is my “cheat sheet” of PowerShell commands to connect to your Azure account and deploy a template.

Login-AzureRmAccount
Get-AzureRmSubscription

$VSSubID = "7ebd5b2e-7d4b-49xe-bab6-4d593ed76x41" # copy the sub-id from the previous command
Set-AzureRmContext -SubscriptionID $VSSubID

# get the "raw" URL from GitHub for the template you want to deploy and break it up as follows:

$assetLocation = "https://raw.githubusercontent.com/techbunny/Templates/master/multiple_nano_server/" 
$templateFileURI  = $assetLocation + "nanoslabdeploy.json"  # deployment template
$parameterFileURI = $assetLocation + "SVCC.parameters.json" # parameters file

$RGName = "PacITPros"  # set your resource group name
New-AzureRmResourceGroup -Name $RGName -Location "West US"  # create your resource group

# deploy the template!
New-AzureRmResourceGroupDeployment -ResourceGroupName $RGName -TemplateParameterUri $parameterFileURI -TemplateUri $templateFileURI -verbose

Deconstructing JSON: Tale of Two VNETs (Linked templates with VNET peering!)

The last month or so has been packed with announcements and training! I’ve been to Ignite in Atlanta, as well as some internal training and some fun community events.  Finally, I’ve had some time to sit down and work on trying out a few things.  If you missed the announcement around Ignite time, Azure VNET Peering is now generally available.   With peering, you can now link virtual networks together without having to set up multiple VNET gateways.

This peering feature can be set up using the Azure Portal, but what fun is that, right?  Let’s do it with some ARM templates instead.  My goal was to create two VNETs with different address spaces (you can’t peer networks with an overlapping address space) then peer them together.  I could do this with one big template, but I wanted to also take some time to try out linking templates together – where one parent template calls others.  I also wanted to take advantage of parameters files to create the different VNETs.

For this example, I ended up with five JSON files:

  • azuredeploy.json – The deployment template for one VNET
  • vnet1.parameters.json – The parameters file for VNET1
  • vnet2.parameters.json – The parameters file for VNET2
  • peeringdeploy.json – Template to peer together the networks once created
  • parentdeploy.json – The template used to manage the complete deployment

Within the parentdeploy.json file we only need to define the schema and resources sections and the only resource I’m calling is the “Microsoft.Resources/deployments” type.  Within that, you’ll need to define a name, mode, template link (located in a repo or blob storage) and an optional parameters link.  For this deployment, I’m calling the deployment resource three times – once for each vnet, plus a final time to peer them. In the snippet below, you can see that I called the azuredeploy.json file and the vnet1.parameters.json file.

 { 
     "apiVersion": "2015-01-01", 
     "type": "Microsoft.Resources/deployments", 
     "name": "linkedTemplateA", 
     "properties": { 
       "mode": "Incremental", 
       "templateLink": {
          "uri":"https://raw.githubusercontent.com/techbunny/Templates/master/two_vnets_same_region/azuredeploy.json",
          "contentVersion":"1.0.0.0"
       }, 
       "parametersLink": { 
          "uri":"https://raw.githubusercontent.com/techbunny/Templates/master/two_vnets_same_region/vnet1.parameters.json",
          "contentVersion":"1.0.0.0"
       } 
     } 
  },

The second resource uses the same template link and uses the vnet2.parameters.json file.  Finally, the last one will only call the peeringdeploy.json template with no parameters file needed. Each deployment resource needs it’s own unique name and you can’t reference anything directly that isn’t included in the template itself.   There are also ways to share state between linked templates to be even more “elegant” in your template creation.

Within the peeringdeploy.json template we also only need to define resources which link the newly created VNETs together. In the snippet below, you can see BigNetA (created with vnet1 parameters) being connected to BigNetB (created with vnet2 parameters).

 {
    "apiVersion": "2016-06-01",
    "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings",
    "name": "BigNetA/LinkToBigNetB",
    "location": "[resourceGroup().location]",
  
    "properties": {
    "allowVirtualNetworkAccess": true,
    "allowForwardedTraffic": false,
    "allowGatewayTransit": false,
    "useRemoteGateways": false,
        "remoteVirtualNetwork": {
        "id": "[resourceId('Microsoft.Network/virtualNetworks', 'BigNetB')]"       
}

Finally, as with all my previous templates, I can deploy the whole thing with just one line of PowerShell!

 New-AzureRmResourceGroupDeployment -ResourceGroupName $RGName -TemplateUri $templateFileURI -verbose

 

Deconstructing JSON: Super Secrets

“Psst… Hey buddy, want to keep your plain text password in your source control?”

Hopefully, you answer is “No”, but if you’d been following along with this JSON/ARM template series of posts, you will have noticed that I’ve defaulted to deployments with straight up, plain text, username/passwords in my template examples. So before we go any further, lets secure this up a bit!

Goal: Deploy a Windows Nano VM using an ARM template in GitHub without having to put the Admin username or password anywhere in plain text.

To do this, we’ll need a place to hide the secrets. In this case, we’ll use Azure Key Vault. With Key Vault, you create a resource group and vault (using PowerShell or an ARM template) and then load up some secrets or keys. You can hide basic passwords or load certificates.

I’m going to create my Key Vault using PowerShell. I’m purposely creating my vault in its own Resource Group. You can create the Key Vault in the same Resource Group as the rest of your deployment, but then if you delete the whole group for during testing, you’ll have to recreate the vault each time.

  1. Log into Azure
  2. Get your Subscription details, copy the GUID of the subscription you want to use for the key vault into the $VSSubID variable.
  3. Customize the variables for the Resource Group name, Vault name and deployment region.
  4. Once the vault is created, customize the “SuperSecretPassword” and “FancyAdminName” to your liking and add those secrets to the vault.
Login-AzureRmAccount
Get-AzureRmSubscription
$VSSubID = {subcription_guid}  
Set-AzureRmContext -SubscriptionID $VSSubID

$RGName = "KeyVaultRG"
$VaultName = "MyVaultName"
$Location = "West US"

New-AzureRmResourceGroup -Name $RGName -Location $Location

New-AzureRmKeyVault -VaultName $VaultName -ResourceGroupName $RGName -Location $Location

Set-AzureRmKeyVaultAccessPolicy -VaultName $VaultName -EnabledForTemplateDeployment -ResourceGroupName $RGName

$secretvalue = ConvertTo-SecureString 'SuperSecretPassword' -AsPlainText -Force
$secret = Set-AzureKeyVaultSecret -VaultName $VaultName -Name 'ServerAdminPassword' -SecretValue $secretvalue

$secretvalue = ConvertTo-SecureString 'FancyAdminName' -AsPlainText -Force
$secret = Set-AzureKeyVaultSecret -VaultName $VaultName -Name 'ServerAdminName' -SecretValue $secretvalue

Now that my vault is crated, I’ll need to edit my ARM template to grab the secrets from the Key Vault when my VM is deployed. Make the change to your parameters.json file, not your deployment template. This way you can use the same template deployment with different subscriptions and vaults easily. Instead of using the basic string type for your adminUsername and adminPassword, reference the secrets you added to the vault.

"adminUsername": {
      "reference": {
      "keyVault": {
        "id": "/subscriptions/{guid}/resourceGroups/KeyVaultRG/providers/Microsoft.KeyVault/vaults/ImperfectKeyVault"
      }, 
      "secretName": "ServerAdminName" 
       } 
    },
   "adminPassword": {
      "reference": {
      "keyVault": {
        "id": "/subscriptions/{guid}/resourceGroups/KeyVaultRG/providers/Microsoft.KeyVault/vaults/ImperfectKeyVault"
      }, 
      "secretName": "ServerAdminPassword" 
       } 
    }

Now, when you run your deployment, the admin username and password will be pulled from the vault and passed to your VM during creation. If you go to log in that server directly, you’ll need to provide the plain text versions of those secrets just like usual.  It’s worth noting that not anyone can pull keys out of your vault.  By default only the subscription admin will be able to access the contents of the vault, but you can add additional permissions to a vault to allow for other users and automation tools to access the same secrets.

You can find the example of this template in my GitHub repo.

Learn more about Azure Key Vault at https://azure.microsoft.com/en-us/documentation/articles/key-vault-get-started/

 

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!

Deconstructing JSON: Adding a Network Security Group

If you’ve come from the Service Manager days of Azure, you’ll be familiar with the endpoints that came with each Cloud Service, handling the port forwarding needed to RDP to your server or to connect with PowerShell (port 5986). You could add or remove endpoints as need to provide access to whatever service you desired (after making the corresponding firewall change on the server itself.)

This is like parking your car in a garage.  You can choose to lock or unlock the doors (aka the OS firewall) and you can choose to provide access to the garage (aka Azure endpoints).

With ARM, when you deploy a server, you’ll be prompted to confirm the naming of your network security group, this network security group takes the place of the endpoints: providing some default rules, including the RDP access to port 3389.

So how does this differ from an endpoint?  One difference is that there is no port forwarding.  With a cloud service endpoint, the external port wasn’t 3389, because you could have multiple servers behind the single IP address assigned to the cloud service sharing the load balancer.   With ARM, the VM is assigned its own IP address without a load balancer, so the standard port number can be used. If you need to open an additional port, you simply add a new rule to the NSG and open the corresponding rule on the server firewall.

The second difference is that without a NSG (or the cloud service load balancer endpoints) to act as a gateway, your VM is basically just “hanging out there” with only its internal firewall to keep it safe.  So you really need to be sure you’ve checked that firewall configuration and you know what’s open to the public network.  By default, a Windows VM created with an Azure image has RDP and PowerShell/WinRM ports open on the operating system.

If you’ve been following along with my JSON template project, you’ll know that VMs can be created in ARM without the NSG.  And this fine if you are good with having your server parked out on the Internet with a few doors unlocked, so to speak.

But if you want to take advantage of the power of Network Security Groups to limit the sources of connections or to customize the way traffic flows between tiers of servers on your network, you’ll need to include the NSG resource in your template.

First you’ll  need to add a parameter to declare the Network Security Group.

  "networkSecurityGroupName": {
            "defaultValue": "NanoVMnsg",
            "type": "string"
    },

Then you’ll to configure the resource itself to include the security rules.

{
      "type": "Microsoft.Network/networkSecurityGroups",
      "name": "[parameters('networkSecurityGroupName')]",
      "apiVersion": "2015-06-15",
      "location": "[parameters('location')]",
      "properties": {
            "securityRules": [
                {
                    "name": "winrm",
                    "properties": {
                        "protocol": "Tcp",
                        "sourcePortRange": "*",
                        "destinationPortRange": "5985-5986",
                        "sourceAddressPrefix": "*",
                        "destinationAddressPrefix": "*",
                        "access": "Allow",
                        "priority": 110,
                        "direction": "Inbound"
                    }
                  },
                  {
                    "name": "rdp",
                    "properties": {
                        "protocol": "Tcp",
                        "sourcePortRange": "*",
                        "destinationPortRange": "3389",
                        "sourceAddressPrefix": "*",
                        "destinationAddressPrefix": "*",
                        "access": "Allow",
                        "priority": 120,
                        "direction": "Inbound"
                    }
                 }
                ]
            },
            "dependsOn": []
        },

Finally, you’ll need to adjust your network interface resource to depend on the NSG.

{
      "apiVersion": "2015-06-15",
      "type": "Microsoft.Network/networkInterfaces",
      "name": "[variables('nicName')]",
      "location": "[parameters('location')]",
      "dependsOn": [
        "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
        "[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',variables('publicIPAddressName'))]"
              },
              "subnet": {
                "id": "[variables('subnetRef')]"
              }
              }
          }
        ],
       "dnsSettings": {
         "dnsServers": []
       },
       "enableIPForwarding": false,
       "networkSecurityGroup": {
               "id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]"
            }
      }
    },

Go to my GitHub repository to find the full version of this template. By default, it will deploy a Nano Server VM.

Now here’s a little gotcha when it comes to NSGs and remote server management (as of this writing): If you created a VM from the Azure Portal and accept all the defaults (which include an NSG), that NSG doesn’t open the ports for WinRM by default.  It only opens RDP.  The OS firewall is open to accept WinRM and PowerShell, but the NSG blocks it.  You need to edit the NSG to include TCP ports 5985 (http) and/or 5986 (https) for remote management.

You might not notice this issue if you are connecting to full GUI servers with RDP, but if you are trying out Nano Server there is no RDP, so you must be able to connect with PowerShell.

Deconstructing JSON: Anatomy of a VM

While the Azure VM on has two official dependencies, the storage account and the network interface, there are a variety of properties that you need to gather or determine ahead of time in order for your deployment to be successful.  If you were doing the manual creation of the VM in the Portal, the properties would be the parts you’d have to enter while shuffling through all the blades.  Let’s shuffle less blades….

With the VM template, we start out like every other resource, requiring a type, name, API version and location for deployment.  You can see the two lines that call the two dependencies.

{
      "apiVersion": "2015-06-15",
      "type": "Microsoft.Compute/virtualMachines",
      "name": "[variables('vmName')]",
      "location": "[parameters('location')]",
      "dependsOn": [
        "[concat('Microsoft.Storage/storageAccounts/', parameters('newStorageAccountName'))]",
        "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
      ],

When you get into the properties, you can see where everything comes together with a variety of profiles for hardware, the operating system, storage, networking and diagnostics.  When deploying via PowerShell, you’ll get prompted to enter the Admin username and password manually.

To fill in the variables for the image reference part of the storage profile, you can use some handy PowerShell to query Azure for the existing image names.  (I’ll put that at the bottom of the post.)

      "properties": {
        "hardwareProfile": {
          "vmSize": "[variables('vmSize')]"
        },
        "osProfile": {
          "computerName": "[variables('vmName')]",
          "adminUsername": "[parameters('adminUsername')]",
          "adminPassword": "[parameters('adminPassword')]"
        },
        "storageProfile": {
          "imageReference": {
            "publisher": "[variables('imagePublisher')]",
            "offer": "[variables('imageOffer')]",
            "sku": "[parameters('windowsOSVersion')]",
            "version": "latest"
          },
          "osDisk": {
            "name": "osdisk",
            "vhd": {
              "uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/',variables('OSDiskName'),'.vhd')]"
            },
            "caching": "ReadWrite",
            "createOption": "FromImage"
          }
          },
            
          "networkProfile": {
          "networkInterfaces": [             
        {
              "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]"
            }
          ]
        },
          "diagnosticsProfile": {
          "bootDiagnostics": {
             "enabled": "true",
             "storageUri": "[concat('https://',parameters('newStorageAccountName'),'.blob.core.windows.net')]"
          }
        }
        }

The PowerShell for the image references: these lines will return the current images available for Windows Server. Be sure to pick the location you intend to use, because some image skus have limited regions and change the publisher name as needed if Windows Server isn’t what you are looking to deploy.

Get-AzureRmVMImagePublisher -Location "West US"

Get-AzureRmVMImageOffer -Location "West US" -PublisherName MicrosoftWindowsServer

Get-AzureRmVMImageSku -Location "West US" -PublisherName MicrosoftWindowsServer -Offer WindowsServer

You’ll find the full template to deploy this one VM here: https://github.com/techbunny/Templates/tree/master/smallcloudlab

Deconstructing JSON: Bring on the NICs

One of the final dependencies for deploying a server on a network is a network card.  For Azure, networking is crucial for VMs so you can remotely access and control your VM after deployment.

Now here’s the thing about JSON… The dependencies.  The resource dependencies allow you to basically pack all your resources, parameters and variables into one file and the deployment sorts out what needs to be created in what order.  This differs from writing a complete deployments in PowerShell, where you have to account for the dependencies yourself, by ensuring things are placed in the proper order and account for any necessary wait times.

So far in this blog series, I’ve done deployments that can stand alone: just the network, just storage, etc.  But now that I’m getting closer to my goal of being able to deploy VMs, the dependency requirements start to appear.

NICs are dependent on a public IP address and the virtual network.  While you can deploy NICs without having a VM to “plug” them into, the template that is used to deploy them does require those other two resources.  So even though I’ve already deployed the virtual network and would be deploying the NIC into that existing resource group and network, the template won’t be considered valid unless the networking resources are included.

So at this point, I’ve started adding the new resources to my existing template.  Upon deployment, ARM will verify the existence and configuration of all the resources an only add or change the ones that are missing or configured differently.

For the public IP address I’ve added:

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

For the network interface I’ve added:

{
      "apiVersion": "2015-06-15",
      "type": "Microsoft.Network/networkInterfaces",
      "name": "[variables('nicName')]",
      "location": "[parameters('location')]",
      "dependsOn": [
        "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
        "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
      ],
      "properties": {
        "ipConfigurations": [
          {
            "name": "ipconfig1",
            "properties": {
             "privateIPAllocationMethod": "Dynamic",
             "publicIPAddress": {
                "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]"
              },
              "subnet": {
                "id": "[variables('subnetRef')]"
              }
              }
          }
        ]
      }
    },

We also needed to add a few variables, but you could make these parameters instead, depending on your needs.  Take note of vnetID and subnetRef variables, they are more complex expressions.

   "variables": {
       "virtualNetworkName": "SmallNet",
       "subnetName": "Subnet20",
       "addressPrefix": "192.168.0.0/16",
       "publicIPAddressName": "smallcloudpubip",
       "publicIPAddressType": "Dynamic",
       "dnsNameForPublicIP": "smallcloudlab",
       "nicName": "smallcloud_nic1",
       "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]",
       "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]"
        
     },

To see the full template up until this point, find it in my GitHub Repository.

Deconstructing JSON: Storage Wars

The last place I left off with my deconstruction of JSON, I had deployed a basic network as the basis for my future home for some virtual machines.  The next key dependency for Virtual Machine is storage.  In this case, I’ll need a storage group in the same regional location (and ideally in the same resource group) as my network.

For the sake experimentation, this template example creates two different storage accounts and takes advantage of both parameters and variables to determine the location and the storage account name.

The default parameters set the storage account as “wrongnamestor” and the location in the East US. These settings will apply to the first instance of the storage account resource.  In the variable section, the storage account name is set as “labweststor2” with a location in the West US region.

{
   "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
   "contentVersion": "1.0.0.0",
   
   "parameters": {
    "newStorageAccountName": {
      "type": "string",
      "defaultValue": "wrongnamestor",
      "metadata": {
        "description": "Unique DNS Name for the Storage Account where the Virtual Machine's disks will be placed."
      }
    },
   
    "location": {
      "type": "string",
      "defaultValue": "East US",
      "allowedValues": [
        "West US",
        "East US"
      ],
      "metadata": {
        "description": "Restricts choices to where premium storage is located in the US."
      }
    }
   },
      
   "variables": {
       "location": "West US",
       "newStorageAccountName": "labweststor2"
           
    },
   
   "resources": [ 
     {
      "type": "Microsoft.Storage/storageAccounts",
      "name": "[parameters('newStorageAccountName')]",
      "apiVersion": "2015-06-15",
      "location": "[parameters('location')]",
      "properties": {
        "accountType": "Standard_LRS"
      }
     },
  
      {
      "type": "Microsoft.Storage/storageAccounts",
      "name": "[variables('newStorageAccountName')]",
      "apiVersion": "2015-06-15",
      "location": "[variables('location')]",
      "properties": {
        "accountType": "Standard_LRS"
      }
     }   
   
    ]
}

Because I would probably like both my storage accounts to be in the same region with similar naming, I’ve also created the additional parameters file named “azuredeploy.parameters.json”.  This short file will override the default parameters in the deployment template and set the desired storage account name of “labweststor1” in the West US.

{
  "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "newStorageAccountName": {
      "value": "labweststor1"
     },
    "location": {
      "value": "West US"
    }

  }
}

I’ll deploy this template using the following PowerShell:

New-AzureRmResourceGroupDeployment -ResourceGroupName 'BigLab' -TemplateURI <templateFileURI> -TemplateParameterURI <parameterFileURI> -Verbose 

This line of PowerShell is exactly the same as the line I used to deploy the networking template. The only thing that would need be changed is the actual details of the file location.

As a bonus, the default behavior of Azure Resource Manager deployments is incremental. Since I’m deploying to the same Resource Group that was created with my networking template, the result will be the addition of the two storage accounts to the resource group that contains the network.

If I wanted the template to remove anything not specified, I would have to add the “-mode complete” switch.  Template deployments in the portal only run in incremental mode.

Now that I know my storage template is working as expected, I’ll integrate those resources and parameters into one template as I build out the complete deployment.

Deconstructing JSON: Networking 101

This leads me to really needing to deconstruct a JSON template so I can really understand what’s going on.  I think it’s great that we can use the Azure Quickstart Templates as resources, as well as export some templates directly from Portal configurations, but I need to break it down to really feel like I understand what I’m working with.

To relate this to real life, I started to map out a simple, yet ambitious improvement to my Imperfect Lab (the subject of prior posts).  Still, this proved to be too ambitious for my “getting started with JSON” goal, so I simplified it.

Within Azure, ARM templates are awesome for doing the equivalent of “racking and stacking” in the real world.  So I went small… let’s get a network deployed.  A small network; the physical equivalent of a switch with a couple VLANs. Let the JSON “unboxing” begin!

A basic JSON template has this format:

{
   "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
   "contentVersion": "",
   "parameters": {  },
   "variables": {  },
   "resources": [  ],
   "outputs": {  }
}

The sections for $schema and resources are required.  Parameters, variables and outputs are optional, depending on your needs.   So let’s check out the resources I’m using and take a few notes:

  1.  The contents of the resources section are flanked with [square brackets].
  2. All resources need to include the apiVersion, type and name.  All the other details vary based on resource.
  3. All these resources in my sample use parameters to determine the properties, not variables. This means they can all be overridden with an additional “azuredeploy.parameters.json” file.

"resources": [
    {
      "apiVersion": "2015-06-15",
      "type": "Microsoft.Network/virtualNetworks",
      "name": "[parameters('vnetName')]",
      "location": "[parameters('location')]",
      "properties": {
        "addressSpace": {
          "addressPrefixes": [
            "[parameters('vnetAddressPrefix')]"
          ]
        },
        "subnets": [
          {
            "name": "[parameters('subnet1Name')]",
            "properties": {
              "addressPrefix": "[parameters('subnet1Prefix')]"
            }
          },
          {
            "name": "[parameters('subnet2Name')]",
            "properties": {
              "addressPrefix": "[parameters('subnet2Prefix')]"
            }
          },
          {
            "name": "[parameters('subnet3Name')]",
            "properties": {
              "addressPrefix": "[parameters('subnet3Prefix')]"
            }
          }
        ]

As you can see, this resources deploys a single virtual network with three subnets.  Because there are no hard coded names and IP addresses, I use the parameters section to set those. (In the sample below, I’ve only shown the parameters for subnet 1, but the parameters for the other two subnets would be similar.)

  1.  The “defaultValue” will allow the deployment to be repeatable, but can be overridden with values provided in a separate parameters file.
  2. The “allowedValues” section used in the location parameter is an optional example.  This would drive selections from the drop down menus using the Azure portal or limit the possibilities that can be passed from a parameters file.

  "parameters": {
    "location": {
      "type": "string",
      "defaultValue": "West US",
      "allowedValues": [
          "West US",
          "East US"
       ],
      "metadata": {
        "description": "Deployment location"
      }
    },
    "vnetName": {
      "type": "string",
      "defaultValue": "SmallNet",
      "metadata": {
        "description": "VNet name"
      }
    },
    "vnetAddressPrefix": {
      "type": "string",
      "defaultValue": "192.168.0.0/16",
      "metadata": {
        "description": "Address prefix"
      }
    },
    "subnet1Prefix": {
      "type": "string",
      "defaultValue": "192.168.20.0/24",
      "metadata": {
        "description": "Subnet 1 Prefix"
      }
    },
    "subnet1Name": {
      "type": "string",
      "defaultValue": "Subnet20",
      "metadata": {
        "description": "Subnet 1 Name"
      }

As none of my resources require variables or outputs, I won’t need those section in my file.  The final version of the file is here.

Now to actually deploy this template, you have a few options:

  1. Copy and paste the code into a blank “Template Deployment” in the Azure Portal.  You’ll be shown the blades for the parameters to edit or accept the default values.
  2. Deploy the template using PowerShell either from a local file or a web location.  Not the different parameters used to determine the location and replace the placeholders with the appropriate paths.  (Because I’m not using a parameters file, I’ll leave off the “-TemplateParameterURI” part.)

For a web location source (like Github) use:

  New-AzureRmResourceGroupDeployment -ResourceGroupName 'BigLab' -TemplateURI <templateFileURI> -TemplateParameterURI <parameterFileURI> -Verbose

 

 

For a local source (on your pc) use:

New-AzureRmResourceGroupDeployment -ResourceGroupName 'BigLab' -TemplateFile <$templateFileURI> -TemplateParameterFile <parameterFileURI> -Verbo

 

Once this template deploys, which only takes a few moments, you’ll be able to review the results in the Azure Portal.