Skip to content

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

  1. Azure CLI

    # Check if installed
    az --version
    
    # Install on Linux
    curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
    
    # Install on macOS
    brew install azure-cli
    

  2. 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
    

  3. Azure Subscription

  4. Active Azure subscription
  5. 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

az login

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

az account show --query id --output tsv

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

cd vnet-vm-lb-learn-microsoft
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

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)

terraform plan -out=tfplan

This saves the plan to a file for later use.

Step 5: Deploy Infrastructure

Run Terraform Apply

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)

terraform apply -auto-approve

⚠️ Warning: Use auto-approve only when you're certain about the changes.

Using Saved Plan

terraform apply tfplan

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

# Send multiple requests
for i in {1..10}; do
  echo "Request $i:"
  curl $URL
  echo ""
done

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

  1. Navigate to https://portal.azure.com
  2. Search for your resource group name
  3. View all created resources
  4. 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

az network nsg rule list \
  --resource-group $RG_NAME \
  --nsg-name test-nsg \
  --output table

Troubleshooting

Issue: Terraform Init Fails

Error: "Failed to install provider"

Solution:

# Clear cache
rm -rf .terraform .terraform.lock.hcl

# Re-initialize
terraform init

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

  1. Edit terraform.tfvars or Terraform files
  2. Run terraform plan to preview changes
  3. Run terraform apply to apply changes

Example: Change VM size

# In terraform.tfvars
virtual_machine_size = "Standard_B2ms"
terraform plan
terraform apply

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
}
terraform apply

Step 10: Cleanup

Destroy All Resources

terraform destroy

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

terraform destroy -auto-approve

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

  1. Add HTTPS: Configure SSL/TLS certificates
  2. Add Monitoring: Enable Azure Monitor
  3. Add Backup: Configure Azure Backup
  4. Add Scaling: Implement VM Scale Sets
  5. Add Database: Add Azure Database for PostgreSQL/MySQL
  6. Add CDN: Add Azure CDN for static content
  7. Add WAF: Add Web Application Firewall

Learning Resources

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.