skip to Main Content

Requirement : I am trying to create a Azure Subnet into an existing VNet. Terraform IAC.

Issue : I am unable to dynamically get the available IP ranges in the VNet’s address space and assign one available IP to Subnet’s address_prefixes variable.

Question : Is there any way to first get the available IP ranges in a VNet address space and then assign one of them to SubNet’s address prefixes.

I tried using [cidrsubnet(<VNet’s Address Space>, ,netnum, <no. of subents to be created>)

module "subnet"{
    source = "./modules/sub-net"
    name =  "${var.environment}${replace(var.servicename,"-","")}Subnet"
    resource_group_name = data.azurerm_resource_group.existing.name
    virtual_network_name = data.azurerm_virtual_network.existing_vnet.name
    address_prefixes= cidrsubnet(data.azurerm_virtual_network.existing_vnet.address_space[0], 4, 2)]
    network_security_group_id = "${var.network_security_group_id}"
    route_table_id = "${var.route_table_id}
}

But this gives an IP that is already in use, it does not validates the availability of the IP and so terraform apply throws an IP overlap error.
Last option used is to harcode the available IP address as value to address_prefixes but this is not a good standard to follow especially if we are creating for multiple environments and so on.

module "subnet"{
    source = "./modules/sub-net"
    name =  "${var.environment}${replace(var.servicename,"-","")}Subnet"
    resource_group_name = data.azurerm_resource_group.existing.name
    virtual_network_name = data.azurerm_virtual_network.existing_vnet.name
    address_prefixes = ["1XX.20.1XX.2XX/28"]
    network_security_group_id = "${var.network_security_group_id}"
    route_table_id = "${var.route_table_id}"
   
}

2

Answers


  1. You can use Terraform CIDR Subnets module to split your vnet into subnets – more details here.

    The only downside is you will have to define all your subnets before you actually create any of your resource. In a way, this is a good practice to define networking first.

    Login or Signup to reply.
  2. azurerm_subnet to create a subnet in an existing azure VNet without giving IP overlap exiting IP using terraform.

    In Terraform, dynamically retrieving an index number without manual input can be complex due to its declarative nature, which lacks the inherent ability to automatically ascertain an index based on specific conditions such as the availability of IP ranges within a virtual network.

    This constraint exists because Terraform requires configurations to be defined prior to execution, limiting dynamic runtime calculations or decisions based on the states of resources created in the same execution.

    To meet the requirement, to create new subnets without defining Index for IP and by picking one from available IPs you might need to utilize external scripts to read the current IP and ensure that the new IP does not conflict with an existing one.

    My file structure:

    ├── main.tf
    ├── variables.tf
    ├── find_next_subnet_index.py
    └── modules/
        └── sub-net/
            ├── main.tf
            ├── variables.tf
    

    main.tf(root):

    provider "azurerm" {
      features {}
    }
    
    data "azurerm_resource_group" "existing" {
      name = "vinay-rg"
    }
    
    data "azurerm_virtual_network" "existing_vnet" {
      name                = "testvksnet"
      resource_group_name = data.azurerm_resource_group.existing.name
    }
    
    data "external" "find_subnet_range" {
      program = ["python", "${path.module}/find_next_subnet_index.py"]
    
      query = {
        vnet_name      = data.azurerm_virtual_network.existing_vnet.name
        resource_group = data.azurerm_resource_group.existing.name
        vnet_cidr      = data.azurerm_virtual_network.existing_vnet.address_space[0]
      }
    }
    
    module "subnet" {
      source               = "./modules/sub-net"
      name                 = "${var.environment}${replace(var.servicename, "-", "")}Subnet"
      resource_group_name  = data.azurerm_resource_group.existing.name
      virtual_network_name = data.azurerm_virtual_network.existing_vnet.name
      subnet_cidr          = data.external.find_subnet_range.result["subnet_cidr"]
    }
    
    output "calculated_subnet_cidr" {
      value = module.subnet.subnet_cidr
      description = "The CIDR block for the newly created subnet."
    }
    

    variable.tf

    variable "environment" {
      description = "The deployment environment (e.g., prod, dev)"
      type        = string
    }
    
    variable "servicename" {
      description = "The service name, used in resource naming"
      type        = string
    }
    

    modules/sub-net/main.tf

    resource "azurerm_subnet" "this" {
      name                 = var.name
      resource_group_name  = var.resource_group_name
      virtual_network_name = var.virtual_network_name
      address_prefixes     = [var.subnet_cidr]
    }
    
    output "subnet_cidr" {
      value = azurerm_subnet.this.address_prefixes[0]
      description = "The CIDR block used for the subnet."
    }
    

    modules/sub-net/variable.tf

    variable "name" {
      description = "The name of the subnet"
      type        = string
    }
    
    variable "resource_group_name" {
      description = "The name of the resource group"
      type        = string
    }
    
    variable "virtual_network_name" {
      description = "The name of the virtual network"
      type        = string
    }
    
    variable "subnet_cidr" {
      description = "CIDR block for the subnet"
      type        = string
    }
    

    find_next_subnet_index.py

    import json
    import sys
    import subprocess
    import ipaddress
    
    def get_existing_subnets(vnet_name, resource_group):
        command = [
            'az', 'network', 'vnet', 'subnet', 'list',
            '--resource-group', resource_group,
            '--vnet-name', vnet_name,
            '--query', '[].addressPrefix'
        ]
        result = subprocess.run(command, stdout=subprocess.PIPE, text=True, shell=True)
        return json.loads(result.stdout)
    
    def find_available_subnet(vnet_cidr, existing_subnets):
        vnet_network = ipaddress.ip_network(vnet_cidr)
        for new_subnet in vnet_network.subnets(new_prefix=24):  # Example: /24 subnets
            if not any(new_subnet.overlaps(ipaddress.ip_network(subnet)) for subnet in existing_subnets):
                return str(new_subnet)
        return None
    
    def main():
        input_json = sys.stdin.read()
        input_data = json.loads(input_json)
    
        existing_subnets = get_existing_subnets(input_data['vnet_name'], input_data['resource_group'])
        available_subnet = find_available_subnet(input_data['vnet_cidr'], existing_subnets)
    
        if available_subnet:
            print(json.dumps({"subnet_cidr": available_subnet}))
        else:
            print(json.dumps({"error": "No available subnet found"}))
    
    if __name__ == "__main__":
        main()
    

    At first i have some preexisting subnet with different ranges

    enter image description here

    now when I run the command terraform apply the python script execute and check with the preexisting IP addresses and make a new IP which doesnt overlap with the existing one and make it available for deployment of next resource.

    Deployment succeeded:

    enter image description here

    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search