Skip to content

CLZ-250 - For Each with Windows VMs

For Each with Windows VMs architecture
Service or workflow illustration. The lesson defines the exact resource settings. Open at full size.

Goal

Give Windows VMs durable, human-readable Terraform identities by creating them from a typed map whose keys, VM sizes, and role values all affect the result.

CLZ-250 learning sequence
Arrows show the learning sequence, not service traffic. Azure deployment. Open at full size.

Prerequisites

  • Complete CLZ-240 and understand the limitations of positional count indexes.
  • Quota and budget for every entry in vm_definitions.
  • A current administrator public IPv4 /32 for this intentional public-IP lesson.

Architecture

CLZ-250 — The web and ops map keys identify matching Windows VM, NIC, and public IP instances without numeric renumbering; no IIS extension is deployed.
Lesson-specific teaching diagram. Read the arrow legend and the accompanying architecture explanation. Azure deployment. On narrow screens, scroll the diagram horizontally. Open at full size.

for_each applies the same VM, NIC, disk, and public-IP pattern to a map. Map keys become stable Terraform addresses and name fragments. Each value supplies its own vm_size and role; the role is also written to Azure tags.

Resources And Cost

Relative cost: High. Every map entry creates a Windows VM, disk, NIC, and public IP; sizes can produce different per-entry costs.

Component Purpose and principal cost driver
Named Windows VMs and disks One set per map key; VM size controls most cost.
Named NICs and public IPs Keyed dependencies and intentional teaching egress.
Shared VNet and NSG RDP is scoped to one administrator /32.
Role tags Value metadata demonstrates why a typed object is more useful than a set of names.

Inputs

Input Required substitution or decision
subscription_id Required for Azure plan, apply, and destroy; use the exact subscription GUID.
environment Keep dev for the lab unless testing another environment boundary.
location Select a supported region with sufficient quota.
name_prefix Use a short lowercase prefix.
admin_username One nonreserved Windows administrator name for the example set.
admin_cidr Required current public IPv4 /32; broad sources fail validation.
vm_definitions Required map of objects shaped as { vm_size = string, role = string }; keys must be stable workload names.

Keep real subscription IDs, CIDRs, domains, endpoint names, and secrets in untracked local files or environment variables.

Deploy

Review the plan and lesson-specific gates before deliberate execution. Local-only and read-only lessons create no Azure resources.
Review the plan and lesson-specific gates before deliberate execution. Local-only and read-only lessons create no Azure resources. Open at full size.

Run from the repository root in PowerShell 7:

Set-Location CLZ-250-for-each-windows-vms
if (-not (Test-Path terraform.tfvars)) {
  Copy-Item terraform.tfvars.example terraform.tfvars
}
$subscriptionId = az account show --query id --output tsv
terraform init -lockfile=readonly
terraform fmt -check
terraform validate
terraform plan -var "subscription_id=$subscriptionId" -out tfplan
terraform show tfplan
terraform apply tfplan

Validate

$resourceGroup = terraform output -raw resource_group_name
terraform state list | Select-String '\[".+"\]'
terraform output -json windows_vm_public_ips | ConvertFrom-Json | Format-Table
az vm list --resource-group $resourceGroup --show-details --query '[].{name:name,role:tags.Role,size:hardwareProfile.vmSize,ip:publicIps}' --output table

Expected result:

  • State addresses contain the map keys rather than numeric positions.
  • Azure VM sizes and Role tags match each object value.
  • The output map uses the same keys and contains one public IP per entry.

Troubleshooting

Gather evidence, repair the cause, and rerun verification.
Gather evidence, repair the cause, and rerun verification. Open at full size.
Symptom Resolution
Renaming a key proposes delete/create Keys are resource identity; use a moved block when a real rename must preserve an object.
Terraform reports the wrong object shape Every value must provide both string attributes: vm_size and role.
One size has no quota Choose an available size for that map value or reduce the map before apply.

Cleanup

Use the lesson state and cleanup instructions. Preserve remote-state dependencies until consumers are removed.
Use the lesson state and cleanup instructions. Preserve remote-state dependencies until consumers are removed. Open at full size.
$resourceGroup = terraform output -raw resource_group_name
terraform destroy -var "subscription_id=$subscriptionId"
terraform state list
az group exists --name $resourceGroup

All keyed resources are deleted, state is empty, and Azure CLI returns false.

Production Delta

  • Intentional teaching exception: remove VM public IPs and use private administration plus explicit egress.
  • Validate role/size policy, use modules for repeated patterns, and preserve key identity with documented moved blocks during refactors.

Exercises

  1. Add a batch entry with a different size and role, then predict every new address.
  2. Rename one key only in a disposable plan and explain how a moved block would avoid replacement.

← CLZ-240 - Count with Windows VMs | Curriculum | CLZ-260 - Windows VMSS Manual Scaling →