Setting Up a Robust VPC with Public and Private Subnets for High Availability
In this blog post, I will guide you through deploying a highly available infrastructure on AWS using Terraform. The infrastructure setup includes public and private subnets, an Internet Gateway, and NAT Gateways to enable outbound internet access from private subnets. Additionally, we'll explore the advantages and disadvantages of this configuration.
Step 1: Create the VPC
The first step is to create the VPC itself. We define the CIDR block for the VPC and any tags we want to associate with it.
resource "aws_vpc" "redo_vpc" {
cidr_block = var.cidr_block
tags = var.tags
}
Step 2: Create Public and Private Subnets
Next, we create public and private subnets within the VPC. We define the CIDR blocks for each subnet and associate them with the appropriate Availability Zones (AZs). The public subnets will have direct internet access, while the private subnets will not.
resource "aws_subnet" "redo_public_subnets" {
count = length(var.public_subnets)
cidr_block = var.public_subnets[count.index]
vpc_id = aws_vpc.redo_vpc.id
availability_zone = var.availability_zones[count.index]
tags = {
Name = "public-subnet-${count.index + 1}"
}
depends_on = [aws_vpc.redo_vpc]
}
resource "aws_subnet" "redo_private_subnets" {
count = length(var.private_subnets)
cidr_block = var.private_subnets[count.index]
vpc_id = aws_vpc.redo_vpc.id
availability_zone = var.availability_zones[count.index]
tags = {
Name = "private-subnet-${count.index + 1}"
}
depends_on = [aws_vpc.redo_vpc]
}
Step 3: Create an Internet Gateway and Public Route Table
We create an Internet Gateway and associate it with the VPC. Then, we create a public route table with a route to the Internet Gateway for all internet-bound traffic (0.0.0.0/0).
resource "aws_internet_gateway" "redo_igw" {
vpc_id = aws_vpc.redo_vpc.id
tags = {
Name = "Main IGW"
}
depends_on = [aws_vpc.redo_vpc]
}
resource "aws_route_table" "redo_public_rt" {
vpc_id = aws_vpc.redo_vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.redo_igw.id
}
tags = {
Name = "Public Route Table"
}
depends_on = [aws_internet_gateway.redo_igw]
}
Step 4: Associate Public Subnets with the Public Route Table
We associate the public subnets with the public route table, ensuring that instances in these subnets have direct internet access.
resource "aws_route_table_association" "redo_public_rt_association" {
count = length(aws_subnet.redo_public_subnets)
subnet_id = aws_subnet.redo_public_subnets[count.index].id
route_table_id = aws_route_table.redo_public_rt.id
depends_on = [aws_subnet.redo_public_subnets, aws_route_table.redo_public_rt]
}
Step 5: Create Elastic IPs and NAT Gateways
We create Elastic IPs and NAT Gateways, one for each public subnet. The NAT Gateways are placed in the public subnets and will be used to provide outbound internet access for instances in the private subnets.
resource "aws_eip" "nat_eip" {
count = length(var.public_subnets)
domain = "vpc"
tags = {
Name = "NAT Gateway EIP ${count.index + 1}"
}
depends_on = [aws_internet_gateway.redo_igw]
}
resource "aws_nat_gateway" "redo_nat_gateway" {
count = length(var.public_subnets)
allocation_id = aws_eip.nat_eip[count.index].id
subnet_id = aws_subnet.redo_public_subnets[count.index].id
tags = {
Name = "NAT Gateway ${count.index + 1}"
}
depends_on = [aws_internet_gateway.redo_igw, aws_eip.nat_eip, aws_subnet.redo_public_subnets]
}
Step 6: Create Private Route Tables and Associate Private Subnets
We create private route tables, one for each private subnet. Each private route table has a route to the corresponding NAT Gateway for all internet-bound traffic (0.0.0.0/0). Finally, we associate each private subnet with its corresponding private route table.
resource "aws_route_table" "redo_private_rt" {
count = length(var.private_subnets)
vpc_id = aws_vpc.redo_vpc.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.redo_nat_gateway[count.index].id
}
tags = {
Name = "Private Route Table ${count.index + 1}"
}
depends_on = [aws_nat_gateway.redo_nat_gateway]
}
resource "aws_route_table_association" "private_subnet_routes" {
count = length(var.private_subnets)
subnet_id = aws_subnet.redo_private_subnets[count.index].id
route_table_id = aws_route_table.redo_private_rt[count.index].id
depends_on = [aws_subnet.redo_private_subnets, aws_route_table.redo_private_rt]
}
Architecture diagram & code base:
The code base can be downloaded form https://github.com/smrutiranjan7674/aws-tfmodule-vpc
Pros of this Infrastructure
High Availability : By creating subnets and NAT Gateways in multiple Availability Zones, we achieve high availability for our infrastructure.
Improved Security : Instances in private subnets are isolated from the internet, reducing the attack surface and improving security.
Scalability : The infrastructure can be easily scaled by adding more subnets or adjusting the CIDR block sizes.
Cons of this Infrastructure
Single Point of Failure : While the NAT Gateways are highly available, the Internet Gateway is a single point of failure for internet access from the public subnets.
Increased Complexity : Managing multiple subnets, route tables, and NAT Gateways can increase the complexity of the infrastructure.
Potential Bottleneck : If the traffic through the NAT Gateways is high, it could become a bottleneck for internet access from the private subnets.
Cost Implications of the Infrastructure
When implementing this highly available VPC architecture, it's important to consider the cost implications. While this setup provides enhanced security and reliability, it does come with associated costs. Let's break down the main components that contribute to the overall cost:
VPC and Subnets : AWS does not charge for creating and using a VPC or subnets. However, you may incur minimal data transfer costs between subnets.
Internet Gateway : There is no additional charge for an Internet Gateway. You only pay for the data that passes through it.
NAT Gateways : This is one of the most significant cost factors in this architecture. As of my last update:
Each NAT Gateway has an hourly charge (approximately $0.045 per hour, but check current pricing).
Data processing charges apply for each gigabyte processed through the NAT Gateway (around $0.045 per GB).
In our setup, we're creating a NAT Gateway for each public subnet, which multiplies these costs.
Elastic IPs : Each Elastic IP associated with a NAT Gateway is free as long as it's being used. However, if you allocate an Elastic IP and don't use it, there's a small hourly charge.
Data Transfer : AWS charges for data transfer between Availability Zones and for data transfer out to the internet. The costs can vary based on your region and the amount of data transferred.
Route Tables : There's no charge for creating and using route tables.
Cost Optimization Strategies :
Reduce NAT Gateways : If cost is a major concern, you could reduce the number of NAT Gateways. Instead of one per public subnet, you could use a single NAT Gateway for all private subnets. However, this introduces a single point of failure and could impact the high availability of your setup.
Use NAT Instances : For lower-traffic scenarios, you could replace NAT Gateways with NAT Instances, which are less expensive but require more management.
Optimize Data Transfer : Design your application to minimize data transfer between Availability Zones and out to the internet where possible.
Use AWS Cost Explorer : Regularly review your costs using AWS Cost Explorer to identify any unexpected expenses and optimize accordingly.
Consider Reserved NAT Gateways : If you're planning to use this infrastructure long-term, you might benefit from purchasing reserved NAT Gateways, which offer a discount for a 1-year commitment.
Subscribe to my newsletter
Read articles from Smruti Ranjan Swain directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Smruti Ranjan Swain
Smruti Ranjan Swain
Hi! I’m a DevOps engineer with over 10 years of experience in IT. I’m passionate about exploring new technologies and constantly looking for ways to improve and automate processes. When I’m not diving into cloud architecture or scripting, you’ll probably find me checking out the latest tech gadgets!