In this article, we’ll use Terraform to set up an AWS VPC Transit Gateway, enabling seamless connectivity and communication between multiple VPCs within your AWS infrastructure.
Table of Contents
What is Terraform?
- It is an open-source IaaC (Infrastructure as a code) software tool where you define and create resources using providers in the declarative configuration language example JSON.
- With Terraform, You can package and reuse the code in the form of modules.
- It supports a number of cloud infrastructure providers such as AWS, Azure, GCP, IBM Cloud, OCI, etc.
What is AWS Transit Gateway?
AWS Transit Gateway is a service that simplifies network connectivity for multiple Amazon Virtual Private Clouds (VPCs) and on-premises networks. It acts as a hub that allows you to connect VPCs and VPN connections, enabling centralized management of network routing and traffic between these resources. This simplifies network architecture, improves scalability, and reduces operational complexity by providing a single gateway for routing traffic across multiple networks.
Example:
- VPC 1: This VPC hosts a web application that needs to communicate with backend services in VPC 2 and also needs access to a shared database in VPC 3.
- VPC 2: This VPC contains backend services such as application servers, databases, and message queues that support the web application in VPC 1.
- VPC 3: This VPC hosts a shared database that is used by multiple applications and services, including the web application in VPC 1.
Using AWS Transit Gateway:
- You deploy an AWS Transit Gateway in Region A.
- You attach all three VPCs (VPC 1, VPC 2, and VPC 3) to the Transit Gateway.
- You configure route tables in the Transit Gateway to allow communication between these VPCs.
- The web application in VPC 1 can now securely communicate with backend services in VPC 2 through the Transit Gateway without requiring direct peering connections.
- Similarly, the web application can access the shared database in VPC 3 via the Transit Gateway.
- You can also connect on-premises networks or other VPCs from different regions to the Transit Gateway, enabling centralized network connectivity management.
This setup simplifies network architecture, reduces the number of peering connections, and provides a scalable and centralized solution for managing network traffic between multiple VPCs and external networks.
Prerequisites
- AWS Account: You should have an active AWS account with the necessary permissions to create and manage resources.
- Terraform: Terraform is an infrastructure provisioning tool that you’ll need to install on your local machine.
- Visual Studio Code Editor Installed
Steps for Setting up AWS VPC Transit Gateway using Terraform
Summary view of the terraform files in VS code:
Step#1:Create provider.tf file
The provider.tf file in Terraform is a configuration file that specifies the cloud provider and its corresponding plugin that Terraform will use to manage resources in that provider.
Provider.tf
provider "aws" {
region = "ap-south-1"
profile= "default"
}
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "5.43.0"
}
}
}
Step#2:Create Web App VPC in Mumbai Region
The Vpc.tf file in Terraform configures an Amazon VPC in Mumbai, defining CIDR blocks, public and private subnets, an internet gateway for external access, route tables, and routes for internet-bound traffic.
web-app-vpc.tf
resource "aws_vpc" "WEB_APP_VPC" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "WEB_APP_VPC"
}
}
resource "aws_subnet" "WEB_APP_SUBNET" {
vpc_id = aws_vpc.WEB_APP_VPC.id
cidr_block = "10.0.1.0/24"
availability_zone = "ap-south-1a"
tags = {
Name = "WEB_APP_SUBNET"
}
}
resource "aws_internet_gateway" "WEB_APP_IGW" {
vpc_id = aws_vpc.WEB_APP_VPC.id
tags = {
Name = "WEB_APP_IGW"
}
}
resource "aws_default_route_table" "WEB_APP_ROUTE" {
default_route_table_id = aws_vpc.WEB_APP_VPC.default_route_table_id
tags = {
Name = "WEB_APP_ROUTE"
}
}
resource "aws_route" "web_app_route" {
route_table_id = aws_default_route_table.WEB_APP_ROUTE.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.WEB_APP_IGW.id
depends_on = [aws_vpc.WEB_APP_VPC] # Ensure VPC is created before route
}
# Route to BACKEND_SERVICES_VPC via Transit Gateway Attachment
resource "aws_route" "web_app_to_backend_services" {
route_table_id = aws_default_route_table.WEB_APP_ROUTE.id
destination_cidr_block = "11.0.0.0/16" # Replace with actual VPC 2 CIDR block
transit_gateway_id = aws_ec2_transit_gateway_vpc_attachment.web_app_attachment.transit_gateway_id
depends_on = [aws_ec2_transit_gateway_vpc_attachment.web_app_attachment]
}
# Route to SHARED_DATABASE_VPC via Transit Gateway Attachment
resource "aws_route" "web_app_to_shared_database" {
route_table_id = aws_default_route_table.WEB_APP_ROUTE.id
destination_cidr_block = "12.0.0.0/16" # Replace with actual VPC 3 CIDR block
transit_gateway_id = aws_ec2_transit_gateway_vpc_attachment.web_app_attachment.transit_gateway_id
depends_on = [aws_ec2_transit_gateway_vpc_attachment.web_app_attachment]
}
Step#3:Create Backend Services VPC in Mumbai Region
bakcend-services-vpc.tf
resource "aws_vpc" "BACKEND_SERVICES_VPC" {
cidr_block = "11.0.0.0/16"
tags = {
Name = "BACKEND_SERVICES_VPC"
}
}
resource "aws_subnet" "BACKEND_SERVICES_SUBNET" {
vpc_id = aws_vpc.BACKEND_SERVICES_VPC.id
cidr_block = "11.0.1.0/24"
availability_zone = "ap-south-1a"
tags = {
Name = "BACKEND_SERVICES_SUBNET"
}
}
resource "aws_internet_gateway" "BACKEND_SERVICES_IGW" {
vpc_id = aws_vpc.BACKEND_SERVICES_VPC.id
tags = {
Name = "BACKEND_SERVICES_IGW"
}
}
# Create a default route table for backend services VPC
resource "aws_default_route_table" "BACKEND_SERVICES_ROUTE" {
default_route_table_id = aws_vpc.BACKEND_SERVICES_VPC.default_route_table_id
tags = {
Name = "BACKEND_SERVICES_ROUTE"
}
}
# Create a default route for backend services VPC
resource "aws_route" "backend_services_route" {
route_table_id = aws_default_route_table.BACKEND_SERVICES_ROUTE.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.BACKEND_SERVICES_IGW.id # Replace with actual internet gateway ID
depends_on = [aws_vpc.BACKEND_SERVICES_VPC] # Ensure VPC is created before route
}
resource "aws_route" "backend_services_to_web_app" {
route_table_id = aws_default_route_table.BACKEND_SERVICES_ROUTE.id
destination_cidr_block = "10.0.0.0/16" # Replace with actual WEB_APP VPC CIDR block
transit_gateway_id = aws_ec2_transit_gateway_vpc_attachment.backend_services_attachment.transit_gateway_id
depends_on = [aws_ec2_transit_gateway_vpc_attachment.backend_services_attachment]
}
resource "aws_route" "backend_services_to_shared_database" {
route_table_id = aws_default_route_table.BACKEND_SERVICES_ROUTE.id
destination_cidr_block = "12.0.0.0/16" # Replace with actual SHARED_DATABASE VPC CIDR block
transit_gateway_id = aws_ec2_transit_gateway_vpc_attachment.backend_services_attachment.transit_gateway_id
depends_on = [aws_ec2_transit_gateway_vpc_attachment.backend_services_attachment]
}
Step#4:Create Shared Database VPC in Mumbai Region
shared-database-vpc.tf
resource "aws_vpc" "SHARED_DATABASE_VPC" {
cidr_block = "12.0.0.0/16"
tags = {
Name = "SHARED_DATABASE_VPC"
}
}
resource "aws_subnet" "SHARED_DATABASE_SUBNET" {
vpc_id = aws_vpc.SHARED_DATABASE_VPC.id
cidr_block = "12.0.1.0/24"
availability_zone = "ap-south-1a"
tags = {
Name = "SHARED_DAYABASE_SUBNET"
}
}
resource "aws_internet_gateway" "SHARED_DATABASE_IGW" {
vpc_id = aws_vpc.SHARED_DATABASE_VPC.id
tags = {
Name = "SHARED_DATABASE_IGW"
}
}
# Create a default route table for shared database VPC
resource "aws_default_route_table" "SHARED_DATABASE_ROUTE" {
default_route_table_id = aws_vpc.SHARED_DATABASE_VPC.default_route_table_id
tags = {
Name = "SHARED_DATABASE_ROUTE"
}
}
# Create a default route for shared database VPC
resource "aws_route" "shared_database_route" {
route_table_id = aws_default_route_table.SHARED_DATABASE_ROUTE.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.SHARED_DATABASE_IGW.id # Replace with actual internet gateway ID
depends_on = [aws_vpc.SHARED_DATABASE_VPC] # Ensure VPC is created before route
}
# Route to backend services VPC via Transit Gateway Attachment
resource "aws_route" "shared_database_to_backend_services" {
route_table_id = aws_default_route_table.SHARED_DATABASE_ROUTE.id
destination_cidr_block = "11.0.0.0/16" # Replace with actual BACKEND_SERVICES VPC CIDR block
transit_gateway_id = aws_ec2_transit_gateway_vpc_attachment.shared_database_attachment.transit_gateway_id
depends_on = [aws_ec2_transit_gateway_vpc_attachment.shared_database_attachment]
}
resource "aws_route" "shared_database_to_webapp" {
route_table_id = aws_default_route_table.SHARED_DATABASE_ROUTE.id
destination_cidr_block = "10.0.0.0/16" # Replace with actual web_app VPC CIDR block
transit_gateway_id = aws_ec2_transit_gateway_vpc_attachment.shared_database_attachment.transit_gateway_id
depends_on = [aws_ec2_transit_gateway_vpc_attachment.shared_database_attachment]
}
Step#5:Create EC2 Instances in each VPC
ec2-web-app.tf
# Create a security group named 'customer-securitygrp' with ingress rules
resource "aws_default_security_group" "default" {
vpc_id = aws_vpc.WEB_APP_VPC.id
tags = {
Name = "Webapp-sg"
}
# Allow SSH access from your specific IP or CIDR block (replace with yours)
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# Allow HTTP access from anywhere for testing (consider restricting later)
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# Remove unnecessary ICMP rule for web applications (optional)
# ingress {
# from_port = -1
# to_port = -1
# protocol = "icmp"
# cidr_blocks = ["0.0.0.0/0"]
# }
# Allow all outbound traffic for simplicity (consider restricting later)
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# Launch the EC2 instance
resource "aws_instance" "webapp_linux" {
# Use data source to retrieve the latest Ubuntu AMI
ami = "ami-007020fd9c84e18c7"
instance_type = "t2.micro" # Adjust instance type as needed
# Ensure you have a key pair created and named "customer-keypair" in AWS
key_name = "webapp_keypair"
# Securely encode the user data script with base64encode
user_data = base64encode(<<EOF
#!/bin/bash
# Update package list for Ubuntu
sudo apt update -y
# Install Nginx web server
sudo apt install nginx -y
# Start the Nginx service
sudo systemctl start nginx
# Enable Nginx to start automatically on boot
sudo systemctl enable nginx
# Create and populate the index.html file with hostname and IP address
cat <<EOF > /var/www/html/index.html
<!DOCTYPE html>
<html>
<head>
<title>Welcome to My Web App!</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
}
.container {
max-width: 800px;
margin: 50px auto;
padding: 20px;
background-color: #fff;
border-radius: 5px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
}
</style>
</head>
<body>
<div class="container">
<h1>Welcome to My Web App!</h1>
<p><strong>Hostname:</strong> $(hostname)</p>
<p><strong>IP Address:</strong> $(hostname -I | awk '{print $1}')</p>
</div>
</body>
</html>
EOF
)
# Tag the instance for easy identification
tags = {
Name = "Webapp-Linux-Instance"
}
# Associate the instance with a security group
vpc_security_group_ids = [aws_default_security_group.default.id]
# Associate the instance with a subnet
subnet_id = aws_subnet.WEB_APP_SUBNET.id
# Enable auto-assign public IP (optional)
associate_public_ip_address = true
}
# Output the public IP address of the instance (optional)
output "webapp_public_ip" {
value = aws_instance.webapp_linux.public_ip
}
ec2-backend-services.tf
# Create a security group named 'customer-securitygrp' with ingress rules
resource "aws_default_security_group" "default2" {
vpc_id = aws_vpc.BACKEND_SERVICES_VPC.id
tags = {
Name = "BackendServices-sg"
}
# Allow SSH access from your specific IP or CIDR block (replace with yours)
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# Allow HTTP access from anywhere for testing (consider restricting later)
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# Remove unnecessary ICMP rule for web applications (optional)
# ingress {
# from_port = -1
# to_port = -1
# protocol = "icmp"
# cidr_blocks = ["0.0.0.0/0"]
# }
# Allow all outbound traffic for simplicity (consider restricting later)
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# Launch the EC2 instance
resource "aws_instance" "backendservices_linux" {
# Use data source to retrieve the latest Ubuntu AMI
ami = "ami-007020fd9c84e18c7"
instance_type = "t2.micro" # Adjust instance type as needed
# Ensure you have a key pair created and named "customer-keypair" in AWS
key_name = "webapp_keypair"
# Securely encode the user data script with base64encode
user_data = base64encode(<<EOF
#!/bin/bash
# Update package list for Ubuntu
sudo apt update -y
# Install Nginx web server
sudo apt install nginx -y
# Start the Nginx service
sudo systemctl start nginx
# Enable Nginx to start automatically on boot
sudo systemctl enable nginx
# Create and populate the index.html file with hostname and IP address
cat <<EOF > /var/www/html/index.html
<!DOCTYPE html>
<html>
<head>
<title>Welcome to My Web App!</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
}
.container {
max-width: 800px;
margin: 50px auto;
padding: 20px;
background-color: #fff;
border-radius: 5px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
}
</style>
</head>
<body>
<div class="container">
<h1>Welcome to My Web App!</h1>
<p><strong>Hostname:</strong> $(hostname)</p>
<p><strong>IP Address:</strong> $(hostname -I | awk '{print $1}')</p>
</div>
</body>
</html>
EOF
)
# Tag the instance for easy identification
tags = {
Name = "Backend-Services-Linux-Instance"
}
# Associate the instance with a security group
vpc_security_group_ids = [aws_default_security_group.default2.id]
# Associate the instance with a subnet
subnet_id = aws_subnet.BACKEND_SERVICES_SUBNET.id
# Enable auto-assign public IP (optional)
associate_public_ip_address = true
}
# Output the public IP address of the instance (optional)
output "backendservices_public_ip" {
value = aws_instance.backendservices_linux.public_ip
}
ec2-shared-database.tf
# Create a security group named 'customer-securitygrp' with ingress rules
resource "aws_default_security_group" "default3" {
vpc_id = aws_vpc.SHARED_DATABASE_VPC.id
tags = {
Name = "Shareddatabase-sg"
}
# Allow SSH access from your specific IP or CIDR block (replace with yours)
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# Allow HTTP access from anywhere for testing (consider restricting later)
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# Remove unnecessary ICMP rule for web applications (optional)
# ingress {
# from_port = -1
# to_port = -1
# protocol = "icmp"
# cidr_blocks = ["0.0.0.0/0"]
# }
# Allow all outbound traffic for simplicity (consider restricting later)
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# Launch the EC2 instance
resource "aws_instance" "shareddatabase_linux" {
# Use data source to retrieve the latest Ubuntu AMI
ami = "ami-007020fd9c84e18c7"
instance_type = "t2.micro" # Adjust instance type as needed
# Ensure you have a key pair created and named "customer-keypair" in AWS
key_name = "webapp_keypair"
# Securely encode the user data script with base64encode
user_data = base64encode(<<EOF
#!/bin/bash
# Update package list for Ubuntu
sudo apt update -y
# Install Nginx web server
sudo apt install nginx -y
# Start the Nginx service
sudo systemctl start nginx
# Enable Nginx to start automatically on boot
sudo systemctl enable nginx
# Create and populate the index.html file with hostname and IP address
cat <<EOF > /var/www/html/index.html
<!DOCTYPE html>
<html>
<head>
<title>Welcome to My Web App!</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
}
.container {
max-width: 800px;
margin: 50px auto;
padding: 20px;
background-color: #fff;
border-radius: 5px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
}
</style>
</head>
<body>
<div class="container">
<h1>Welcome to My Web App!</h1>
<p><strong>Hostname:</strong> $(hostname)</p>
<p><strong>IP Address:</strong> $(hostname -I | awk '{print $1}')</p>
</div>
</body>
</html>
EOF
)
# Tag the instance for easy identification
tags = {
Name = "SharedDatabase-Linux-Instance"
}
# Associate the instance with a security group
vpc_security_group_ids = [aws_default_security_group.default3.id]
# Associate the instance with a subnet
subnet_id = aws_subnet.SHARED_DATABASE_SUBNET.id
# Enable auto-assign public IP (optional)
associate_public_ip_address = true
}
# Output the public IP address of the instance (optional)
output "shared_database_public_ip" {
value = aws_instance.shareddatabase_linux.public_ip
}
Step#6:Create Transit Gateway
transit_gateway.tf
resource "aws_ec2_transit_gateway" "example" {
description = "tg-web-backend-database"
tags = {
Name = "Web-Backend-Database Transit Gateway"
}
}
Step#7:Create Transit Gateway Attachment
transit_gateway_attachment.tf
# Attach WEB_APP_VPC to the Transit Gateway
resource "aws_ec2_transit_gateway_vpc_attachment" "web_app_attachment" {
# ID of the Transit Gateway
transit_gateway_id = aws_ec2_transit_gateway.example.id
subnet_ids = [
aws_subnet.WEB_APP_SUBNET.id # Reference the created subnet ID
]
# VPC ID to be attached
vpc_id = aws_vpc.WEB_APP_VPC.id
# Optional tags for identification
tags = {
Name = "Web App VPC Attachment"
}
}
# Attach WEB_APP_VPC to the Transit Gateway
resource "aws_ec2_transit_gateway_vpc_attachment" "backend_services_attachment" {
# ID of the Transit Gateway
transit_gateway_id = aws_ec2_transit_gateway.example.id
subnet_ids = [
aws_subnet.BACKEND_SERVICES_SUBNET.id # Reference the created subnet ID
]
# VPC ID to be attached
vpc_id = aws_vpc.BACKEND_SERVICES_VPC.id
# Optional tags for identification
tags = {
Name = "Backend Services VPC Attachment"
}
}
# Attach WEB_APP_VPC to the Transit Gateway
resource "aws_ec2_transit_gateway_vpc_attachment" "shared_database_attachment" {
# ID of the Transit Gateway
transit_gateway_id = aws_ec2_transit_gateway.example.id
subnet_ids = [
aws_subnet.SHARED_DATABASE_SUBNET.id # Reference the created subnet ID
]
# VPC ID to be attached
vpc_id = aws_vpc.SHARED_DATABASE_VPC.id
# Optional tags for identification
tags = {
Name = "Shared database VPC Attachment"
}
}
Step#8:Let’s Apply Configuration
Let’s run our configuration and make sure everything works correctly. On the command line, run the following commands:
- terraform init
- terraform plan
- terraform apply -auto-approve
Step#9:Verify Connectivity Between VPCs
This step is optional but highly recommended to confirm successful communication between your VPCs through the Transit Gateway. Here’s how to proceed:
1. Gather Private IPs:
- In the AWS Management Console, navigate to the EC2 service.
- Locate the EC2 instances you launched in each VPC (web-app-vpc, backend-services-vpc, shared-database-vpc).
- Identify Private IPs:
- For each instance, note down its private IP address. You’ll need the private IPs of the backend service instance in backend-services-vpc and the shared database instance in shared-database-vpc.
2. Connect to Web Application Instance (web-app-vpc):
- Use a method like SSH to connect to the EC2 instance launched in web-app-vpc. This is the instance hosting your web application.
3. Test Connectivity with curl:
- Once connected to the web application instance, use the curl command to attempt communication with the backend service and shared database instances in other VPCs.
Step#10:Delete all the resources created
To delete all resources created just enter the below command:
terraform destroy -auto-approve
Conclusion:
In conclusion, leveraging Terraform for AWS VPC Transit Gateway setup streamlines network management, enhances VPC connectivity, and optimizes resource accessibility across multiple VPCs, ensuring efficient and scalable infrastructure deployment.
Reference:-
For reference visit the official website .
Any queries pls contact us @Fosstechnix.com.
Related Articles: