Part 3 of 1640 min read · 9 diagramsAI-assisted

Compute: Virtual Machines & Scale Sets

Assumes the RBAC and managed identity concepts from Part 2 — this chapter attaches both to real, running compute, and assumes Part 1's subscription-quota discussion as background for this chapter's own quota and capacity-reservation sections.

Table of Contents#

  1. Compute in Azure — the IaaS-to-Serverless Spectrum
  2. Anatomy of an Azure VM
  3. VM Sizes and Series — Choosing the Right SKU
  4. Custom Images and Azure Compute Gallery
  5. Creating and Configuring a Virtual Machine
  6. Managed Disks Deep Dive
  7. Disk Operations — Resize, Snapshot, Encryption at Host
  8. Availability Sets — Fault and Update Domains
  9. Availability Zones for Virtual Machines
  10. Proximity Placement Groups — Low-Latency Colocation
  11. Choosing Between Availability Set, Availability Zone, and Proximity Placement Group
  12. Virtual Machine Scale Sets — Orchestration Modes
  13. Autoscale — Metric-Based and Schedule-Based Scaling
  14. VM Extensions and Custom Script Automation
  15. Moving and Resizing VMs Without Rebuilding Them
  16. On-Demand Capacity Reservations — Guaranteeing Availability Ahead of Need
  17. Azure Dedicated Hosts — Compliance-Driven Hardware Isolation
  18. Spot Virtual Machines — Trading Availability for Cost
  19. Trusted Launch and Confidential VMs — Hardening the Boot Chain
  20. Ephemeral OS Disks — Trading Persistence for Speed and Cost
  21. Boot Diagnostics, Serial Console, and Run Command
  22. Azure Update Manager — Patching at Fleet Scale
  23. A Full Worked Compute Bootstrap for Meridian Freight
  24. Part 3 CLI Cheat Sheet
  25. Common Mistakes and Interview Traps
  26. Worked Practice Problems
  27. Summary and What's Next

Compute in Azure — the IaaS-to-Serverless Spectrum#

Before going deep on VMs specifically, it's worth placing them on Azure's full compute spectrum — this chapter covers the IaaS end; Part 10 covers the PaaS and serverless end, and the choice between them recurs throughout this series.

Diagram

Meridian Freight's driver-portal — a stateful, always-on service handling field-staff traffic — runs on a VM Scale Set in this chapter's design; the docs-processor pipeline, by contrast, is naturally event-driven and lands on Azure Functions in Part 10. Neither choice is universally "better" — the rest of this chapter, and Part 10, build the actual decision criteria.

Where a workload sitsWho patches the OSWho manages scalingBilling model
Virtual MachineYouYou (manually, or via autoscale rules you configure)Per-second compute + attached disk cost
VM Scale SetYou (at fleet scale, via Update Manager)You configure autoscale rules; Azure executes themSame as VM, multiplied across instances
AKS / Container Apps (Part 10)Managed control plane; you patch worker nodes in AKS, not in Container AppsLargely automated, container-levelPer-node (AKS) or per-execution (Container Apps)
App Service / Functions (Part 10)Fully managedFully automatedPer-plan tier or per-execution

Anatomy of an Azure VM#

An Azure VM is not one resource — it's a composition of several, each independently manageable, which matters the moment a design needs to reason about what survives a VM deletion versus what doesn't.

Diagram

Why this composition matters concretely: deleting a VM resource does NOT automatically delete its disks or NIC unless explicitly requested — they're independent Azure resources that merely reference the VM, a detail that explains both a common source of forgotten, still-billed orphaned disks, and the mechanism that lets a disk be detached and reattached to a completely different VM. The same independence is what makes disk swapping between VMs possible at all — detaching a data disk from one VM and attaching it to another is a metadata operation on the disk resource, not a data migration.

# Deleting a VM but explicitly also cleaning up its disks and NIC —
# otherwise they persist, orphaned and still billed
az vm delete --name vm-example --resource-group rg-example \
  --yes --force-deletion true
az disk delete --name vm-example_OsDisk --resource-group rg-example --yes
az network nic delete --name vm-exampleNIC --resource-group rg-example

# Find every orphaned, unattached disk in a subscription —
# a genuinely useful periodic cost-hygiene check
az disk list --query "[?diskState=='Unattached'].{name:name, resourceGroup:resourceGroup, sizeGB:diskSizeGb}" --output table

VM Sizes and Series — Choosing the Right SKU#

Azure organizes VM sizes into named series, each optimized for a different workload shape — picking the wrong family is a common, costly early mistake.

SeriesOptimized forTypical fit
B (Burstable)Low baseline CPU with occasional bursts, credit-basedDev/test, low-traffic web servers, driver-portal's staging environment
D/Dsv5 (General purpose)Balanced CPU/memoryMost application servers — Meridian Freight's driver-portal production tier
E (Memory optimized)High memory-to-vCPU ratioIn-memory caches, large database buffers
F (Compute optimized)High CPU-to-memory ratioBatch processing, docs-processor's classification workload before its Part 10 serverless move
L (Storage optimized)High disk throughput/IOPS, local NVMeData-intensive workloads with heavy local disk I/O
N (GPU)GPU-accelerated computeML training/inference, rendering
# List available VM sizes and their specs in a given region
az vm list-sizes --location eastus --output table

# Check current quota usage for a family before committing to it (Part 1)
az vm list-usage --location eastus --query "[?contains(name.value, 'standardDSv5')]"

Why checking quota (Part 1) before finalizing a size choice belongs in this step, not an afterthought: a design that settles on the Dsv5 family without confirming the subscription's regional quota can hit exactly the silent capacity ceiling Part 1's quota discussion warned about, the moment a scale set (later in this chapter) tries to grow past it.


Deploying every VM from a stock marketplace image and configuring it entirely via cloud-init works at small scale, but a growing fleet benefits from a golden image baked with the organization's own baseline (agents, hardened OS settings, common dependencies) pre-installed — dramatically faster boot-to-ready time than running the same configuration via cloud-init on every single instance.

Diagram
# Create a gallery and an image definition within it
az sig create --gallery-name meridian-gallery --resource-group rg-platform
az sig image-definition create --gallery-name meridian-gallery --resource-group rg-platform \
  --gallery-image-definition meridian-base-ubuntu \
  --publisher MeridianFreight --offer BaseImages --sku ubuntu-2404-hardened --os-type Linux

# Capture a configured VM as a new image version, then replicate it
# across regions for low-latency deployment everywhere it's needed
az sig image-version create --gallery-name meridian-gallery --resource-group rg-platform \
  --gallery-image-definition meridian-base-ubuntu --gallery-image-version 1.3.0 \
  --managed-image "/subscriptions/<sub-id>/resourceGroups/rg-platform/providers/Microsoft.Compute/images/vm-driver-portal-golden" \
  --target-regions eastus westus2

# Deploy a scale set from a specific gallery image version
az vmss create --name vmss-driver-portal --resource-group rg-driver-portal-prod \
  --image "/subscriptions/<sub-id>/resourceGroups/rg-platform/providers/Microsoft.Compute/galleries/meridian-gallery/images/meridian-base-ubuntu/versions/1.3.0"

Why versioning and multi-region replication matter concretely, worth stating explicitly: a gallery image version is immutable once published — a scale set pinned to version 1.3.0 keeps deploying EXACTLY that baseline until deliberately updated to a newer version, giving a platform team the same explicit, auditable control over the "base OS layer" that a container registry (Part 10) gives over container image versions. Replicating a version to every region a workload actually deploys into avoids a slow, cross-region image pull at deployment time — the image is already locally available in each target region before a VM ever requests it.


Creating and Configuring a Virtual Machine#

# Create a VM with a managed identity attached from creation,
# and cloud-init custom data for first-boot configuration
az vm create \
  --resource-group rg-driver-portal-staging \
  --name vm-driver-portal-01 \
  --image Ubuntu2404 \
  --size Standard_D2s_v5 \
  --vnet-name vnet-meridian-staging --subnet snet-app \
  --assign-identity \
  --custom-data cloud-init.yaml \
  --admin-username azureuser \
  --generate-ssh-keys
# cloud-init.yaml — first-boot configuration, the Azure-portable
# equivalent of AWS's EC2 user-data
#cloud-config
package_update: true
packages:
  - nginx
runcmd:
  - systemctl enable nginx
  - systemctl start nginx

Why --assign-identity at creation time, rather than as a separate step afterward, is worth making a habit: it means the VM never has a window where it exists without its intended managed identity (Part 2) already in place — a small but real reduction in the surface area for a misconfiguration to slip through during a manual, multi-step build process.


Managed Disks Deep Dive#

Every VM disk in modern Azure is a managed disk — Azure handles the underlying storage account entirely; the disk is just a top-level resource with its own performance tier and lifecycle.

Diagram
Disk typeIOPS ceilingThroughput ceilingBest fit
Standard HDDLow, size-dependentLowBackups, infrequently accessed data, dev/test
Standard SSDModerateModerateWeb servers, lightly used app servers
Premium SSDHigh, tied to disk sizeHigh, tied to disk sizeMost production VM workloads — the default recommendation
Premium SSD v2Up to ~80,000 IOPS, independently tunableUp to ~1,200 MB/s, independently tunableProduction workloads needing to tune performance without resizing capacity
Ultra DiskUp to ~400,000 IOPSUp to ~10,000 MB/sSAP HANA, top-tier transactional databases, latency-critical I/O
# Premium SSD v2's key differentiator: capacity, IOPS, and throughput
# are tuned INDEPENDENTLY, without resizing the whole disk
az disk create --name disk-data-01 --resource-group rg-driver-portal-prod \
  --sku PremiumV2_LRS --size-gb 512 \
  --disk-iops-read-write 10000 --disk-mbps-read-write 400

Why Premium SSD v2's independent tuning is worth calling out as a genuine design improvement over classic Premium SSD, not just a naming refresh: a classic Premium SSD's IOPS and throughput are entirely determined by its provisioned SIZE, forcing an over-provisioned, oversized disk purely to reach a performance target the workload's actual capacity need doesn't require. Premium SSD v2 decouples the two, letting Meridian Freight's rates-db VM (Part 9 covers the managed-database alternative, but a self-managed database on a VM is a real, valid pattern too) tune IOPS to its actual query load without paying for capacity it doesn't use.

Shared Disks — Attaching One Disk to Multiple VMs#

A specialized but genuinely important capability for clustered workloads: a shared disk can be attached to multiple VMs simultaneously, using SCSI persistent reservations to coordinate access — the mechanism a Windows Server Failover Cluster or a clustered database uses for shared-storage failover, without each node needing its own separate copy of the data.

az disk create --name disk-cluster-shared --resource-group rg-shared-db \
  --sku Premium_LRS --size-gb 512 --max-shares 2

az vm disk attach --name vm-cluster-node-01 --resource-group rg-shared-db --disk disk-cluster-shared
az vm disk attach --name vm-cluster-node-02 --resource-group rg-shared-db --disk disk-cluster-shared

Worth stating precisely why this is narrower than it might first sound: a shared disk provides the shared BLOCK STORAGE a cluster-aware application needs — it does NOT provide filesystem-level coordination on its own. The application or cluster software (a clustered filesystem, or an application explicitly designed for SCSI persistent reservation-based coordination) must still handle concurrent access correctly; simply attaching an ordinary, non-cluster-aware application's disk to two VMs simultaneously would produce filesystem corruption, not a working active-active setup.


Disk Operations — Resize, Snapshot, Encryption at Host#

# Resize a disk (the VM must be deallocated for most size changes)
az vm deallocate --name vm-driver-portal-01 --resource-group rg-driver-portal-prod
az disk update --name vm-driver-portal-01_OsDisk --resource-group rg-driver-portal-prod --size-gb 128
az vm start --name vm-driver-portal-01 --resource-group rg-driver-portal-prod

# Create a point-in-time snapshot — the basis for both ad-hoc
# recovery and Part 14's backup strategy
az snapshot create --name snap-driver-portal-01-osdisk \
  --resource-group rg-driver-portal-prod \
  --source vm-driver-portal-01_OsDisk

# Enable encryption AT HOST — encrypts data on the host itself,
# a stronger guarantee than the default encryption-at-rest,
# since data is encrypted before it ever leaves the VM
az vm update --name vm-driver-portal-01 --resource-group rg-driver-portal-prod \
  --set securityProfile.encryptionAtHost=true

Encryption at host, worth distinguishing precisely from Azure's DEFAULT encryption-at-rest (which is always on, encrypting data on the physical storage media itself): encryption at host additionally encrypts the temporary/cache disk and the data flowing BETWEEN the VM and storage, not just the data sitting at rest on the physical disk — a meaningfully stronger guarantee some compliance frameworks specifically require, and worth enabling proactively for anything handling sensitive data rather than assuming default encryption-at-rest alone satisfies every audit.


Availability Sets — Fault and Update Domains#

An availability set groups VMs to spread them across independent hardware within one datacenter, protecting against hardware-level and planned-maintenance failures without requiring Availability Zone support in the region.

Diagram
az vm availability-set create --name as-driver-portal \
  --resource-group rg-driver-portal-prod \
  --platform-fault-domain-count 3 --platform-update-domain-count 5

Why availability sets still matter even in regions with full Availability Zone support, worth stating precisely: VMs within one availability set have LOWER inter-VM network latency than VMs spread across separate Availability Zones, since fault domains sit within a single datacenter rather than physically separate ones — a genuine tradeoff between the stronger fault isolation of zones and the lower latency of a single-datacenter availability set, covered fully in this chapter's comparison section.


Availability Zones for Virtual Machines#

Building directly on Part 1's Availability Zone geography: a zonal VM is pinned to run in one specific zone; a zone-redundant deployment (typically via a Scale Set or a zone-redundant load balancer, Part 6) spreads instances across multiple zones for the strongest available fault tolerance.

# Deploy a VM pinned to a specific zone
az vm create --name vm-driver-portal-z1 --resource-group rg-driver-portal-prod \
  --image Ubuntu2404 --size Standard_D2s_v5 --zone 1
Protection levelMechanismProtects againstAzure's SLA guarantee
No redundancySingle VM, no availability set/zone, with Premium/Ultra disksNothing beyond the VM's own uptime99.9%
Availability SetFault + Update Domains within one datacenterRack-level hardware failure, planned maintenance99.95%
Zone-redundant (multiple zones)Instances spread across 2-3 zonesFull datacenter loss — the strongest single-region guarantee99.99%

Worth stating precisely why the single-instance SLA has a disk-type condition attached: Azure's 99.9% single-VM guarantee applies only when EVERY disk attached to that VM — OS and data disks alike — uses Premium SSD, Premium SSD v2, or Ultra Disk. A single VM using Standard SSD or Standard HDD for any attached disk doesn't qualify for a connectivity SLA at all — a real, easy-to-miss detail when a team reaches for a cheaper disk tier on a "less important" data disk without realizing it silently voids the SLA for the whole VM, not just that one disk.


Proximity Placement Groups — Low-Latency Colocation#

A proximity placement group (PPG) does the opposite job of an availability set or zone: instead of spreading VMs apart for fault tolerance, it colocates them as physically close as possible for the lowest achievable inter-VM latency.

az ppg create --name ppg-shipment-api --resource-group rg-shipment-api-prod
az vm create --name vm-app-tier --resource-group rg-shipment-api-prod \
  --ppg ppg-shipment-api --image Ubuntu2404 --size Standard_D2s_v5

A real, worth-stating constraint: a single proximity placement group cannot span Availability Zones — colocating VMs for latency and spreading them for zone-level fault tolerance are fundamentally in tension, so a design needing BOTH (extreme fault tolerance AND minimal cross-tier latency) needs one PPG per zone, not one PPG spanning all of them.


Choosing Between Availability Set, Availability Zone, and Proximity Placement Group#

Diagram
NeedRecommendation
Strongest fault tolerance, latency less criticalZone-redundant deployment across 2-3 zones
Lowest possible latency between tightly coupled tiersProximity placement group
Fault tolerance in a region without zone support, or lower latency than cross-zoneAvailability set
Both extreme fault tolerance AND low cross-tier latencyOne PPG per zone, deployed as a zone-redundant set of per-zone PPG groups

For Meridian Freight, driver-portal's VMSS (next section) uses zone-redundant deployment — its field-staff traffic tolerates the small added cross-zone latency in exchange for surviving a full datacenter loss, which matters more for a customer-facing service than shaving milliseconds off inter-instance calls.

# Confirm zone support for the target region and VM size BEFORE
# committing to a zone-redundant design (Part 1 flagged this gap)
az vm list-skus --location eastus --size Standard_D2s_v5 --zone --output table

# Confirm every disk attached to a single-instance VM actually
# qualifies for the 99.9% SLA before relying on it
az vm show --name vm-driver-portal-01 --resource-group rg-driver-portal-prod \
  --query "storageProfile.{osDisk: osDisk.managedDisk.storageAccountType, dataDisks: dataDisks[].managedDisk.storageAccountType}"

Virtual Machine Scale Sets — Orchestration Modes#

A VM Scale Set (VMSS) manages a fleet of identical (or, in Flexible mode, similar) VMs as one unit, the direct Azure analog of an AWS Auto Scaling Group.

Diagram
# Create a scale set in Flexible orchestration mode — Microsoft's
# current recommended default for new deployments
az vmss create \
  --name vmss-driver-portal \
  --resource-group rg-driver-portal-prod \
  --image Ubuntu2404 \
  --orchestration-mode Flexible \
  --vm-sku Standard_D2s_v5 \
  --zones 1 2 3 \
  --instance-count 3 \
  --assign-identity \
  --vnet-name vnet-meridian-prod --subnet snet-app \
  --load-balancer lb-driver-portal
Uniform modeFlexible mode
VM identityEphemeral, scale-set-managed instancesStandard, independently addressable VM resources
Instance mixingNo — one VM model for the whole setYes — mix VM sizes, and Spot with on-demand
Max instances1,000 (600 in some older configs)1,000
Current recommendationLegacy — existing workloads onlyRecommended for all new deployments

Why Flexible mode's ability to mix Spot and on-demand instances in the SAME scale set is worth calling out as a genuine cost-and-resilience win, worth stating explicitly: a design can run a guaranteed on-demand baseline (say, 2 instances) plus Spot instances (later in this chapter) for the elastic portion of capacity, getting Spot's cost savings for the bursty part of demand without risking the baseline itself being evicted.


Autoscale — Metric-Based and Schedule-Based Scaling#

# Metric-based: scale out when average CPU exceeds 70% for 5 minutes
az monitor autoscale create --resource-group rg-driver-portal-prod \
  --resource vmss-driver-portal --resource-type Microsoft.Compute/virtualMachineScaleSets \
  --name autoscale-driver-portal --min-count 3 --max-count 10 --count 3

az monitor autoscale rule create --resource-group rg-driver-portal-prod \
  --autoscale-name autoscale-driver-portal \
  --condition "Percentage CPU > 70 avg 5m" \
  --scale out 2

# Schedule-based: pre-scale ahead of a known daily traffic pattern —
# field staff clock in heavily around 6am local time
az monitor autoscale profile create --resource-group rg-driver-portal-prod \
  --autoscale-name autoscale-driver-portal \
  --name morning-shift --min-count 6 --max-count 10 --count 6 \
  --recurrence week Mon Tue Wed Thu Fri --timezone "Eastern Standard Time" --start 05:30

Why combining both mechanisms, rather than relying on metric-based scaling alone, matters concretely for a workload with a genuinely predictable pattern like driver-portal's morning shift-start spike: metric-based autoscale reacts AFTER load has already increased, with real provisioning lag before new instances are ready to serve traffic — a schedule-based profile pre-scales ahead of a KNOWN pattern, avoiding that reactive lag entirely for the predictable portion of demand, while metric-based rules still handle genuinely unpredictable spikes on top of it.

Predictive Autoscale — Machine-Learning-Assisted Scaling#

A newer autoscale mode worth knowing about specifically because it sits between the two mechanisms above: predictive autoscale analyzes a scale set's historical CPU usage pattern over time and proactively scales ahead of an anticipated spike it has learned to expect, even for patterns too irregular for a fixed schedule-based profile to capture cleanly.

az monitor autoscale predictive enable --resource-group rg-driver-portal-prod \
  --autoscale-name autoscale-driver-portal --scale-mode Enabled --look-ahead-time 10

Why this is worth treating as a complement to, not a replacement for, the schedule-based profile already covered: predictive autoscale needs a real history of past load patterns to learn from, and provides no benefit for a brand-new workload with no usage history yet — it earns its keep on an established workload with a recurring-but-not-perfectly-fixed pattern (say, a shift start time that drifts by 20-30 minutes week to week), a middle ground the rigid schedule-based profile and the purely reactive metric-based rule don't individually cover as well.


VM Extensions and Custom Script Automation#

VM extensions run post-deployment configuration or agents inside a VM, without requiring a custom image build for every small configuration change.

# Run an arbitrary script post-deployment via the Custom Script Extension
az vm extension set --vm-name vm-driver-portal-01 --resource-group rg-driver-portal-prod \
  --name CustomScriptExtension --publisher Microsoft.Azure.Extensions \
  --settings '{"fileUris": ["https://meridianscripts.blob.core.windows.net/setup.sh"], "commandToExecute": "bash setup.sh"}'

# Install the Azure Monitor Agent extension — required for the
# VM Insights monitoring covered in Part 13
az vm extension set --vm-name vm-driver-portal-01 --resource-group rg-driver-portal-prod \
  --name AzureMonitorLinuxAgent --publisher Microsoft.Azure.Monitor

Moving and Resizing VMs Without Rebuilding Them#

# Change a VM's size without rebuilding it (deallocate first,
# and confirm the target size is available in the current cluster)
az vm deallocate --name vm-driver-portal-01 --resource-group rg-driver-portal-prod
az vm resize --name vm-driver-portal-01 --resource-group rg-driver-portal-prod --size Standard_D4s_v5

# Move a VM to a different resource group (metadata operation —
# does not recreate the underlying compute/disk resources)
az resource move --destination-group rg-driver-portal-archive \
  --ids "/subscriptions/<sub-id>/resourceGroups/rg-driver-portal-prod/providers/Microsoft.Compute/virtualMachines/vm-driver-portal-01"

From the Trenches: An engineer attempted a live resize of a running production VM expecting it to apply immediately like a cloud-native, always-elastic resource. The resize failed outright — most VM size changes require the VM to be deallocated first, since the underlying physical host may not have the target size's resource profile available, and Azure needs to potentially re-place the VM onto different hardware. The two-level lesson: the SYMPTOM was a failed resize command; the underlying cause was the assumption that VM compute is as fluid as, say, adjusting a Cosmos DB throughput setting (Part 9) — a VM's size is tied to physical host capability in a way a fully abstracted PaaS resource generally isn't.


On-Demand Capacity Reservations — Guaranteeing Availability Ahead of Need#

Part 1's quota discussion covered guaranteeing a subscription is allowed to request a given amount of compute; capacity reservations solve a related but distinct problem — guaranteeing that capacity is physically available in the datacenter when a scale-out actually happens, not just permitted by quota.

Diagram
# Reserve capacity for a specific VM size, ahead of a known future need
az capacity reservation group create --name crg-driver-portal-peak \
  --resource-group rg-driver-portal-prod --location eastus

az capacity reservation create --capacity-reservation-group crg-driver-portal-peak \
  --resource-group rg-driver-portal-prod --name reservation-peak-season \
  --sku Standard_D2s_v5 --capacity 10

# Deploy a VMSS explicitly against the reservation
az vmss create --name vmss-driver-portal --resource-group rg-driver-portal-prod \
  --capacity-reservation-group crg-driver-portal-peak --vm-sku Standard_D2s_v5

Why this distinction between quota and physical capacity matters concretely, worth stating explicitly: quota approval is purely an ACCOUNT-level permission check — it says nothing about whether that many VMs of that size can actually be physically provisioned in a specific region at the moment of a real scale-out event, particularly for less common VM sizes or during genuinely high-demand periods. Meridian Freight's driver-portal fleet, expecting a known seasonal peak (a major shipping season with predictably higher field-staff activity), would reserve capacity ahead of that peak specifically to eliminate the risk of a scale-out failing due to regional capacity exhaustion — a real, distinct failure mode from the quota-exceeded errors Part 1 covered, billed whether the reserved capacity is actually used or not, which is the real cost tradeoff worth weighing before reserving more than a genuinely justified peak requires.


Azure Dedicated Hosts — Compliance-Driven Hardware Isolation#

For organizations with a genuine regulatory or licensing requirement for physical hardware isolation — no other tenant's VMs ever sharing the same physical server — Azure Dedicated Hosts provision an entire physical server for one subscription's exclusive use.

az vm host group create --name hostgroup-compliance --resource-group rg-compliance --platform-fault-domain-count 2
az vm host create --host-group hostgroup-compliance --name host-01 --resource-group rg-compliance --sku DSv5-Type1

Worth stating as a real, deliberate tradeoff rather than a strictly "more secure" default: Dedicated Hosts cost meaningfully more than shared multi-tenant VMs for the same compute, and are worth reaching for only when a specific compliance requirement (certain per-core licensing models, specific government/regulatory mandates) genuinely demands physical isolation — not as a default hardening measure for workloads without that specific requirement.

# View the physical capacity already committed to and remaining
# on a specific dedicated host, before deciding whether a new
# workload fits on it or needs an additional host
az vm host list --host-group hostgroup-compliance --resource-group rg-compliance --output table

Spot Virtual Machines — Trading Availability for Cost#

Spot VMs use Azure's spare capacity at up to ~90% off standard pricing, with the tradeoff that Azure can evict them with as little as 30 seconds' notice when it needs that capacity back.

az vmss create --name vmss-batch-processing --resource-group rg-docs-processor \
  --priority Spot --eviction-policy Deallocate --max-price -1 \
  --image Ubuntu2404 --vm-sku Standard_F4s_v2 --instance-count 5
Eviction policyBehavior when evicted
DeallocateVM stops, disk persists — can be restarted later if capacity returns
DeleteVM and its resources are deleted outright
# Subscribe to eviction notice events so a workload can checkpoint
# and shut down gracefully within the ~30-second warning window,
# rather than being killed mid-task with no chance to save state
curl -H Metadata:true "http://169.254.169.254/metadata/scheduledevents?api-version=2020-07-01"

Why Spot is a genuinely strong fit for a batch-style workload like an earlier iteration of docs-processor's document-classification jobs, but a poor fit for driver-portal, worth stating the underlying reasoning rather than a blanket rule: a fault-tolerant, checkpoint-able batch job simply resumes on different capacity after an eviction, losing at most in-flight work — a stateful, latency-sensitive customer-facing service losing instances with 30 seconds' notice is a real availability risk, not a cost optimization. Flexible-mode scale sets mixing Spot and on-demand (covered earlier) is the middle path for workloads that want partial Spot savings without accepting full eviction risk on their entire capacity.


Trusted Launch and Confidential VMs — Hardening the Boot Chain#

Two security-focused VM generations worth knowing precisely, since they solve different threats at different layers of the boot process.

Diagram
# Enable Trusted Launch at VM creation — Secure Boot and vTPM,
# defending against boot/rootkit-level malware
az vm create --name vm-driver-portal-secure --resource-group rg-driver-portal-prod \
  --image Ubuntu2404 --security-type TrustedLaunch \
  --enable-secure-boot true --enable-vtpm true

# Confidential VMs go further — hardware memory encryption keeps
# data encrypted even from a privileged Azure host administrator
az vm create --name vm-sensitive-workload --resource-group rg-compliance \
  --image Ubuntu2404 --security-type ConfidentialVM \
  --os-disk-security-encryption-type DiskWithVMGuestState
Trusted LaunchConfidential VM
Protects againstBoot-kit/rootkit malware tampering with the boot sequenceAny party — including a privileged Azure host administrator — reading memory contents
MechanismSecure Boot (signed bootloaders only) + virtual TPM + boot integrity attestationHardware-based memory encryption isolating the VM even from the hypervisor host
Performance costNegligibleSmall, workload-dependent overhead from encryption
Best fitDefault recommendation for essentially all new VMsWorkloads with a genuine "must be protected from the cloud provider itself" requirement — regulated data processing, multi-party computation

Why Trusted Launch is worth treating as a near-default rather than a specialized option, worth stating plainly: it defends against a real, non-theoretical attack class (boot-level rootkits establishing persistence below the OS's own visibility) at essentially no performance cost, which is why Microsoft enables it by default for many new VM images today. Confidential VMs solve a narrower, more specific problem — trusting the CLOUD PROVIDER'S OWN INFRASTRUCTURE less — and are worth the added complexity only when a design genuinely has that requirement, not as a blanket upgrade.


Ephemeral OS Disks — Trading Persistence for Speed and Cost#

An ephemeral OS disk stores the OS disk on the VM's local host storage rather than as a separate managed-disk resource — faster, free of separate disk billing, but the entire OS disk is lost if the VM is redeployed to different host hardware for any reason (a host-level hardware failure, certain resizing scenarios).

az vm create --name vm-stateless-worker --resource-group rg-docs-processor \
  --image Ubuntu2404 --os-disk-caching ReadOnly \
  --ephemeral-os-disk true --size Standard_D2s_v5

Why this is a genuinely good fit specifically for instances in a VM Scale Set running fully stateless application code, worth stating the underlying reasoning: a VMSS instance is already expected to be disposable and interchangeable — every instance boots from the same base image and receives its actual configuration/code from elsewhere (a container, a deployment pipeline, cloud-init) — so losing one instance's OS disk on redeploy costs nothing beyond the normal instance-replacement flow the scale set already handles. It's a poor fit for driver-portal's VMs if they ever accumulate meaningful local state on the OS disk itself rather than treating it as fully disposable — a design assumption worth confirming explicitly before adopting ephemeral disks, not after a redeploy unexpectedly loses something.

Ephemeral OS diskStandard managed OS disk
Storage locationVM's local host storageSeparate managed-disk resource
BillingIncluded in VM compute cost, no separate disk chargeBilled separately per disk
Survives host redeploy?No — fully lostYes — persists independently of the VM's host
Best fitStateless VMSS instancesAnything with genuine local state on the OS disk

Boot Diagnostics, Serial Console, and Run Command#

Three complementary tools for a VM that's unreachable over the network entirely — the compute equivalent of an out-of-band management interface.

# Boot diagnostics captures a screenshot and boot log output —
# the first stop when a VM appears "stuck" during startup
az vm boot-diagnostics get-boot-log --name vm-driver-portal-01 --resource-group rg-driver-portal-prod

# Serial console gives a text-mode connection to the VM's serial port,
# reachable even when the VM's own network stack is fully misconfigured
az vm boot-diagnostics enable --name vm-driver-portal-01 --resource-group rg-driver-portal-prod

# Run Command executes a script INSIDE the VM via the Azure platform,
# not over SSH/RDP — works even when network security rules would
# otherwise block a direct connection
az vm run-command invoke --name vm-driver-portal-01 --resource-group rg-driver-portal-prod \
  --command-id RunShellScript --scripts "systemctl status nginx"

Why Run Command is worth knowing as a genuinely distinct tool from SSH, not a redundant alternative: it executes through the Azure Resource Manager control plane itself, authenticated by Azure RBAC rather than network reachability — a VM whose NSG rules (Part 7) accidentally block all inbound SSH, or one sitting in a fully private subnet with no bastion configured yet, can still be diagnosed and fixed via Run Command, precisely because it doesn't depend on the network path SSH requires.

# The serial console itself is reached through the portal or CLI,
# authenticated the same RBAC-based way as Run Command
az serial-console connect --name vm-driver-portal-01 --resource-group rg-driver-portal-prod

From the Trenches: A misapplied NSG rule accidentally blocked all inbound SSH to an entire driver-portal VMSS during a network security tightening pass. Rather than a slow, stressful rollback of the NSG change under production pressure (which risked reopening a genuinely intended restriction), the on-call engineer used Run Command to inspect the running configuration and confirm the application itself was healthy and serving traffic normally through the load balancer — the SSH block only affected direct administrative access, not the actual service. This distinction, only confirmable because Run Command doesn't depend on the same network path SSH does, changed an "emergency rollback" into a "scheduled, reviewed NSG fix," a meaningfully lower-risk path to the same outcome.


Azure Update Manager — Patching at Fleet Scale#

Manually patching VMs one at a time doesn't scale past a handful of machines — Azure Update Manager centrally assesses and orchestrates OS patching across an entire fleet, whether the VMs are Azure-native or Arc-enabled (Part 1) on-premises servers.

# Check patch compliance status across the driver-portal fleet
az vm assess-patches --name vm-driver-portal-01 --resource-group rg-driver-portal-prod

# Schedule a recurring maintenance window that installs patches
# automatically, rather than relying on manual per-VM patching
az maintenance configuration create --resource-group rg-driver-portal-prod \
  --resource-name patch-window-driver-portal \
  --maintenance-scope InGuestPatch \
  --recur-every "1Week Sunday" --duration "03:00" --start-date-time "2026-09-01 02:00"

Why a scheduled maintenance window across a whole VM Scale Set, applied with a defined update sequence rather than all instances simultaneously, matters concretely: patching every driver-portal instance at the exact same moment would briefly take the entire fleet offline for reboots — Update Manager's fleet-aware orchestration patches in controlled batches, keeping enough capacity online throughout the window to avoid a self-inflicted outage during routine, entirely planned maintenance.

# Review overall patch compliance across the fleet before AND after
# a maintenance window — the verification step that closes the loop
az update-assessment list --resource-group rg-driver-portal-prod --output table
Update Manager modeBehavior
Automatic by platform (Hotpatch-eligible images)Critical/security patches applied with minimal reboot, no maintenance window needed
Automatic by OSThe guest OS's own update mechanism (e.g. unattended-upgrades) applies patches on its own schedule
Customer-scheduled (Update Manager)A defined maintenance configuration controls exactly when and in what batches patching happens — the right choice for anything requiring predictable, low-risk patch timing

A Full Worked Compute Bootstrap for Meridian Freight#

# 1. Create a zone-redundant VM Scale Set in Flexible mode for driver-portal
az vmss create --name vmss-driver-portal --resource-group rg-driver-portal-prod \
  --image Ubuntu2404 --orchestration-mode Flexible --vm-sku Standard_D2s_v5 \
  --zones 1 2 3 --instance-count 3 --assign-identity \
  --vnet-name vnet-meridian-prod --subnet snet-app --load-balancer lb-driver-portal

# 2. Configure metric-based autoscale with a schedule-based
#    morning-shift profile layered on top
az monitor autoscale create --resource-group rg-driver-portal-prod \
  --resource vmss-driver-portal --resource-type Microsoft.Compute/virtualMachineScaleSets \
  --name autoscale-driver-portal --min-count 3 --max-count 10 --count 3

# 3. Attach the Azure Monitor Agent extension for Part 13's observability
az vmss extension set --vmss-name vmss-driver-portal --resource-group rg-driver-portal-prod \
  --name AzureMonitorLinuxAgent --publisher Microsoft.Azure.Monitor

# 4. Use Premium SSD v2 for any self-managed data disk needing
#    independently tunable IOPS
az disk create --name disk-driver-portal-cache --resource-group rg-driver-portal-prod \
  --sku PremiumV2_LRS --size-gb 256 --disk-iops-read-write 5000

# 5. Run the docs-processor batch workload on Spot VMs, Deallocate
#    eviction policy, since it's fault-tolerant and checkpoint-able
az vmss create --name vmss-docs-processor --resource-group rg-docs-processor \
  --priority Spot --eviction-policy Deallocate --image Ubuntu2404 \
  --vm-sku Standard_F4s_v2 --instance-count 5

Part 3 CLI Cheat Sheet#

AreaCommandPurpose
VM creationaz vm createCreate a virtual machine
Imagesaz sig image-version createPublish a new golden-image version to Azure Compute Gallery
Sizingaz vm list-sizes / az vm resizeList available sizes / change an existing VM's size
Cost hygieneaz disk list --query "[?diskState=='Unattached']"Find orphaned, still-billed disks
Disksaz disk createCreate a managed disk with a specific SKU/performance
Disksaz snapshot createCreate a point-in-time disk snapshot
Availabilityaz vm availability-set createCreate an availability set
Availabilityaz ppg createCreate a proximity placement group
Scale setsaz vmss createCreate a VM Scale Set (specify --orchestration-mode Flexible)
Autoscaleaz monitor autoscale create / rule create / profile createConfigure metric- and schedule-based autoscale
Extensionsaz vm extension setInstall a VM extension (Custom Script, Monitor Agent, etc.)
Spotaz vmss create --priority SpotCreate Spot-priced instances
Dedicated hostsaz vm host group create / az vm host createProvision physically isolated hardware
Securityaz vm create --security-type TrustedLaunchEnable Secure Boot + vTPM
Securityaz vm create --security-type ConfidentialVMEnable hardware-based memory encryption
Diagnosticsaz vm boot-diagnostics get-boot-logRetrieve boot screenshot/log for an unreachable VM
Diagnosticsaz vm run-command invokeExecute a script inside a VM via the control plane, not SSH
Patchingaz vm assess-patchesCheck OS patch compliance
Capacityaz capacity reservation createReserve physical capacity ahead of a known need

Common Mistakes and Interview Traps#

MistakeWhy It's WrongFix
Deleting a VM and assuming its disks/NIC are gone tooVMs, disks, and NICs are independent resources — deletion doesn't cascade by defaultExplicitly delete associated disks and NICs, or use --force-deletion where applicable
Choosing a VM size without checking regional quota firstCan hit a silent capacity ceiling the moment a scale set tries to growCheck az vm list-usage against the chosen family before committing to a design
Assuming a live VM resize applies without deallocationMost size changes require the VM to be deallocated firstDeallocate, resize, then restart — plan for the brief downtime window
Using classic Premium SSD when independent IOPS tuning is actually neededClassic Premium SSD's performance is fixed by disk SIZE, forcing over-provisioning for performance aloneUse Premium SSD v2 to tune capacity, IOPS, and throughput independently
Assuming default encryption-at-rest satisfies every compliance requirementSome frameworks specifically require encryption at host, a stronger guaranteeEnable encryption at host explicitly for workloads with that requirement
Deploying a proximity placement group spanning multiple Availability ZonesA single PPG cannot span zones — this configuration simply isn't possibleUse one PPG per zone if both extreme fault tolerance and low latency are required
Running a stateful, latency-sensitive service entirely on Spot VMs30-second eviction notice is a real availability risk for anything not fault-tolerant/checkpoint-ableReserve Spot for batch/fault-tolerant workloads, or mix Spot with an on-demand baseline in Flexible mode
Choosing Uniform orchestration mode for a new scale setLegacy mode with less flexibility — Flexible is Microsoft's current recommendation for all new deploymentsDefault to Flexible orchestration mode unless a specific legacy dependency requires Uniform
Using an ephemeral OS disk for a VM that accumulates meaningful local stateThe entire OS disk is lost on host redeploy — fine for disposable, stateless instances, a real data-loss risk otherwiseConfirm the workload is genuinely stateless before adopting ephemeral disks
Assuming subscription quota approval guarantees a scale-out will succeedQuota is an account-level permission check, not a guarantee of physical capacity availability in the region at that momentUse a capacity reservation ahead of a known peak if scale-out failure risk is unacceptable
Patching an entire VM Scale Set fleet in one simultaneous operationCan take the whole fleet offline for reboots at once, causing a self-inflicted outageUse Azure Update Manager's scheduled, batched maintenance windows
Baking a golden image but deploying from it only in the region it was captured inCross-region image pulls at deployment time add real latency to every new VM's boot-to-ready timeReplicate the gallery image version to every region a workload actually deploys into
Referencing a gallery image by "latest" rather than a pinned version numberDeploys can silently pick up an unreviewed newer image version, breaking reproducibilityPin scale sets and VM deployments to an explicit, tested image version
Attaching a Standard SSD/HDD data disk to an otherwise all-Premium single-instance VMSilently voids the VM's 99.9% single-instance connectivity SLA entirely, not just for that one diskUse Premium SSD, Premium SSD v2, or Ultra Disk for every attached disk if the SLA is being relied on

Worked Practice Problems#

Problem 1: An engineer deletes Meridian Freight's vm-driver-portal-old VM as part of a cleanup after migrating to the new VMSS-based design, expecting the cleanup to be complete. A month later, a cost review finds an unattached managed disk still being billed, tracing back to that deleted VM. What happened, and how should the original cleanup have been done?

Answer: Deleting a VM in Azure does not automatically delete its associated managed disks or network interface — they are independent resources that merely reference the VM, and persist as orphaned, still-billed resources unless explicitly deleted alongside it. The correct cleanup either passes the appropriate delete-associated-resources flags on the original az vm delete command, or performs an explicit follow-up az disk delete (and NIC cleanup) as a mandatory second step — treating "delete the VM" and "delete its resources" as two separate, both-required actions rather than assuming the first implies the second.

Problem 2: Meridian Freight's shipment-api application tier and its self-managed database tier are deployed as separate VMs with a hard requirement for the lowest possible network latency between them, and no requirement to survive a full datacenter outage (the database has its own separate DR strategy covered in Part 14). Which grouping mechanism fits, and why not the alternatives?

Answer: A proximity placement group is the right fit — it colocates the VMs as physically close as possible for minimal inter-VM latency, which directly matches the stated requirement. An availability set would spread the VMs across different fault domains within the datacenter, adding some latency compared to true colocation, for a fault-tolerance benefit the requirements explicitly say isn't needed here. Zone-redundant deployment would add even more latency by spreading VMs across physically separate datacenters entirely — the opposite of what a low-latency requirement calls for, and proximity placement groups cannot span zones in the first place, making that combination technically unavailable besides being the wrong tradeoff.

Problem 3: A platform team configures Meridian Freight's driver-portal VMSS with a metric-based autoscale rule (scale out when CPU exceeds 70%) but no schedule-based profile. Field staff consistently begin heavy portal usage at 6:00 AM local time each weekday, and the team observes several minutes of degraded response times every single morning before autoscale catches up. What's missing, and why doesn't tuning the CPU threshold lower fix it completely?

Answer: A schedule-based autoscale profile is missing. Metric-based autoscale is inherently REACTIVE — it only scales out after CPU usage has already crossed the threshold, and new instances take real time to provision and become ready to serve traffic, during which the existing fleet is already under load. Lowering the CPU threshold reduces the delay somewhat but doesn't eliminate it, since the rule still can't fire until load has already started rising. A schedule-based profile that pre-scales the fleet to a higher instance count starting at 5:30 AM, ahead of the KNOWN 6:00 AM traffic pattern, eliminates the reactive lag entirely for this specific, predictable spike — metric-based rules remain valuable on top of it for genuinely unpredictable load beyond the scheduled baseline.

Problem 4: Meridian Freight considers moving the entirely-batch, checkpoint-able docs-processor classification workload to Spot VMs to cut compute costs, but a team member objects that "Spot VMs aren't reliable enough for production workloads." Evaluate this objection.

Answer: The objection conflates "less available" with "unsuitable for production" without examining the specific workload's actual failure tolerance. A batch job that checkpoints its progress and can resume after an interruption is exactly the profile Spot VMs are well suited for — an eviction costs, at most, the in-flight work since the last checkpoint, and the job simply continues once capacity is available again (with Deallocate eviction policy preserving the disk state to resume from). The objection would be entirely valid for driver-portal, a stateful, latency-sensitive, customer-facing service where a 30-second eviction notice is a genuine availability risk — but rejecting Spot for docs-processor on the same blanket reasoning ignores that the two workloads have fundamentally different tolerance for interruption, and the decision should be made per-workload based on that tolerance, not as a universal production/non-production rule.

Problem 5: An organization migrating a licensed, per-core-billed enterprise database product to Azure has a strict licensing term requiring the software run on hardware not shared with any other tenant. What Azure compute option satisfies this, and what's the real cost tradeoff worth surfacing to the business before committing to it?

Answer: Azure Dedicated Hosts satisfy this requirement — they provision an entire physical server for exclusive use by one subscription, guaranteeing no co-tenancy with any other customer's VMs, which is exactly what a per-core licensing term requiring non-shared hardware demands. The real tradeoff worth surfacing explicitly: Dedicated Hosts cost meaningfully more than equivalent shared multi-tenant VM capacity, since the organization is paying for the entire physical host's capacity regardless of how much of it any single workload actually uses — this is a compliance/licensing-driven cost, not a performance or reliability upgrade, and shouldn't be adopted more broadly than the specific licensing requirement that necessitates it.

Problem 6: During a major shipping-season peak, Meridian Freight's driver-portal VMSS attempts to scale out from 3 to 15 instances of a specific, less common VM size. The scale-out fails partway through with a capacity error, even though the subscription's quota for that VM family was confirmed sufficient the week before. What's the likely cause, and how should the team have prevented it?

Answer: Subscription quota approval and physical datacenter capacity availability are two different things — the failure is most likely regional capacity exhaustion for that specific VM size at that specific moment, not a quota problem, since quota was already confirmed sufficient. This is exactly the gap On-Demand Capacity Reservations close: reserving the needed capacity ahead of the known seasonal peak guarantees physical availability at scale-out time, rather than discovering a capacity shortfall during the actual peak event when it's most costly to hit. The team should reserve capacity for the expected peak instance count ahead of the season, accepting the cost of paying for reserved capacity whether fully used or not, in exchange for eliminating this specific failure mode during the highest-stakes traffic period of the year.

Problem 7: A security review recommends Meridian Freight enable Trusted Launch on every existing VM as a blanket hardening measure, while a separate recommendation suggests Confidential VMs for the same fleet. An engineer proposes doing both everywhere "to be as secure as possible." Evaluate this proposal.

Answer: Trusted Launch is a reasonable near-default recommendation across the fleet — it defends against boot-level rootkit/malware persistence at essentially no performance cost, and there's little reason not to enable it broadly for new and existing VMs that support it. Confidential VMs are a different matter: they solve a narrower, more specific threat (protecting data in use even from a privileged Azure host administrator) at the cost of some performance overhead, and are genuinely justified only for workloads with an actual "must be protected from the cloud provider itself" requirement — regulated data processing or specific contractual/compliance mandates. Applying Confidential VMs universally "to be as secure as possible" adds real performance cost and operational complexity across the entire fleet for a threat model most of Meridian Freight's workloads don't actually have; the correct recommendation is Trusted Launch broadly, Confidential VMs selectively, based on which specific workloads carry that stricter requirement.

Problem 8: A platform team bakes a new golden image for Meridian Freight's driver-portal fleet in eastus and configures the production VMSS (deployed across eastus and westus2) to reference it. New instances launched in eastus boot quickly; new instances launched in westus2 consistently take several minutes longer to become ready, with no application-level explanation found. What's the likely infrastructure-level cause?

Answer: The gallery image version was very likely never replicated to westus2 — when a region requests an image version that only exists in a different region, Azure has to pull the image data across regions before the VM can boot from it, adding real, otherwise-unexplained latency to boot-to-ready time in every region the image wasn't explicitly replicated to. The fix is republishing (or updating) the image version with westus2 included in its --target-regions, ensuring the image is already locally available in every region the workload actually deploys into, rather than being pulled cross-region on every deployment.


Summary and What's Next#

  • An Azure VM is a composition of independent resources (compute, disks, NIC) — deleting the VM resource itself doesn't cascade-delete the others by default.
  • VM sizing follows named series optimized for different workload shapes (B, D, E, F, L, N) — checking regional quota (Part 1) before committing to a family is a required step, not an afterthought.
  • Premium SSD v2 decouples capacity from IOPS/throughput tuning, generally the best price-performance choice for production workloads that classic Premium SSD forces into over-provisioning to hit a performance target.
  • Availability Sets, Availability Zones, and Proximity Placement Groups trade fault tolerance against inter-VM latency in different ways — the right choice depends on which the specific workload actually needs, not a universal default.
  • Flexible orchestration mode is Microsoft's current recommendation for all new VM Scale Sets — it supports instance mixing (including Spot alongside on-demand) that Uniform mode cannot.
  • Schedule-based autoscale profiles eliminate the reactive lag inherent to metric-based scaling for any workload with a genuinely predictable traffic pattern.
  • Spot VMs are a workload-fit decision, not a universal cost lever — excellent for fault-tolerant, checkpoint-able batch work, a real availability risk for stateful, latency-sensitive services.
  • Trusted Launch is a near-default hardening measure; Confidential VMs solve a narrower, stricter threat model (protection from the cloud provider's own infrastructure) worth reaching for only when a genuine requirement demands it.
  • Subscription quota and physical capacity availability are separate concerns — a capacity reservation guarantees the latter ahead of a known peak, closing a real failure mode quota approval alone doesn't cover.
  • Azure Compute Gallery gives golden images the same versioned, auditable, multi-region-replicated treatment a container registry gives container images — pin deployments to explicit versions, and replicate to every region a workload actually runs in.

Continue to Part 4 (04-networking-foundations-vnets-ip-and-dns.md) for the VNets, subnets, and DNS this chapter's VMs and scale sets have been quietly assuming already exist.