Stage 5 · Platform
Infrastructure as Code on Azure
Terraform for Azure
azurerm provider, state backend in Azure Storage, and AKS with Terraform.
Terraform on Azure
Terraform is an infrastructure-as-code tool by HashiCorp that uses a declarative configuration language (HCL) to provision and manage cloud resources. The azurerm provider is the most popular provider for Azure with comprehensive resource coverage.
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.85"
}
}
required_version = ">= 1.5"
}
provider "azurerm" {
features {}
subscription_id = var.subscription_id
tenant_id = var.tenant_id
}Always pin provider versions with ~> to prevent breaking changes. The azurerm provider requires a subscription_id and supports feature flags for resource-specific behavior.
Provider Configuration
# OIDC authentication for CI/CD (recommended)
provider "azurerm" {
features {}
subscription_id = var.subscription_id
# OIDC credentials are configured via environment variables:
# ARM_CLIENT_ID, ARM_TENANT_ID, ARM_SUBSCRIPTION_ID, ARM_USE_OIDC
}
# Variables for the configuration
variable "subscription_id" {
type = string
description = "Azure subscription ID"
sensitive = true
}
variable "tenant_id" {
type = string
description = "Azure AD tenant ID"
sensitive = true
}OIDC authentication eliminates long-lived secrets. Configure federated credentials on a service principal and set ARM_USE_OIDC=true in CI/CD.
State Backend in Azure
resource "azurerm_resource_group" "state" {
name = "rg-terraform-state"
location = "eastus"
}
resource "azurerm_storage_account" "state" {
name = "stterraformstateprod"
resource_group_name = azurerm_resource_group.state.name
location = azurerm_resource_group.state.location
account_tier = "Standard"
account_replication_type = "GRS"
blob_properties {
versioning_enabled = true
}
}
resource "azurerm_storage_container" "state" {
name = "tfstate"
storage_account_name = azurerm_storage_account.state.name
container_access_type = "private"
}
terraform {
backend "azurerm" {
resource_group_name = "rg-terraform-state"
storage_account_name = "stterraformstateprod"
container_name = "tfstate"
key = "prod.terraform.tfstate"
}
}State files contain sensitive data (passwords, keys). Use GRS replication, enable versioning, and restrict access with RBAC. Never store state in Git.
Terraform state stores sensitive values in plain text. Use azurerm_key_vault_secret for sensitive values, enable state encryption, and restrict state access with RBAC.
Resource Patterns
resource "azurerm_resource_group" "main" {
name = "rg-payments-${var.environment}"
location = var.location
tags = local.common_tags
}
resource "azurerm_virtual_network" "main" {
name = "vnet-${var.environment}"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
address_space = ["10.0.0.0/16"]
tags = local.common_tags
}
resource "azurerm_subnet" "aks" {
name = "subnet-aks"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = ["10.0.1.0/24"]
}
locals {
common_tags = {
Environment = var.environment
ManagedBy = "terraform"
CostCenter = "CC-1234"
}
}Use locals for computed values referenced multiple times. Tags should be consistent across all resources for cost management and governance.
AKS with Terraform
resource "azurerm_kubernetes_cluster" "main" {
name = "aks-payments-${var.environment}"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
dns_prefix = "aks-payments-${var.environment}"
kubernetes_version = "1.29.4"
default_node_pool {
name = "system"
node_count = 3
vm_size = "Standard_D4s_v3"
vnet_subnet_id = azurerm_subnet.aks.id
enable_auto_scaling = false
}
identity {
type = "SystemAssigned"
}
network_profile {
network_plugin = "azure"
network_policy = "calico"
service_cidr = "10.1.0.0/16"
dns_service_ip = "10.1.0.10"
}
tags = local.common_tags
}
resource "azurerm_kubernetes_cluster_node_pool" "app" {
name = "app"
kubernetes_cluster_id = azurerm_kubernetes_cluster.main.id
vm_size = "Standard_D4s_v3"
node_count = 2
vnet_subnet_id = azurerm_subnet.aks.id
enable_auto_scaling = true
min_count = 2
max_count = 10
mode = "User"
node_labels = {
workload = "app"
}
tags = local.common_tags
}Terraform manages both the AKS resource and node pools. The SystemAssigned identity creates a managed identity for the cluster. User pools are separate resources.
Modules and Workspaces
# modules/aks/main.tf
variable "name" { type = string }
variable "location" { type = string }
variable "node_count" { type = number }
variable "vm_size" { type = string }
resource "azurerm_kubernetes_cluster" "this" {
name = var.name
location = var.location
resource_group_name = var.resource_group_name
dns_prefix = var.name
default_node_pool {
name = "default"
node_count = var.node_count
vm_size = var.vm_size
}
identity { type = "SystemAssigned" }
}
output "cluster_id" { value = azurerm_kubernetes_cluster.this.id }
output "kube_config" { value = azurerm_kubernetes_cluster.this.kube_config_raw; sensitive = true }
# root module
module "aks_prod" {
source = "./modules/aks"
name = "aks-payments-prod"
location = "eastus"
resource_group_name = azurerm_resource_group.main.name
node_count = 3
vm_size = "Standard_D4s_v3"
}Modules encapsulate reusable infrastructure. Use workspaces (terraform workspace new prod) for environment-specific state isolation.
Create separate workspaces for dev, staging, and prod: terraform workspace new dev. Each workspace maintains its own state file while sharing the same codebase.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.