September 22, 2021

Article
7 min

Creating an AWS VPC with Terraform

Tips on quickly building virtual private clouds

Arun Daniel
Creating an AWS VPC with Terraform

Whenever I want to test certain applications, a networking infrastructure is often a mandatory prerequisite. Also, since these are test environments, I usually build and tear down this infrastructure multiple times (sometimes every hour, sometimes daily).

Using IaC for VPC in AWS

Creating a Virtual Private Cloud (VPC) in AWS can be accomplished within the AWS Console with a plethora of typing, mouse clicks and switching between windows. As much fun as this sounds, the “work smarter, not harder” method is to use Infrastructure as Code (IaC) to create the VPC with code. This option limits your screen time when configuring the VPC and can be used to create (or recreate) VPCs in other regions and environments.

The time savings, not to mention the human error limitations, with IaC can be significant when you do these types of “rinse and repeats” in your testing/creating lifecycle.

What follows are some insights into how I use Terraform, at a very basic level, to obtain the repeatable infrastructure whenever there is a need, which is daily in my line of work. The prerequisites needs are an AWS account, access to Terraform, and a code editor.

Discover how CDW services and solutions can help you with AWS.

AWS VPCs

An AWS VPC is the bridge that connects the various AWS services that you may (or may soon have). Some services rely on the networking that VPC provides to be in place before these services are able to be provisioned.

A VPC is a single isolated network that is broken up into subnets. These subnets can be either private or public, depending on the routing attached to that subnet. A subnet is deemed public if the route table associated to the subnet sends all traffic to the internet directly through the internet gateway. On the flip side, a subnet is deemed private if the route table it is tied to doesn't have access to the public internet or if access to the public internet is sent through a Network Address Translation (NAT) gateway (which sits in the public subnet).

VPCs are restricted by region and cannot be spanned across regions. As stated above, VPCs are isolated from each other (unless some type of peering is done), so you can have multiple VPCs with the same VPC Classless Inter-Domain Routing (CIDR) block. However these same CIDR blocks cannot be peered since overlapping is frowned upon.

Creating an AWS VPC with Terraform

Terraform Configuration: VPC

Terraform reads and processes files with the .tf suffix. With that being said, simple Terraform configurations can be done using one file (for example main.tf), which incorporates all attributes of the VPC (VPC CIDR, subnets, route tables, etc.). As the configurations get complex, I have found that segregating each component of the VPC and providing that component with its own .tf file results in better administration (and troubleshooting). For this example, I will use segregated files that will have all my respective components:

  • Terraform provider (logical piece for API and related attributes to connect into AWS): main.tf
  • VPC: vpc.tf
  • Gateways (internet gateway and NAT gateway): gateways.tf
  • Subnets: subnets.tf
  • Route tables: routetables.tf
  • variables.tf: names of variables found in the code which you can modify to your liking and for portability
  • terraform.tfvars: actual data for the variables for this iteration of code
  • data.tf: special type of data source that is defined outside of Terraform, which (in our case) provides information by the AWS provider. Even though we are using this for availability zone lists, we can use this for a variety of other resources.

main.tf

provider "aws" {

region                                                       = "us-west2"

#your region where your VPC will be created

shared_credentials_file = "c:\\users\\arundaniel\\.aws\\credentials"

#location of your credentials file hosting the Secret Key and Secret Access Key (and sometimes Session Token)

profile                 = "myAWSProfile"

#name of the profile in your credential file

}

vpc.tf

#vpc resource

resource "aws_vpc" "vpc" {

cidr_block = var.vpccidr

# cidr block iteration found in the terraform.tfvars file

tags = {

Name = "vpc-arun"

}

}

gateways.tf

#Elastic IP for NAT Gateway resource

resource "aws_eip" "nat" {

vpc = true

tags = {

Name = "vpc-arun" }

}

 

#NAT Gateway object and attachment of the Elastic IP Address from above

resource "aws_nat_gateway" "ngw" {

allocation_id = aws_eip.nat.id

subnet_id = aws_subnet.pubsub1.id

depends_on = [aws_internet_gateway.igw]

tags = {

 Name = "ngw-arun"

}

 

#Internet Gateway

resource "aws_internet_gateway" "igw" {

vpc_id = aws_vpc.vpc.id

tags = {

Name = "igw-arun"

}

}

subnets.tf

#Public Subnet 1

resource "aws_subnet" "pubsub1" {

cidr_block = var.pubsub1cidr

# public subnet 1 cidr block iteration found in the terraform.tfvars file

vpc_id = aws_vpc.vpc.id

map_public_ip_on_launch = true

availability_zone = data.aws_availability_zones.available.names[0]

#0 indicates the first AZ

tags = {

Name = "sub-pubsub1-arun"

}

}

 

#Public Subnet 2

resource "aws_subnet" "pubsub2" {

cidr_block = var.pubsub2cidr

# public subnet 2 cidr block iteration found in the terraform.tfvars file

vpc_id = aws_vpc.vpc.id

map_public_ip_on_launch = true

availability_zone = data.aws_availability_zones.available.names[1]

#1 indicates the second AZ

tags = {

Name = "sub-pubsub2-arun"

}

}

 

#Public Subnet 3

resource "aws_subnet" "pubsub3" {

cidr_block = var.pubsub3cidr

# public subnet 3 cidr block iteration found in the terraform.tfvars file

vpc_id = aws_vpc.vpc.id

map_public_ip_on_launch = true

availability_zone = data.aws_availability_zones.available.names[2]

#2 indicates the 3rd AZ

tags = {

Name = "sub-pubsub3-arun"

}

}

 

#Private Subnet 1

resource "aws_subnet" "prisub1" {

cidr_block = var.prisub1cidr

# private subnet 1 cidr block iteration found in the terraform.tfvars file

vpc_id = aws_vpc.vpc.id

map_public_ip_on_launch = false

availability_zone = data.aws_availability_zones.available.names[0]

tags = {

Name = "sub-prisub1-arun"

}

}

 

#Private Subnet 2

resource "aws_subnet" "prisub2" {

cidr_block = var.prisub2cidr

# private subnet 2 cidr block iteration found in the terraform.tfvars file

vpc_id = aws_vpc.vpc.id

map_public_ip_on_launch = false

availability_zone = data.aws_availability_zones.available.names[1]

tags = {

Name = "sub-prisub2-arun"

}

}

 

#Private Subnet 3

resource "aws_subnet" "prisub3" {

cidr_block = var.prisub3cidr

# private subnet 3 cidr block iteration found in the terraform.tfvars file

vpc_id = aws_vpc.vpc.id

map_public_ip_on_launch = false

availability_zone = data.aws_availability_zones.available.names[2]

tags = {

Name = "sub-prisub3-arun"

}

}

routetables.tf

 #Public Route Table

resource "aws_route_table" "routetablepublic" {

vpc_id = aws_vpc.vpc.id

route {

cidr_block = "0.0.0.0/0"

gateway_id = aws_internet_gateway.igw.id

}

tags = {

Name = "rt-pubrt-arun"

}

}

 

#Associate Public Route Table to Public Subnets

resource "aws_route_table_association" "pubrtas1" {

subnet_id = aws_subnet.pubsub1.id

route_table_id = aws_route_table.routetablepublic.id

}

 

resource "aws_route_table_association" "pubrtas2" {

subnet_id = aws_subnet.pubsub2.id

route_table_id = aws_route_table.routetablepublic.id

}

 

resource "aws_route_table_association" "pubrtas3" {

subnet_id = aws_subnet.pubsub3.id

route_table_id = aws_route_table.routetablepublic.id

}

 

#Private Route Table

resource "aws_route_table" "routetableprivate" {

vpc_id = aws_vpc.vpc.id

route {

cidr_block = "0.0.0.0/0"

gateway_id = aws_nat_gateway.ngw.id

}

tags = {

Name = "rt-prirt-arun"

}

}

 

#Associate Private Route Table to Private Subnets

resource "aws_route_table_association" "prirtas1" {

subnet_id = aws_subnet.prisub1.id

route_table_id = aws_route_table.routetableprivate.id

}

 

resource "aws_route_table_association" "prirtas2" {

subnet_id = aws_subnet.prisub2.id

route_table_id = aws_route_table.routetableprivate.id

}

 

resource "aws_route_table_association" "prirtas3" {

subnet_id = aws_subnet.prisub3.id

route_table_id = aws_route_table.routetableprivate.id

}

variables.tf

variable "vpccidr" {}

variable "pubsub1cidr" {}

variable "pubsub2cidr" {}

variable "pubsub3cidr" {}

variable "prisub1cidr" {}

variable "prisub2cidr" {}

variable "prisub3cidr" {}

terraform.tfvars

 vpccidr     = "10.10.0.0/16"

pubsub1cidr = "10.10.0.0/24"

pubsub2cidr = "10.10.1.0/24"

pubsub3cidr = "10.10.2.0/24"

prisub1cidr = "10.10.3.0/24"

prisub2cidr = "10.10.4.0/24"

prisub3cidr = "10.10.5.0/24"

data.tf

 data "aws_availability_zones" "available" {

state = "available"

}

My folder structure looks as follows, with my folder name of "VPCBuild" and the segregated files with the .tf extension underneath the main folder:

VPC Folder Structure

Terraform Run: VPC

Now we have the stage set with the code, the file structure and the prerequisites met, we can start pulling the Terraform strings to create our VPC.

The "correct" steps to run terraform code include:

  • terraform init:
    This step initializes the plugins and providers, which are needed to work with the various resources we “coded.”
Terraform
  • terraform plan:
    An optional step, but a silent hero that confirms our configuration code syntax is correct and provides an overview of which resources will be created in your infrastructure world. As more complex code and modifications are done to your code, this step is a lifesaver when it comes to crossing the t's and dotting the i's before committing. Notice the second screenshot shows that 18 resources will be added.
Terraform
Terraform
  • terraform apply:
    This is the actual launch step that will create your infrastructure. Notice in the second screenshot that you will have to confirm the actions.
Terraform
Terraform

Confirmation

If all goes well in the previous sections, you can log into the AWS console and confirm all resources were successfully created.

VPC

Terraform

Subnets

Terraform

Route Tables

Terraform

Gateways

Terraform
Terraform

I hope this helps as you work within AWS to create your own VPCs.

Arun Daniel is a Sr. Consulting Engineer for Data Center and Cloud Services at CDW with more than 20 years of experience designing, deploying and managing all aspects of data center and cloud services. For the past 10 years, his primary focus has been migrations from on-premises data centers to Amazon Web Services. Arun holds numerous AWS certifications, as well as HashiCorp, Microsoft Azure, VMware and Cisco certifications.

Discover how CDW services and solutions can help you with AWS.