Automatic Virtual Network CIDR Assignments With Azure IPAM and Bicep
Recently Microsoft announced a public preview of native IP Address Management in Azure, powered by Virtual Network Manager. Being new technology, and with a new landing zone to build, I decided to test and see if we could use it to make IP management simpler.
The starting point was to completely miss the documentation and try and work it out myself. Sadly, the API documentation has yet to be updated to cover the new properties, and tracing the portal requests didn’t help either, since it uses a slightly different flow (sigh!).
But in the end we got there.
The first step is to create a Virtual Network Manager, since IPAM is a feature. For demo purposes, let’s create one and assign it to the Tenant Root Scope management group so it can manage all our resources. For larger orgs, this will likely be scoped to the intermediate root of a full landing zone as required.
resource networkManager 'Microsoft.Network/networkManagers@2024-03-01' = {
name: 'NetworkManager'
location: resourceGroup().location
properties: {
networkManagerScopes: {
managementGroups: [
'/providers/Microsoft.Management/managementGroups/${tenant().tenantId}'
]
}
networkManagerScopeAccesses: []
}
}
After creating the Network Manager, we now create our various IP Pools.
resource azurePool 'Microsoft.Network/networkManagers/ipamPools@2024-01-01-preview' = {
parent: networkManager
name: 'Azure'
location: resourceGroup().location
properties: {
displayName: 'Azure'
description: 'Allocated IP range for ${resourceGroup().location}'
addressPrefixes: [
'172.24.0.0/13'
]
}
}
resource regionalPool 'Microsoft.Network/networkManagers/ipamPools@2024-01-01-preview' = {
parent: networkManager
name: resourceGroup().location
location: resourceGroup().location
properties: {
displayName: resourceGroup().location
description: 'Allocated IP range for ${resourceGroup().location}'
addressPrefixes: [
cidrSubnet(azurePool.properties.addressPrefixes[0], 16, 0)
]
parentPoolName: azurePool.name
}
}
resource corePool 'Microsoft.Network/networkManagers/ipamPools@2024-01-01-preview' = {
parent: networkManager
name: 'CoreNetwork-${resourceGroup().location}'
location: resourceGroup().location
properties: {
displayName: 'Core Network'
description: 'Allocated IP range for Core Network in ${resourceGroup().location}'
addressPrefixes: [
cidrSubnet(regionalPool.properties.addressPrefixes[0], 20, 0)
]
parentPoolName: regionalPool.name
}
}
First, we create a pool to cover all of Azure, using the 172.24.0.0/13
range.
From this, we allocate the first /16
to our deployment region using Bicep’s cidrSubnet()
function, and then we allocate the first /20
of the regional allocation for our core network IP addresses.
This gives us a layout like this in IPAM (with the addition of allocations for a second region):
At this point, we probably need to create a static reservation for our Virtual WAN, since it doesn’t (yet?) support fetching an allocation automatically. We also need to add our static reservations before attempting to dynamically allocate IPs, otherwise our intended allocation might be assigned before we get there.
resource vwanStaticCidr 'Microsoft.Network/networkManagers/ipamPools/staticCidrs@2024-05-01' = {
parent: corePool
name: 'VirtualHub'
properties: {
description: 'Virtual WAN Hub space allocation'
addressPrefixes: [
cidrSubnet(regionalPool.properties.addressPrefixes[0], 23, 0)
]
}
}
Having created our IP pools, we are now ready to create a virtual network. In this case, we will create one with three subnets, and ask IPAM to allocate IP ranges to everything.
resource dynamicVnet 'Microsoft.Network/virtualNetworks@2024-03-01' = {
name: 'dynamic-vnet'
location: resourceGroup().location
properties: {
addressSpace: {
ipamPoolPrefixAllocations: [
{
pool: { id: corePool.id }
numberOfIpAddresses: 500
}
]
}
}
resource subnet1 'subnets' = {
name: 'subnet1'
properties: {
ipamPoolPrefixAllocations: [
{
pool: { id: corePool.id }
numberOfIpAddresses: 5
}
]
}
}
resource subnet2 'subnets' = {
name: 'subnet2'
properties: {
ipamPoolPrefixAllocations: [
{
pool: { id: corePool.id }
numberOfIpAddresses: 100
}
]
}
}
resource subnet3 'subnets' = {
name: 'subnet3'
properties: {
ipamPoolPrefixAllocations: [
{
pool: { id: corePool.id }
numberOfIpAddresses: 39
}
]
}
}
}
The key property here is ipamPoolPrefixAllocations
.
While it is not yet documented, the two key properties are firstly the ID of the IPAM pool from which we should allocate the IP range, and then the number of IP addresses we need.
This might throw those who have a networking background - why can’t I pick a CIDR prefix?
I suspect the answer is that Microsoft are seeking to make it accessible for everyone (in the same vein that led to them adding the number of IPs in the portal creation flows).
On point to note on the numberOfIpAddresses
option - IPAM will happily allocation more than one CIDR range to get as close to your requested number of IPs as possible.
If you deploy the above code, notice that subnet3
gets allocated two CIDRs - a /27
and a /29
to provide the requested 39 IPs.
If you’re fussy on this, you may want to make this option large enough to require the CIDR you want.