Chapter 5
Virtual machines: sizes, disks, availability and scale sets
VMs are still the backbone of many Azure environments. This chapter covers the decisions you make when you create one, how to keep it available, how to protect its disks, and how to scale out with scale sets. In the lab you put two web servers in the spoke network.
Theory
What a VM is made of
A "VM" in Azure is really a set of resources that work together. When you create one in the portal, you typically get:
- The virtual machine itself (size, image, settings).
- An OS disk (a managed disk) and optionally data disks.
- A network interface (NIC) in a subnet, with a private IP.
- Optionally a public IP and an NSG on the NIC.
Deleting a VM doesn't automatically delete its disks, NIC and public IP unless you tick Delete with VM for each of them. Forgotten disks are a classic source of cost (and an Advisor recommendation).
Decisions on the Create VM page
- Availability options: no redundancy, availability zone or availability set (see below). You can't change this after creation without recreating the VM.
- Security type: Trusted launch (default: Secure Boot, vTPM, boot integrity monitoring), Standard, or Confidential.
- Image: a Marketplace image, your own image from an Azure Compute Gallery, or a specialised disk.
- Size: CPU, memory, disk and network limits (see below).
- Authentication: SSH key or password for Linux, password for Windows. You can also enable Entra ID login (with the roles Virtual Machine Administrator/User Login).
- Inbound ports: opening RDP/SSH to the internet is a bad idea; use Azure Bastion (chapter 6) or just-in-time access from Defender for Cloud.
- Advanced: custom data (a cloud-init script for Linux, run once at first boot), user data, extensions and the Custom Script Extension.
VM sizes
Size names look cryptic, but they follow a pattern. Take Standard_D4as_v5:
- D = family (general purpose)
- 4 = number of vCPUs
- a = AMD processor, s = supports Premium SSD (other letters: d local temp disk, m extra memory)
- v5 = generation
| Family | Optimised for | Example use |
|---|---|---|
| B | Burstable: builds up CPU credits while idle | Dev/test, small web servers (our lab) |
| D | General purpose, balanced CPU/memory | Most applications |
| E | Memory optimised | Databases, caches |
| F | Compute optimised | Batch, gaming servers |
| L | Storage optimised (fast local NVMe) | Big data, NoSQL |
| M | Very large memory | SAP HANA |
| N | GPU | AI, rendering |
Resizing a VM restarts it. If the new size isn't available on the hardware cluster the VM runs on, you must deallocate it first, and then you can pick from all sizes in the region. Resizing can also fail because of your vCPU quota per family and per region.
Disks
A VM has three kinds of disks:
- OS disk: contains the operating system. Managed disk, persistent.
- Temporary disk (
D:on Windows,/mntor/dev/sdbon Linux, on sizes that have one): fast local storage on the host. Data is lost when the VM is deallocated, resized, redeployed or moved to another host. Use it only for swap/page files and scratch data. - Data disks: extra managed disks for application data. The maximum number depends on the VM size.
Managed disk types
| Type | Good for | Notes |
|---|---|---|
| Standard HDD | Backups, rarely used data | Cheapest, slowest |
| Standard SSD | Web servers, light workloads, dev/test | Consistent latency |
| Premium SSD | Production and databases | Needs an "s" VM size. Required for the single-VM 99.9% SLA |
| Premium SSD v2 | Databases needing tunable IOPS | Set size, IOPS and throughput separately; can't be an OS disk |
| Ultra Disk | SAP HANA, top-tier databases | Lowest latency; can't be an OS disk |
Things you'll be asked to do with disks:
- Attach/detach data disks while the VM runs. A new disk must then be initialised and formatted inside the OS before you can use it.
- Expand a disk. Many disks can now be expanded while running; otherwise deallocate first. You then extend the partition inside the OS. Disks can never be shrunk.
- Change the disk type (e.g. Standard SSD → Premium SSD): the VM must be deallocated.
- Host caching: ReadOnly for read-heavy data disks (e.g. database data files), None for write-heavy logs, ReadWrite is the default for OS disks.
- Redundancy: disks are LRS by default; Premium and Standard SSDs can be ZRS (useful for shared disks across zones).
- Snapshots (a point-in-time copy of one disk) and images (a template for new VMs).
Disk encryption
Three different things are called "disk encryption", and the exam expects you to tell them apart:
| Option | Where it encrypts | Keys | Notes |
|---|---|---|---|
| Server-side encryption (SSE) | At rest in the storage service | Platform-managed (default) or customer-managed via a Disk Encryption Set + Key Vault | Always on; you can't turn it off |
| Encryption at host | On the VM host, so also temp disk and caches | Platform or customer-managed | Must be enabled on the subscription feature first |
| Azure Disk Encryption (ADE) | Inside the guest OS: BitLocker (Windows), DM-Crypt (Linux) | In Key Vault, optionally wrapped by a key encryption key (KEK) | Microsoft plans to retire ADE (2028) in favour of encryption at host, but it's still in the exam |
For ADE, remember the Key Vault requirements:
- The Key Vault must be in the same region and subscription as the VM.
- The vault's access configuration must allow "Azure Disk Encryption for volume encryption".
- With a KEK, the vault should have soft delete and purge protection on.
"Data on the temporary disk must be encrypted." SSE doesn't cover the temp disk. You need encryption at host (or ADE).
Availability: sets and zones
A single VM can fail when its host fails or is updated. To protect against that, you run two or more VMs and spread them out, in one of two ways:
- Fault domain (FD): a group of hardware that shares power and a network switch (a rack). Up to 3 per availability set.
- Update domain (UD): a group of VMs that Azure may reboot at the same time during planned maintenance. 5 by default, up to 20.
- Availability zone: a physically separate datacenter (or group of them) within a region. Most large regions have three.
SLA numbers to remember: 99.9% for a single VM with Premium SSD (or better) disks, 99.95% for two or more VMs in an availability set, 99.99% for two or more VMs across availability zones.
"Add an existing VM to an availability set." Not possible. A VM is placed in an availability set (or zone) only at creation. You must delete the VM (keeping the disks) and recreate it in the set. Also, a VM can't be in both an availability set and a zone.
Virtual Machine Scale Sets
A scale set (VMSS) manages a group of VMs as one unit, with autoscale and automatic spreading across zones or fault domains. Typical use: a web tier behind a load balancer that grows during the day and shrinks at night.
Orchestration modes
- Flexible (default and recommended): instances are normal VMs that you can manage individually, can mix sizes, and work with availability zones. Replaces availability sets for most new designs.
- Uniform: all instances are identical and created from one model. Older, optimised for very large stateless fleets.
Scaling
You set a minimum, maximum and default instance count, and rules:
- Metric-based rules: "if average CPU > 70% over 10 minutes, add 2 instances"; "if < 30%, remove 1". Always create a scale-out and a scale-in rule as a pair.
- Schedule-based: "run 10 instances on weekdays 08:00–18:00".
- Cool down: how long to wait after a scale action before evaluating again (default 5 min), to prevent flapping.
- Scale-in policy: which VM to remove first: Default (balance across zones, then highest ID), Newest or Oldest.
The upgrade policy decides how instances get a changed model (like a new image): Manual (you upgrade instances yourself), Automatic (all at once, with downtime) or Rolling (in batches, with health checks).
Moving VMs
The skills list says "move a VM to another resource group, subscription or region". These are different operations:
- Resource group or subscription: use Move on the resource group. Move the VM together with its disks, NIC and public IP. The VNet can stay behind if it's in the same subscription. The VM keeps running; resource IDs change.
- Region: you can't "move" across regions. Use Azure Resource Mover (which uses Site Recovery replication under the hood), or Azure Site Recovery directly (chapter 10), or redeploy from an image or template.
Two other operations that sound like moving:
- Redeploy moves the VM to a new host in the same cluster. It fixes stubborn connectivity problems, but the temp disk is wiped.
- Reapply re-runs the VM's provisioning state without moving it, for a VM stuck in a failed state.
AZ-305 asks you to choose a compute service ("VM, scale set, App Service, Container Apps, AKS or Functions?") and to meet an SLA. Practise the reasoning: two VMs across zones behind a zone-redundant load balancer = 99.99% for the VM tier; a composite SLA multiplies the SLAs of all tiers in the path.
Self-check
1. You need the highest SLA for a web tier of two VMs in one region. What do you deploy?
C. 99.99% for zones vs 99.95% for sets.
2. You resize a VM, but the size you want isn't in the list. What should you do?
A. Running VMs can only use sizes available on their current cluster. After deallocation, all regional sizes are available (quota permitting).
3. You attach a new 128 GiB data disk to a running Linux VM. df -h doesn't show it. Why?
D. Azure attaches a raw block device. lsblk shows it; df only shows mounted filesystems.
4. Which encryption option uses BitLocker inside a Windows VM with keys in Key Vault?
B. ADE is the only one that encrypts inside the guest OS.
5. A scale set scales out at CPU > 75% but never scales back in. What's missing?
C. Autoscale rules come in pairs; without a scale-in rule the count only grows (until the maximum).
Lab: two web servers in two zones
You create vm-web1 and vm-web2 in snet-web, each running nginx. Chapter 6 puts them behind the load balancer, chapter 7 mounts a file share on one, and chapters 9 and 10 monitor and back them up. Paste the variables first:
RG=rg-az104-lab; LOC=westeurope; LOC2=northeurope; INIT=xx
mkdir -p ~/az104 && cd ~/az104
Both VMs use this cloud-init file as custom data. It installs nginx and writes a page with the VM's name:
#cloud-config
package_update: true
packages:
- nginx
runcmd:
- echo "<h1>Hello from $(hostname)</h1>" > /var/www/html/index.html
-
Task
Create
vm-web1Create a Linux VM with these requirements:
- Name
vm-web1, Ubuntu Server 24.04 LTS, sizeStandard_B1s, availability zone 1. - In
vnet-spoke/snet-web, with no public IP and no NSG on the NIC (chapter 6 puts an NSG on the subnet). - Admin user
azureuserwith a password (simplest for Bastion later) or SSH key. - OS disk Standard SSD, deleted together with the VM.
- Uses the cloud-init file above.
Done when- The VM runs with private IP
10.20.1.4(or similar) and no public IP. - The private DNS zone
az104.labhas a new A recordvm-web1. Why did it appear?
Hint
If
Standard_B1sisn't available in zone 1, chooseStandard_B2ats_v2orStandard_B1ms. Custom data is on the Advanced tab.Solution · Portal
- Create a resource → Virtual machine. Basics: RG
rg-az104-lab, namevm-web1, region, Availability options Availability zone → Zone 1, Security type Trusted launch, image Ubuntu Server 24.04 LTS, sizeStandard_B1s, auth type password, userazureuser, Public inbound ports: None. - Disks: OS disk type Standard SSD, tick Delete with VM.
- Networking: VNet
vnet-spoke, subnetsnet-web, Public IP None, NIC network security group None, tick Delete NIC when VM is deleted. - Advanced: paste the cloud-init file into Custom data.
- Review + create. Then open the zone
az104.lab→ Recordsets:vm-web1was added by auto-registration onlink-spoke.
Solution · Azure CLI
nano cloud-init.txt # paste the cloud-init file, save (Ctrl+O), exit (Ctrl+X) az vm create -g $RG -n vm-web1 -l $LOC --zone 1 \ --image Ubuntu2404 --size Standard_B1s \ --vnet-name vnet-spoke --subnet snet-web \ --public-ip-address "" --nsg "" \ --admin-username azureuser --admin-password '<choose-a-strong-password>' \ --storage-sku StandardSSD_LRS --os-disk-delete-option Delete --nic-delete-option Delete \ --custom-data cloud-init.txt az network private-dns record-set a list -g $RG -z az104.lab -o table - Name
-
Task
Create
vm-web2in another zoneCreate
vm-web2exactly likevm-web1, but in zone 2. Tip: the CLI is quicker the second time.Done when- Both VMs are running, in zones 1 and 2.
- You can say what SLA this gives you, and what it would be with an availability set instead.
Solution · Azure CLI
az vm create -g $RG -n vm-web2 -l $LOC --zone 2 \ --image Ubuntu2404 --size Standard_B1s \ --vnet-name vnet-spoke --subnet snet-web \ --public-ip-address "" --nsg "" \ --admin-username azureuser --admin-password '<choose-a-strong-password>' \ --storage-sku StandardSSD_LRS --os-disk-delete-option Delete --nic-delete-option Delete \ --custom-data cloud-init.txt az vm list -g $RG -d --query "[].{name:name, zone:zones[0], ip:privateIps, state:powerState}" -o tableTwo VMs across zones: 99.99%. In an availability set: 99.95%.
-
Task
Test the web servers without logging in
The VMs have no public IP and you don't have Bastion yet. Still, check that nginx runs on
vm-web1, and thatvm-web1can reachvm-web2by its DNS namevm-web2.az104.lab.Done when- You see
Hello from vm-web1andHello from vm-web2in the output of a command run onvm-web1.
Hint
Look for Run command on the VM (Operations section).
Solution · Portal
vm-web1→ Operations → Run command → RunShellScript, runcurl -s localhost; curl -s http://vm-web2.az104.lab.Solution · Azure CLI
az vm run-command invoke -g $RG -n vm-web1 --command-id RunShellScript \ --scripts "curl -s localhost; curl -s http://vm-web2.az104.lab" --query "value[0].message" -o tsvIf nginx isn't there yet, cloud-init may still be running; wait two minutes. If it never installs, check that
snet-webhas default outbound access enabled (chapter 3, step 2). - You see
-
Task
Add and mount a data disk
Give
vm-web1an 8 GiB Standard SSD data disk namedvm-web1-data01without stopping the VM. Then make it usable inside Linux, mounted at/data.Done whenlsblkon the VM shows an 8G disk anddf -h /datashows it mounted.
Solution · Portal
vm-web1→ Settings → Disks → Create and attach a new disk: namevm-web1-data01, Standard SSD, 8 GiB, LUN 0, host caching ReadOnly or None → Apply.- Run command (see below) to partition, format and mount it.
Solution · Azure CLI
az vm disk attach -g $RG --vm-name vm-web1 --name vm-web1-data01 --new --size-gb 8 --sku StandardSSD_LRS --lun 0 az vm run-command invoke -g $RG -n vm-web1 --command-id RunShellScript --scripts ' DISK=$(readlink -f /dev/disk/azure/scsi1/lun0) # the data disk at LUN 0 parted -s $DISK mklabel gpt mkpart data ext4 0% 100% sleep 2; mkfs.ext4 -q ${DISK}1 mkdir -p /data && mount ${DISK}1 /data echo "${DISK}1 /data ext4 defaults,nofail 0 2" >> /etc/fstab lsblk; df -h /data' --query "value[0].message" -o tsvIn production you'd put the disk's UUID in
/etc/fstab(device names like/dev/sdccan change; that's also why the script finds the disk by its LUN) and keepnofailso the VM still boots without the disk. -
Task
Resize a VM
Find out which sizes
vm-web2can be resized to right now, resize it toStandard_B1ms, check that it still serves its page, then resize it back toStandard_B1s.Done whenvm-web2is back onStandard_B1sand you noticed the VM restarted during the resize.
Solution · Portal
vm-web2→ Availability + scale → Size → pickB1ms→ Resize. The status goes to Updating and the VM restarts. Repeat to go back.Solution · Azure CLI
az vm list-vm-resize-options -g $RG -n vm-web2 --query "[?starts_with(name,'Standard_B')].name" -o tsv az vm resize -g $RG -n vm-web2 --size Standard_B1ms az vm resize -g $RG -n vm-web2 --size Standard_B1s -
Task
Check the routes your VM actually uses
Look at the effective routes of
vm-web1's network interface. Find the route you created in chapter 3, the route to the hub, and the default internet route.Done when- You see
203.0.113.0/24 → None(source User),10.10.0.0/16 → VNet peeringand0.0.0.0/0 → Internet.
Solution · Portal
vm-web1→ Networking → click the network interface → Help → Effective routes.Solution · Azure CLI
NIC=$(az vm show -g $RG -n vm-web1 --query "networkProfile.networkInterfaces[0].id" -o tsv) az network nic show-effective-route-table --ids $NIC -o table - You see
-
Task · optional
Try a scale set (5 minutes, then delete)
Create a Flexible scale set
vmss-testwith 1 UbuntuStandard_B1sinstance insnet-web, spread across zones 1–3, with no public IPs. Add an autoscale setting: min 1, max 3, scale out by 1 at CPU > 70%, scale in by 1 at CPU < 30%. Look at it, then delete it.Done when- You've seen the autoscale rules and the scale set is deleted again.
Solution · Azure CLI
az vmss create -g $RG -n vmss-test --orchestration-mode Flexible --image Ubuntu2404 \ --vm-sku Standard_B1s --instance-count 1 --zones 1 2 3 \ --vnet-name vnet-spoke --subnet snet-web --public-ip-address "" \ --admin-username azureuser --generate-ssh-keys az monitor autoscale create -g $RG --resource vmss-test --resource-type Microsoft.Compute/virtualMachineScaleSets \ -n autoscale-test --min-count 1 --max-count 3 --count 1 az monitor autoscale rule create -g $RG --autoscale-name autoscale-test --condition "Percentage CPU > 70 avg 10m" --scale out 1 az monitor autoscale rule create -g $RG --autoscale-name autoscale-test --condition "Percentage CPU < 30 avg 10m" --scale in 1 # look in the portal: vmss-test → Availability + scale → Scaling az vmss delete -g $RG -n vmss-test --force-deletion true
Two B1s VMs cost roughly €0.25 per day together while running. When you stop for the day, Stop them in the portal (that deallocates them) or run az vm deallocate -g $RG -n vm-web1 --no-wait (and the same for vm-web2). Start them again before the next lab.
vm-web1(zone 1, with data disk on/data) andvm-web2(zone 2), both serving "Hello from …" on port 80.- Both have A records in
az104.lab. - No public IPs, no NSGs yet. That changes in chapter 6.