¯\_(ツ)_/¯

thunder@home:~$

This is my home blog, mostly to share some useful info or code snippets
~ 2 mins

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. +1 required since in indexed arrays count starts with 0
  2. 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!

Thank You For Reading