Create AWS VPC Infrastructure with Terraform

Stefano Monti
AWS Infrastructure
Published in
5 min readDec 25, 2022

Prerequisites

You must have an AWS account and Terraform installed on your machine. Here you can find instructions to satisfy the prerequisites for this tutorial.

At this point, after the initialization, you should have the following folder structure:

The file main.tf should contain the following lines:

terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "3.66.0"
}
}
}


provider "aws" {
region = "here-your-destination-region"
profile = "here-your-aws-profile-name"
}

VPC Resources

Let’s start adding all the AWS resources needed to provide a VPC!!

First of all, maybe could look obvious, but the first resource to create is a vpc 😅

resource "aws_vpc" "stw_vpc" {
cidr_block = "10.0.0.0/16"

tags = {
Name = "stw_vpc"
}
}

Now we’ve created the container for all the network resources we are going to provide.

Next, let’s create the internet gateway for permitting the communication with public internet and link it to the vpc already created.

resource "aws_internet_gateway" "stw_gw" {
vpc_id = aws_vpc.stw_vpc.id

tags = {
Name = "stw_gw"
}
}

Time for creating subnets!! 🤩

We will create different subnets spread over 2 different availability zones (if you don’t know what an availability zone is, check this).

resource "aws_subnet" "stw_subnet_private_1" {
vpc_id = aws_vpc.stw_vpc.id
cidr_block = "10.0.1.0/24"
availability_zone = "eu-west-1a"

tags = {
group = var.stack_name
Name = "stw_subnet_private_1"
}
}

resource "aws_subnet" "stw_subnet_private_2" {
vpc_id = aws_vpc.stw_vpc.id
cidr_block = "10.0.2.0/24"
availability_zone = "eu-west-1b"

tags = {
Name = "stw_subnet_private_2"
}
}

resource "aws_subnet" "stw_subnet_public_1" {
vpc_id = aws_vpc.stw_vpc.id
cidr_block = "10.0.101.0/24"
map_public_ip_on_launch = true
availability_zone = "eu-west-1a"
tags = {
Name = "stw_subnet_public_1"
}
}

resource "aws_subnet" "stw_subnet_public_2" {
vpc_id = aws_vpc.stw_vpc.id
cidr_block = "10.0.102.0/24"
map_public_ip_on_launch = true
availability_zone = "eu-west-1b"
tags = {
Name = "stw_subnet_public_2"
}
}

As you can see we have declared 4 resources of type aws_subnet in the region eu-west-1 (Ireland, the same region where has been declared also the vpc) across 2 different AZ (eu-west-1a and eu-west-1b).

Now we have created all the subnets where our computing resources will be placed but, something is missing 🧐. For example, how can we instruct our system to route the traffic based on the requested address? Here come routing tables!

Let’s begin with the public subnets!

We will create a route table with the following rules:
— if a request is directed to the vpc cidr (10.0.0.0/16) forward it inside the vpc (is implicit in the resource definition);
— if a request is directed to any other address (0.0.0.0/0) forward it to the internet gateway in order to permit the request to reach the public internet.

resource "aws_route_table" "stw_rt_public" {
vpc_id = aws_vpc.stw_vpc.id

route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.stw_gw.id
}

tags = {
Name = "stw-rt-public"
}
}

Now is the time to assign the created route table to the public subnets with the terraform resource called aws_route_table_association.

resource "aws_route_table_association" "stw_rta_public_1" {
subnet_id = aws_subnet.stw_subnet_public_1.id
route_table_id = aws_route_table.stw_rt_public.id
}
resource "aws_route_table_association" "stw_rta_public_2" {
subnet_id = aws_subnet.stw_subnet_public_2.id
route_table_id = aws_route_table.stw_rt_public.id
}

Ok, the public subnets are done! Unfortunately is not possible to do the same thing with the private subnets because the ec2 won’t have a public ip and so they are not able to make public internet request via the internet gateway 😓.
The solution is to provide a nat gateway with a public ip associated 🤯.
The aim of the nat gateway is to forward every request made by any resource inside the private network to the internet using his public ip.
Obviously, the nat gateway must be provisioned inside a public subnet; the result will be that a machine inside a private subnet can communicate with the internet but it will not be available outside of the vpc (this could be useful for databases).

resource "aws_nat_gateway" "stw_nat_gw" {
allocation_id = aws_eip.stw_eip.id
subnet_id = aws_subnet.stw_subnet_public_1.id

tags = {
Name = "stw_nat_gw"
}
}

resource "aws_eip" "stw_eip" {
tags = {
Name = "stw_eip"
}
}

Similarly to the public subnet, we must instruct our resources inside the private subnets to route the requests based on the target:
— if a request is directed to the vpc cidr (10.0.0.0/16) forward it inside the vpc (is implicit in the resource definition);
— if a request is directed to any other address (0.0.0.0/0) forward it to the nat gateway in order to permit the request to reach the public internet via the internet gateway (the public route table will be used).
The route table must be associated with the private subnets.

resource "aws_route_table" "stw_rt_public" {
vpc_id = aws_vpc.stw_vpc.id

route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.stw_gw.id
}

tags = {
Name = "stw-rt-public"
}
}

resource "aws_route_table_association" "stw_rta_private_1" {
subnet_id = aws_subnet.stw_subnet_private_1.id
route_table_id = aws_route_table.stw_rt_private.id
}
resource "aws_route_table_association" "stw_rta_private_2" {
subnet_id = aws_subnet.stw_subnet_private_2.id
route_table_id = aws_route_table.stw_rt_private.id
}

In order to deploy the resources you must run the following commands:

$ terraform plan

The terraform plan command creates an execution plan, which lets you preview the changes that Terraform plans to make to your infrastructure. By default, when Terraform creates a plan it:

  • Reads the current state of any already-existing remote objects to make sure that the Terraform state is up-to-date.
  • Compares the current configuration to the prior state and noting any differences.
  • Proposes a set of change actions that should, if applied, make the remote objects match the configuration.
$ terraform apply

The terraform apply command executes the actions proposed in a Terraform plan

You can find the final version of this template on my GitHub profile at this link: terraform-infrastructure-aws-vpc.

Last Tip

Wouldn’t be awesome if you could provide an infrastructure like this in one minute with less the 10 lines of code? 😱

Here’s a terraform module you can use to create a vpc infrastructure:

module "vpc" {
source = "terraform-aws-modules/vpc/aws"

name = "stw_vpc"
cidr = "10.0.0.0/16"

azs = ["eu-west-1a", "eu-west-1b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]

enable_nat_gateway = true

}

With these simple lines, we can deploy a complete vpc stack within 2 minutes.

References:

--

--