10.9 Deployment Guide¶
Overview¶
This guide provides step-by-step instructions for deploying the complete Azure infrastructure using Terraform. Follow these steps to create a working load-balanced web application.
Prerequisites¶
Required Tools¶
-
Azure CLI
-
Terraform
# Check if installed terraform --version # Install on Linux wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list sudo apt update && sudo apt install terraform # Install on macOS brew tap hashicorp/tap brew install hashicorp/tap/terraform -
Azure Subscription
- Active Azure subscription
- Sufficient permissions to create resources
Verify Prerequisites¶
# Check Azure CLI
az --version
# Check Terraform
terraform --version
# Check Azure login status
az account show
Step 1: Azure Authentication¶
Login to Azure¶
This opens a browser window for authentication. After successful login, you'll see your subscriptions.
Set Active Subscription¶
# List subscriptions
az account list --output table
# Set active subscription
az account set --subscription "YOUR_SUBSCRIPTION_ID"
# Verify
az account show
Get Subscription ID¶
Example Output: ba5a5064-fa8a-404a-9fa3-4fd5fb6430df
Step 2: Prepare Terraform Files¶
Directory Structure¶
vnet-vm-lb-learn-microsoft/
├── main.tf # Main infrastructure configuration
├── variables.tf # Variable definitions
├── outputs.tf # Output definitions
├── providers.tf # Provider configuration
└── terraform.tfvars # Variable values (optional)
Update Provider Configuration¶
Edit providers.tf and add your subscription ID:
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~>4.0"
}
random = {
source = "hashicorp/random"
version = "~>3.0"
}
}
}
provider "azurerm" {
features {}
subscription_id = "YOUR_SUBSCRIPTION_ID" # Replace with your ID
}
Create terraform.tfvars (Optional)¶
Create terraform.tfvars to customize values:
# Location
resource_group_location = "eastus"
# Naming
resource_group_name_prefix = "demo"
virtual_machine_name = "web-vm"
load_balancer_name = "web-lb"
# VM Configuration
virtual_machine_size = "Standard_B2s"
username = "azureadmin"
# password = "" # Leave empty for random generation
Step 3: Initialize Terraform¶
Run Terraform Init¶
What This Does: - Downloads required providers (azurerm, random) - Initializes backend - Prepares working directory
Expected Output:
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/azurerm versions matching "~> 4.0"...
- Finding hashicorp/random versions matching "~> 3.0"...
- Installing hashicorp/azurerm v4.x.x...
- Installing hashicorp/random v3.x.x...
Terraform has been successfully initialized!
Step 4: Review the Plan¶
Run Terraform Plan¶
What This Does: - Shows what resources will be created - Validates configuration - Estimates changes
Expected Output:
Terraform will perform the following actions:
# azurerm_lb.example will be created
+ resource "azurerm_lb" "example" {
+ id = (known after apply)
+ name = "test-lb"
+ sku = "Standard"
...
}
# azurerm_linux_virtual_machine.example[0] will be created
+ resource "azurerm_linux_virtual_machine" "example" {
+ id = (known after apply)
+ name = "test-vm0"
+ size = "Standard_B2s"
...
}
# ... (more resources)
Plan: 15 to add, 0 to change, 0 to destroy.
Save Plan (Optional)¶
This saves the plan to a file for later use.
Step 5: Deploy Infrastructure¶
Run Terraform Apply¶
Interactive Mode: Terraform will show the plan and ask for confirmation.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
Auto-Approve (Non-Interactive)¶
⚠️ Warning: Use auto-approve only when you're certain about the changes.
Using Saved Plan¶
Deployment Progress¶
You'll see resources being created:
random_pet.rg_name: Creating...
random_pet.rg_name: Creation complete after 0s [id=rg-golden-panther]
azurerm_resource_group.example: Creating...
azurerm_resource_group.example: Creation complete after 2s
azurerm_virtual_network.example: Creating...
azurerm_public_ip.example: Creating...
...
azurerm_linux_virtual_machine.example[0]: Creating...
azurerm_linux_virtual_machine.example[1]: Creating...
azurerm_linux_virtual_machine.example[0]: Still creating... [10s elapsed]
azurerm_linux_virtual_machine.example[1]: Still creating... [10s elapsed]
...
azurerm_virtual_machine_extension.example[0]: Creating...
azurerm_virtual_machine_extension.example[1]: Creating...
...
Apply complete! Resources: 15 added, 0 changed, 0 destroyed.
Outputs:
public_ip_address = "http://20.121.45.67"
resource_group_name = "rg-golden-panther"
vm_password = <sensitive>
Typical Duration: 5-10 minutes
Step 6: Verify Deployment¶
Check Outputs¶
# View all outputs
terraform output
# Get public IP
terraform output -raw public_ip_address
# Get VM password
terraform output -raw vm_password
Test the Application¶
# Get the URL
URL=$(terraform output -raw public_ip_address)
# Test with curl
curl $URL
# Expected output:
# Hello World from test-vm0
# or
# Hello World from test-vm1
Test Load Balancing¶
You should see responses from both VMs.
Open in Browser¶
# Linux
xdg-open $(terraform output -raw public_ip_address)
# macOS
open $(terraform output -raw public_ip_address)
# Windows (PowerShell)
Start-Process (terraform output -raw public_ip_address)
Step 7: Verify Resources in Azure¶
Using Azure CLI¶
# Get resource group name
RG_NAME=$(terraform output -raw resource_group_name)
# List all resources
az resource list --resource-group $RG_NAME --output table
# Check VMs
az vm list --resource-group $RG_NAME --output table
# Check load balancer
az network lb list --resource-group $RG_NAME --output table
# Check public IP
az network public-ip show \
--resource-group $RG_NAME \
--name test-public-ip \
--query "{Name:name, IP:ipAddress, Status:provisioningState}" \
--output table
Using Azure Portal¶
- Navigate to https://portal.azure.com
- Search for your resource group name
- View all created resources
- Check VM status, load balancer configuration, etc.
Step 8: Monitor and Manage¶
Check VM Status¶
RG_NAME=$(terraform output -raw resource_group_name)
# Get VM status
az vm get-instance-view \
--resource-group $RG_NAME \
--name test-vm0 \
--query instanceView.statuses \
--output table
View Load Balancer Health¶
# Check backend pool health
az network lb show \
--resource-group $RG_NAME \
--name test-lb \
--query "backendAddressPools[0].backendIpConfigurations[*].id" \
--output table
Check NSG Rules¶
Troubleshooting¶
Issue: Terraform Init Fails¶
Error: "Failed to install provider"
Solution:
Issue: Authentication Error¶
Error: "subscription ID could not be determined"
Solution:
# Re-login
az login
# Set subscription
az account set --subscription "YOUR_SUBSCRIPTION_ID"
# Update providers.tf with subscription ID
Issue: Resource Already Exists¶
Error: "A resource with the ID already exists"
Solution:
# Import existing resource
terraform import azurerm_resource_group.example /subscriptions/.../resourceGroups/name
# Or destroy and recreate
terraform destroy
terraform apply
Issue: VM Extension Fails¶
Error: "Extension provisioning failed"
Solution:
# Check extension logs on VM
RG_NAME=$(terraform output -raw resource_group_name)
# Run command on VM
az vm run-command invoke \
--resource-group $RG_NAME \
--name test-vm0 \
--command-id RunShellScript \
--scripts "cat /var/log/azure/custom-script/handler.log"
Issue: Cannot Access Application¶
Checklist: 1. ✅ VMs are running 2. ✅ NSG allows port 80 3. ✅ Nginx is installed and running 4. ✅ Health probe is passing 5. ✅ Load balancer rules are configured
Debug:
RG_NAME=$(terraform output -raw resource_group_name)
# Check VM status
az vm list --resource-group $RG_NAME --show-details --output table
# Check NSG rules
az network nsg rule list --resource-group $RG_NAME --nsg-name test-nsg --output table
# Test from VM directly (if you have access)
az vm run-command invoke \
--resource-group $RG_NAME \
--name test-vm0 \
--command-id RunShellScript \
--scripts "curl localhost"
Step 9: Making Changes¶
Modify Configuration¶
- Edit
terraform.tfvarsor Terraform files - Run
terraform planto preview changes - Run
terraform applyto apply changes
Example: Change VM size
Add More VMs¶
Edit main.tf and change the count:
resource "azurerm_linux_virtual_machine" "example" {
count = 3 # Changed from 2 to 3
# ... rest of configuration
}
Step 10: Cleanup¶
Destroy All Resources¶
Interactive Mode: Terraform will ask for confirmation.
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
Auto-Approve Destroy¶
Verify Cleanup¶
# Check if resource group still exists
az group show --name $(terraform output -raw resource_group_name)
# Should return: ResourceGroupNotFound
Manual Cleanup (if needed)¶
# Delete resource group and all resources
az group delete --name <resource-group-name> --yes --no-wait
Best Practices¶
1. Version Control¶
# Initialize git repository
git init
# Create .gitignore
cat > .gitignore <<EOF
# Terraform
.terraform/
*.tfstate
*.tfstate.backup
.terraform.lock.hcl
tfplan
# Sensitive files
terraform.tfvars
*.auto.tfvars
.secrets/
EOF
# Commit code
git add .
git commit -m "Initial infrastructure code"
2. State Management¶
For team environments, use remote state:
# In providers.tf
terraform {
backend "azurerm" {
resource_group_name = "terraform-state-rg"
storage_account_name = "tfstateXXXXX"
container_name = "tfstate"
key = "vnet-vm-lb.tfstate"
}
}
3. Environment Separation¶
# Development
terraform workspace new dev
terraform apply -var-file="dev.tfvars"
# Production
terraform workspace new prod
terraform apply -var-file="prod.tfvars"
4. Documentation¶
Document your deployment:
# Generate documentation
cat > DEPLOYMENT.md <<EOF
# Deployment Information
**Date**: $(date)
**Resource Group**: $(terraform output -raw resource_group_name)
**Public IP**: $(terraform output -raw public_ip_address)
## Access
- URL: $(terraform output -raw public_ip_address)
- Username: azureadmin
- Password: (see secrets manager)
## Resources
- 2 Linux VMs (Ubuntu 22.04)
- 1 Load Balancer (Standard SKU)
- 1 Virtual Network
- 1 Network Security Group
EOF
Quick Reference¶
Common Commands¶
# Initialize
terraform init
# Validate
terraform validate
# Format code
terraform fmt
# Plan changes
terraform plan
# Apply changes
terraform apply
# Show current state
terraform show
# List resources
terraform state list
# View outputs
terraform output
# Destroy infrastructure
terraform destroy
Useful Aliases¶
# Add to ~/.bashrc or ~/.zshrc
alias tf='terraform'
alias tfi='terraform init'
alias tfp='terraform plan'
alias tfa='terraform apply'
alias tfd='terraform destroy'
alias tfo='terraform output'
Next Steps¶
Enhancements¶
- Add HTTPS: Configure SSL/TLS certificates
- Add Monitoring: Enable Azure Monitor
- Add Backup: Configure Azure Backup
- Add Scaling: Implement VM Scale Sets
- Add Database: Add Azure Database for PostgreSQL/MySQL
- Add CDN: Add Azure CDN for static content
- Add WAF: Add Web Application Firewall
Learning Resources¶
- Terraform Azure Provider Documentation
- Azure Load Balancer Documentation
- Azure Virtual Network Documentation
- Terraform Best Practices
Conclusion¶
You now have a fully functional load-balanced web application running on Azure! The infrastructure includes:
✅ Virtual Network with subnet ✅ Network Security Group with HTTP access ✅ Two Linux VMs running Nginx ✅ Azure Load Balancer distributing traffic ✅ Health probes monitoring VM status ✅ Automated deployment with Terraform
The application is accessible via the public IP address and demonstrates load balancing across multiple VMs.