I have been updating my ARM templates to create VMs and VM Scale Sets (VMSS) to support the new Availability Zones. I have learnt some important concepts that were not obvious for me along the way, and I would like to share them. Before going forward, you can see a template where you can deploy VMs with different options (single VMs or VMSSs, with or without AZs, with or without NSGs, with or without LB, etc) in this Github repository.
What did not change
You deploy Vnets and Subnets exactly the same as without AZs. The reason is because Vnets and Subnets are regional resources (not zonal): when you define a Vnet, it is present in all the Availability Zones in a region.
Something that did not change either is the Network Interface object (aka NIC or vnic). When you create a NIC you do not need to specify in which AZ you want to deploy it.
Network Security Groups are also a regional resource: you don’t need to specify in which AZ you want to create an NSG, and can reference it from any AZ.
What changed: public IP addresses
One exception to the NIC regionality is its Public IP Address: “standard” public addresses have been now introduced (the “normal” public IP addresses we knew and loved are now called “basic”). Basic public IP addresses can be assigned to an AZ (they are zonal resources), and Standard public IP addresses can be assigned to a Region (they are regional resources).
At first sight, if you want to have a public IP in your VM, you would think of a basic public IP, right? Well, that is in essence true. But if you ever plan to do cross-AZ external load balancing to that VM, you would have an issue.
The problem is that you would assign a standard public IP to the external load balancer, and Azure today does not support mixing standard and basic public IPs to a single VM (even if the basic public IP is directly associated to the NIC, and the standard public IP indirectly over a load balancer). It makes kind of sense, because if you have an external load balancer you typically want to configure external access to the VMs over Inbound NAT rules.
Another interesting effect is that with regional standard public IP addresses I had to use Static allocation, since dynamic allocation was not allowed for basic public IPs.
Adding up, here the attributes that you would add to a public IP address resource to make it standard and static:
"sku": { "name": "standard" }, "properties": { "publicIPAllocationMethod": "static", "idleTimeoutInMinutes": 4 },
What changed: Load Balancer
Similar to the public IP addresses, you have now the Standard version of the Azure Load Balancer, that makes it a regional resource. The Basic version is a zonal resource. So if you plan to load balance across AZs, make sure to deploy standard load balancers associated to standard public IP addressses.
In order to deploy a standard Load Balancer, you just need to add the root attribute “sku” to the load balancer resource in your template:
"sku": { "name": "[parameters('lbSku')]" },
What changed: Availability Sets
You are probably used to deploy your VMs in Availability Sets, for a good reason. So it is only normal that even if you deploy your VM across two AZs, inside of each AZ you would want to have your VMs in Availability Sets, to decrease the likelihood of a full AZ going down (in case all your VMs in the AZ are deployed in the same physical host or rack, for example).
Nice try, but at the time of this writing Availability Sets and Availability Zones are two mutually exclusive features. The idea is that Availability Zones are a kind of “unmanaged” Availability Sets, so you can use one or the other, but not both.
What changed: Virtual Machines and Virtual Machine Scale Sets (and Managed Disks)
Obviously, the last piece is defining in which AZ you want to deploy your VMs and VMSSs. There is a new ARM property of VMs and VMSSs originally called “zones”. You can use the copy attribute of an ARM resource to place VMs or VMSSs on a certain AZ, for example like this:
"zones": [ "[string(add(mod(copyIndex(0), variables('maxZones')), 1))]" ]
The variable ‘maxZones’ is the maximum number of zones that you want to use. For example, assuming maxZones=2, With this formula, the first VM will be placed in AZ 1, since 0\2+1 = 1. The second one in AZ 2, the third one again in AZ 1, and so forth.
A corollary of the previous paragraphs is that VMSSs are zonal objects, the same as VMs. You cannot have a VMSS stretching two zones, but you would create two VMSSs, one in each AZ, each with half the total capacity of your planned compute power.
Feel free to try the ARM template in the Github repo with these commands (two options provided, with VMs and with VMSSs, play with the other parameters for more options). Note that you can use these commands in Linux, for Windows you would likely have to do some formatting changes (such as the line breaks).
az group deployment create -g azlab --name azLabDeployment --template-uri https://raw.githubusercontent.com/erjosito/Iac-Test/master/IaCLab\_master.json --parameters '{ \ "adminUsername":{"value":"lab-user"}, \ "adminPassword":{"value":"yourSuperSecretPassw0rd"}, \ "vmType":{"value":"ubuntu"}, \ "vmSize":{"value":"Standard_D1_v2"}, \ "vmCount":{"value": 2}, \ "azYesNo":{"value":"yes"}, \ "pipYesNo":{"value":"no"}, \ "deployLBYesNo":{"value":"yes"}, \ "lbType":{"value":"external"}, \ "lbSku":{"value":"standard"}}'
az group deployment create -g azlab --name azLabDeployment --template-uri https://raw.githubusercontent.com/erjosito/Iac-Test/master/IaCLab\_master.json --parameters '{ \ "adminUsername":{"value":"lab-user"}, \ "adminPassword":{"value":"yourSuperSecretPassw0rd"}, \ "vmType":{"value":"ubuntuScaleSet"}, \ "vmSize":{"value":"Standard_D1_v2"}, \ "CapacityMin":{"value": 1}, \ "CapacityDef":{"value": 2}, \ "CapacityMax":{"value": 4}, \ "azYesNo":{"value":"yes"}, \ "pipYesNo":{"value":"no"}, \ "deployLBYesNo":{"value":"yes"}, \ "lbType":{"value":"external"}, \ "lbSku":{"value":"standard"}}'
These templates will deploy VMs with a Web site on them that will show you information about the VM the load balancer takes you to.
What is your opinion about Availability Zones? Would be happy to read your comments!
You mentioned the ability to deploy to an AZ or not. I’ve been working with an ARM template and the Zones property will not take anything except 1,2,or3. It wouldn’t take json(null), ”, “”, or other variations of empty strings. How were you able to deploy a VM to No Zone with your code?
LikeLike
Hey Micah, you dont need to specify any zone if you dont care about the AZ, so essentially do not include the “zones” property in ARM (as we still do for Azure regions that do not support AZs)
LikeLike
Hi
How would you specify that a particular VM goes into Zone 1, and another VM goes into Zone2? The VM I’m using doesn’t support managed storage.
Thanks
Steve
LikeLike
You mean individual VMs? Or VMSS? If it is individual VMs, you can specify for each one where they should go…
LikeLike
Hi, thank you for this post! I was looking to add availability zones to my bicep template and your code snippet is exactly what I needed! A quick suggestion, I think you should elaborate on the “mod” function. It’s not exactly division but the remainder after the division. I had to dig around on the internet to find a good explanation. This site was best one I found: https://corporatefinanceinstitute.com/resources/excel/functions/mod-function/
LikeLike
Thanks for sharing Jason! I am so glad this post is still useful after so long. In Microsoft docs there should be information about these ARM functions, for example here the link for the `mod` function: https://docs.microsoft.com/azure/azure-resource-manager/templates/template-functions-numeric#mod
LikeLike