Dynamic subnet CIDR calculation with Terraform
terraform devops ip

Provisioning Azure Subnet dynamically with calculated CIDR address using Terraform
June 23, 2020

Networking is fun... In a recent project I got the chance to calculate subnet addresses and masks. The last time I did this was probably 15 years ago at school, so it was a good refresher. Nontheless, having tools which can help with CIDR (Classless Inter-Domain Routing) calculations is always good.

This post is about provisioning Azure Subnet dynamically with calculated CIDR address using Terraform.

tl;dr

Use cidrsubnet function in Terraform to calculate subnet addresses within an IP address space.

cidrsubnet(iprange, newbits, netnum)

Context and goal

We used Terraform modules and for_each logic to deploy mutliple Azure Web Apps and assign them into corresponding subnets within Azure VNET.

Given a variable IP address space the goal was to calculate subnets for (potentially) dynamic number of Web Apps (1..N) with the size of /27 and use the available address space efficiently. Each subnet was supposed to have the capacity to host at least 20 instances.

Schematic view on the VNET and subnets with Web Apps

There are plenty of tools on the internet which can perform these calculations. There's also Linux ipcalc:

$ ipcalc 172.10.1.0/23 /27

Address:   172.10.1.0           10101100.00001010.0000000 1.00000000
Netmask:   255.255.254.0 = 23   11111111.11111111.1111111 0.00000000
Wildcard:  0.0.1.255            00000000.00000000.0000000 1.11111111
=>
Network:   172.10.0.0/23        10101100.00001010.0000000 0.00000000
HostMin:   172.10.0.1           10101100.00001010.0000000 0.00000001
HostMax:   172.10.1.254         10101100.00001010.0000000 1.11111110
Broadcast: 172.10.1.255         10101100.00001010.0000000 1.11111111
Hosts/Net: 510                   Class B

Subnets after transition from /23 to /27

Netmask:   255.255.255.224 = 27 11111111.11111111.11111111.111 00000
Wildcard:  0.0.0.31             00000000.00000000.00000000.000 11111

 1.
Network:   172.10.0.0/27        10101100.00001010.00000000.000 00000
HostMin:   172.10.0.1           10101100.00001010.00000000.000 00001
HostMax:   172.10.0.30          10101100.00001010.00000000.000 11110
Broadcast: 172.10.0.31          10101100.00001010.00000000.000 11111
Hosts/Net: 30                    Class B

 2.
Network:   172.10.0.32/27       10101100.00001010.00000000.001 00000
HostMin:   172.10.0.33          10101100.00001010.00000000.001 00001
HostMax:   172.10.0.62          10101100.00001010.00000000.001 11110
Broadcast: 172.10.0.63          10101100.00001010.00000000.001 11111
Hosts/Net: 30                    Class B

 3.
Network:   172.10.0.64/27       10101100.00001010.00000000.010 00000
HostMin:   172.10.0.65          10101100.00001010.00000000.010 00001
HostMax:   172.10.0.94          10101100.00001010.00000000.010 11110
Broadcast: 172.10.0.95          10101100.00001010.00000000.010 11111
Hosts/Net: 30                    Class B

 4.
Network:   172.10.0.96/27       10101100.00001010.00000000.011 00000
HostMin:   172.10.0.97          10101100.00001010.00000000.011 00001
HostMax:   172.10.0.126         10101100.00001010.00000000.011 11110
Broadcast: 172.10.0.127         10101100.00001010.00000000.011 11111
Hosts/Net: 30                    Class B

 5.
Network:   172.10.0.128/27      10101100.00001010.00000000.100 00000
HostMin:   172.10.0.129         10101100.00001010.00000000.100 00001
HostMax:   172.10.0.158         10101100.00001010.00000000.100 11110
Broadcast: 172.10.0.159         10101100.00001010.00000000.100 11111
Hosts/Net: 30                    Class B

...etc...

This is great and produces exactly the result I wanted, but is there a way to use just Terraform?

CIDR subnets in Terraform

Terraform provides handy function called cidrsubnet which is able to calculate subnet address within the given IP network address space.

Since the function is quite cryptic, I was happy to find this awesome blogpost from Lisa Hagemann which dissects the individual parameters and explains how the calculation works.

In short: cidrsubnet(iprange, newbits, netnum) where:

Implementation

Example parameters which define inputs for the cidrsubnet function:

location_short = "neu"

address_spaces = {
  "neu" = "172.10.1.0/23"
  "weu" = "172.10.3.0/23"
  "uks" = "172.20.28.0/23"
}

subnet_list = {
  "front" = 0
  "api1"  = 1
  "api2"  = 2
  "other" = 3
}

  

And subnet definition itself:

resource "azurerm_subnet" "subnets" {
  for_each             = local.subnet_list
  name                 = "${each.key}-subnet"
  resource_group_name  = azurerm_resource_group.main.name
  virtual_network_name = azurerm_virtual_network.main.name

  # Size of the subnets is /27 - exactly the size we need for AppService VNET-integration
  address_prefixes  = [cidrsubnet(var.address_spaces[var.location_short], 4, each.value)]
  service_endpoints = ["Microsoft.Web"]

  # Delegation to enable app services to join the subnet later
  delegation {
    name = "appservicedelegation"
    service_delegation {
      name    = "Microsoft.Web/serverFarms"
      actions = ["Microsoft.Network/virtualNetworks/subnets/action"]
    }
  }
}

Found something inaccurate or plain wrong? Was this content helpful to you? Let me know!

šŸ“§ codez@deedx.cz