Lab 03: Custom Script Extensions¶
Use Custom Script Extension to bootstrap software safely, inspect extension state, and recover from common execution errors.
Prerequisites¶
- Azure subscription with contributor-level access to compute, network, backup, and monitoring resources
- Azure CLI installed and signed in
- Variables set for the lab:
RGVM_NAMELOCATIONVNET_NAMESUBNET_NAME
- A Log Analytics workspace or backup vault where the lab requires it
Architecture Diagram¶
graph TD
A[Lab 03: Custom Script Extensions] --> B[Resource group and networking baseline]
B --> C[Virtual machine deployment]
C --> D[Security and access controls]
D --> E[Observability and validation]
E --> F[Cleanup and cost review] Lab Metadata¶
| Field | Value |
|---|---|
| Lab file | lab-03-custom-script-extensions.md |
| Estimated duration | 45-75 minutes |
| Difficulty | Intermediate |
| Focus technologies | Custom Script Extension, logs, idempotent bootstrap |
| Cost profile | Moderate; deallocate or clean up immediately after validation |
Step-by-step Instructions¶
Step 1: Create the resource group and network baseline¶
az group create \
--name $RG \
--location $LOCATION \
--output json
az network vnet create \
--resource-group $RG \
--name $VNET_NAME \
--address-prefixes 10.40.0.0/16 \
--subnet-name $SUBNET_NAME \
--subnet-prefixes 10.40.1.0/24 \
--output json
| Command | Purpose |
|---|---|
az group create | Create the resource group for the lab. |
--name | Name of the resource group to create. |
--location | Azure region for the resource group. |
--output | Output format for the response (table here). |
az network vnet create | Create the virtual network and its initial subnet. |
--resource-group | Resource group that will contain the virtual network. |
--name | Name of the virtual network to create. |
--address-prefixes | Address space (CIDR) for the virtual network. |
--subnet-name | Name of the initial subnet to create. |
--subnet-prefixes | CIDR range assigned to the subnet. |
--output | Output format for the response (JSON here). |
Expected outcome:
- The resource group exists in the intended region.
- The virtual network and subnet are available for VM deployment.
Step 2: Deploy the base VM¶
az vm create \
--resource-group $RG \
--name $VM_NAME \
--image Ubuntu2204 \
--size Standard_D4s_v5 \
--admin-username azureuser \
--generate-ssh-keys \
--vnet-name $VNET_NAME \
--subnet $SUBNET_NAME \
--public-ip-sku Standard \
--storage-sku Premium_LRS \
--output json
| Command | Purpose |
|---|---|
az vm create | Create a virtual machine in the lab virtual network. |
--resource-group | Resource group that will contain the virtual machine. |
--name | Name of the virtual machine to create. |
--image | Marketplace image or alias for the OS (Ubuntu2204). |
--size | VM size (SKU) to provision. |
--admin-username | Administrator user name for the guest OS. |
--generate-ssh-keys | Generate an SSH key pair if one does not already exist. |
--vnet-name | Existing virtual network to attach the VM to. |
--subnet | Subnet within the virtual network for the VM NIC. |
--public-ip-sku | SKU for the public IP address (Standard). |
--storage-sku | Managed-disk storage SKU for the OS disk (Premium_LRS). |
--output | Output format for the response (JSON here). |
Expected outcome:
- The VM deploys with Premium SSD-backed storage and a predictable network baseline.
- You have enough CPU, memory, and NIC capability to test the scenario without using a tiny burstable SKU.
Step 3: Capture the extension target state before bootstrap¶
Before you test Custom Script Extension, confirm that the VM does not already have conflicting extension state and record the clean target you expect your bootstrap to modify.
| Command | Purpose |
|---|---|
az vm extension list | List the VM extensions currently installed on the virtual machine. |
--resource-group | Resource group that contains the virtual machine. |
--vm-name | Name of the virtual machine whose extensions you want to inspect. |
--output | Output format for the response (JSON here). |
Recommended operator notes:
- Record whether any extension is already present so you can distinguish bootstrap issues from preexisting extension drift.
- Note package repository, storage, and outbound connectivity dependencies required by the script.
- Record how the script proves idempotency so reruns do not become a hidden production risk.
Step 4: Validate the scenario end to end¶
Run both control-plane and workload validation so you can compare extension deployment results with a known-good VM baseline.
az vm get-instance-view \
--resource-group $RG \
--name $VM_NAME \
--output json
az monitor activity-log list \
--resource-group $RG \
--offset 2h \
--output table
| Command | Purpose |
|---|---|
az vm get-instance-view | Retrieve the runtime instance view (power and provisioning state) of the VM. |
--resource-group | Resource group that contains the virtual machine. |
--name | Name of the virtual machine to inspect. |
--output | Output format for the response (JSON here). |
az monitor activity-log list | List recent control-plane activity-log events. |
--resource-group | Resource group to scope activity-log events to. |
--offset | Look-back window for events (2h here). |
--output | Output format for the response (table here). |
Step 5: Optional operational hardening¶
- Review whether the bootstrap should move to cloud-init, image baking, or a configuration-management tool after the lab.
- Review whether extension retries, logging, and rollback behavior are sufficient for production rollout.
- Review whether script secrets or storage dependencies need a safer delivery path.
Validation Steps¶
Use the following validation checklist before marking the lab complete:
- [ ] The VM is in the expected power and provisioning state
- [ ] The intended feature change is visible in Azure resource properties
- [ ] At least one CLI verification command was captured after the change
- [ ] You can explain how the lab outcome would change production design or troubleshooting
Cleanup Instructions¶
az vm delete \
--resource-group $RG \
--name $VM_NAME \
--yes
az network nic delete \
--resource-group $RG \
--name "${VM_NAME}VMNic"
az group delete \
--name $RG \
--yes \
--no-wait
| Command | Purpose |
|---|---|
az vm delete | Delete the virtual machine. |
--resource-group | Resource group that contains the virtual machine. |
--name | Name of the virtual machine to delete. |
--yes | Skip the confirmation prompt. |
az network nic delete | Delete the network interface left behind by the VM. |
--resource-group | Resource group that contains the network interface. |
--name | Name of the network interface to delete. |
az group delete | Delete the resource group and all remaining lab resources. |
--name | Name of the resource group to delete. |
--yes | Skip the confirmation prompt. |
--no-wait | Return immediately without waiting for deletion to finish. |
Cleanup notes:
- Delete associated disks, public IPs, Bastion hosts, vault items, or replication resources if the lab created them.
- Review whether backup vaults or recovery services still hold retained items that continue billing after VM deletion.