Today marks the Day 15 of 30days of aws terraform challenge initiative by Piyush Sachdev. Today we will deep into the concept of VPC Peering. What exactly is VPC-Peering, what are its use-cases and why we need that in the first place.
VPC-Peering:
As we all know VPC is nothing but a Virtual Private Cloud where we will be hosting our apps or services within the assigned IP addresses of that VPC. Suppose you have 2 VPC's in 2 different regions and you want to have communication between them.
Like app service inside a EC2 instance of one region VPC needs to communicate with DB service of another region VPC. Then you need to establish VPC-Peering for communication to happen, else there will be no communication happening between them.
Simply, In the world of cloud infrastructure, network isolation is the default. But what happens when your App service in us-east-1 needs to fetch data from a database in us-west-2
Routing this traffic over the public internet is slow, insecure, and expensive (NAT Gateway costs add up!). The solution is VPC Peering - a networking connection that allows two VPCs to communicate as if they are in the same network.
Low-Level Architecture (LLD):
Before we write code, let's visualize the packet flow. Below is the architecture we are building. Note how traffic flows privately through the AWS backbone, bypassing the public internet entirely for inter-VPC communication.
- The Traffic Flow: EC2 Instance (East) sends a packet to 10.1.x.x (West).
- Route Table (East): sees the destination is the Peering Connection (pcx-id), not the Internet Gateway.
- AWS Backbone routes traffic securely across regions.
- Security Group (West) validates the inbound request (is it from 10.0.0.0/16?).
- EC2 Instance (West) processes the request.
Below are the resources that will be created during this project:
Networking Components
Two VPCs:
Primary VPC in us-east-1 (10.0.0.0/16)
Secondary VPC in us-west-2 (10.1.0.0/16)
Subnets:
One public subnet in each VPC
Configured with auto-assign public IP
Internet Gateways:
One for each VPC to allow internet access
Route Tables:
Custom route tables with routes to internet and peered VPC
Routes for VPC peering traffic.
VPC Peering Connection:
Cross-region peering between the two VPCs
Automatic acceptance configured
Compute Resources
EC2 Instances:
One t2.micro instance in each VPC
Running Amazon Linux 2
Apache web server installed
Custom web page showing VPC information
Security Groups:
SSH access from anywhere (port 22)
ICMP (ping) allowed from peered VPC
All TCP traffic allowed between VPCs
The Terraform Implementation(Step-by-Step):
Creating 2 new key-pairs for both Ec2 Instances which will be created before:
# For us-east-1
aws ec2 create-key-pair --key-name vpc-peering-demo --region us-east-1 --query 'KeyMaterial' --output text > vpc-peering-demo.pem
# For us-west-2
aws ec2 create-key-pair --key-name vpc-peering-demo --region us-west-2 --query 'KeyMaterial' --output text > vpc-peering-demo.pem
# Set permissions (on Linux/Mac)
chmod 400 vpc-peering-demo.pem
provider.tf:
provider "aws" {
region = "us-east-1"
alias = "primary"
}
provider "aws" {
region = "us-west-2"
alias = "secondary"
}
Creating 2 VPC's:
# Primary VPC in us-east-1
resource "aws_vpc" "primary_vpc" {
provider = aws.primary
cidr_block = var.primary_vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "Primary-VPC-${var.primary_region}"
Environment = "Demo"
Purpose = "VPC-Peering-Demo"
}
}
# Secondary VPC in us-west-2
resource "aws_vpc" "secondary_vpc" {
provider = aws.secondary
cidr_block = var.secondary_vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "Secondary-VPC-${var.secondary_region}"
Environment = "Demo"
Purpose = "VPC-Peering-Demo"
}
}
The above block creates 2 new VPC's in 2 different regions with different CIDR's block.
Creating 2 Subnets:
# Subnet in Primary VPC
resource "aws_subnet" "primary_subnet" {
provider = aws.primary
vpc_id = aws_vpc.primary_vpc.id
cidr_block = var.primary_subnet_cidr
availability_zone = data.aws_availability_zones.primary.names[0]
map_public_ip_on_launch = true
tags = {
Name = "Primary-Subnet-${var.primary_region}"
Environment = "Demo"
}
}
# Subnet in Secondary VPC
resource "aws_subnet" "secondary_subnet" {
provider = aws.secondary
vpc_id = aws_vpc.secondary_vpc.id
cidr_block = var.secondary_subnet_cidr
availability_zone = data.aws_availability_zones.secondary.names[0]
map_public_ip_on_launch = true
tags = {
Name = "Secondary-Subnet-${var.secondary_region}"
Environment = "Demo"
}
}
The above block creates 2 subnets in 2 different regions within the VPC CIDR block.
Creating Internet Gateways:
# Internet Gateway for Primary VPC
resource "aws_internet_gateway" "primary_igw" {
provider = aws.primary
vpc_id = aws_vpc.primary_vpc.id
tags = {
Name = "Primary-IGW"
Environment = "Demo"
}
}
# Internet Gateway for Secondary VPC
resource "aws_internet_gateway" "secondary_igw" {
provider = aws.secondary
vpc_id = aws_vpc.secondary_vpc.id
tags = {
Name = "Secondary-IGW"
Environment = "Demo"
}
}
The above block creates 2 Internet Gateways associating with the already existing VPC in 2 regions.
Creating Route Tables:
# Route table for Primary VPC
resource "aws_route_table" "primary_rt" {
provider = aws.primary
vpc_id = aws_vpc.primary_vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.primary_igw.id
}
tags = {
Name = "Primary-Route-Table"
Environment = "Demo"
}
}
# Route table for Secondary VPC
resource "aws_route_table" "secondary_rt" {
provider = aws.secondary
vpc_id = aws_vpc.secondary_vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.secondary_igw.id
}
tags = {
Name = "Secondary-Route-Table"
Environment = "Demo"
}
}
The above block demonstrates aws_route_table for creating Route tables and creating a route for internet gateway.
Creating Route_table_association
# Associate route table with Primary subnet
resource "aws_route_table_association" "primary_rta" {
provider = aws.primary
subnet_id = aws_subnet.primary_subnet.id
route_table_id = aws_route_table.primary_rt.id
}
# Associate route table with Secondary subnet
resource "aws_route_table_association" "secondary_rta" {
provider = aws.secondary
subnet_id = aws_subnet.secondary_subnet.id
route_table_id = aws_route_table.secondary_rt.id
}
In this block, we are associating route tables with the subnets.
VPC Peering Connection:
# VPC Peering Connection (Requester side - Primary VPC)
resource "aws_vpc_peering_connection" "primary_to_secondary" {
provider = aws.primary
vpc_id = aws_vpc.primary_vpc.id
peer_vpc_id = aws_vpc.secondary_vpc.id
peer_region = var.secondary_region
auto_accept = false
tags = {
Name = "Primary-to-Secondary-Peering"
Environment = "Demo"
Side = "Requester"
}
}
# VPC Peering Connection Accepter (Accepter side - Secondary VPC)
resource "aws_vpc_peering_connection_accepter" "secondary_accepter" {
provider = aws.secondary
vpc_peering_connection_id = aws_vpc_peering_connection.primary_to_secondary.id
auto_accept = true
tags = {
Name = "Secondary-Peering-Accepter"
Environment = "Demo"
Side = "Accepter"
}
}
VPC Peering is a two-step process: Request and Accept.
The Requester (aws_vpc_peering_connection): Initiates the call from the Primary VPC. We must specify the peer_region because this is a cross-region peer.
The Accepter (aws_vpc_peering_connection_accepter): Lives in the Secondary region. It "picks up the phone" and establishes the tunnel.
Adding routes for VPC-Peering
# Add route to Secondary VPC in Primary route table
resource "aws_route" "primary_to_secondary" {
provider = aws.primary
route_table_id = aws_route_table.primary_rt.id
destination_cidr_block = var.secondary_vpc_cidr
vpc_peering_connection_id = aws_vpc_peering_connection.primary_to_secondary.id
depends_on = [aws_vpc_peering_connection_accepter.secondary_accepter]
}
# Add route to Primary VPC in Secondary route table
resource "aws_route" "secondary_to_primary" {
provider = aws.secondary
route_table_id = aws_route_table.secondary_rt.id
destination_cidr_block = var.primary_vpc_cidr
vpc_peering_connection_id = aws_vpc_peering_connection.primary_to_secondary.id
depends_on = [aws_vpc_peering_connection_accepter.secondary_accepter]
}
Creating the peering connection is not enough. You must tell the VPCs how to use it. We modify the Route Tables in both VPCs to point traffic destined for the other VPC's CIDR block to the peering connection.
Many engineers create the peering link but forget to update the Route Tables. Without these routes, packets will be dropped.
Now create terraform blocks for Security Group and EC2 Instances creation too. I was not including that here as it would make blog bigger. We will be using Data Sources block in EC2 instance creation for availability zones and AMI.
Deployment & Verification
Once all the files were run, run the below 3 commands for creation of Infra.
terraform init
terraform plan
terraform apply
After running terraform plan or apply, you could see 18 resources to be created.
Plan: 18 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ primary_instance_id = (known after apply)
+ primary_instance_private_ip = (known after apply)
+ primary_instance_public_ip = (known after apply)
+ primary_vpc_cidr = "10.0.0.0/16"
+ primary_vpc_id = (known after apply)
+ secondary_instance_id = (known after apply)
+ secondary_instance_private_ip = (known after apply)
+ secondary_instance_public_ip = (known after apply)
+ secondary_vpc_cidr = "10.1.0.0/16"
+ secondary_vpc_id = (known after apply)
+ test_connectivity_command = (known after apply)
+ vpc_peering_connection_id = (known after apply)
+ vpc_peering_status = (known after apply)
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
The below images shows the creation of VPC Peering Connection from Primary to Secondary.
You could see the creation of 2 EC2 instances created through Terraform.
ubuntu@ip-10-0-1-126:~$ ping 10.1.1.113
PING 10.1.1.113 (10.1.1.113) 56(84) bytes of data.
64 bytes from 10.1.1.113: icmp_seq=1 ttl=64 time=63.0 ms
64 bytes from 10.1.1.113: icmp_seq=2 ttl=64 time=62.8 ms
64 bytes from 10.1.1.113: icmp_seq=3 ttl=64 time=62.9 ms
64 bytes from 10.1.1.113: icmp_seq=4 ttl=64 time=62.8 ms
64 bytes from 10.1.1.113: icmp_seq=5 ttl=64 time=62.8 ms
^C
--- 10.1.1.113 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4007ms
rtt min/avg/max/mdev = 62.752/62.830/63.021/0.101 ms
ubuntu@ip-10-0-1-126:~$
ubuntu@ip-10-0-1-126:~$
ubuntu@ip-10-0-1-126:~$ curl 10.1.1.113
<h1>Secondary VPC Instance - us-west-2</h1>
<p>Private IP: 10.1.1.113 </p
You could see that I have Pinged the secondary EC2 instance from Primary Ec2 instance and we are able to connect between them.
You could also see while trying to access Primary EC2 instance from Secondary one.
Make sure to terminate all the resources once the project is done.
terraform destroy --auto-approve
Conclusion
This marks the Day 15 of 30 Days of AWS Terraform challenge. Today we have done a mini-project on VPC Peering Connection by trying to establish connection between 2 VPC's in 2 different regions.






Top comments (0)