Azure Virtual Network Manager (AVNM) has recently gone GA with a powerful new feature: IP Address Management (IPAM). I’ve been diving deep into this feature—exploring how to implement it using Infrastructure as Code (IaC) and designing operational models around it. Link to the module.
While working with IPAM, I encountered a few challenges when designing a reusable Terraform module. In this post, I’ll walk through the problem, propose two solutions, and share my thoughts. I’d love to hear how others are approaching this as well.
The Problem: Hierarchical IPAM Pools
IPAM in AVNM is hierarchically structured. You start with a root pool, then create child pools from it, and so on. When using Terraform, this introduces a sequencing challenge:
- The root pool must be created first.
- Then, you must wait before creating child pools to ensure the parent is fully provisioned.
- This pattern repeats for each level in the hierarchy.
A common workaround is to use the time_sleep resource in Terraform:
resource "time_sleep" "wait_before_child_pools" {
depends_on = [azurerm_network_manager_ipam_pool.parent_pools]
create_duration = "180s"
}
This works—but designing a flexible, reusable module that supports arbitrary hierarchies becomes tricky.
Solution 1: Fully Abstracted Module with Dynamic Logic
In this approach, the module accepts a single input variable representing the entire IPAM hierarchy. Internally, the module:
- Parses the hierarchy.
- Dynamically creates resources for each level.
- Adds
time_sleepbetween levels to ensure sequencing.
Pros
- Fully abstracted: Users only need to provide one input.
- Scalable: Can support any depth of hierarchy.
- Clean interface: Minimal repetition in Terraform code.
Cons
- Complex logic: Requires advanced Terraform constructs (e.g., nested
for_each, dynamic blocks). - Hard to debug: Errors inside the module can be opaque.
- Low transparency: Users may not understand what’s happening under the hood.
- Maintenance overhead: Changes to IPAM structure may require deep module changes.
Solution 2: Explicit Multi-Level Module Calls (Current Implementation)
Instead of hiding everything inside one module, this approach limits each module call to two levels. You then chain multiple module calls, separated by time_sleep, to build the full hierarchy.
module "ipam_pools" {
source = "../"
virtual_network_manager_name = var.virtual_network_manager_name
virtual_network_manager_resource_group_name = var.virtual_network_manager_resource_group_name
ipam_pools = var.ipam_pools
}
resource "time_sleep" "wait_180_seconds" {
depends_on = [module.ipam_pools]
create_duration = "180s"
}
module "ipam_pools_level_3_and_4" {
depends_on = [time_sleep.wait_180_seconds]
source = "../"
virtual_network_manager_name = var.virtual_network_manager_name
virtual_network_manager_resource_group_name = var.virtual_network_manager_resource_group_name
ipam_pools = var.ipam_pools_level_3_and_4
}
Pros
- Simple logic: Easier to implement and debug.
- Transparent: Users see exactly what’s being created and when.
- Modular: Each level can be managed independently.
Cons
- Manual effort: Users must manage sequencing and module calls.
- Limited flexibility: Only supports two levels per module call.
- Repetitive: More boilerplate code in Terraform configurations.
My Thoughts
Right now, I’m leaning toward Solution 2 for its simplicity and transparency. While it’s not as elegant as a fully abstracted module, it’s easier to maintain and debug—especially in large environments where clarity is key.
That said, I’m still exploring ways to improve the developer experience. Maybe a hybrid approach could work: abstracting some logic while keeping the module interface simple and predictable.
I’d Love Your Feedback
- How are you handling IPAM hierarchies in Terraform?
- Have you tried abstracting the logic into a single module?
- What trade-offs have you encountered?
Feel free to share your thoughts, ideas, or even code snippets. Let’s learn from each other and build better Terraform modules together!
Legg igjen en kommentar