Arrow keys or click to navigate
←
→
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.
Feature Setting Purpose
key_vault purge_soft_delete_on_destroy Auto-purge Key Vaults on destroy
resource_group prevent_deletion_if_contains_resources Safety check before destroying RGs
virtual_machine delete_os_disk_on_deletion Clean up disks when VMs are destroyed
log_analytics_workspace permanently_delete_on_destroy Skip 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"
}
}
Use a consistent naming convention: rg-{workload}-{region}
Tags enable cost tracking, compliance, and automation
Deleting a resource group deletes all resources inside it
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
}
address_space defines the CIDR range for the entire VNet
/16 gives 65,536 IP addresses - plan for growth
VNets are free; subnets divide the space
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
Subnet CIDR IPs Purpose
snet-aks-nodes 10.0.1.0/24 251 AKS node VMs
snet-aks-pods 10.0.2.0/22 1,021 Pod IPs (Azure CNI)
snet-appgw 10.0.8.0/24 251 Application Gateway / Ingress
snet-private-endpoints 10.0.10.0/24 251 Private 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
}
Lower priority number = higher precedence
Rules are evaluated in priority order (100-4096)
Azure has default rules (65000+) that allow VNet traffic and deny internet inbound
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 account names must be globally unique, 3-24 chars, lowercase alphanumeric only
GRS = Geo-Redundant Storage (replicated to a paired region)
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.
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"
]
}
data.azurerm_client_config.current gets the identity running Terraform
Follow the principle of least privilege
Consider using Azure RBAC mode instead of access policies for new Key Vaults
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" ]
}
Use with external-dns controller in AKS for automatic DNS record management
Supports A, AAAA, CNAME, MX, TXT, and other record types
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
}
Private AKS clusters use private DNS for API server resolution
Must link the DNS zone to the VNet
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
Data sources are read-only - they never create or modify resources
Useful for referencing shared infrastructure managed by another team
Common Azure Data Sources
Data Source Use 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
}
}
Rule Purpose
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
Terraform builds a Directed Acyclic Graph (DAG) of all resources
Resources without dependencies are created in parallel
The graph determines creation and destruction order
Use terraform graph to debug dependency issues
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