IPAM in Bicep: je spoke-VNet krijgt zijn adresruimte uit een pool

Bijgewerkt

Elke nieuwe spoke in een landing zone begint met dezelfde vraag: welke adresruimte is nog vrij? Het antwoord staat meestal in een spreadsheet. Met Azure IPAM en de nieuwste versie van de AVM-module voor virtual networks hoeft dat niet meer. Je vraagt een aantal adressen aan, en Azure kiest een vrije range uit een centrale pool.

Het adresplan in Excel

Je kent het vast. Er is een adresplan, ooit netjes opgezet. Een /16 voor Azure, verdeeld in blokken voor hub, spokes en reserve. Het staat in Excel of op een wikipagina.

Dan komt er een nieuwe spoke. Iemand zoekt een vrij blok, zet het in de Bicep-parameters en werkt de spreadsheet bij. Of vergeet dat laatste. Een half jaar later blijken twee spokes dezelfde range te hebben. Peering lukt niet, of een route naar on-premises gaat de verkeerde kant op.

Het probleem is niet dat mensen slordig zijn. Het probleem is dat de bron van waarheid buiten Azure staat. De code weet niet welke ranges al bezet zijn, de spreadsheet weet niet wat er echt is uitgerold.

Wat Azure IPAM doet

IPAM (IP address management) is een onderdeel van Azure Virtual Network Manager. Je maakt een Network Manager aan, en daarin IP-adrespools. Een pool is een container met een of meer CIDR-blokken. Azure houdt bij welke delen van die blokken in gebruik zijn.

Pools vormen een hiërarchie:

  • Rootpool: de eerste pool, met je hele adresruimte. Bijvoorbeeld 10.100.0.0/16.
  • Childpool: een deel van de rootpool of van een andere childpool. Bijvoorbeeld een pool voor spokes en een pool per regio. Je kunt tot zeven lagen diep gaan.

Uit een pool kun je op twee manieren adresruimte toewijzen:

  • Static CIDR: je reserveert zelf een vaste range. Dat is bedoeld voor ruimte buiten Azure, zoals on-premises, of voor Azure-onderdelen die IPAM niet ondersteunt, zoals een Virtual WAN-hub.
  • Toewijzing aan een resource: een VNet vraagt een aantal adressen aan, bijvoorbeeld 1024. IPAM kiest zelf een vrije, niet-overlappende range van die grootte. Dit noem ik in dit artikel de dynamische manier.

Die tweede manier is de winst. Je schrijft in je code niet meer 10.100.68.0/22, maar “geef me 1024 adressen uit de spoke-pool”. IPAM is daarmee de bron van waarheid. In de portal zie je per pool hoeveel ruimte bezet is, en door wie.

IPAM is algemeen beschikbaar in alle regio’s met Virtual Network Manager, op een paar na (onder andere Qatar Central en South Africa West). Microsoft Learn heeft de actuele lijst.

Wat er in AVM ontbrak, en wat PR #7271 toevoegt

De AVM-module avm/res/network/virtual-network ondersteunt IPAM sinds mei 2025 (PR #5176). Maar dat werkte met een omweg. Je zette de resource-ID van de pool als eerste item in addressPrefixes, een lijst die eigenlijk voor CIDR-blokken is. Het aantal adressen gaf je op in een aparte parameter, ipamPoolNumberOfIpAddresses. De module herkende de pool aan de tekst /Microsoft.Network/networkManagers/ in het eerste item.

Dat werkt, maar het heeft beperkingen. Je kunt maar één pool opgeven. Een resource-ID in een lijst met CIDR’s leest verwarrend. En er was een issue (#6695) dat een bestaand VNet niet aan een pool te koppelen was.

PR #7271 lost dat op. De PR is op 20 augustus 2026 gemerged en zit in versie 0.10.2 van de module. Dit verandert er:

  • Er is een eigen parameter ipamPoolPrefixAllocations. Het type komt rechtstreeks uit de Azure-API (Microsoft.Network/virtualNetworks@2025-05-01), dus je krijgt dezelfde structuur als in een gewone Bicep-resource: een pool.id en een numberOfIpAddresses.
  • addressPrefixes is niet langer verplicht. Je gebruikt het een of het ander, niet allebei. Volgens de module staat Azure niet toe dat je vaste prefixen en pool-toewijzingen mengt in dezelfde adresruimte.
  • De oude manier blijft werken. De wijziging is backwards compatible.
  • De moduletest koppelt nu ook een bestaand VNet aan een pool.

Subnetten konden al langer hun ruimte uit een pool halen, via subnets[].ipamPoolPrefixAllocations. Nu kan het hele VNet dat ook netjes.

Praktijkvoorbeeld: een spoke uit de pool

Hieronder een spoke-VNet via de AVM-module. Het krijgt 1024 adressen uit een bestaande spoke-pool, en twee subnetten die hun ruimte ook uit die pool halen. De Network Manager en de pool zelf laat ik buiten beschouwing; de spoke krijgt alleen de resource-ID van de pool mee als parameter.

Ik heb het bestand gecontroleerd met bicep build en bicep lint, inclusief de typecontrole tegen de module uit de registry. Uitgerold op een echte subscriptie heb ik dit voorbeeld niet; doe dat eerst in een testomgeving.

spoke.bicep, het VNet via AVM:

// Spoke VNet whose address space comes from an IPAM pool instead of a hardcoded CIDR.
param location string = resourceGroup().location
param name string
param ipamPoolId string

@description('Size of the VNet in IP addresses, as a string (e.g. 1024 = /22).')
param numberOfIpAddresses string = '1024'

module virtualNetwork 'br/public:avm/res/network/virtual-network:0.10.2' = {
  params: {
    name: name
    location: location
    // Since 0.10.2: allocate from a pool. Do not set addressPrefixes as well.
    ipamPoolPrefixAllocations: [
      {
        pool: {
          id: ipamPoolId
        }
        numberOfIpAddresses: numberOfIpAddresses
      }
    ]
    // Subnets also take their size from the pool, within the VNet allocation.
    subnets: [
      {
        name: 'snet-app'
        ipamPoolPrefixAllocations: [
          {
            pool: {
              id: ipamPoolId
            }
            numberOfIpAddresses: '256'
          }
        ]
      }
      {
        name: 'snet-data'
        ipamPoolPrefixAllocations: [
          {
            pool: {
              id: ipamPoolId
            }
            numberOfIpAddresses: '128'
          }
        ]
      }
    ]
  }
}

output virtualNetworkId string = virtualNetwork.outputs.resourceId

Let op twee dingen. numberOfIpAddresses is een string, geen getal. En nergens in de spoke staat een CIDR. Welke /22 de spoke krijgt, bepaalt IPAM.

In een echte landing zone staan de Network Manager en de spokes meestal in verschillende subscripties. Dan moeten de spoke-subscripties in de scope van de Network Manager vallen (networkManagerScopes), via losse subscripties of een management group.

Aandachtspunten

Rechten. Wie pools aanmaakt en beheert, heeft de rol Network Contributor op de Network Manager nodig. Wie een VNet uit een pool laat toewijzen, zoals de identity van je spoke-pipeline, heeft genoeg aan de rol IPAM Pool User op de pool. Die rol mag ruimte toewijzen, maar de pool niet wijzigen. Ziet die identity de pool of de VNets niet, voeg dan Network Manager Read toe op de Network Manager.

Scope. Een VNet kan alleen uit een pool tekenen als zijn subscriptie binnen de scope van de Network Manager valt. Krijg je een fout bij de eerste spoke in een nieuwe subscriptie, kijk dan eerst daar.

API-versies. De module gebruikt Microsoft.Network/virtualNetworks@2025-05-01. Voor Microsoft.Network/networkManagers/ipamPools is 2025-09-01 de nieuwste versie. De oudste niet-previewversie in de referentie is 2024-05-01. In het voorbeeld gebruik ik 2025-05-01, net als de module en zijn test. Eén of twee pools per VNet: Microsoft noemt maximaal één IPv4- en één IPv6-pool.

Redeploy. De AVM-test rolt de module twee keer achter elkaar uit, een keer om aan te maken en een keer om te controleren dat er niets verandert. Houd numberOfIpAddresses dus stabiel. Wat er gebeurt als je de grootte van een bestaand VNet verandert, heb ik niet getest. Doe dat eerst in een testomgeving.

De toegewezen range zie je pas na de deployment. De module (0.10.2) geeft geen output met de adresruimte van het VNet. Regels die een CIDR nodig hebben, zoals firewallregels of routes, kun je dus niet vooraf invullen. De toewijzing zie je in de portal bij de pool, onder Allocations.

Subnetten. Je weet vooraf niet welke range het VNet krijgt. Vaste subnetprefixen zijn dan onhandig. Laat subnetten daarom ook uit de pool tekenen, zoals in het voorbeeld en in de moduletest. Zorg dat de subnetten samen binnen de grootte van het VNet passen.

Bestaande VNets. De moduletest laat zien hoe je een bestaand VNet koppelt. Het VNet bestaat al met een gewone /24 binnen de pool-range. Daarna rolt de module het opnieuw uit met alleen ipamPoolPrefixAllocations en 256 adressen, dezelfde grootte. Ik zou de bestaande range binnen de pool laten vallen en de grootte gelijk houden. Test dit eerst op een niet-productie-VNet, want je raakt de adresruimte van een draaiend netwerk aan.

IPAM als bron van waarheid

De echte winst zit niet in één spoke. Die zit erin dat IPAM de plek wordt waar je adresruimte wordt beheerd, voor alles.

Niet alles in een landing zone wordt met Bicep beheerd. Vaak beheert een netwerkteam de hub met Azure Firewall in Terraform. Daar zijn twee nette oplossingen voor:

  • Reserveer de range met een static CIDR in de rootpool. Het Terraform-team gebruikt die range, en IPAM geeft hem nooit aan een spoke.
  • Laat Terraform zelf uit de pool tekenen. De IPAM-resources en de ipamPoolPrefixAllocations van een VNet zijn gewone Azure-API’s. Met de AzAPI-provider kun je ze ook vanuit Terraform gebruiken; Microsoft Learn geeft voor de pools en static CIDR’s AzAPI-voorbeelden.

Hetzelfde geldt voor on-premises ranges, VPN-klanten of een Virtual WAN-hub. Zet ze als static CIDR in de rootpool. Dan kan IPAM ze nooit per ongeluk aan een spoke geven, en staat je hele adresplan op één plek.

De spreadsheet kan dan weg. Of beter: hij wordt een rapport dat je uit IPAM haalt, in plaats van een lijst die je met de hand bijhoudt.

Zoek je iets lichters, kijk dan ook naar Azure IPAM, een open-sourceproject van Microsoft (MIT-licentie). Het is een kleine app die je zelf in je tenant draait, op App Service of Azure Functions met Cosmos DB. Je krijgt een webinterface en een REST API om adresruimte te plannen en te reserveren, en het vindt zelf de bestaande netwerken in je tenant. Het verschil met Virtual Network Manager: het is geen onderdeel van Azure Resource Manager. Je roept de API dus aan vanuit je pipeline, in plaats van ipamPoolPrefixAllocations in Bicep. En je beheert de app zelf; ondersteuning loopt via GitHub, niet via Microsoft Support.

Conclusie

Handmatig CIDR-beheer schaalt niet. Elke nieuwe spoke is handwerk, en elke vergeten regel in de spreadsheet is een overlap die later pijn doet. Azure IPAM maakt de adresruimte centraal beheerbaar, en sinds versie 0.10.2 van avm/res/network/virtual-network vraag je die ruimte netjes aan met ipamPoolPrefixAllocations.

Mijn advies: begin bij de volgende nieuwe spoke. Zet een rootpool en een spoke-pool op, reserveer je hub en on-premises ranges als static CIDR, en laat die ene spoke zijn adresruimte uit de pool halen. Werkt dat, migreer dan de bestaande spokes één voor één, eerst in een testomgeving.

Wil je weten hoe dit in jouw landing zone past? Neem contact op, dan kijken we er samen naar.

Bronnen