TL;DR

AKS Terraform usually provisions a resource group, VNet/subnets, AKS cluster, node pools, managed identities, role assignments, NSGs, Log Analytics, and private DNS pieces. Watch permissions carefully: AKS often needs Network Contributor on custom network resources.

Provider And Backend

This block pins the Azure provider and stores state in Azure Blob Storage. Use it for each AKS environment root so state is centralized, access-controlled, and separated by backend key.

hclversions.tf
terraform {
  required_version = ">= 1.6.0"
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 4.0"
    }
  }
  backend "azurerm" {
    resource_group_name  = "rg-tfstate-prod"
    storage_account_name = "prodtfstate001"
    container_name       = "tfstate"
    key                  = "aks/prod.tfstate"
  }
}

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

Resource Group, VNet, NSG

This creates the Azure landing zone pieces AKS needs: a resource group, virtual network, AKS subnet, and network security group. Use this when the client wants AKS in a controlled custom network instead of defaults.

hclnetwork.tf
resource "azurerm_resource_group" "platform" {
  name     = "rg-${var.name}-${var.environment}"
  location = var.location
  tags     = var.tags
}

resource "azurerm_virtual_network" "platform" {
  name                = "vnet-${var.name}-${var.environment}"
  address_space       = ["10.50.0.0/16"]
  location            = azurerm_resource_group.platform.location
  resource_group_name = azurerm_resource_group.platform.name
}

resource "azurerm_subnet" "aks" {
  name                 = "snet-aks"
  resource_group_name  = azurerm_resource_group.platform.name
  virtual_network_name = azurerm_virtual_network.platform.name
  address_prefixes     = ["10.50.1.0/24"]
}

resource "azurerm_network_security_group" "aks" {
  name                = "nsg-aks-${var.environment}"
  location            = azurerm_resource_group.platform.location
  resource_group_name = azurerm_resource_group.platform.name
}

AKS Cluster

This AKS cluster uses system-assigned managed identity, Azure CNI networking, and Log Analytics integration. Use it as the base cluster template before adding private cluster, workload identity, or node-pool-specific settings.

hclaks.tf
resource "azurerm_kubernetes_cluster" "this" {
  name                = "aks-${var.name}-${var.environment}"
  location            = azurerm_resource_group.platform.location
  resource_group_name = azurerm_resource_group.platform.name
  dns_prefix          = "aks-${var.name}-${var.environment}"
  kubernetes_version  = var.kubernetes_version

  identity {
    type = "SystemAssigned"
  }

  default_node_pool {
    name           = "system"
    node_count     = 3
    vm_size        = "Standard_D4s_v5"
    vnet_subnet_id = azurerm_subnet.aks.id
    os_disk_size_gb = 128
  }

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

  oms_agent {
    log_analytics_workspace_id = azurerm_log_analytics_workspace.aks.id
  }
}

User Node Pool

This creates a separate user node pool for application workloads. Use user pools to isolate app capacity from the system pool and to apply different VM sizes, labels, taints, or autoscaling rules.

hclnodepool.tf
resource "azurerm_kubernetes_cluster_node_pool" "apps" {
  name                  = "apps"
  kubernetes_cluster_id = azurerm_kubernetes_cluster.this.id
  vm_size               = "Standard_D4s_v5"
  node_count            = 3
  mode                  = "User"
  vnet_subnet_id        = azurerm_subnet.aks.id

  node_labels = {
    workload = "apps"
  }
}

Managed Identity Role Assignment

When AKS uses a custom VNet, the cluster identity often needs permissions on that network resource group.

hclrole-assignment.tf
resource "azurerm_role_assignment" "aks_network_contributor" {
  scope                = azurerm_virtual_network.platform.id
  role_definition_name = "Network Contributor"
  principal_id         = azurerm_kubernetes_cluster.this.identity[0].principal_id
}

Workload Identity Shape

This maps a Kubernetes ServiceAccount subject to an Azure user-assigned identity. Use it when pods need Azure API access without storing client secrets in Kubernetes Secrets.

hclworkload-identity.tf
resource "azurerm_user_assigned_identity" "external_dns" {
  name                = "id-external-dns-${var.environment}"
  location            = azurerm_resource_group.platform.location
  resource_group_name = azurerm_resource_group.platform.name
}

resource "azurerm_federated_identity_credential" "external_dns" {
  name                = "external-dns"
  resource_group_name = azurerm_resource_group.platform.name
  parent_id           = azurerm_user_assigned_identity.external_dns.id
  audience            = ["api://AzureADTokenExchange"]
  issuer              = azurerm_kubernetes_cluster.this.oidc_issuer_url
  subject             = "system:serviceaccount:external-dns:external-dns"
}

Log Analytics

This workspace stores AKS monitoring data for Container Insights. Use it when the client wants cluster logs and metrics centralized for operations, incident review, or compliance retention.

hclmonitoring.tf
resource "azurerm_log_analytics_workspace" "aks" {
  name                = "log-${var.name}-${var.environment}"
  location            = azurerm_resource_group.platform.location
  resource_group_name = azurerm_resource_group.platform.name
  sku                 = "PerGB2018"
  retention_in_days   = 30
}