Civica Training - Terraform on Azure

Azure Fundamentals
with Terraform

Building the foundation for your AKS platform

Module 2 of 5 Beginner-Intermediate

Where We Are

"Before we can deploy AKS, we need the supporting infrastructure: resource groups, networks, subnets, security groups, storage, and secrets management. Let's build each piece with Terraform."

Azure Provider Setup

terraform { required_version = ">= 1.5.0" required_providers { azurerm = { source = "hashicorp/azurerm" version = "~> 3.75" } } } provider "azurerm" { features { key_vault { purge_soft_delete_on_destroy = true } resource_group { prevent_deletion_if_contains_resources = false } } }

The Features Block

The features {} block is required in azurerm and controls provider behavior.

FeatureSettingPurpose
key_vaultpurge_soft_delete_on_destroyAuto-purge Key Vaults on destroy
resource_groupprevent_deletion_if_contains_resourcesSafety check before destroying RGs
virtual_machinedelete_os_disk_on_deletionClean up disks when VMs are destroyed
log_analytics_workspacepermanently_delete_on_destroySkip soft-delete for Log Analytics

Even if you don't configure any settings, features {} must be present.

Authentication Methods

Azure CLI (Local Dev)

$ az login $ az account set \ --subscription "my-sub" # Provider auto-detects CLI creds

Service Principal (CI/CD)

# Set environment variables: export ARM_CLIENT_ID="..." export ARM_CLIENT_SECRET="..." export ARM_TENANT_ID="..." export ARM_SUBSCRIPTION_ID="..."

Managed Identity

provider "azurerm" { features {} use_msi = true }

OIDC (GitHub Actions)

provider "azurerm" { features {} use_oidc = true }

Resource Groups

Logical containers for Azure resources. Every resource must belong to one.

resource "azurerm_resource_group" "aks" { name = "rg-aks-platform-uksouth" location = "uksouth" tags = { Project = "AKS Platform" Environment = var.environment ManagedBy = "Terraform" CostCentre = "IT-Infrastructure" } }

Knowledge Check 1

1. What happens if you omit the features {} block from the azurerm provider?

2. Which authentication method is recommended for GitHub Actions CI/CD?

3. What happens when you delete an Azure resource group?

Virtual Networks (VNet)

The foundational network for your AKS cluster and all related resources.

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

Subnets

Divide the VNet into logical segments for different workloads.

resource "azurerm_subnet" "aks_nodes" { name = "snet-aks-nodes" resource_group_name = azurerm_resource_group.aks.name virtual_network_name = azurerm_virtual_network.main.name address_prefixes = ["10.0.1.0/24"] } resource "azurerm_subnet" "aks_pods" { name = "snet-aks-pods" resource_group_name = azurerm_resource_group.aks.name virtual_network_name = azurerm_virtual_network.main.name address_prefixes = ["10.0.2.0/22"] # 1024 IPs for pods } resource "azurerm_subnet" "appgw" { name = "snet-appgw" resource_group_name = azurerm_resource_group.aks.name virtual_network_name = azurerm_virtual_network.main.name address_prefixes = ["10.0.8.0/24"] }

Subnet Planning for AKS

SubnetCIDRIPsPurpose
snet-aks-nodes10.0.1.0/24251AKS node VMs
snet-aks-pods10.0.2.0/221,021Pod IPs (Azure CNI)
snet-appgw10.0.8.0/24251Application Gateway / Ingress
snet-private-endpoints10.0.10.0/24251Private endpoints (ACR, KV)
With Azure CNI, every pod gets a VNet IP. Plan subnet sizes carefully - you cannot resize subnets that have resources.

Network Security Groups

NSGs filter traffic to and from Azure resources in a VNet.

resource "azurerm_network_security_group" "aks" { name = "nsg-aks-nodes" location = azurerm_resource_group.aks.location resource_group_name = azurerm_resource_group.aks.name tags = azurerm_resource_group.aks.tags } # Associate NSG with subnet resource "azurerm_subnet_network_security_group_association" "aks" { subnet_id = azurerm_subnet.aks_nodes.id network_security_group_id = azurerm_network_security_group.aks.id }

NSG Rules

resource "azurerm_network_security_rule" "allow_https" { name = "AllowHTTPS" priority = 100 direction = "Inbound" access = "Allow" protocol = "Tcp" source_port_range = "*" destination_port_range = "443" source_address_prefix = "*" destination_address_prefix = "*" resource_group_name = azurerm_resource_group.aks.name network_security_group_name = azurerm_network_security_group.aks.name }

Azure Storage Accounts

resource "azurerm_storage_account" "main" { name = "stakstfstate${var.environment}" resource_group_name = azurerm_resource_group.aks.name location = azurerm_resource_group.aks.location account_tier = "Standard" account_replication_type = "GRS" min_tls_version = "TLS1_2" blob_properties { versioning_enabled = true delete_retention_policy { days = 30 } } tags = azurerm_resource_group.aks.tags }

Storage Container for Terraform State

resource "azurerm_storage_container" "tfstate" { name = "tfstate" storage_account_name = azurerm_storage_account.main.name container_access_type = "private" }

This container will hold our Terraform state files. We'll configure the backend in Module 4.

Always set container_access_type = "private". State files contain sensitive infrastructure details.

Knowledge Check 2

1. Why does AKS with Azure CNI need larger subnet ranges?

2. In NSG rules, which priority number has the highest precedence?

3. Storage account names must be:

Azure Key Vault

Securely store and manage secrets, keys, and certificates for your AKS platform.

data "azurerm_client_config" "current" {} resource "azurerm_key_vault" "main" { name = "kv-aks-platform-001" location = azurerm_resource_group.aks.location resource_group_name = azurerm_resource_group.aks.name tenant_id = data.azurerm_client_config.current.tenant_id sku_name = "standard" purge_protection_enabled = true soft_delete_retention_days = 90 tags = azurerm_resource_group.aks.tags }

Key Vault Access Policies

resource "azurerm_key_vault_access_policy" "terraform" { key_vault_id = azurerm_key_vault.main.id tenant_id = data.azurerm_client_config.current.tenant_id object_id = data.azurerm_client_config.current.object_id secret_permissions = [ "Get", "List", "Set", "Delete", "Purge" ] key_permissions = [ "Get", "List", "Create" ] }

Storing Secrets in Key Vault

resource "azurerm_key_vault_secret" "db_password" { name = "database-password" value = var.db_password key_vault_id = azurerm_key_vault.main.id depends_on = [azurerm_key_vault_access_policy.terraform] }
The secret value will be stored in Terraform state. Use sensitive = true on variables and outputs to prevent it from showing in logs. For production, consider generating secrets outside Terraform.

Azure DNS Zone

Manage DNS records for your AKS services and ingress.

resource "azurerm_dns_zone" "main" { name = "aks.example.com" resource_group_name = azurerm_resource_group.aks.name tags = azurerm_resource_group.aks.tags } resource "azurerm_dns_a_record" "app" { name = "myapp" zone_name = azurerm_dns_zone.main.name resource_group_name = azurerm_resource_group.aks.name ttl = 300 records = ["10.0.1.100"] }

Private DNS Zones

For private AKS clusters and private endpoints.

resource "azurerm_private_dns_zone" "aks" { name = "privatelink.uksouth.azmk8s.io" resource_group_name = azurerm_resource_group.aks.name } resource "azurerm_private_dns_zone_virtual_network_link" "aks" { name = "vnet-link-aks" resource_group_name = azurerm_resource_group.aks.name private_dns_zone_name = azurerm_private_dns_zone.aks.name virtual_network_id = azurerm_virtual_network.main.id }

Knowledge Check 3

1. What does data.azurerm_client_config.current provide?

2. Why should Key Vault have purge protection enabled?

3. What is a Private DNS Zone used for with AKS?

Data Sources

Read information about existing resources that Terraform doesn't manage.

Look Up Existing Resources

data "azurerm_resource_group" "existing" { name = "rg-shared-services" } data "azurerm_virtual_network" "hub" { name = "vnet-hub" resource_group_name = "rg-networking" }

Use the Data

# Reference data source attributes location = data.azurerm_resource_group.existing.location # Peer with existing hub VNet remote_virtual_network_id = data.azurerm_virtual_network.hub.id

Common Azure Data Sources

Data SourceUse Case
azurerm_client_configCurrent authentication context (tenant, subscription)
azurerm_subscriptionCurrent subscription details
azurerm_resource_groupLook up existing resource group
azurerm_virtual_networkReference an existing VNet (e.g., hub network)
azurerm_subnetGet subnet ID for deployment into existing network
azurerm_key_vaultReference shared Key Vault for secrets
azurerm_key_vault_secretRead a secret value from Key Vault
azurerm_container_registryReference existing ACR for AKS integration

Implicit Dependencies

Terraform automatically detects dependencies when one resource references another.

# Terraform knows to create the RG first, then the VNet resource "azurerm_resource_group" "main" { name = "rg-aks" location = "uksouth" } resource "azurerm_virtual_network" "main" { name = "vnet-aks" resource_group_name = azurerm_resource_group.main.name # implicit dep! location = azurerm_resource_group.main.location address_space = ["10.0.0.0/16"] }

The reference azurerm_resource_group.main.name tells Terraform the VNet depends on the resource group.

Explicit Dependencies

Sometimes Terraform can't infer the dependency. Use depends_on.

resource "azurerm_key_vault_secret" "example" { name = "my-secret" value = "super-secret-value" key_vault_id = azurerm_key_vault.main.id # Explicit: can't create secret until access policy exists depends_on = [ azurerm_key_vault_access_policy.terraform ] }

When to Use depends_on

  • Access policies before secrets
  • Role assignments before usage
  • DNS zones before records in other modules

Best Practice

  • Prefer implicit dependencies (references)
  • Use depends_on only when necessary
  • Document why the explicit dep exists

Resource Lifecycle Rules

Control how Terraform manages resource changes.

resource "azurerm_kubernetes_cluster" "main" { # ... configuration ... lifecycle { ignore_changes = [ default_node_pool[0].node_count # managed by autoscaler ] prevent_destroy = true # safety net for production } }
RulePurpose
ignore_changesDon't update this attribute even if it changes outside Terraform
prevent_destroyError if Terraform tries to destroy this resource
create_before_destroyCreate replacement before destroying original
replace_triggered_byForce replacement when referenced values change

Putting It All Together

Our AKS platform foundation so far

# Resource dependency graph for our AKS platform: azurerm_resource_group.aks ├── azurerm_virtual_network.main │ ├── azurerm_subnet.aks_nodes │ │ └── azurerm_subnet_network_security_group_association.aks │ ├── azurerm_subnet.aks_pods │ └── azurerm_subnet.appgw ├── azurerm_network_security_group.aks ├── azurerm_storage_account.main │ └── azurerm_storage_container.tfstate ├── azurerm_key_vault.main │ ├── azurerm_key_vault_access_policy.terraform │ └── azurerm_key_vault_secret.db_password └── azurerm_dns_zone.main └── azurerm_dns_a_record.app

Visualizing Dependencies

# Generate a dependency graph in DOT format $ terraform graph | dot -Tpng > graph.png # Or view it in the terminal $ terraform graph

Knowledge Check 4

1. What is the difference between a data source and a resource?

2. When should you use depends_on?

3. What does lifecycle { prevent_destroy = true } do?

Azure + Terraform Best Practices

Naming Conventions

  • rg-{workload}-{env}
  • vnet-{workload}
  • snet-{purpose}
  • nsg-{purpose}
  • kv-{workload}-{seq}

Tagging Strategy

  • Environment (dev/staging/prod)
  • ManagedBy (Terraform)
  • CostCentre
  • Team / Owner
  • Project

Module 2 Summary

What We Built

  • Provider setup with features block
  • Resource Groups with consistent tagging
  • VNet + Subnets sized for AKS
  • NSGs with security rules
  • Storage, Key Vault, DNS
  • Data sources and dependencies

Next: Module 3

  • AKS cluster provisioning
  • Node pools and scaling
  • Networking choices (CNI vs kubenet)
  • Identity and ACR integration
  • Monitoring with Log Analytics
Module 2 Complete
← Back