10/21/2025

Building Your Production-Ready Azure Landing Zone with Terraform and Azure DevOps




A well-architected Azure environment is crucial for any cloud adoption effort. This guide outlines a production-ready Azure Landing Zone implemented entirely as code, providing a secure, scalable, and automated foundation for your workloads. We'll leverage Terraform for Infrastructure as Code (IaC) and Azure DevOps for a robust Continuous Integration/Continuous Deployment (CI/CD) pipeline.

Github: https://github.com/dhanuka84/azure_landing_zone_project

✅ Azure Landing Zone Hierarchy

🔹 Billing and Identity

  • Billing Account → Single top-level EA or MCA. (an Enterprise Agreement (EA), which is a traditional, multi-year contract for large enterprises, or a Microsoft Customer Agreement (MCA), which is a more modern, flexible, and digital pay-as-you-go model)

  • Microsoft Entra ID Tenant → Hosts users, groups, and 3 app registrations:

    • app-cicd-dev, app-cicd-qa, app-cicd-prod.

🔹 Management Group Hierarchy

Tenant Root MG

├── Platform MG

│   └── Subscription: Connectivity

│       └── rg-platform-connectivity

│           ├── vnet-hub-weu

│           │   ├── AzureFirewallSubnet

│           │   ├── GatewaySubnet

│           │   └── AzureBastionSubnet

│           └── Private DNS Zones

├── Non-Production MG

│   ├── Subscription: Dev Environment

│   │   └── rg-dev-user-a

│   │       └── vnet-dev-spoke (peered with hub)

│   └── Subscription: QA Environment

│       └── rg-qa-app-services

│           └── vnet-qa-spoke (peered with hub)

└── Production MG

    └── Subscription: Prod Environment

        └── rg-prod-app-services

            ├── vnet-prod-spoke (peered with hub)

            ├── aks-prod-main

            ├── acr-prod-main

            └── kv-prod-secrets


🔹 Networking Model

  • Hub VNet (vnet-hub-weu) in Connectivity subscription.

  • Spoke VNets (vnet-dev-spoke, vnet-qa-spoke, vnet-prod-spoke) in each environment subscription.

  • Peering from each spoke to Hub.

  • UDR in spokes forcing outbound traffic via Azure Firewall in Hub.

  • Private DNS zones hosted centrally in Hub.

🔹 CI/CD (Azure DevOps)

  • Uses 3 service connections:

    • azrm-platform → Connectivity subscription

    • azrm-nonprod → Dev + QA subscriptions

    • azrm-prod → Production subscription

  • Stages: platform_mg → platform_connectivity → env_dev → env_qa → env_prod


🧭 1. The Mental Model: What We’re Building


Governance

  • Management Groups
    PlatformNon-ProductionProduction
    Apply policies, logging, and guardrails at each level.


Networking


https://dhanuka84.blogspot.com/p/azure-landing-zone.html


  • Hub-and-Spoke Topology

    • Hub VNet (vnet-hub-weu) → Shared services:

      • AzureFirewallSubnet with Azure Firewall

      • GatewaySubnet reserved for VPN/ExpressRoute Gateway

      • AzureBastionSubnet for secure RDP/SSH

      • Private DNS Zones for Key Vault, ACR etc.

    • Spoke VNets (vnet-dev-spoke, vnet-qa-spoke, vnet-prod-spoke) peer back to the hub.

    • UDRs route 0.0.0.0/0 through the firewall for outbound inspection.

Subscriptions

  • Connectivity → Hub + shared infra

  • Dev, QA, Prod → isolated spokes


🧱 2. Identity and Security – Microsoft Entra ID


https://dhanuka84.blogspot.com/p/azure-entra-id.html




Identity

Purpose

Access

app-cicd-pipeline

Azure DevOps pipelines

Contributor (Dev/QA), AKS Admin (Prod)

key-vault-api

Application workloads

Key Vault + ACR access

app-monitor

Monitoring/alerting

Reader

Terraform assigns RBAC roles declaratively:

module "rbac" {

  assignments = [

    {

      scope_id           = module.acr.id

      role_definition    = "AcrPush"

      principal_objectId = var.spn_app_cicd_prod

    },

    {

      scope_id           = module.kv.id

      role_definition    = "Key Vault Secrets User"

      principal_objectId = var.spn_key_vault_api_prod

    }

  ]

}



🧩 3. The Project Layout

infra/

├─ modules/                 # Reusable IaC building blocks

│  ├─ networking-hub/

│  ├─ azure-firewall/

│  ├─ udr/

│  ├─ networking-spoke/

│  ├─ aks/ acr/ keyvault/ private-endpoint/ rbac/

├─ platform/

│  ├─ mg/                   # Management Groups & Policy

│  └─ connectivity/          # Hub + Firewall + DNS

├─ envs/

│  ├─ dev/ qa/ prod/         # Spoke VNets, AKS, etc.

└─ pipelines/

   └─ azure-pipelines.yml    # CI/CD definition



Module Directory

Primary Azure Resource

Purpose & Key Functionality

networking-hub/

azurerm_virtual_network

Deploys the central Hub VNet (e.g., vnet-hub-weu) and its essential subnets: AzureFirewallSubnet, GatewaySubnet, and AzureBastionSubnet. It also provisions Private DNS Zones for private linking services like Key Vault and ACR.

azure-firewall/

azurerm_firewall

Deploys the Azure Firewall and its required Public IP address into the AzureFirewallSubnet of the Hub VNet. It outputs the Firewall's private IP address for use in routing tables.

networking-spoke/

azurerm_virtual_network

Deploys an isolated Spoke VNet and its user-defined subnets. Critically, it creates bi-directional VNet Peering to the central Hub VNet, enabling traffic to traverse the hub.

udr/

azurerm_route_table

Creates and manages User-Defined Routes (UDRs). Its primary role is to add a default route (0.0.0.0/0) to the route table, pointing all outbound traffic to the Azure Firewall's private IP. This route table is then associated with the specified spoke subnets (e.g., snet-aks-nodes in Prod)2.

aks/

azurerm_kubernetes_cluster

Deploys a secure Azure Kubernetes Service (AKS) cluster. It integrates the default node pool with a dedicated spoke subnet ID (var.subnet_id) and enables Azure AD RBAC for centralized identity management.

acr/

azurerm_container_registry

Deploys an Azure Container Registry (ACR), defaulting to the Premium SKU for geo-replication and private link support. It enforces security by setting admin_enabled to false.

keyvault/

azurerm_key_vault

Deploys a hardened Azure Key Vault. It is configured by default to enforce Purge Protection and disable public network access (false), requiring all access through a Private Endpoint for a secure network boundary.

private-endpoint/

azurerm_private_endpoint

Creates a Private Endpoint for a target resource (like ACR or Key Vault) within a specified spoke subnet. This enables secure, private communication between the VNet and Azure PaaS services, bypassing the public internet3.

rbac/

azurerm_role_assignment

Manages Role-Based Access Control (RBAC) assignments. It declaratively applies roles (e.g., AcrPush, Key Vault Secrets User) to Service Principals over specific resource scopes (e.g., module.acr.id, module.kv.id)4444.




⚙️ 4. Step-by-Step Deployment

Step 1 – Remote State

az group create -n rg-tfstate -l westeurope

az storage account create -n saterraformstate123 -g rg-tfstate \

  --sku Standard_LRS --encryption-services blob

az storage container create -n tfstate --account-name saterraformstate123


Record these for the variable group.


Step 2 – Azure DevOps Setup

Service Connections

  • azrm-platform → Connectivity subscription

  • azrm-nonprod → Dev + QA

  • azrm-prod → Production

Variable Group vg-terraform

TF_STATE_RG=rg-tfstate

TF_STATE_SA=saterraformstate123

TF_STATE_CONTAINER=tfstate

TF_STATE_KEY_PLATFORM_MG=platform_mg.tfstate

TF_STATE_KEY_CONNECTIVITY=connectivity.tfstate

TF_STATE_KEY_DEV=dev.tfstate

TF_STATE_KEY_QA=qa.tfstate

TF_STATE_KEY_PROD=prod.tfstate



Step 3 – Run the Pipeline

In Pipelines → New Pipeline
Choose existing YAML → /infra/pipelines/azure-pipelines.yml

Stages executed:

Stage

Purpose

platform_mg

Create Management Groups

platform_connectivity

Deploy hub VNet, Firewall, DNS

env_dev

Deploy Dev spoke + UDR

env_qa

Deploy QA spoke + ACR + UDR

env_prod

Manual approval → deploy Prod AKS/ACR/KV/PEs + UDR


🌐 5. Inspecting the Hub

After the pipeline:

rg-platform-connectivity

 ├─ vnet-hub-weu

 │   ├─ AzureFirewallSubnet  → Azure Firewall

 │   ├─ GatewaySubnet        → (optional) VPN/ER Gateway

 │   └─ AzureBastionSubnet   → Bastion

 └─ Private DNS Zones


Tip: you can extend the hub with:

resource "azurerm_virtual_network_gateway" "vpngw" { … }


to enable hybrid connectivity via the existing GatewaySubnet.


🧠 6. Environment Spokes

Each environment’s main file composes modules.
For example, Prod creates:

  • Spoke VNet + subnets

  • AKS cluster (aks-prod-main)

  • ACR (acrprodmain)

  • Key Vault (kv-prod-secrets)

  • Private Endpoints

  • UDR routing → Firewall


✅ 7. Validate Deployment

az network vnet list -g rg-platform-connectivity -o table

az network firewall show -n afw-hub -g rg-platform-connectivity

az aks get-credentials -n aks-prod-main -g rg-prod-app-services


Confirm traffic between spokes routes through the firewall (check UDRs).


🧱 8. Extending the Landing Zone

  • Add Azure Policy Assignments to enforce tagging, TLS, logging.

  • Add Log Analytics workspace and diagnostics for Firewall/AKS.

  • Implement Private DNS zone links for name resolution across spokes.

  • Enable ExpressRoute Gateway using the existing GatewaySubnet.


🏁 Conclusion

You now have a production-ready Azure Landing Zone implemented entirely as code:

  • Governance with Management Groups

  • Secure hub with Firewall + UDRs

  • Isolated spokes for Dev/QA/Prod

  • Automated CI/CD via Azure DevOps

  • Identity and RBAC managed by Entra ID

This foundation scales as you onboard new workloads—just add new spokes or modules without breaking compliance or security boundaries.



No comments:

Post a Comment