azureterraform

How can I assign unique, sticky, gap-filling subnets to VMs in Terraform without external scripts or manual mapping?


I’m trying to solve a subnet assignment problem in Terraform and want to double-check if there’s a way to meet all my requirements using only Terraform (no external scripts or manual mapping).

In short: I want to solve the challenge of dynamic, stateful assignment of subnets to resources over time —specifically, to achieve both sticky assignment (so each resource keeps its subnet) and gap-filling (so freed subnets are reused for new resources).

Requirements:

The only way I’ve found to guarantee unique assignments is to use a sequential/index-based approach, like this:

locals {
  subnets = ["subnet-1", "subnet-2", "subnet-3"]
  vms     = ["vm-1", "vm-2", "vm-3"]

  # Assign subnets by index
  vm_subnet_map = {
    for idx, vm in local.vms :
    vm => local.subnets[idx]
  }
}

(This code is a simplified example of what I’m actually doing in my real setup.)

This works for uniqueness, but if I remove (for example) vm-2, then vm-3 will be reassigned from subnet-3 to subnet-2 on the next apply. This is not acceptable for my use case, as I need the assignments to be sticky and not change for existing VMs.

I’ve also tried:

Am I overlooking something very simple or straightforward here?

If there's not a way in pure Terraform only, what is the recommended best practice way of achieving this?

Thanks for any advice or patterns!


Solution

  • There is no way to solve this fully automatically in Terraform: a robust solution requires an external system to keep track of which address ranges have already been assigned so that Terraform can think of an address range assignment as a resource that can be created to establish a new assignment and later destroyed to reliquish that assignment for use elsewhere. For example, Amazon's VPC IPAM, Azure IPAM, etc.

    The closest you can get with Terraform alone is to maintain the data structure representing the current allocations, including explicit records of "gaps" left by earlier allocations having been released, and then modify that data structure only in ways that preserve the guarantees you need to preserve.

    HashiCorp maintains a module hashicorp/subnets/cidr that is intended to make that manual management somewhat easier, but you do still need to take care to change it only in the ways described under Changing Networks Later, which includes:

    1. When a previously-allocated subnet is released, it must be retained with a null name to preserve the now-vacant space and avoid renumbering anything allocated afterwards.

    2. When adding a new allocation, you must first search through the current settings for any null ranges that are large enough to be reassigned to your new purpose.

      This means that either the null range is exactly the size you need or can have its size divided by two some number of times to reach the size you need.

    For a partially-automated solution you could try putting the networks data structure in a separate JSON file and writing a script that modifies that JSON file as needed for adding or removing address space allocations, including reusing any null-named ranges that can fit a new allocation, and then check the resulting file into your version control system along with the calling module. This would then effectively use your version control system as the data store for a rudimentary IPAM, though with the drawback that two concurrent attempts to add a new allocation are likely to produce version control conflicts.