Creating A Load-Balanced Azure VM Availability Set With An ARM Template

With our plan together for creating two Azure virtual machines to handle inbound FTP requests, we can now configure an ARM template to actually deploy these resources to Azure.

I am going to pirate most of this template from the Azure quickstart template for creating two VMs behind an internal load balancer. Microsoft has made lots of ARM templates available there, some of which are pretty much ready for immediate deployment.

However, we need to modify this template a little bit to accomplish what we are after; especially because we have some specific networking we want applied.

This template is going to create:

  • A Storage account, which will hold our virtual disks.
  • An availability set, which should protect our VMs from suffering a hardware casualty at the same time, or from restarting at the same time due to software patches.
  • A public IP address, which will be attached to our virtual network. This will provide the IP address and domain name through which all external requests will be addressed.
  • A virtual network and subnet, where the VMs will live.
  • A load balancer. This will route traffic across the FTP and HTTP ports that are shared between the two VMs.
  • Two network interfaces, one per VM. These interfaces will control each VM’s ability to respond to endpoint / port requests on the virtual network. These interfaces will be created only after the virtual network and load balancer have been created.
  • And of course, two virtual machines.

After this template has run, I will have two VMs that are ready to be configured with Internet Information Server and FTP service.

As I previously mentioned, my templates could contain the necessary instructions to use Desired State Configuration and PowerShell to install and configure IIS. However, I am going to need to tinker around in these VMs to properly configure FTP (you’ll see this in an upcoming post), so I might as well just add IIS and FTP while I am in there.

ARM template overview

My day job is teaching fine people like you how to use Azure. Over at Linux Academy, we have created an extensive section of information about ARM templates that gets very detailed into how they are structured and what each section does.

So I am not going to get into that in this post. I will show you the template and point out some special configurations; but if you want to learn how to author and modify templates, then I invite you to join and learn more.

If you have a Visual Studio Dev Essentials account — and just about everyone who programs in Visual Studio does — you can get a 90-day free trial of Linux Academy. And once you’re there, I’ll be happy to get into all the detail you like about ARM templates. Thanks for understanding.

For those of you new to Azure — or whom are more used to the Azure Service Management, or “classic,” mode of managing services — an Azure Resource Manager, or ARM, template is a way to write JSON that specifies what resources you want to create and how you want them configured.

This provides a number of benefits over creating things via a series of PowerShell / CLI commands or one-at-a-time in the portal.

Namely, it lets you be sure things are configured the way you want and in the correct order. Also, it allows you to version control your deployments and to be able to repeat the deployment, which is useful for disaster recovery or deployments to new Azure regions.

You don’t need to use ARM templates to deploy simple resources but as a rule, the more complex the thing you are trying to deploy proves, the more you’ll come to appreciate ARM templates.

Also, a protip: You can export existing Azure Resource Group deployments to an ARM template. This is super-handy for building new ARM templates and to protect yourself against a datacenter outage, as well as a rogue subscription admin deleting resources.

The ARM template

The ARM template for this deployment is huge (654 lines), so I’m not going to embed it here. But I have saved it as a GitHub gist, at

There are some sections of the template I’d like to go over. First, let’s look at the configuration of each of the two network interfaces we’ll create. Specifically, look at the loadBalancerInboundNatRules, beginning at Line 187:

	"apiVersion": "2015-05-01-preview",
	"type": "Microsoft.Network/networkInterfaces",
	"name": "[concat(parameters('nicNamePrefix'), copyindex())]",
	"location": "[resourceGroup().location]",
	"copy": {
		"name": "nicLoop",
		"count": "[variables('numberOfInstances')]"
	"dependsOn": [
		"[concat('Microsoft.Network/virtualNetworks/', parameters('vnetName'))]",
		"[concat('Microsoft.Network/loadBalancers/', parameters('lbName'))]"
	"properties": {
				"name": "ipconfig1",
				"properties": {
					"privateIPAllocationMethod": "Dynamic",
					"subnet": {
						"id": "[variables('subnetRef')]"
					"loadBalancerBackendAddressPools": [
							"id": "[concat(variables('lbID'), '/backendAddressPools/BackendPool1')]"
					"loadBalancerInboundNatRules": [
							"id": "[concat(variables('lbID'), '/inboundNatRules/RDP-VM', copyindex())]"
							"id": "[concat(variables('lbID'), '/inboundNatRules/VM', copyindex(), '-FTP0')]"
							"id": "[concat(variables('lbID'), '/inboundNatRules/VM', copyindex(), '-FTP1')]"
							"id": "[concat(variables('lbID'), '/inboundNatRules/VM', copyindex(), '-FTP2')]"
							"id": "[concat(variables('lbID'), '/inboundNatRules/VM', copyindex(), '-FTP3')]"
							"id": "[concat(variables('lbID'), '/inboundNatRules/VM', copyindex(), '-FTP4')]"
							"id": "[concat(variables('lbID'), '/inboundNatRules/VM', copyindex(), '-FTP5')]"
							"id": "[concat(variables('lbID'), '/inboundNatRules/VM', copyindex(), '-FTP6')]"
							"id": "[concat(variables('lbID'), '/inboundNatRules/VM', copyindex(), '-FTP7')]"
							"id": "[concat(variables('lbID'), '/inboundNatRules/VM', copyindex(), '-FTP8')]"
							"id": "[concat(variables('lbID'), '/inboundNatRules/VM', copyindex(), '-FTP9')]"

I am configuring 11 machine-specific NAT rules: one for RDP, and another 10 for the PASV ports.

I am building the IDs of these rules dynamically, so that each network interface — and, by association, each virtual machine — will have assigned to it the correct RDP and FTP data endpoints.

You can see where I set these port values later in the template, where I create the load balancer (starting at Line 226).

Within the load balancer configuration are these inbound NAT rules, starting at Line 250 and going to Line 515.

At Line 516, I apply the rules for the load-balanced ports, which are 80 and 443 (HTTP / HTTPS) and 20, 21 and 22 (active FTP data, standard FTP communication and secure FTP communication).

Finally, starting at Line 583, I indicate I want to use HTTPS for the health probe, not HTTP as originally specified in the quickstart template.

I am going to feed my ARM template its parameters via a file, rather than entering them at runtime. It’s at

Executing the ARM template

Before I try to execute this deployment, I want to run both the ARM template and the parameters file through a JSON linter. The one I like to use online is but there are several of them out there.

Once those JSON files lint, I am ready to deploy the solution.

To do that, I am going to use PowerShell. So, I need to ensure I have PowerShell installed (it runs on PC, Mac and Linux), and that I have installed the latest Azure RM module.

I am also going to execute these templates locally. It’s generally best to put your templates in Azure storage and execute them from there — and in some cases, you have to do that, such as when you are linking together multiple templates — but I am being a bit lazy here.

First I log in to my account:


Because I have more than one Azure subscription on this account, I set a context for the subscription I am going to use. This step isn’t necessary if you only have one Azure subscription on your account.

Set-AzureRmContext -SubscriptionName "mySubscription"

If I set a context, I will get back a confirmation indicating the subscription to be used:

Environment           : AzureCloud
Account               :
TenantId              : 12345678-123a-4567-8bcd-e9f0g1b23c4v
SubscriptionId        : 98765432-1ab0-1234-56z8-12kj34h56j76
SubscriptionName      : mySubscription
CurrentStorageAccount :

Since this is a new deployment, I need to create a resource group for this deployment. If I was updating a deployment, or I was going to deploy to an existing resource group, I would skip this step.

New-AzureRmResourceGroup -Name dvFTP -Location "East US"

Now I am set to test whether Azure can parse my ARM template.

Test-AzureRmResourceGroupDeployment -ResourceGroupName dvFTP -TemplateFile c:/x/azuredeploy.json -TemplateParameterFile c:/x/azuredeploy.parameters.json

If the template tests OK, I will see the command prompt come back up after a few seconds.

If your parameter file doesn’t provide all the needed parameters in your ARM template, you’ll be prompted to enter those parameters.

If your parameter file contains parameters that aren’t in your ARM template, you’ll get an error.

Code    : InvalidTemplate
Message : Deployment template validation failed: 'The template parameters 'foo' in the parameters file are not valid;
          they are not present in the original template and can therefore not be provided at deployment time. The only
          supported parameters for this template are 'storageAccountName, adminUsername, adminPassword,
          dnsNameforLBIP, vmNamePrefix, imagePublisher, imageOffer, imageSKU, lbName, nicNamePrefix,
          publicIPAddressName, vnetName, vmSize'. Please see for usage
Details :

If there is a structural problem with your file, Azure will tell you there is an error, but it probably won’t be specific about what’s wrong.

Code    : InvalidTemplateDeployment
Message : The template deployment 'abc123ab-f63f-4cfb-9ba8-xyz395471e5b' is not valid according to the validation
          procedure. The tracking id is '1682hb9e-2779-49b1-a95f-3ccj7a26984'. See inner errors for details. Please
          see for usage details.
Details : {Microsoft.Azure.Commands.ResourceManager.Cmdlets.SdkModels.PSResourceManagerError}

If your get one of these errors, you’ve probably given one of the resources you are trying to spin up a name that’s already taken or that is illegal. For example, you can’t provide a DNS name that someone else is using, or a DNS name with capital letters.

Check your parameters file to make sure you’re passing valid parameters; then check your template for correct values, as well as any missing or misspelled variable names, resource names and the like.

Once the template tests OK, we’re ready to run the actual deployment:

New-AzureRmResourceGroupDeployment -Name dvFTPdeploy01 -ResourceGroupName dvFTP -TemplateFile c:/x/azuredeploy.json -TemplateParameterFile c:/x/azuredeploy.parameters.json

The template will take a while to run, during which PowerShell will appear to do nothing. Don’t close PowerShell during this time.

When the template is done executing, you will get a short summary table discussing the results:

DeploymentName    : dvFTPdeploy01 
ResourceGroupName : dvFTP
ProvisioningState : Succeeded
Timestamp         : 12/7/2016 5:00:27 PM
Mode              : Incremental

And if you look in the portal, you should see your deployment there.

The result of running this ARM template. You can see all the specified resources were properly deployed and the portal reports deployment success.
The result of running this ARM template. You can see all the specified resources were properly deployed and the portal reports deployment success.

That’s it for this post. Next up: Configuring IIS and FTP services on the virtual machines.

Featured photo of the New Temple Shri Swaminarayan Mandir, Bhuj by BANITAtour via pixabay, in the public domain.
Featured photo of the New Temple Shri Swaminarayan Mandir, Bhuj by BANITAtour via pixabay, in the public domain.

Leave a Reply

  • Check out the Commenting Guidelines before commenting, please!
  • Want to share code? Please put it into a GitHub Gist, CodePen or pastebin and link to that in your comment.
  • Just have a line or two of markup? Wrap them in an appropriate SyntaxHighlighter Evolved shortcode for your programming language, please!