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#
- Compute in Azure — the IaaS-to-Serverless Spectrum
- Anatomy of an Azure VM
- VM Sizes and Series — Choosing the Right SKU
- Custom Images and Azure Compute Gallery
- Creating and Configuring a Virtual Machine
- Managed Disks Deep Dive
- Disk Operations — Resize, Snapshot, Encryption at Host
- Availability Sets — Fault and Update Domains
- Availability Zones for Virtual Machines
- Proximity Placement Groups — Low-Latency Colocation
- Choosing Between Availability Set, Availability Zone, and Proximity Placement Group
- Virtual Machine Scale Sets — Orchestration Modes
- Autoscale — Metric-Based and Schedule-Based Scaling
- VM Extensions and Custom Script Automation
- Moving and Resizing VMs Without Rebuilding Them
- On-Demand Capacity Reservations — Guaranteeing Availability Ahead of Need
- Azure Dedicated Hosts — Compliance-Driven Hardware Isolation
- Spot Virtual Machines — Trading Availability for Cost
- Trusted Launch and Confidential VMs — Hardening the Boot Chain
- Ephemeral OS Disks — Trading Persistence for Speed and Cost
- Boot Diagnostics, Serial Console, and Run Command
- Azure Update Manager — Patching at Fleet Scale
- A Full Worked Compute Bootstrap for Meridian Freight
- Part 3 CLI Cheat Sheet
- Common Mistakes and Interview Traps
- Worked Practice Problems
- 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.
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 sits | Who patches the OS | Who manages scaling | Billing model |
|---|---|---|---|
| Virtual Machine | You | You (manually, or via autoscale rules you configure) | Per-second compute + attached disk cost |
| VM Scale Set | You (at fleet scale, via Update Manager) | You configure autoscale rules; Azure executes them | Same as VM, multiplied across instances |
| AKS / Container Apps (Part 10) | Managed control plane; you patch worker nodes in AKS, not in Container Apps | Largely automated, container-level | Per-node (AKS) or per-execution (Container Apps) |
| App Service / Functions (Part 10) | Fully managed | Fully automated | Per-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.
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 tableVM 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.
| Series | Optimized for | Typical fit |
|---|---|---|
B (Burstable) | Low baseline CPU with occasional bursts, credit-based | Dev/test, low-traffic web servers, driver-portal's staging environment |
D/Dsv5 (General purpose) | Balanced CPU/memory | Most application servers — Meridian Freight's driver-portal production tier |
E (Memory optimized) | High memory-to-vCPU ratio | In-memory caches, large database buffers |
F (Compute optimized) | High CPU-to-memory ratio | Batch processing, docs-processor's classification workload before its Part 10 serverless move |
L (Storage optimized) | High disk throughput/IOPS, local NVMe | Data-intensive workloads with heavy local disk I/O |
N (GPU) | GPU-accelerated compute | ML 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.
Custom Images and Azure Compute Gallery#
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.
# 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 nginxWhy --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.
| Disk type | IOPS ceiling | Throughput ceiling | Best fit |
|---|---|---|---|
| Standard HDD | Low, size-dependent | Low | Backups, infrequently accessed data, dev/test |
| Standard SSD | Moderate | Moderate | Web servers, lightly used app servers |
| Premium SSD | High, tied to disk size | High, tied to disk size | Most production VM workloads — the default recommendation |
| Premium SSD v2 | Up to ~80,000 IOPS, independently tunable | Up to ~1,200 MB/s, independently tunable | Production workloads needing to tune performance without resizing capacity |
| Ultra Disk | Up to ~400,000 IOPS | Up to ~10,000 MB/s | SAP 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 400Why 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-sharedWorth 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=trueEncryption 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.
az vm availability-set create --name as-driver-portal \
--resource-group rg-driver-portal-prod \
--platform-fault-domain-count 3 --platform-update-domain-count 5Why 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 level | Mechanism | Protects against | Azure's SLA guarantee |
|---|---|---|---|
| No redundancy | Single VM, no availability set/zone, with Premium/Ultra disks | Nothing beyond the VM's own uptime | 99.9% |
| Availability Set | Fault + Update Domains within one datacenter | Rack-level hardware failure, planned maintenance | 99.95% |
| Zone-redundant (multiple zones) | Instances spread across 2-3 zones | Full datacenter loss — the strongest single-region guarantee | 99.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_v5A 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#
| Need | Recommendation |
|---|---|
| Strongest fault tolerance, latency less critical | Zone-redundant deployment across 2-3 zones |
| Lowest possible latency between tightly coupled tiers | Proximity placement group |
| Fault tolerance in a region without zone support, or lower latency than cross-zone | Availability set |
| Both extreme fault tolerance AND low cross-tier latency | One 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.
# 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 mode | Flexible mode | |
|---|---|---|
| VM identity | Ephemeral, scale-set-managed instances | Standard, independently addressable VM resources |
| Instance mixing | No — one VM model for the whole set | Yes — mix VM sizes, and Spot with on-demand |
| Max instances | 1,000 (600 in some older configs) | 1,000 |
| Current recommendation | Legacy — existing workloads only | Recommended 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:30Why 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 10Why 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.MonitorMoving 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.
# 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_v5Why 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-Type1Worth 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 tableSpot 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 policy | Behavior when evicted |
|---|---|
Deallocate | VM stops, disk persists — can be restarted later if capacity returns |
Delete | VM 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.
# 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 Launch | Confidential VM | |
|---|---|---|
| Protects against | Boot-kit/rootkit malware tampering with the boot sequence | Any party — including a privileged Azure host administrator — reading memory contents |
| Mechanism | Secure Boot (signed bootloaders only) + virtual TPM + boot integrity attestation | Hardware-based memory encryption isolating the VM even from the hypervisor host |
| Performance cost | Negligible | Small, workload-dependent overhead from encryption |
| Best fit | Default recommendation for essentially all new VMs | Workloads 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_v5Why 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 disk | Standard managed OS disk | |
|---|---|---|
| Storage location | VM's local host storage | Separate managed-disk resource |
| Billing | Included in VM compute cost, no separate disk charge | Billed separately per disk |
| Survives host redeploy? | No — fully lost | Yes — persists independently of the VM's host |
| Best fit | Stateless VMSS instances | Anything 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-prodFrom the Trenches: A misapplied NSG rule accidentally blocked all inbound SSH to an entire
driver-portalVMSS 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 mode | Behavior |
|---|---|
| Automatic by platform (Hotpatch-eligible images) | Critical/security patches applied with minimal reboot, no maintenance window needed |
| Automatic by OS | The 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 5Part 3 CLI Cheat Sheet#
| Area | Command | Purpose |
|---|---|---|
| VM creation | az vm create | Create a virtual machine |
| Images | az sig image-version create | Publish a new golden-image version to Azure Compute Gallery |
| Sizing | az vm list-sizes / az vm resize | List available sizes / change an existing VM's size |
| Cost hygiene | az disk list --query "[?diskState=='Unattached']" | Find orphaned, still-billed disks |
| Disks | az disk create | Create a managed disk with a specific SKU/performance |
| Disks | az snapshot create | Create a point-in-time disk snapshot |
| Availability | az vm availability-set create | Create an availability set |
| Availability | az ppg create | Create a proximity placement group |
| Scale sets | az vmss create | Create a VM Scale Set (specify --orchestration-mode Flexible) |
| Autoscale | az monitor autoscale create / rule create / profile create | Configure metric- and schedule-based autoscale |
| Extensions | az vm extension set | Install a VM extension (Custom Script, Monitor Agent, etc.) |
| Spot | az vmss create --priority Spot | Create Spot-priced instances |
| Dedicated hosts | az vm host group create / az vm host create | Provision physically isolated hardware |
| Security | az vm create --security-type TrustedLaunch | Enable Secure Boot + vTPM |
| Security | az vm create --security-type ConfidentialVM | Enable hardware-based memory encryption |
| Diagnostics | az vm boot-diagnostics get-boot-log | Retrieve boot screenshot/log for an unreachable VM |
| Diagnostics | az vm run-command invoke | Execute a script inside a VM via the control plane, not SSH |
| Patching | az vm assess-patches | Check OS patch compliance |
| Capacity | az capacity reservation create | Reserve physical capacity ahead of a known need |
Common Mistakes and Interview Traps#
| Mistake | Why It's Wrong | Fix |
|---|---|---|
| Deleting a VM and assuming its disks/NIC are gone too | VMs, disks, and NICs are independent resources — deletion doesn't cascade by default | Explicitly delete associated disks and NICs, or use --force-deletion where applicable |
| Choosing a VM size without checking regional quota first | Can hit a silent capacity ceiling the moment a scale set tries to grow | Check az vm list-usage against the chosen family before committing to a design |
| Assuming a live VM resize applies without deallocation | Most size changes require the VM to be deallocated first | Deallocate, resize, then restart — plan for the brief downtime window |
| Using classic Premium SSD when independent IOPS tuning is actually needed | Classic Premium SSD's performance is fixed by disk SIZE, forcing over-provisioning for performance alone | Use Premium SSD v2 to tune capacity, IOPS, and throughput independently |
| Assuming default encryption-at-rest satisfies every compliance requirement | Some frameworks specifically require encryption at host, a stronger guarantee | Enable encryption at host explicitly for workloads with that requirement |
| Deploying a proximity placement group spanning multiple Availability Zones | A single PPG cannot span zones — this configuration simply isn't possible | Use one PPG per zone if both extreme fault tolerance and low latency are required |
| Running a stateful, latency-sensitive service entirely on Spot VMs | 30-second eviction notice is a real availability risk for anything not fault-tolerant/checkpoint-able | Reserve 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 set | Legacy mode with less flexibility — Flexible is Microsoft's current recommendation for all new deployments | Default to Flexible orchestration mode unless a specific legacy dependency requires Uniform |
| Using an ephemeral OS disk for a VM that accumulates meaningful local state | The entire OS disk is lost on host redeploy — fine for disposable, stateless instances, a real data-loss risk otherwise | Confirm the workload is genuinely stateless before adopting ephemeral disks |
| Assuming subscription quota approval guarantees a scale-out will succeed | Quota is an account-level permission check, not a guarantee of physical capacity availability in the region at that moment | Use 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 operation | Can take the whole fleet offline for reboots at once, causing a self-inflicted outage | Use Azure Update Manager's scheduled, batched maintenance windows |
| Baking a golden image but deploying from it only in the region it was captured in | Cross-region image pulls at deployment time add real latency to every new VM's boot-to-ready time | Replicate the gallery image version to every region a workload actually deploys into |
| Referencing a gallery image by "latest" rather than a pinned version number | Deploys can silently pick up an unreviewed newer image version, breaking reproducibility | Pin 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 VM | Silently voids the VM's 99.9% single-instance connectivity SLA entirely, not just for that one disk | Use 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.