In a previous blog post I covered how to manage the IPAM service on Azure with Terraform.
AWS also offers an IPAM service, and naturally you might want to manage it using Terraform too! That is why this blog post is dedicated to do just that: manage AWS IPAM with Terraform.
What is IPAM?#
I wrote about what IPAM is in the corresponding blog post for IPAM on Azure, so I will not spend time repeating this information here.
The essentials are: An IPAM (IP Address Management) service helps you manage a pool of IP addresses instead of using a manual paper-and-pen approach to keep track of what IP address range is in use where.
Provision IPAM on AWS#
We start by taking on the platform engineering hat. We are part of a platform team that is responsible for the network in our cloud environments. AWS is our primary cloud environment.
We have been tasked with configuring an IPAM for the European branch of our organization. We currently have an IPAM solution in our US branch.
Automation is a keyword that our platform engineers live by, so we plan to use Terraform to create this new IPAM in our AWS environment.
We create a brand new directory where we will create our Terraform configuration for the IPAM.
In a new file named variables.tf
we add a variable for the main target region for our AWS resources:
variable "aws_region" {
description = "The AWS region to deploy resources in"
type = string
default = "eu-west-1"
}
Remember that since version 6 of the AWS provider you can provision resources to multiple locations using a single provider instance? I wrote a different blog post covering this (then upcoming, but now in GA) feature where you can learn more about this change.
A good engineering practice with Terraform is to specify what Terraform providers our Terraform configuration will use, as well as configuring these providers. We add another file named providers.tf
where we add the following code:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "6.2.0"
}
}
}
provider "aws" {
region = var.aws_region
}
As mentioned above, we can provision resources to multiple regions with this single AWS provider instance. However, it’s a great idea to configure what the default region is like we have done above.
Ideally we would add a variable for the CIDR block that makes up the overall pool of IP addresses for the European branch. But this Terraform configuration is one-of-a-kind so instead we add a local value for the CIDR block in a new file named locals.tf
:
locals {
cidr = "10.0.0.0/16"
}
Configuring IPAM requires a few different resources. The main resource is the IPAM itself, configured through the aws_vpc_ipam
resource type.
We can create an IPAM that is available in multiple regions. In this case we have offices in Ireland (eu-west-1
), Frankfurt (eu-central-1
), Stockholm (eu-north-1
), and London (eu-west-2
).
We configure the IPAM resource like this in a file named main.tf
:
resource "aws_vpc_ipam" "europe" {
description = "IPAM for VPCs in Europe"
operating_regions {
region_name = "eu-west-1"
}
operating_regions {
region_name = "eu-central-1"
}
operating_regions {
region_name = "eu-north-1"
}
operating_regions {
region_name = "eu-west-2"
}
tags = {
Name = "ipam-europe"
}
}
Each IPAM has two scopes by default. One private scope, and one public scope. We are only concerned about the private scope because we will be distributing non-publicly-routable private IP addresses from the 10.0.0.0/16
range.
We add pools to our IPAM resource. The pool is configured with a scope, and an address family (either IPv4 or IPv6). To each pool we assign CIDRs.
In our example we create a main pool for the whole European branch and name it ipam-pool-europe
, and we assign the full range of 10.0.0.0/16
to this pool:
resource "aws_vpc_ipam_pool" "europe" {
ipam_scope_id = aws_vpc_ipam.europe.private_default_scope_id
address_family = "ipv4"
tags = {
Name = "ipam-pool-europe"
}
}
resource "aws_vpc_ipam_pool_cidr" "europe" {
ipam_pool_id = aws_vpc_ipam_pool.europe.id
cidr = local.cidr
}
Next we configure child-pools to the European pool. We create one pool for each of the four office regions listed above, and for each pool we configure the source_ipam_pool_id
argument and reference the European pool.
The code for the region in Ireland (eu-west-1
) looks like this:
resource "aws_vpc_ipam_pool" "ireland" {
ipam_scope_id = aws_vpc_ipam.europe.private_default_scope_id
source_ipam_pool_id = aws_vpc_ipam_pool.europe.id
address_family = "ipv4"
locale = "eu-west-1"
tags = {
Name = "ipam-pool-europe-ireland"
}
}
resource "aws_vpc_ipam_pool_cidr" "ireland" {
ipam_pool_id = aws_vpc_ipam_pool.ireland.id
cidr = cidrsubnet(local.cidr, 2, 0)
}
# the other three regions left out for brevity ...
Note here that we in the platform team need to be aware of and handle CIDR blocks. The IPAM service does not abstract this part from us. Luckily we are all fluent in CIDR-speak!
We go through terraform init
, terraform plan
, and terraform apply
to provision the IPAM and all the pools.
Now we are ready to let our consumers start claiming IP addresses from these pools!
Consuming IPAM on AWS#
It’s time to shift hat from the platform engineer hat to the developer hat. We are part of a development team who want to provision infrastructure for a new application we are building in Ireland.
We know the platform team have provisioned an IPAM solution on AWS and they have given us instructions for how it should be used.
We have similar variables.tf
and providers.tf
files as the platform team, so we omit them here for brevity.
In our main.tf
file we configure a data source for the IPAM pool, and we use a filter
block to look for the pool with a given Name
tag:
data "aws_vpc_ipam_pool" "ireland" {
filter {
name = "tag:Name"
values = ["ipam-pool-europe-ireland"]
}
}
That was easy1!
To configure the Virtual Private Cloud (VPC) for our application our original approach would have been like this:
resource "aws_vpc" "ireland" {
cidr_block = "10.0.0.0/24"
tags = {
Name = "vpc-europe-ireland"
}
}
The old approach requires us to know the full CIDR block that we should use. This information is communicated out from the platform team to the development team using email, Slack, Teams, Excel sheets, secret rituals, or something else.
Since we now have access to an IPAM pool we can simplify this to the following configuration:
resource "aws_vpc" "ireland" {
ipv4_ipam_pool_id = data.aws_vpc_ipam_pool.ireland.id
ipv4_netmask_length = 24
tags = {
Name = "vpc-europe-ireland"
}
}
First of all, we use the data source reference to the pool as the value to the ipv4_ipam_pool_id
argument. Next we ask for an ipv4_netmask_length
of 24
. This means we are asking for a CIDR block on the form x.x.x.x/24
. However, here we do not need to know what x.x.x.x
is! We just need to ask for an appropriately sized network, in this case a /24
.
Note the difference with the Azure IPAM service. On Azure we asked for a number of IP addresses, e.g. “I need 135 IP addresses, please!”. The result of that request would be a /24
network because to accommodate 135 addresses we need to allocate 256 (which is what you get in a /24
network).
On AWS, instead of asking for a number of IP addresses we instead ask for a network of a given size, e.g. “I need a /24
network please!”.
How should you manage IPAM at scale on AWS?#
You should use Terraform, of course!
If you are using AWS organizations you can delegate the management of IPAM to a specific account. In this account you can configure all IPAM pools that your organization requires, and you can share them with accounts in your organization using AWS Resource Access Manager2.
In this way you get a single pane of glass (the delegated AWS account) where you can see the current state of your IPAM pools.
Conclusions#
In this blog post we learned about how to provision and consume IPAM resources on AWS using Terraform. At a high level the steps are:
- Configure a VPC IPAM resource.
- Add one or more IPAM pools, perhaps in a nested hierarchy.
- Assign CIDR blocks to the IPAM pools.
- Consume CIDR blocks from the IPAM pools when you create VPC resources by referencing the pool ID.
There are more configuration options for the IPAM service, but this post will get you started using IPAM on AWS.
In the corresponding Azure post we learned that there is (currently) no data source for the IPAM pool on Azure, which means we must use a long ID to reference the IPAM pool. ↩︎
My current private setup of AWS accounts does not include a large enough organization structure to demo what this would look like - so I will leave that as an exercise for the reader (you). ↩︎