Use Case — US prod VNet in Azure

Subsidiary US (10.0.0.0/12)
Provider / region Azure · West US 2 (westus2)
Environment prod
VNet Regional (vnet-us-prod-westus2), address space 10.8.0.0/19
Subnets 4 regional tier subnets (pub, app, data, mgmt) + platform subnets
Each subnet is regional and automatically spans West US 2 zones 1, 2, and 3 — zone redundancy is achieved by deploying resources across zones (zonal VMs, zone-spread VMSS/AKS, zone-redundant services). "West US" (California) has no Availability Zones; West US 2 / West US 3 do.

1. Where this fits in the plan

US subsidiary      10.0.0.0/12
  Azure            10.8.0.0/14
    West US 2      10.8.0.0/16
      prod env     10.8.0.0/18
        VNet       10.8.0.0/19   <-- this use case

Each tier keeps the /22 block used across the AWS and GCP designs; the VNet is /19 to leave room for Azure's dedicated platform subnets.


2. Subnet plan

Tier Name CIDR Egress
Public snet-us-prod-westus2-pub 10.8.0.0/22 external LB / public IPs
App snet-us-prod-westus2-app 10.8.4.0/22 NAT Gateway
Data snet-us-prod-westus2-data 10.8.8.0/22 none (isolated)
Mgmt snet-us-prod-westus2-mgmt 10.8.12.0/22 NAT Gateway; Bastion access
Gateway GatewaySubnet 10.8.16.0/27 VPN / ExpressRoute gateway
Bastion AzureBastionSubnet 10.8.16.64/26 Azure Bastion
Firewall AzureFirewallSubnet 10.8.16.128/26 Azure Firewall

AKS on app uses CNI Overlay: pods draw from a cluster-level overlay CIDR from the enterprise 100.64.0.0/10 block — pods 100.66.0.0/16, services 100.67.0.0/20 — set at cluster creation, not on the subnet.

VNet · vnet-us-prod-westus2 · 10.8.0.0/19   (West US 2)
│   regional — every subnet below spans zones 1 · 2 · 3
│
├─ pub      10.8.0.0/22
├─ app     10.8.4.0/22     (AKS overlay: pods 100.66.0.0/16 · svcs 100.67.0.0/20)
├─ data     10.8.8.0/22
├─ mgmt     10.8.12.0/22
│
└─ platform   10.8.16.0/24
    ├─ GatewaySubnet         10.8.16.0/27
    ├─ AzureBastionSubnet    10.8.16.64/26
    └─ AzureFirewallSubnet   10.8.16.128/26
Usable hosts: Azure reserves 5 addresses per subnet, so a /22 yields 1,019 assignable addresses.

3. Routing and access design

  • pub — internet-facing via an external Load Balancer / public IPs. NSG allows 80/443 inbound and the Azure load-balancer probe.
  • app and mgmt — private; outbound through a shared NAT Gateway. Mgmt reachable only via Azure Bastion.
  • data — no NAT Gateway and an explicit outbound-internet deny in its NSG; reachable on DB ports only from app.
  • NSGs are per tier and reference the /22 tier summaries.

4. Terraform implementation

###############################################################################
# US office — Azure West US 2 — prod VNet (pub / app / data / mgmt)
#
#   US subsidiary      10.0.0.0/12
#     Azure            10.8.0.0/14
#       West US 2      10.8.0.0/16
#         prod env     10.8.0.0/18
#           VNet       10.8.0.0/19   <-- this file
#
# Azure subnets are REGIONAL and span all zones; one subnet per tier suffices.
# Zone spread is a resource property (zonal VM / zone-spread VMSS, AKS).
###############################################################################

terraform {
  required_version = ">= 1.5"
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 4.0"
    }
  }
}

variable "subscription_id" {
  type        = string
  description = "Azure subscription ID for the US prod environment"
}

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

###############################################################################
# Locals — the addressing plan
###############################################################################

locals {
  location = "westus2"

  tags = {
    subsidiary  = "us"
    provider    = "azure"
    region      = "westus2"
    environment = "prod"
    managed_by  = "terraform"
  }

  # tier key => CIDR (keys become the subnet name suffixes)
  subnets = {
    pub  = "10.8.0.0/22"
    app = "10.8.4.0/22"
    data = "10.8.8.0/22"
    mgmt = "10.8.12.0/22"
  }
}

###############################################################################
# Resource group + VNet
###############################################################################

resource "azurerm_resource_group" "net" {
  name     = "rg-us-prod-westus2-network"
  location = local.location
  tags     = local.tags
}

resource "azurerm_virtual_network" "vnet" {
  name                = "vnet-us-prod-westus2"
  location            = local.location
  resource_group_name = azurerm_resource_group.net.name
  address_space       = ["10.8.0.0/19"]
  tags                = local.tags
}

###############################################################################
# Tier subnets (one per tier)
###############################################################################

resource "azurerm_subnet" "tier" {
  for_each             = local.subnets
  name                 = "snet-us-prod-westus2-${each.key}"
  resource_group_name  = azurerm_resource_group.net.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = [each.value]
}

###############################################################################
# Platform subnets (names are fixed/required by Azure)
###############################################################################

resource "azurerm_subnet" "gateway" {
  name                 = "GatewaySubnet"
  resource_group_name  = azurerm_resource_group.net.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = ["10.8.16.0/27"]
}

resource "azurerm_subnet" "bastion" {
  name                 = "AzureBastionSubnet"
  resource_group_name  = azurerm_resource_group.net.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = ["10.8.16.64/26"]
}

resource "azurerm_subnet" "firewall" {
  name                 = "AzureFirewallSubnet"
  resource_group_name  = azurerm_resource_group.net.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = ["10.8.16.128/26"]
}

###############################################################################
# NAT Gateway — outbound for the private tiers (app, mgmt)
###############################################################################

resource "azurerm_public_ip" "nat" {
  name                = "pip-natgw-us-prod-westus2"
  location            = local.location
  resource_group_name = azurerm_resource_group.net.name
  allocation_method   = "Static"
  sku                 = "Standard"
  zones               = ["1", "2", "3"] # zone-redundant
  tags                = local.tags
}

resource "azurerm_nat_gateway" "natgw" {
  name                = "natgw-us-prod-westus2"
  location            = local.location
  resource_group_name = azurerm_resource_group.net.name
  sku_name            = "Standard"
  tags                = local.tags
}

resource "azurerm_nat_gateway_public_ip_association" "natgw" {
  nat_gateway_id       = azurerm_nat_gateway.natgw.id
  public_ip_address_id = azurerm_public_ip.nat.id
}

resource "azurerm_subnet_nat_gateway_association" "app" {
  subnet_id      = azurerm_subnet.tier["app"].id
  nat_gateway_id = azurerm_nat_gateway.natgw.id
}

resource "azurerm_subnet_nat_gateway_association" "mgmt" {
  subnet_id      = azurerm_subnet.tier["mgmt"].id
  nat_gateway_id = azurerm_nat_gateway.natgw.id
}

###############################################################################
# Network Security Groups — one per tier
###############################################################################

resource "azurerm_network_security_group" "pub" {
  name                = "nsg-us-prod-westus2-pub"
  location            = local.location
  resource_group_name = azurerm_resource_group.net.name
  tags                = local.tags

  security_rule {
    name                       = "allow-https-inbound"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_ranges    = ["80", "443"]
    source_address_prefix      = "Internet"
    destination_address_prefix = "*"
  }

  security_rule {
    name                       = "allow-lb-health-probe"
    priority                   = 110
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "*"
    source_port_range          = "*"
    destination_port_range     = "*"
    source_address_prefix      = "AzureLoadBalancer"
    destination_address_prefix = "*"
  }
}

resource "azurerm_network_security_group" "app" {
  name                = "nsg-us-prod-westus2-app"
  location            = local.location
  resource_group_name = azurerm_resource_group.net.name
  tags                = local.tags

  security_rule {
    name                       = "allow-from-public-tier"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_ranges    = ["443", "8080"]
    source_address_prefix      = "10.8.0.0/22" # pub tier
    destination_address_prefix = "*"
  }

  security_rule {
    name                       = "deny-internet-inbound"
    priority                   = 4000
    direction                  = "Inbound"
    access                     = "Deny"
    protocol                   = "*"
    source_port_range          = "*"
    destination_port_range     = "*"
    source_address_prefix      = "Internet"
    destination_address_prefix = "*"
  }
}

resource "azurerm_network_security_group" "data" {
  name                = "nsg-us-prod-westus2-data"
  location            = local.location
  resource_group_name = azurerm_resource_group.net.name
  tags                = local.tags

  security_rule {
    name                       = "allow-db-from-app"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_ranges    = ["1433", "3306", "5432"]
    source_address_prefix      = "10.8.4.0/22" # app tier
    destination_address_prefix = "*"
  }

  security_rule {
    name                       = "deny-internet-outbound"
    priority                   = 4000
    direction                  = "Outbound"
    access                     = "Deny"
    protocol                   = "*"
    source_port_range          = "*"
    destination_port_range     = "*"
    source_address_prefix      = "*"
    destination_address_prefix = "Internet"
  }
}

resource "azurerm_network_security_group" "mgmt" {
  name                = "nsg-us-prod-westus2-mgmt"
  location            = local.location
  resource_group_name = azurerm_resource_group.net.name
  tags                = local.tags

  security_rule {
    name                       = "allow-bastion-ssh-rdp"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_ranges    = ["22", "3389"]
    source_address_prefix      = "10.8.16.64/26" # AzureBastionSubnet
    destination_address_prefix = "*"
  }
}

###############################################################################
# NSG -> subnet associations
###############################################################################

resource "azurerm_subnet_network_security_group_association" "pub" {
  subnet_id                 = azurerm_subnet.tier["pub"].id
  network_security_group_id = azurerm_network_security_group.pub.id
}

resource "azurerm_subnet_network_security_group_association" "app" {
  subnet_id                 = azurerm_subnet.tier["app"].id
  network_security_group_id = azurerm_network_security_group.app.id
}

resource "azurerm_subnet_network_security_group_association" "data" {
  subnet_id                 = azurerm_subnet.tier["data"].id
  network_security_group_id = azurerm_network_security_group.data.id
}

resource "azurerm_subnet_network_security_group_association" "mgmt" {
  subnet_id                 = azurerm_subnet.tier["mgmt"].id
  network_security_group_id = azurerm_network_security_group.mgmt.id
}

###############################################################################
# Outputs
###############################################################################

output "vnet_id" {
  value = azurerm_virtual_network.vnet.id
}

output "tier_subnet_ids" {
  description = "Map of tier => subnet id"
  value       = { for k, s in azurerm_subnet.tier : k => s.id }
}

output "platform_subnet_ids" {
  value = {
    gateway  = azurerm_subnet.gateway.id
    bastion  = azurerm_subnet.bastion.id
    firewall = azurerm_subnet.firewall.id
  }
}

5. Notes

  • Zone redundancy comes from deploying across zones 1/2/3 (zonal VMs, zone-spread VM Scale Sets, AKS node pools with zones = [1,2,3], zone-redundant services); the zone-redundant NAT public IP keeps egress available during a zonal outage.
  • Splitting the app tier later. If you ever need more than one app subnet, subdivide the 10.8.4.0/22 app block into /24s (e.g. app-a 10.8.4.0/24, app-b 10.8.5.0/24) without disturbing the other tiers.
  • Hard data isolation is enforced by no NAT Gateway plus the outbound-internet deny rule; for inspected egress instead, route 0.0.0.0/0 through AzureFirewallSubnet via a UDR.
  • IPAM-driven allocation. CIDRs are explicit here for clarity; in production allocate them from your IPAM source of truth.

Read more

SOP — Create and Manage Microsoft Azure for Enterprise

Version 1.0 Date 2026-06-22 Domain mptwork.com Model Identity rooted in the existing Microsoft Entra tenant; hierarchy of Management Groups → Subscriptions → Resource Groups → Resources Consoles Azure portal portal.azure.com · Entra admin center entra.microsoft.com Companion SOPs M365/Entra — sop-subscribe-manage-m365-mptwork.md, sop-entra-id-authentication-sso-mptwork.md · Network/IPAM — enterprise-ipam-reference-architecture.md · AWS

By admin

SOP — Create and Manage a Google Cloud (GCP) Account

Version 1.0 Date 2026-06-22 Domain mptwork.com Model GCP Organization rooted in Cloud Identity / Google Workspace for mptwork.com; hierarchy of Folders → Projects; workforce access via Google Groups, optionally federated to Microsoft Entra Companion SOPs Google Workspace — sop-subscribe-manage-google-workspace-mptwork.md · Entra auth & SSO — sop-entra-id-authentication-sso-mptwork.md · Network/IPAM — enterprise-ipam-reference-architecture.md

By admin