Infrastructure as Code with Terraform and Azure

Infrastructure as Code with Terraform and Azure

Learn how to manage your Azure infrastructure using Terraform, from basic concepts to advanced patterns like modules and workspaces.

Infrastructure as Code (IaC) has revolutionized how we manage cloud resources. With Terraform, you can define, provision, and manage your Azure infrastructure using declarative configuration files.

Why Terraform for Azure?

While Azure provides its own IaC solution (Bicep/ARM templates), Terraform offers several advantages:

  • Multi-cloud support - Use the same tool across Azure, AWS, and GCP
  • Large ecosystem - Extensive provider and module registry
  • State management - Track the current state of your infrastructure
  • Plan before apply - Preview changes before making them

Setting Up Your Terraform Environment

Install Terraform

# Using Homebrew (macOS/Linux)
brew install terraform

# Verify installation
terraform version

Configure Azure Provider

# providers.tf
terraform {
  required_version = ">= 1.6.0"
  
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.80"
    }
  }
  
  backend "azurerm" {
    resource_group_name  = "terraform-state-rg"
    storage_account_name = "tfstate${var.environment}"
    container_name       = "tfstate"
    key                  = "main.tfstate"
  }
}

provider "azurerm" {
  features {}
  subscription_id = var.subscription_id
}

Building Your First Infrastructure

Let’s create a complete web application infrastructure:

# main.tf
resource "azurerm_resource_group" "main" {
  name     = "webapp-${var.environment}-rg"
  location = var.location
  
  tags = local.common_tags
}

resource "azurerm_virtual_network" "main" {
  name                = "webapp-vnet"
  resource_group_name = azurerm_resource_group.main.name
  location            = azurerm_resource_group.main.location
  address_space       = ["10.0.0.0/16"]
}

resource "azurerm_subnet" "app" {
  name                 = "app-subnet"
  resource_group_name  = azurerm_resource_group.main.name
  virtual_network_name = azurerm_virtual_network.main.name
  address_prefixes     = ["10.0.1.0/24"]
}

Using Modules for Reusability

Terraform modules allow you to create reusable components:

# modules/aks/main.tf
resource "azurerm_kubernetes_cluster" "main" {
  name                = var.cluster_name
  location            = var.location
  resource_group_name = var.resource_group_name
  dns_prefix          = var.dns_prefix

  default_node_pool {
    name       = "default"
    node_count = var.node_count
    vm_size    = var.vm_size
    
    upgrade_settings {
      max_surge = "10%"
    }
  }

  identity {
    type = "SystemAssigned"
  }

  network_profile {
    network_plugin = "azure"
    network_policy = "azure"
  }
}

Call the module from your root configuration:

module "aks" {
  source = "./modules/aks"
  
  cluster_name        = "myapp-aks-${var.environment}"
  location            = var.location
  resource_group_name = azurerm_resource_group.main.name
  dns_prefix          = "myapp-${var.environment}"
  node_count          = var.environment == "prod" ? 5 : 2
  vm_size             = "Standard_DS2_v2"
}

Managing State with Remote Backend

Always use a remote backend in team environments:

# Create storage account for state
az storage account create \
  --name tfstate$RANDOM \
  --resource-group terraform-state-rg \
  --location eastus \
  --sku Standard_LRS \
  --encryption-services blob

# Create container
az storage container create \
  --name tfstate \
  --account-name <storage-account-name>

CI/CD Pipeline Integration

Integrate Terraform with GitHub Actions:

name: 'Terraform'

on:
  push:
    branches: ['main']
  pull_request:

jobs:
  terraform:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - uses: hashicorp/setup-terraform@v3
      with:
        terraform_version: 1.6.0
    
    - name: Terraform Init
      run: terraform init
      env:
        ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
        ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }}
        ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
        ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
    
    - name: Terraform Plan
      run: terraform plan -out=tfplan
    
    - name: Terraform Apply
      if: github.ref == 'refs/heads/main'
      run: terraform apply -auto-approve tfplan

Best Practices

  1. Always plan before applying - Use terraform plan to preview changes
  2. Use workspaces - Separate environments (dev, staging, prod)
  3. Lock provider versions - Pin specific versions for reproducibility
  4. Enable state locking - Prevent concurrent modifications
  5. Use terraform fmt - Keep code consistently formatted

Conclusion

Terraform with Azure provides a powerful, flexible way to manage your cloud infrastructure. By following the patterns outlined here, you can build maintainable, scalable infrastructure that scales with your team’s needs.