Create a Production Grade AWS VPC using Terraform
By: Tucker Clinton
Tags: Terraform, Vpc

A Virtual Private Cloud (VPC) allows you to launch resources in an isolated virtual network without the use of VPN’s or hardware. With a VPC, you define how the resources in your network are exposed to the internet.

For this VPC, 3 public and 3 private subnets will be created, along with an Internet Gateway, Route Table, and NAT Gateway.

The goal for this VPC setup is to allow our private resources access to the internet, and restrict access from the internet to our resources.

To begin, create a new directory to isolate these resources from any others in your workspace.

mkdir vpc cd vpc

VPC: In a new file, specify the provider (AWS) and a variable region so that we can us the same setup for other regions. Create a resource block for the vpc.

provider "aws" { region = var.region} resource "aws_vpc" "productionvpc" { cidr_block = var.vpc_cidr enable_dns_hostnames = true tags = { Name = "Production" } }

Variables: Create a new file called vars.tf to define the region and vpc_cidr variables.

variable "region" { default = "us-east-1" description = "AWS Region" } variable "vpc_cidr" { description = "VPC CIDR" }

Add the variables for the 3 public and 3 private subnets to the vars.tf file. We will create the file for the subnets in the next step.

variable "public_subnet_1_cidr" { description = "Public Subnet 1 CIDR" } variable "public_subnet_2_cidr" { description = "Public Subnet 2 CIDR" } variable "public_subnet_3_cidr" { description = "Public Subnet 3 CIDR" } variable "private_subnet_1_cidr" { description = "Private Subnet 1 CIDR" } variable "private_subnet_2_cidr" { description = "Private Subnet 2 CIDR" } variable "private_subnet_3_cidr" { description = "Private Subnet 3 CIDR" }

Subnets: Create a new file for the 6 subnets (subnets.tf) and add a resource block for each public and private subnet at 3 different availability zones.

resource "aws_subnet" "public-subnet-1" { cidr_block = var.public_subnet_1_cidr vpc_id = aws_vpc.productionvpc.id availability_zone = "us-east-1a" tags = { Name = "Public-Subent-1" } } resource "aws_subnet" "public-subnet-2" { cidr_block = var.public_subnet_2_cidr vpc_id = aws_vpc.productionvpc.id availability_zone = "us-east-1b" tags = { Name = "Public-Subent-2" } } resource "aws_subnet" "public-subnet-3" { cidr_block = var.public_subnet_3_cidr vpc_id = aws_vpc.productionvpc.id availability_zone = "us-east-1c" tags = { Name = "Public-Subent-3" } } resource "aws_subnet" "private-subnet-1" { cidr_block = var.private_subnet_1_cidr vpc_id = aws_vpc.productionvpc.id availability_zone = "us-east-1a" tags = { Name = "Private-Subent-1" } } resource "aws_subnet" "private-subnet-2" { cidr_block = var.private_subnet_2_cidr vpc_id = aws_vpc.productionvpc.id availability_zone = "us-east-1b" tags = { Name = "Private-Subent-2" } } resource "aws_subnet" "private-subnet-3" { cidr_block = var.private_subnet_3_cidr vpc_id = aws_vpc.productionvpc.id availability_zone = "us-east-1c" tags = { Name = "Private-Subent-3" } }

Route Table: Add a new file for the Route Table. Create the route table resource block and associate the subnets to it.

resource "aws_route_table" "public-route-table" { vpc_id = aws_vpc.productionvpc.id tags = { Name = "Public-Route-Table" } } resource "aws_route_table" "private-route-table" { vpc_id = aws_vpc.productionvpc.id tags = { Name = "Private-Route-Table" } } resource "aws_route_table_association" "public-subnet-1-association" { route_table_id = aws_route_table.public-route-table.id subnet_id = aws_subnet.public-subnet-1.id } resource "aws_route_table_association" "public-subnet-2-association" { route_table_id = aws_route_table.public-route-table.id subnet_id = aws_subnet.public-subnet-2.id } resource "aws_route_table_association" "public-subnet-3-association" { route_table_id = aws_route_table.public-route-table.id subnet_id = aws_subnet.public-subnet-3.id } resource "aws_route_table_association" "private-subnet-1-association" { route_table_id = aws_route_table.private-route-table.id subnet_id = aws_subnet.private-subnet-1.id } resource "aws_route_table_association" "private-subnet-2-association" { route_table_id = aws_route_table.private-route-table.id subnet_id = aws_subnet.private-subnet-2.id } resource "aws_route_table_association" "private-subnet-3-association" { route_table_id = aws_route_table.private-route-table.id subnet_id = aws_subnet.private-subnet-3.id }

NAT Gateway: The NAT Gateway will give the private resources access to the internet. To create the NAT Gateway, we’ll need to also create an Elastic IP first.

It’s recommended to denote that the NAT Gateway depends on the Internet Gateway for the VPC in which the NAT Gateway’s subnet is located.

resource "aws_nat_gateway" "nat-gw" { allocation_id = aws_eip.eip.id subnet_id = aws_subnet.public-subnet-1.id tags = { Name = "Production-NAT-GW" } depends_on = [aws_eip.eip] }

Internet Gateway: An internet gateway serves two purposes: to provide a target in your VPC route tables for internet-routable traffic, and to perform network address translation (NAT) for instances that have been assigned public IPv4 addresses.

To enable access to or from the internet for instances in a subnet in a VPC, you must do the following:

Create an internet gateway and attach it to your VPC.

Add a route to your subnet’s route table that directs internet-bound traffic to the internet gateway.

Ensure that instances in your subnet have a globally unique IP address (public IPv4 address, Elastic IP address, or IPv6 address).

Ensure that your network access control lists and security group rules allow the relevant traffic to flow to and from your instance.

Create a new file named igw.tf and add an internet gateway resource block.

resource "aws_internet_gateway" "production-igw" { vpc_id = aws_vpc.productionvpc.id tags = { Name = "Production-IGW" } }

Update the route table file (rt.tf) by adding the following resource blocks in order to allow internet access inside the subnets.

resource "aws_route" "public-internet-gw-route" { route_table_id = aws_route_table.public-route-table.id gateway_id = aws_internet_gateway.production-igw.id destination_cidr_block = "0.0.0.0/0" } resource "aws_route" "nat-gw-route" { route_table_id = aws_route_table.private-route-table.id nat_gateway_id = aws_nat_gateway.nat-gw.id destination_cidr_block = "0.0.0.0/0" }

Output file: We can create an output file detailing what we want displayed in the output. In this case, we’ll have it display the subnet id’s, VPC id, and VPC IP.

output "vpc_id" { value = aws_vpc.productionvpc.id } output "vpc_cidr_block" { value = aws_vpc.productionvpc.cidr_block } output "public_subnet_1_id" { value = aws_subnet.public-subnet-1.id } output "public_subnet_2_id" { value = aws_subnet.public-subnet-2.id } output "public_subnet_3_id" { value = aws_subnet.public-subnet-3.id } output "private_subnet_1_id" { value = aws_subnet.private-subnet-1.id } output "private_subnet_2_id" { value = aws_subnet.private-subnet-2.id } output "private_subnet_3_id" { value = aws_subnet.private-subnet-3.id }

tfvars file: Create a tfvars file containing the missing variables.

vpc_cidr = "192.168.0.0/16" public_subnet_1_cidr = "192.168.1.0/24" public_subnet_2_cidr = "192.168.2.0/24" public_subnet_3_cidr = "192.168.3.0/24" private_subnet_1_cidr = "192.168.4.0/24" private_subnet_2_cidr = "192.168.5.0/24" private_subnet_3_cidr = "192.168.6.0/24"

Initialize the working directory, then run plan and apply steps.

terraform init terraform plan — vars-file=”production.tfvars” terraform apply — vars-file=”production.tfvars”

Running terraform plan and apply with the .tfvars file allows us to change the values of those variables dynamically. We can change the values in the .tfvars file instead of changing them in each individual file. One more advantage also if you don’t pass the variable file while applying the terraform it will ask you to enter the value at run time. We can also provide the values on the go.

The output should show something like below:

Great! If you made it this far, you‘ve used Terraform to create a production grade virtual private cloud in AWS.

When you are done, destroy the infrastructure with the following command