AZ-104 Refresher Chapter 5 · Virtual machines

Chapter 5

Virtual machines: sizes, disks, availability and scale sets

📖 Theory 30 min🛠 Lab 25 minCompute · 20–25%

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.

Part A

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:

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

VM sizes

Size names look cryptic, but they follow a pattern. Take Standard_D4as_v5:

FamilyOptimised forExample use
BBurstable: builds up CPU credits while idleDev/test, small web servers (our lab)
DGeneral purpose, balanced CPU/memoryMost applications
EMemory optimisedDatabases, caches
FCompute optimisedBatch, gaming servers
LStorage optimised (fast local NVMe)Big data, NoSQL
MVery large memorySAP HANA
NGPUAI, 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:

Managed disk types

TypeGood forNotes
Standard HDDBackups, rarely used dataCheapest, slowest
Standard SSDWeb servers, light workloads, dev/testConsistent latency
Premium SSDProduction and databasesNeeds an "s" VM size. Required for the single-VM 99.9% SLA
Premium SSD v2Databases needing tunable IOPSSet size, IOPS and throughput separately; can't be an OS disk
Ultra DiskSAP HANA, top-tier databasesLowest latency; can't be an OS disk

Things you'll be asked to do with disks:

Disk encryption

Three different things are called "disk encryption", and the exam expects you to tell them apart:

OptionWhere it encryptsKeysNotes
Server-side encryption (SSE)At rest in the storage servicePlatform-managed (default) or customer-managed via a Disk Encryption Set + Key VaultAlways on; you can't turn it off
Encryption at hostOn the VM host, so also temp disk and cachesPlatform or customer-managedMust 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:

Exam trap

"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:

Availability setone datacenter · SLA 99.95% FD 0rack + power FD 1rack + power FD 2rack + power VM1UD 0 VM2UD 1 VM3UD 2 VM4UD 3 up to 3 fault domains · up to 20 update domains Availability zonesseparate datacenters in one region · SLA 99.99% Zone 1own power,cooling, network Zone 2 Zone 3 vm-web1 vm-web2 survives the loss of a whole datacenter
Availability sets protect against rack and host failures; zones protect against a datacenter failure.

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.

Exam trap

"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

Scaling

You set a minimum, maximum and default instance count, and rules:

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:

Two other operations that sound like moving:

AZ-305 link

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).

Part B

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
  1. Task

    Create vm-web1

    Create a Linux VM with these requirements:

    • Name vm-web1, Ubuntu Server 24.04 LTS, size Standard_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 azureuser with 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.lab has a new A record vm-web1. Why did it appear?
    Hint

    If Standard_B1s isn't available in zone 1, choose Standard_B2ats_v2 or Standard_B1ms. Custom data is on the Advanced tab.

    Solution · Portal
    1. Create a resource → Virtual machine. Basics: RG rg-az104-lab, name vm-web1, region, Availability options Availability zone → Zone 1, Security type Trusted launch, image Ubuntu Server 24.04 LTS, size Standard_B1s, auth type password, user azureuser, Public inbound ports: None.
    2. Disks: OS disk type Standard SSD, tick Delete with VM.
    3. Networking: VNet vnet-spoke, subnet snet-web, Public IP None, NIC network security group None, tick Delete NIC when VM is deleted.
    4. Advanced: paste the cloud-init file into Custom data.
    5. Review + create. Then open the zone az104.lab → Recordsets: vm-web1 was added by auto-registration on link-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
  2. Task

    Create vm-web2 in another zone

    Create vm-web2 exactly like vm-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 table

    Two VMs across zones: 99.99%. In an availability set: 99.95%.

  3. 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 that vm-web1 can reach vm-web2 by its DNS name vm-web2.az104.lab.

    Done when
    • You see Hello from vm-web1 and Hello from vm-web2 in the output of a command run on vm-web1.
    Hint

    Look for Run command on the VM (Operations section).

    Solution · Portal

    vm-web1 → Operations → Run command → RunShellScript, run curl -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 tsv

    If nginx isn't there yet, cloud-init may still be running; wait two minutes. If it never installs, check that snet-web has default outbound access enabled (chapter 3, step 2).

  4. Task

    Add and mount a data disk

    Give vm-web1 an 8 GiB Standard SSD data disk named vm-web1-data01 without stopping the VM. Then make it usable inside Linux, mounted at /data.

    Done when
    • lsblk on the VM shows an 8G disk and df -h /data shows it mounted.
    Solution · Portal
    1. vm-web1 → Settings → Disks → Create and attach a new disk: name vm-web1-data01, Standard SSD, 8 GiB, LUN 0, host caching ReadOnly or None → Apply.
    2. 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 tsv

    In production you'd put the disk's UUID in /etc/fstab (device names like /dev/sdc can change; that's also why the script finds the disk by its LUN) and keep nofail so the VM still boots without the disk.

  5. Task

    Resize a VM

    Find out which sizes vm-web2 can be resized to right now, resize it to Standard_B1ms, check that it still serves its page, then resize it back to Standard_B1s.

    Done when
    • vm-web2 is back on Standard_B1s and you noticed the VM restarted during the resize.
    Solution · Portal

    vm-web2 → Availability + scale → Size → pick B1ms → 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
  6. 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 peering and 0.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
  7. Task · optional

    Try a scale set (5 minutes, then delete)

    Create a Flexible scale set vmss-test with 1 Ubuntu Standard_B1s instance in snet-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
Cost

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.

Checkpoint
  • vm-web1 (zone 1, with data disk on /data) and vm-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.