Over the years, IP Address Management (IPAM) is one area that I’ve often seen challenges within client environments. Many environments do not have dedicated IPAM services from companies such as Infoblox, so they’ve had no real way to manage assignments. Instead, they’ve often relied on manual tasks to find free blocks and input them verbosely, or funky workarounds to try and define the next free block.

Whilst neither of these are the end of the world, manual tasks are not ideal, and some of the inventive ways I’ve seen to try and imitate IPAM are likely to result in race conditions and challenges.

This is always an area Azure has lacked functionality in, however as of the backend of last year, this functionality is now available in preview as part of the Network Manager toolset - https://learn.microsoft.com/en-us/azure/virtual-network-manager/concept-ip-address-management.


The Challenge

If you are aligning to the ELZ architecture in Azure, you will have a hub and spoke type model, where your spoke VNets are peered to a central hub for connectivity and security. This means that each spoke VNet is going to require unique IP addressing that does not clash. Without IPAM, someone is going to have to work out what that free IP space is going to be, and then manually input it into whatever “vending” code they have.

Alternatively, there are open-source alternatives such as NetBox or https://azure.github.io/ipam/. NetBox gives you a lot more than just IPAM, and we aren’t going to get into that today, and Azure IPAM is an open-source application developed by Microsoft that will either run as an App Service or Function App. I’ve not used it personally, but I know others have. Ideally, I don’t want to have to deploy an entire application just for this one purpose.


Network Manager and IPAM

Network Manager is a management tool in Azure that provides a centralised way to manage network configurations and security policies across multiple virtual networks and subscriptions. It is something I’ve always known about, but never used, probably as a result of a lot of my work being Infrastructure as Code driven. Nothing has changed here - I’ve still not used it in anger (except for IPAM!) and is one of those things that will have to go on my ever growing “must play with” list.

IPAM functionality has now been added to this as a preview feature, and as you’d expect, it allows you to define pools of IP addressing in a hierarchical nature, that you can then assign to your VNets (or other resources) as you need! If an associated VNet gets deleted, the IPAM will automatically release the IP addresses back to the pool.


Let’s Build an Example

Many of the settings we will use are not available in the regular azurerm provider at the time of writing (due to the preview status), so we will need to use the azapi provider to create our resources.


Addressing Scheme

For this example, we will use the following example addressing scheme:

Region Shortname Description CIDR Block Future Growth
UK South sbx Sandbox 10.0.0.0/16 10.1.0.0/16
UK South dev Development 10.2.0.0/16 10.3.0.0/16
UK South tst Testing 10.4.0.0/16 10.5.0.0/16
UK South stg Staging 10.6.0.0/16 10.7.0.0/16
UK South prd Production 10.8.0.0/16 10.9.0.0/16
UK West sbx Sandbox 10.16.0.0/16 10.17.0.0/16
UK West dev Development 10.18.0.0/16 10.19.0.0/16
UK West tst Testing 10.20.0.0/16 10.21.0.0/16
UK West stg Staging 10.22.0.0/16 10.23.0.0/16
UK West prd Production 10.24.0.0/16 10.25.0.0/16

A couple of thoughts on the way I like to approach addressing schemes.

  • One large CIDR per region - in this instance I have a /12 for each region (10.0.0.0/12, 10.16.0.0/12). This allows for a lot of flexibility in the future. I’d always advise allocating as much as you can. No one’s ever been shot for having too much IP space!
  • I then create a subnet per environment type within each region. My logic here, is it is nice and easy on a central firewall to have a rule right at the top of the rule base that prevents different environment types from talking to each other. This will keep your security people happy!
  • Keep some addressing spare for future growth. A /16 may seem huge now (and indeed - it should be, I don’t often create subnets this large!), but it is amazing how quickly you can run out of IPs sometimes. By leaving the adjacent block free, we could just change the /16 to a /15 and have a lot more space.

In summary - go as big as you can on addressing. Your future self will thank you for it!


Build the IPAM

We are going to create a Network Manager to manage IP pools for both regions in a hierarchical fashion. I’ve provided some rough and ready Terraform code below!

As mentioned, we have to use the azapi provider to create the IPAM pools, as these are not yet supported in the azurerm provider.

Network Managers have a “scope” that determines the boundary of their management. Realistically, this would be a management group, but for my purposes I’m just scoping it to my current test subscription.

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "4.15.0"
    }
    azapi = {
      source  = "Azure/azapi"
      version = "2.1.0"
    }
  }
}

provider "azurerm" {
  features {}
}

provider "azapi" {}

locals {
  regions = {
    uksouth = {
      shortcode = "uks"
      cidr = "10.0.0.0/12"
      environments = {
        sbx = { cidr = "10.0.0.0/16" }
        dev = { cidr = "10.2.0.0/16" }
        tst = { cidr = "10.4.0.0/16" }
        stg = { cidr = "10.6.0.0/16" }
        prd = { cidr = "10.8.0.0/16" }
      }
    }
    ukwest = {
      shortcode = "ukw"
      cidr = "10.16.0.0/12"
      environments = {
        sbx = { cidr = "10.16.0.0/16" }
        dev = { cidr = "10.18.0.0/16" }
        tst = { cidr = "10.20.0.0/16" }
        stg = { cidr = "10.22.0.0/16" }
        prd = { cidr = "10.24.0.0/16" }
      }
    }
  }

  environment_pools = toset(flatten(
    [
      for region_k, region_v in local.regions : [
        for env_k, env_v in region_v.environments : {
          unique_key = "${region_k}_${env_k}"
          region      = region_k
          environment = env_k
          cidr        = env_v.cidr
        }
      ]
    ]
  ))
}

data "azurerm_subscription" "current" {}

resource "azurerm_resource_group" "this" {
  name     = "rg-ipam-uks"
  location = "uksouth"
}

resource "azurerm_network_manager" "this" {
  name                = "netman-ipam"
  location            = "uksouth"
  resource_group_name = azurerm_resource_group.this.name
  scope {
    subscription_ids = [data.azurerm_subscription.current.id]
  }
  scope_accesses = ["Connectivity", "Routing", "SecurityAdmin"]
}

resource "azapi_resource" "regional_pools" {
  for_each   = local.regions
  type       = "Microsoft.Network/networkManagers/ipamPools@2024-05-01"
  name       = "pool-ipam-${each.key}"
  location   = each.key
  parent_id  = azurerm_network_manager.this.id
  body = {
    properties = {
      addressPrefixes = [each.value.cidr]
      description    = "${each.key} regional pool"
      displayName    = "${each.key} regional pool"
    }
  }
}

resource "azapi_resource" "environment_pools" {
  for_each  = { for entry in local.environment_pools : entry.unique_key => entry }
  type      = "Microsoft.Network/networkManagers/ipamPools@2024-05-01"
  name      = "pool-ipam-${each.value.region}-${each.value.environment}"
  location  = each.value.region
  parent_id = azurerm_network_manager.this.id
  body = {
    properties = {
      addressPrefixes = [each.value.cidr]
      description    = "${each.value.region} ${each.value.environment} environment pool"
      displayName    = "${each.value.region} ${each.value.environment} environment pool"
      parentPoolName = azapi_resource.regional_pools[each.value.region].name
    }
  }
}

Once built, we have some IPAM pools in each region that are ready to be used.

IPAM Pools

That’s a lot of free address space!


Consuming the IPAM Services

To consume the IPAM services, we can reference the pools when we are creating our VNets. Again, this is not currently supported in the azurerm provider, so we will have to use the azapi provider to create our VNets.

I’ve just hardcoded things such as the environment and the number of IPs, but obviously you could just add in some string interpolation etc. to ensure the right values are selected.

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "4.15.0"
    }
    azapi = {
      source  = "Azure/azapi"
      version = "2.1.0"
    }
  }
}

provider "azurerm" {
  features {}
}

provider "azapi" {}

locals {
    regions = {
        uksouth = { shortname = "uks" }
        ukwest = { shortname = "ukw" }
    }
}

resource "azurerm_resource_group" "vnet" {
  for_each = local.regions
  name     = "rg-myproject-network-${each.value.shortname}-prd"
  location = each.key
}


resource "azapi_resource" "prd_vnet" {
for_each = local.regions
  type      = "Microsoft.Network/virtualNetworks@2024-05-01"
  name      = "vnet-myproject-${each.value.shortname}-prd"
  location  = each.key
  parent_id = azurerm_resource_group.vnet[each.key].id
  body = {
    properties = {
      addressSpace = {
        ipamPoolPrefixAllocations = [
          {
            pool = {
              id = "/subscriptions/165cba3b-8642-4aa1-bbab-35e1140dd81b/resourceGroups/rg-ipam-uks/providers/Microsoft.Network/networkManagers/netman-ipam/ipamPools/pool-ipam-${each.key}-prd"
            }
            numberOfIpAddresses = "256"
          }
        ]
      }
    }
  }
}

Once the VNet has been created, we can now see their assignment in the relevant IPAM pools.

UK South Allocation

UK West Allocation


Cost

Unfortunately, nothing in life is free! The regular pricing for network manager in UK South is £0.08 per hour, per subscription managed.

At £0.08 per hour, this works out to £58.03 per month, per subscription managed (assuming 730 hours). Whilst it may not seem bank-breaking on the face of it, this could add up when managing lots of environments over time, especially where you tend to have a subscription per environment.

Potentially not the most cost effective solution if all you are using it for is IPAM.


Summary

Whilst still in preview, this is a great addition to Azure, and I am looking forward to using it with clients (if we can justify the cost). This will make IP addressing a tonne easier when deploying environments and prevent us from needing to rely on human logic to find free blocks.