Creating a Production Grade VPC in AWS using terraform
What is VPC?
A VPC is a private network within AWS. In Layman term, we can say it’s a private datacenter inside AWS platform. It can be configured as public, private or mixture. It’s region-specific i.e. a single VPC can’t place across regions. We can connect our private data centres and corporate networks to VPC.
* It’s isolated from other VPC by default.
* A VPC can have maximum CIDR as /16(65,536 IPs) and minimum /28(16 IPs).
What is custom VPC?
A custom VPC can be designed and configured as per requirement. We can provide IP Ranges, create multiple subnets, provision gateways and networking as well as design and implement security.
Let’s create a custom VPC with 3 public and 3 private subnets, A Internet Gateway, and few other components like Route Table, NAT Gateway etc.
The main purpose of this setup is that our private resources can get Internet connectivity but not vice-versa. Our private resources remain isolated from the direct reach of the internet and hackers.
To begin the configuration we need to create a separate folder in our wor station which will isolate our resources from conflict with others.
$ mkdir vpc
$ cd vpc
First, we have to provide terraform which service is going to use this. for this, we need to create a vpc.tf
file. We don’t need to hardcode the region so we can use the same setup for other regions as well.
provider "aws" {
region = var.region
}resource "aws_vpc" "production" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true tags = {
Name = "Production"
}
}
Now, we have to declare the region
, vpc CIDR
in the file which will contain all the variable details. We will name that as vars.tf
for our clear understanding.
variable "region" {
default = "ap-south-1"
description = "AWS Region"
}variable "vpc_cidr" {
description = "VPC CIDR"
}
It’s time to create one private and one public subnet in each Availability Zone. We will name the file as subnet.tf
. but before creating subnet.tf
we need to update the vars.tf
file to use in the subnet.tf
file. The updated vars.tf
file will be like the below,
variable "region" {
default = "ap-south-1"
description = "AWS Region"
}variable "vpc_cidr" {
description = "vpc CIDR Block"
}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"
}
After declaring the required variables in the vars.tf
file we will now move on to create the subents.tf
resource "aws_subnet" "public-subnet-1" {
cidr_block = var.public_subnet_1_cidr
vpc_id = aws_vpc.production.id
availability_zone = "ap-south-1a" tags = {
Name = "Public-Subent-1"
}
}resource "aws_subnet" "public-subnet-2" {
cidr_block = var.public_subnet_2_cidr
vpc_id = aws_vpc.production.id
availability_zone = "ap-south-1b" tags = {
Name = "Public-Subent-2"
}
}resource "aws_subnet" "public-subnet-3" {
cidr_block = var.public_subnet_3_cidr
vpc_id = aws_vpc.production.id
availability_zone = "ap-south-1c" tags = {
Name = "Public-Subent-3"
}
}resource "aws_subnet" "private-subnet-1" {
cidr_block = var.private_subnet_1_cidr
vpc_id = aws_vpc.production.id
availability_zone = "ap-south-1a" tags = {
Name = "Private-Subent-1"
}
}resource "aws_subnet" "private-subnet-2" {
cidr_block = var.private_subnet_2_cidr
vpc_id = aws_vpc.production.id
availability_zone = "ap-south-1b" tags = {
Name = "Private-Subent-2"
}
}resource "aws_subnet" "private-subnet-3" {
cidr_block = var.private_subnet_3_cidr
vpc_id = aws_vpc.production.id
availability_zone = "ap-south-1c" tags = {
Name = "Private-Subent-3"
}
}
After creating the subnet it’s time to create the Route Table and associate the respective subnets to it. We will give the name of the file as rt.tf
.
resource "aws_route_table" "public-route-table" {
vpc_id = aws_vpc.production.id tags = {
Name = "Public-Route-Table"
}
}resource "aws_route_table" "private-route-table" {
vpc_id = aws_vpc.production.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
}
To give the private instances internet connectivity we can opt either NAT Instance
or NAT Gateway
. Now a Days NAT Instance
is an outdated concept. If you compare NAT Gateway
over NAT Instance
we can found that,
* NAT Gateway
can have a bandwidth of 45 Gbps whereas inNAT Instance
it depends upon the instance family. Lower the type lower the speed.
* The main advantage of NAT Gateway
over NAT Instance
is that aNATGateway
is managed by AWS. we do not need to perform any maintenance. Whereas in NAT Instance
we have to manage everything, for example, by installing software updates or operating system patches on the instance.
For this demo let’s go with NAT Gateway
. To create NAT Gateway
we also need to create an Elastic IP because NAT Gateway
internally uses Elastic IP and we have to prove that in the terraform file. So before creating the terraform file for NAT Gateway
we will create the terraform file for Elastic IP. We can give it a name as eip.tf
. We can also assign a private IP to EIP so it won’t pick up any random private IP.
resource "aws_eip" "eip" {
vpc = true
associate_with_private_ip = "192.168.0.5" tags = {
Name = "Production-EIP"
}
}
After created the terraform file for Elastic IP let’s jump over and create the terraform file for NAT Gateway
. We can give the file a name as nat.tf
.
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]
}
To get internet inside the VPC we have to create an Internet Gateway
and update the Route table as well. To create an Internet Gateway
we will now create a terraform file named igw.tf
. The content of the file is as below,
resource "aws_internet_gateway" "production-igw" {
vpc_id = aws_vpc.production.id tags = {
Name = "Production-IGW"
}
}
After the Internet Gateway
has been created we need to update the Route Table
to allow internet inside the subnets. The updated Route Table
file i.e rt.tf
will look as follows,
resource "aws_route_table" "public-route-table" {
vpc_id = aws_vpc.production.id tags = {
Name = "Public-Route-Table"
}
}resource "aws_route_table" "private-route-table" {
vpc_id = aws_vpc.production.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
}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"
}
Our infrastructure setup is completed let’s create an outputs.tf
file to get the output in the console which resources have been created.
output "vpc_id" {
value = aws_vpc.production.id
}output "vpc_cidr_block" {
value = aws_vpc.production.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
}
The final step is to create a tfvars
file which will contain all the missing variables as well as it easy to modify the value later in one file rather than changing the whole infrastructure. The production.tfvars
file looks something like the below,
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"
Let’s apply the code by providing the variable and see what going to happen.
$ terraform init
$ terraform plan --var-file="production.tfvars"
$ terraform apply --var-file="production.tfvars"
If everything is written correctly then the terraform will create a New VPC
with three subnets each in Private
and Public
. An Internet Gateway
with the update Routes
in Route Table
. A NAT Gateway
with an EIP.
The reason behind passing the variable values in a separate file i.e. .tfvars
is that we can change the value dynamically. It’s easy to change the value at one place rather than changing at every 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.
Note: If you’re running the terraform without tfvars
file, then you have to comment or delete the associate_with_private_ip
line in eip.tf
file so it won’t throw any error.
All the codes are available in Github. Repo URL: https://github.com/saumik8763/terraform-vpc