Heya,
I always wanted to make all things automated. And now I decided to created VPC with subnets by providing only VPC CIDR. And Terraform side should create public and private subnets based on AZ count for selected region. In this example we assume that the region is eu-west-1
(Ireland).
First of all, lets define one input variable – CIDR
:
variable "cidr_block" {
type = string
default = "10.100.0.0/29"
description = "The CIDR block for the VPC"
}
Now, we need to create VPC and get a list of available availability zones:
## Create AWS VPC
resource "aws_vpc" "this" {
cidr_block = var.cidr_block
enable_dns_hostnames = true
enable_dns_support = true
instance_tenancy = "default"
}
## Get all availability zones for this region
data "aws_availability_zones" "available" {
state = "available"
}
locals {
## Just to be sure they're in right order
az_names = sort(data.aws_availability_zones.available.names)
}
And here is some magic begins.
Lets create Public subnets, Internet gateway, Public Route Table and set required routing associations with created subnets
### Public subnets configuration
resource "aws_subnet" "public" {
for_each = toset(local.az_names)
vpc_id = aws_vpc.this.id
availability_zone = each.value
cidr_block = cidrsubnet(var.cidr_block, length(local.az_names), index(local.az_names, each.value) + 1)
map_public_ip_on_launch = false
tags = { Name = join("-", ["public", element(reverse(split("-", each.value)), 0)]) }
}
Notes on code above:
+1
required since in indexed arrays count starts with0
map_public_ip_on_launch
=false
to aviod Public IP mapping by default.
resource "aws_internet_gateway" "this" {
vpc_id = aws_vpc.this.id
tags = { Name = "igw" }
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.this.id
tags = { Name = "public" }
}
resource "aws_route" "public" {
route_table_id = aws_route_table.public.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.this.id
}
resource "aws_route_table_association" "public" {
for_each = toset(local.az_names)
route_table_id = aws_route_table.public.id
subnet_id = aws_subnet.public[each.value].id
}
And we’ll get beautiful Mapped list of subnets by AZ name which you can easily access like this:
aws_subnet.public["eu-west-1a"]
Lets do same for Private subnets, the only difference that for Private subnets we’re going to create NAT Gateway with ElasticIP:
## Private subnets configuration
## Note: `+1` required since in indexed arrays count starts with `0`
resource "aws_subnet" "private" {
for_each = toset(local.az_names)
vpc_id = aws_vpc.this.id
availability_zone = each.value
cidr_block = cidrsubnet(var.cidr_block, length(local.az_names), index(local.az_names, each.value) + 1 + length(local.az_names))
map_public_ip_on_launch = false
tags = { Name = join("-", ["private", element(reverse(split("-", each.value)), 0)]) }
}
resource "aws_eip" "nat" {
vpc = true
tags = { Name = "nat" }
}
resource "aws_nat_gateway" "this" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public[element(local.az_names, 1)].id
tags = { Name = "ngw" }
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.this.id
tags = { Name = "private" }
}
resource "aws_route" "private" {
route_table_id = aws_route_table.private.id
destination_cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.this.id
}
resource "aws_route_table_association" "private" {
for_each = toset(local.az_names)
route_table_id = aws_route_table.private.id
subnet_id = aws_subnet.private[each.value].id
}
And we’ll also get beautiful Mapped list of subnets by AZ name which you can easily access like this:
aws_subnet.private["eu-west-1a"]
That’s it. Happy terraforming
!