GitHub Repository Available
Network Overview #
Flowchart #
graph LR
%% VPC 1
subgraph VPC1["VPC 1: 10.10.0.0/16"]
VPC1-IGW["Internet Gateway"]
VPC1-NAT["NAT Gateway
Public Subnet
us-east-1a"] %% Routing Tables subgraph VPC1-RoutingTables["Routing Tables"] VPC1-PublicRT["Public Routing Table"] VPC1-PrivateRT["Private Routing Table"] end %% Subnets subgraph VPC1-Subnets["Subnets"] VPC1-Subnet1["Public Subnet 10.10.0.0/24
us-east-1a"] VPC1-Subnet2["Private Subnet 10.10.1.0/24
us-east-1a"] VPC1-Subnet3["Private Subnet 10.10.2.0/24
us-east-1b"] end VPC1-PublicRT -->|Route: 0.0.0.0/0 to IGW| VPC1-IGW VPC1-PrivateRT -->|Route: 0.0.0.0/0 to NAT| VPC1-NAT VPC1-Subnet1 -->|Associated| VPC1-PublicRT VPC1-Subnet2 -->|Associated| VPC1-PrivateRT VPC1-Subnet3 -->|Associated| VPC1-PrivateRT VPC1-NAT -->|Outbound Internet Traffic| VPC1-IGW end %% VPC 2 subgraph VPC2["VPC 2: 10.20.0.0/16"] VPC2-IGW["Internet Gateway"] VPC2-NAT["NAT Gateway
Public Subnet
us-east-1a"] %% Routing Tables subgraph VPC2-RoutingTables["Routing Tables"] VPC2-PublicRT["Public Routing Table"] VPC2-PrivateRT["Private Routing Table"] end %% Subnets subgraph VPC2-Subnets["Subnets"] VPC2-Subnet1["Public Subnet 10.20.0.0/24
us-east-1a"] VPC2-Subnet2["Private Subnet 10.20.1.0/24
us-east-1a"] VPC2-Subnet3["Private Subnet 10.20.2.0/24
us-east-1b"] end VPC2-PublicRT -->|Route: 0.0.0.0/0 to IGW| VPC2-IGW VPC2-PrivateRT -->|Route: 0.0.0.0/0 to NAT| VPC2-NAT VPC2-Subnet1 -->|Associated| VPC2-PublicRT VPC2-Subnet2 -->|Associated| VPC2-PrivateRT VPC2-Subnet3 -->|Associated| VPC2-PrivateRT VPC2-NAT -->|Outbound Internet Traffic| VPC2-IGW end %% Transit Gateway TransitGatewayR1["Transit Gateway"] %% Transit Gateway Attachments VPC1 -->|Attachment VPC1:
Subnet 10.10.1.0/24
Subnet 10.10.2.0/24| TransitGatewayR1 VPC2 -->|Attachment VPC2:
Subnet 10.20.1.0/24
Subnet 10.20.2.0/24| TransitGatewayR1 %% Transit Gateway Routes VPC1-PublicRT -->|Route: 10.20.0.0/16 to VPC2| VPC2 VPC1-PrivateRT -->|Route: 10.20.0.0/16 to VPC2| VPC2 VPC2-PublicRT -->|Route: 10.10.0.0/16 to VPC1| VPC1 VPC2-PrivateRT -->|Route: 10.10.0.0/16 to VPC1| VPC1 %% Dashed Border classDef BoarderDash1 stroke-dasharray:5 5; class VPC1-Subnets,VPC1-RoutingTables,VPC1,VPC2 BoarderDash1 classDef BoarderDash2 stroke-dasharray:5 5; class VPC2-Subnets,VPC2-RoutingTables,VPC2,VPC2 BoarderDash2 %% Node Colors VPC1 style VPC1-Subnet1 fill:#d1f7d6,stroke:#333,stroke-width:2px style VPC1-Subnet2 fill:#f7d6d6,stroke:#333,stroke-width:2px style VPC1-Subnet3 fill:#f7d6d6,stroke:#333,stroke-width:2px style VPC1-PublicRT fill:#d1f7d6,stroke:#333,stroke-width:2px style VPC1-PrivateRT fill:#f7d6d6,stroke:#333,stroke-width:2px style VPC1-NAT fill:#d6f7f7,stroke:#333,stroke-width:2px %% Node Colors VPC2 style VPC2-Subnet1 fill:#d1f7d6,stroke:#333,stroke-width:2px style VPC2-Subnet2 fill:#f7d6d6,stroke:#333,stroke-width:2px style VPC2-Subnet3 fill:#f7d6d6,stroke:#333,stroke-width:2px style VPC2-PublicRT fill:#d1f7d6,stroke:#333,stroke-width:2px style VPC2-PrivateRT fill:#f7d6d6,stroke:#333,stroke-width:2px style VPC2-NAT fill:#d6f7f7,stroke:#333,stroke-width:2px
Public Subnet
us-east-1a"] %% Routing Tables subgraph VPC1-RoutingTables["Routing Tables"] VPC1-PublicRT["Public Routing Table"] VPC1-PrivateRT["Private Routing Table"] end %% Subnets subgraph VPC1-Subnets["Subnets"] VPC1-Subnet1["Public Subnet 10.10.0.0/24
us-east-1a"] VPC1-Subnet2["Private Subnet 10.10.1.0/24
us-east-1a"] VPC1-Subnet3["Private Subnet 10.10.2.0/24
us-east-1b"] end VPC1-PublicRT -->|Route: 0.0.0.0/0 to IGW| VPC1-IGW VPC1-PrivateRT -->|Route: 0.0.0.0/0 to NAT| VPC1-NAT VPC1-Subnet1 -->|Associated| VPC1-PublicRT VPC1-Subnet2 -->|Associated| VPC1-PrivateRT VPC1-Subnet3 -->|Associated| VPC1-PrivateRT VPC1-NAT -->|Outbound Internet Traffic| VPC1-IGW end %% VPC 2 subgraph VPC2["VPC 2: 10.20.0.0/16"] VPC2-IGW["Internet Gateway"] VPC2-NAT["NAT Gateway
Public Subnet
us-east-1a"] %% Routing Tables subgraph VPC2-RoutingTables["Routing Tables"] VPC2-PublicRT["Public Routing Table"] VPC2-PrivateRT["Private Routing Table"] end %% Subnets subgraph VPC2-Subnets["Subnets"] VPC2-Subnet1["Public Subnet 10.20.0.0/24
us-east-1a"] VPC2-Subnet2["Private Subnet 10.20.1.0/24
us-east-1a"] VPC2-Subnet3["Private Subnet 10.20.2.0/24
us-east-1b"] end VPC2-PublicRT -->|Route: 0.0.0.0/0 to IGW| VPC2-IGW VPC2-PrivateRT -->|Route: 0.0.0.0/0 to NAT| VPC2-NAT VPC2-Subnet1 -->|Associated| VPC2-PublicRT VPC2-Subnet2 -->|Associated| VPC2-PrivateRT VPC2-Subnet3 -->|Associated| VPC2-PrivateRT VPC2-NAT -->|Outbound Internet Traffic| VPC2-IGW end %% Transit Gateway TransitGatewayR1["Transit Gateway"] %% Transit Gateway Attachments VPC1 -->|Attachment VPC1:
Subnet 10.10.1.0/24
Subnet 10.10.2.0/24| TransitGatewayR1 VPC2 -->|Attachment VPC2:
Subnet 10.20.1.0/24
Subnet 10.20.2.0/24| TransitGatewayR1 %% Transit Gateway Routes VPC1-PublicRT -->|Route: 10.20.0.0/16 to VPC2| VPC2 VPC1-PrivateRT -->|Route: 10.20.0.0/16 to VPC2| VPC2 VPC2-PublicRT -->|Route: 10.10.0.0/16 to VPC1| VPC1 VPC2-PrivateRT -->|Route: 10.10.0.0/16 to VPC1| VPC1 %% Dashed Border classDef BoarderDash1 stroke-dasharray:5 5; class VPC1-Subnets,VPC1-RoutingTables,VPC1,VPC2 BoarderDash1 classDef BoarderDash2 stroke-dasharray:5 5; class VPC2-Subnets,VPC2-RoutingTables,VPC2,VPC2 BoarderDash2 %% Node Colors VPC1 style VPC1-Subnet1 fill:#d1f7d6,stroke:#333,stroke-width:2px style VPC1-Subnet2 fill:#f7d6d6,stroke:#333,stroke-width:2px style VPC1-Subnet3 fill:#f7d6d6,stroke:#333,stroke-width:2px style VPC1-PublicRT fill:#d1f7d6,stroke:#333,stroke-width:2px style VPC1-PrivateRT fill:#f7d6d6,stroke:#333,stroke-width:2px style VPC1-NAT fill:#d6f7f7,stroke:#333,stroke-width:2px %% Node Colors VPC2 style VPC2-Subnet1 fill:#d1f7d6,stroke:#333,stroke-width:2px style VPC2-Subnet2 fill:#f7d6d6,stroke:#333,stroke-width:2px style VPC2-Subnet3 fill:#f7d6d6,stroke:#333,stroke-width:2px style VPC2-PublicRT fill:#d1f7d6,stroke:#333,stroke-width:2px style VPC2-PrivateRT fill:#f7d6d6,stroke:#333,stroke-width:2px style VPC2-NAT fill:#d6f7f7,stroke:#333,stroke-width:2px
Prerequisites #
Install AWS CLI #
# Update packages
sudo apt update
# Unstall zip tool
sudo apt install unzip -y
# Download AWS CLI zip file
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
# Unzip
unzip awscliv2.zip
# Install
sudo ./aws/install
# Verify installation / check version
/usr/local/bin/aws --version
Configure AWS CLI #
# Start AWS CLI configuration
aws configure
Install Terraform #
# Install the HashiCorp GPG key
wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg > /dev/null
# Verify the GPG key fingerprint
gpg --no-default-keyring --keyring /usr/share/keyrings/hashicorp-archive-keyring.gpg --fingerprint
# Add the official HashiCorp repository
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
# Install Terraform
sudo apt update && sudo apt-get install terraform
# Verify installation / check version
terraform version
Terraform Network Stack #
File and Folder Structure #
The file and folder structure of the Terraform project looks like this:
aws-network-stack
├── terraform.tf
├── transitgateway.tf
├── variables.tf
├── vpc1_gateways.tf
├── vpc1_outputs.tf
├── vpc1_routetable.tf
├── vpc1.tf
├── vpc2_gateways.tf
├── vpc2_outputs.tf
├── vpc2_routetable.tf
└── vpc2.tf
Terraform Configuration Files #
Project Folder & Terraform Provider #
# Create Terraform project folder
TF_PROJECT_NAME=aws-network-stack
mkdir $TF_PROJECT_NAME && cd $TF_PROJECT_NAME
- terraform.tf
# Terraform Provider
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
# Provider AWS Region
provider "aws" {
alias = "aws_region"
region = var.aws_region
}
Variables #
## AWS Region & Availability Zones
variable "aws_region" {
description = "AWS Region"
type = string
default = "us-east-1"
}
variable "availability_zone_1" {
description = "The availability zone for the subnet"
type = string
default = "us-east-1a"
}
variable "availability_zone_2" {
description = "The availability zone for the subnet"
type = string
default = "us-east-1b"
}
variable "availability_zone_3" {
description = "The availability zone for the subnet"
type = string
default = "us-east-1c"
}
## VPC 1: CIDR Blocks
variable "vpc1_cidr" {
description = "VPC CIDR block"
type = string
default = "10.10.0.0/16"
}
variable "vpc1_subnet_cidr_1" {
description = "Subnet CIDR block"
type = string
default = "10.10.0.0/24"
}
variable "vpc1_subnet_cidr_2" {
description = "Subnet CIDR block"
type = string
default = "10.10.1.0/24"
}
variable "vpc1_subnet_cidr_3" {
description = "Subnet CIDR block"
type = string
default = "10.10.2.0/24"
}
## VPC 2: CIDR Blocks
variable "vpc2_cidr" {
description = "VPC CIDR block"
type = string
default = "10.20.0.0/16"
}
variable "vpc2_subnet_cidr_1" {
description = "Subnet CIDR block"
type = string
default = "10.20.0.0/24"
}
variable "vpc2_subnet_cidr_2" {
description = "Subnet CIDR block"
type = string
default = "10.20.1.0/24"
}
variable "vpc2_subnet_cidr_3" {
description = "Subnet CIDR block"
type = string
default = "10.20.2.0/24"
}
VPCs and Subnets #
- vpc1.tf
# VPC 1
resource "aws_vpc" "vpc1" {
provider = aws.aws_region
cidr_block = var.vpc1_cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "VPC1"
Env = "Production"
}
}
# Public Subnet "10.10.0.0/24"
resource "aws_subnet" "vpc1_subnet_public1" {
provider = aws.aws_region
vpc_id = aws_vpc.vpc1.id
cidr_block = var.vpc1_subnet_cidr_1
availability_zone = var.availability_zone_1
map_public_ip_on_launch = true
tags = {
Name = "VPC1 Subnet-Public-1"
Env = "Production"
}
}
# Private Subnet "10.10.1.0/24"
resource "aws_subnet" "vpc1_subnet_private1" {
provider = aws.aws_region
vpc_id = aws_vpc.vpc1.id
cidr_block = var.vpc1_subnet_cidr_2
availability_zone = var.availability_zone_1
tags = {
Name = "VPC1 Subnet-Private-1"
Env = "Production"
}
}
# Private Subnet "10.10.2.0/24"
resource "aws_subnet" "vpc1_subnet_private2" {
provider = aws.aws_region
vpc_id = aws_vpc.vpc1.id
cidr_block = var.vpc1_subnet_cidr_3
availability_zone = var.availability_zone_2
tags = {
Name = "VPC1 Subnet-Private-2"
Env = "Production"
}
}
- vpc2.tf
# VPC 2
resource "aws_vpc" "vpc2" {
provider = aws.aws_region
cidr_block = var.vpc2_cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "vpc2"
Env = "Production"
}
}
# Public Subnet "10.20.0.0/24"
resource "aws_subnet" "vpc2_subnet_public1" {
provider = aws.aws_region
vpc_id = aws_vpc.vpc2.id
cidr_block = var.vpc2_subnet_cidr_1
availability_zone = var.availability_zone_1
map_public_ip_on_launch = true
tags = {
Name = "VPC2 Subnet-Public-1"
Env = "Production"
}
}
# Private Subnet "10.20.1.0/24"
resource "aws_subnet" "vpc2_subnet_private1" {
provider = aws.aws_region
vpc_id = aws_vpc.vpc2.id
cidr_block = var.vpc2_subnet_cidr_2
availability_zone = var.availability_zone_1
tags = {
Name = "VPC2 Subnet-Private-1"
Env = "Production"
}
}
# Private Subnet "10.20.2.0/24"
resource "aws_subnet" "vpc2_subnet_private2" {
provider = aws.aws_region
vpc_id = aws_vpc.vpc2.id
cidr_block = var.vpc2_subnet_cidr_3
availability_zone = var.availability_zone_2
tags = {
Name = "VPC2 Subnet-Private-2"
Env = "Production"
}
}
Internet & NAT Gateways #
- vpc1_gateways.tf
# Elastic IP (EIP) for NAT Gateway
resource "aws_eip" "vpc1_nat_gw_eip" {
provider = aws.aws_region
domain = "vpc"
tags = {
Name = "VPC1 NAT-GW EIP"
Env = "Production"
}
}
# NAT Gateway
resource "aws_nat_gateway" "vpc1_nat_gw" {
provider = aws.aws_region
allocation_id = aws_eip.vpc1_nat_gw_eip.id
subnet_id = aws_subnet.vpc1_subnet_public1.id
tags = {
Name = "VPC1 NAT-GW"
Env = "Production"
}
depends_on = [aws_internet_gateway.vpc1_igw]
}
# Internet Gateway
resource "aws_internet_gateway" "vpc1_igw" {
provider = aws.aws_region
vpc_id = aws_vpc.vpc1.id
tags = {
Name = "VPC1 IGW"
Env = "Production"
}
}
vpc2_gateways.tf
# Elastic IP (EIP) for NAT Gateway
resource "aws_eip" "vpc2_nat_gw_eip" {
provider = aws.aws_region
domain = "vpc"
tags = {
Name = "VPC2 NAT-GW EIP"
Env = "Production"
}
}
# NAT Gateway
resource "aws_nat_gateway" "vpc2_nat_gw" {
provider = aws.aws_region
allocation_id = aws_eip.vpc2_nat_gw_eip.id
subnet_id = aws_subnet.vpc2_subnet_public1.id
tags = {
Name = "VPC2 NAT-GW"
Env = "Production"
}
depends_on = [aws_internet_gateway.vpc2_igw]
}
# Internet Gateway
resource "aws_internet_gateway" "vpc2_igw" {
provider = aws.aws_region
vpc_id = aws_vpc.vpc2.id
tags = {
Name = "VPC2 IGW"
Env = "Production"
}
}
Route Tables #
- vpc1_routetable.tf
# Private Routing Table
resource "aws_route_table" "vpc1_private_routetable" {
provider = aws.aws_region
vpc_id = aws_vpc.vpc1.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.vpc1_nat_gw.id
}
tags = {
Name = "VPC1 Private Route Table"
Env = "Production"
}
}
# Public Routing Table
resource "aws_route_table" "vpc1_public_routetable" {
provider = aws.aws_region
vpc_id = aws_vpc.vpc1.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.vpc1_igw.id
}
tags = {
Name = "VPC1 Public Route Table"
Env = "Production"
}
}
# Associate Routes with Subnets
resource "aws_route_table_association" "vpc1_subnet_public1_ra" {
provider = aws.aws_region
subnet_id = aws_subnet.vpc1_subnet_public1.id
route_table_id = aws_route_table.vpc1_public_routetable.id
}
resource "aws_route_table_association" "vpc1_subnet_private1_ra" {
provider = aws.aws_region
subnet_id = aws_subnet.vpc1_subnet_private1.id
route_table_id = aws_route_table.vpc1_private_routetable.id
}
resource "aws_route_table_association" "vpc1_subnet_private2_ra" {
provider = aws.aws_region
subnet_id = aws_subnet.vpc1_subnet_private2.id
route_table_id = aws_route_table.vpc1_private_routetable.id
}
- vpc2_routetable.tf
# Private Routing Table
resource "aws_route_table" "vpc2_private_routetable" {
provider = aws.aws_region
vpc_id = aws_vpc.vpc2.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.vpc2_nat_gw.id
}
tags = {
Name = "VPC2 Private Route Table"
Env = "Production"
}
}
# Public Routing Table
resource "aws_route_table" "vpc2_public_routetable" {
provider = aws.aws_region
vpc_id = aws_vpc.vpc2.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.vpc2_igw.id
}
tags = {
Name = "VPC2 Public Route Table"
Env = "Production"
}
}
# Associate Routes with Subnets
resource "aws_route_table_association" "vpc2_subnet_public1_ra" {
provider = aws.aws_region
subnet_id = aws_subnet.vpc2_subnet_public1.id
route_table_id = aws_route_table.vpc2_public_routetable.id
}
resource "aws_route_table_association" "vpc2_subnet_private1_ra" {
provider = aws.aws_region
subnet_id = aws_subnet.vpc2_subnet_private1.id
route_table_id = aws_route_table.vpc2_private_routetable.id
}
resource "aws_route_table_association" "vpc2_subnet_private2_ra" {
provider = aws.aws_region
subnet_id = aws_subnet.vpc2_subnet_private2.id
route_table_id = aws_route_table.vpc2_private_routetable.id
}
Outputs #
- vpc1_outputs.tf
# VPC1 ID
output "VPC1_ID" {
value = aws_vpc.vpc1.id
}
# Public Subnet ID
output "VPC1_Public_Subnet_IDs" {
value = [
aws_subnet.vpc1_subnet_public1.id
]
}
# Private Subnet IDs
output "VPC1_Private_Subnet_IDs" {
value = [
aws_subnet.vpc1_subnet_private1.id,
aws_subnet.vpc1_subnet_private2.id
]
}
# Public Routing Table ID
output "VPC1_Public_Route_Table_ID" {
value = aws_route_table.vpc1_public_routetable.id
}
# Private Routing Table ID
output "VPC1_Private_Route_Table_ID" {
value = aws_route_table.vpc1_private_routetable.id
}
# Internet Gateway ID
output "VPC1_Internet_Gateway_ID" {
value = aws_internet_gateway.vpc1_igw.id
}
# NAT Gateway ID
output "VPC1_NAT_Gateway_ID" {
value = aws_nat_gateway.vpc1_nat_gw.id
}
- vpc2_outputs.tf
# VPC2 ID
output "VPC2_ID" {
value = aws_vpc.vpc2.id
}
# Public Subnet ID
output "VPC2_Public_Subnet_IDs" {
value = [
aws_subnet.vpc2_subnet_public1.id
]
}
# Private Subnet IDs
output "VPC2_Private_Subnet_IDs" {
value = [
aws_subnet.vpc2_subnet_private1.id,
aws_subnet.vpc2_subnet_private2.id
]
}
# Public Routing Table ID
output "VPC2_Public_Route_Table_ID" {
value = aws_route_table.vpc2_public_routetable.id
}
# Private Routing Table ID
output "VPC2_Private_Route_Table_ID" {
value = aws_route_table.vpc2_private_routetable.id
}
# Internet Gateway ID
output "VPC2_Internet_Gateway_ID" {
value = aws_internet_gateway.vpc2_igw.id
}
# NAT Gateway ID
output "VPC2_NAT_Gateway_ID" {
value = aws_nat_gateway.vpc2_nat_gw.id
}
Transit Gateway #
Note: Regarding the Transit Gateway Attachment, specifying one subnet from an Availability Zone enables traffic to reach resources in every subnet in that Availability Zone!
- transitgateway.tf
# Transit Gateway
resource "aws_ec2_transit_gateway" "transit_gateway" {
provider = aws.aws_region
description = "Transit Gateway for inter-VPC communication"
default_route_table_association = "enable"
default_route_table_propagation = "enable"
dns_support = "enable"
vpn_ecmp_support = "enable"
tags = {
Name = "Transit Gateway"
Env = "Production"
}
}
# Transit Gateway Attachment VPC1
resource "aws_ec2_transit_gateway_vpc_attachment" "vpc1_attachment" {
provider = aws.aws_region
transit_gateway_id = aws_ec2_transit_gateway.transit_gateway.id
vpc_id = aws_vpc.vpc1.id
subnet_ids = [
aws_subnet.vpc1_subnet_private1.id,
aws_subnet.vpc1_subnet_private2.id
]
depends_on = [
aws_subnet.vpc1_subnet_private1,
aws_subnet.vpc1_subnet_private2,
aws_ec2_transit_gateway.transit_gateway
]
tags = {
Name = "VPC1-TGW-Attachment"
Env = "Production"
}
}
# Transit Gateway Attachment VPC2
resource "aws_ec2_transit_gateway_vpc_attachment" "vpc2_attachment" {
provider = aws.aws_region
transit_gateway_id = aws_ec2_transit_gateway.transit_gateway.id
vpc_id = aws_vpc.vpc2.id
subnet_ids = [
aws_subnet.vpc2_subnet_private1.id,
aws_subnet.vpc2_subnet_private2.id
]
depends_on = [
aws_subnet.vpc2_subnet_private1,
aws_subnet.vpc2_subnet_private2,
aws_ec2_transit_gateway.transit_gateway
]
tags = {
Name = "VPC2-TGW-Attachment"
Env = "Production"
}
}
# Route in VPC1 private route table to reach VPC2
resource "aws_route" "vpc1_to_vpc2" {
provider = aws.aws_region
route_table_id = aws_route_table.vpc1_private_routetable.id
destination_cidr_block = aws_vpc.vpc2.cidr_block
transit_gateway_id = aws_ec2_transit_gateway.transit_gateway.id
depends_on = [aws_ec2_transit_gateway.transit_gateway]
}
# Route in VPC1 public route table to reach VPC2 (if needed)
resource "aws_route" "vpc1_public_to_vpc2" {
provider = aws.aws_region
route_table_id = aws_route_table.vpc1_public_routetable.id
destination_cidr_block = aws_vpc.vpc2.cidr_block
transit_gateway_id = aws_ec2_transit_gateway.transit_gateway.id
depends_on = [aws_ec2_transit_gateway.transit_gateway]
}
# Route in VPC2 private route table to reach VPC1
resource "aws_route" "vpc2_to_vpc1" {
provider = aws.aws_region
route_table_id = aws_route_table.vpc2_private_routetable.id
destination_cidr_block = aws_vpc.vpc1.cidr_block
transit_gateway_id = aws_ec2_transit_gateway.transit_gateway.id
depends_on = [aws_ec2_transit_gateway.transit_gateway]
}
# Route in VPC2 public route table to reach VPC1 (if needed)
resource "aws_route" "vpc2_public_to_vpc1" {
provider = aws.aws_region
route_table_id = aws_route_table.vpc2_public_routetable.id
destination_cidr_block = aws_vpc.vpc1.cidr_block
transit_gateway_id = aws_ec2_transit_gateway.transit_gateway.id
depends_on = [aws_ec2_transit_gateway.transit_gateway]
}
Configuration Deployment #
Initialize Terraform Project #
This will download and install the AWS Terraform provider defined in the terraform.tf file with “hashicorp/aws”, as well as setting up the configuration files in the project directory.
# Initialize the Terraform project
terraform init
Validate Configuration Files #
# Validates the syntax and structure of Terraform configuration files
terraform validate
# Shell output:
Success! The configuration is valid.
Plan the Deployment #
# Dry run / preview changes before applying them
terraform plan
Apply the Configuration #
# Create network stack
terraform apply -auto-approve
# Shell output:
Apply complete! Resources: 31 added, 0 changed, 0 destroyed.
Outputs:
VPC1_ID = "vpc-071dcfce48b759a68"
VPC1_Internet_Gateway_ID = "igw-01bbff3f501b7711a"
VPC1_NAT_Gateway_ID = "nat-0e47369945cb5686d"
VPC1_Private_Route_Table_ID = "rtb-0b17c486ca80d8b7f"
VPC1_Private_Subnet_IDs = [
"subnet-033ce39cda60b51bf",
"subnet-0b32e2c7094a45080",
]
VPC1_Public_Route_Table_ID = "rtb-0c6ea3bccdcc88960"
VPC1_Public_Subnet_IDs = [
"subnet-03c9c3b8306bb930a",
]
VPC2_ID = "vpc-0965aad09b58d739d"
VPC2_Internet_Gateway_ID = "igw-05a5ae071e50924b1"
VPC2_NAT_Gateway_ID = "nat-07b5f7e0433d628e5"
VPC2_Private_Route_Table_ID = "rtb-08c96f153040afc33"
VPC2_Private_Subnet_IDs = [
"subnet-0612c5002535e3410",
"subnet-0fdb15a9a047a79ff",
]
VPC2_Public_Route_Table_ID = "rtb-00bee8c60c9167e47"
VPC2_Public_Subnet_IDs = [
"subnet-04ce09a8f41e689ee",
]
Verify Deployment State #
# Lists all resources tracked in the Terraform state file
terraform state list
# Shell output:
aws_ec2_transit_gateway.transit_gateway
aws_ec2_transit_gateway_vpc_attachment.vpc1_attachment
aws_ec2_transit_gateway_vpc_attachment.vpc2_attachment
aws_eip.vpc1_nat_gw_eip
aws_eip.vpc2_nat_gw_eip
aws_internet_gateway.vpc1_igw
aws_internet_gateway.vpc2_igw
aws_nat_gateway.vpc1_nat_gw
aws_nat_gateway.vpc2_nat_gw
aws_route.vpc1_public_to_vpc2
aws_route.vpc1_to_vpc2
aws_route.vpc2_public_to_vpc1
aws_route.vpc2_to_vpc1
aws_route_table.vpc1_private_routetable
aws_route_table.vpc1_public_routetable
aws_route_table.vpc2_private_routetable
aws_route_table.vpc2_public_routetable
aws_route_table_association.vpc1_subnet_private1_ra
aws_route_table_association.vpc1_subnet_private2_ra
aws_route_table_association.vpc1_subnet_public1_ra
aws_route_table_association.vpc2_subnet_private1_ra
aws_route_table_association.vpc2_subnet_private2_ra
aws_route_table_association.vpc2_subnet_public1_ra
aws_subnet.vpc1_subnet_private1
aws_subnet.vpc1_subnet_private2
aws_subnet.vpc1_subnet_public1
aws_subnet.vpc2_subnet_private1
aws_subnet.vpc2_subnet_private2
aws_subnet.vpc2_subnet_public1
aws_vpc.vpc1
aws_vpc.vpc2
Terraform ECS2 & Security Group Stack #
File and Folder Structure #
The file and folder structure of the Terraform project looks like this:
aws-compute-stack
├── main.tf
├── outputs.tf
├── security-groups.tf
├── terraform.tf
└── variables.tf
Terraform Configuration Files #
Project Folder & Terraform Provider #
# Create Terraform project folder
TF_PROJECT_NAME=aws-compute-stack
mkdir $TF_PROJECT_NAME && cd $TF_PROJECT_NAME
- terraform.tf
# Terraform Provider
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
# Provider AWS Region
provider "aws" {
alias = "aws_region"
region = var.aws_region
}
Variables #
- variables.tf
# Input Variables for VPC, Subnets, SSH key pair and EC2 image IDs
## AWS Region
variable "aws_region" {
description = "AWS Region"
type = string
default = "us-east-1"
}
## VPC1
variable "vpc1_id" {
default = "vpc-071dcfce48b759a68" # Define VPC ID
}
# Public Subnet
variable "vpc1_public_subnet_id" {
default = "subnet-03c9c3b8306bb930a" # Define public subnet ID
}
# Private Subnet 1
variable "vpc1_private_subnet1_id" {
default = "subnet-033ce39cda60b51bf" # Define private subnet 1 ID
}
# Private Subnet 2
variable "vpc1_private_subnet2_id" {
default = "subnet-0b32e2c7094a45080" # Define private subnet 2 ID
}
## VPC2
variable "vpc2_id" {
default = "vpc-0965aad09b58d739d" # Define VPC ID
}
# Public Subnet
variable "vpc2_public_subnet_id" {
default = "subnet-04ce09a8f41e689ee" # Define public subnet ID
}
# Private Subnet 1
variable "vpc2_private_subnet1_id" {
default = "subnet-0612c5002535e3410" # Define private subnet 1 ID
}
# Private Subnet 2
variable "vpc2_private_subnet2_id" {
default = "subnet-0fdb15a9a047a79ff" # Define private subnet 2 ID
}
## SSH Key and EC2 Image
# SSH key pair name
variable "key_name" {
default = "us-east-1-pc-le" # Define key pair name
}
# EC2 Image ID
variable "ami_id" {
default = "ami-0e2c8caa4b6378d8c" # Define EC2 AMI ID
}
Main Configuration #
- main.tf
# VPC1: EC2 Instance in Public Subnet
resource "aws_instance" "ec2_vpc1_public_subnet" {
provider = aws.aws_region
ami = var.ami_id
instance_type = "t2.micro"
subnet_id = var.vpc1_public_subnet_id
key_name = var.key_name
vpc_security_group_ids = [aws_security_group.vpc1_sg.id]
tags = {
Name = "public-subnet-vm"
Env = "Production"
}
}
# VPC1: EC2 Instance in Private Subnet 1
resource "aws_instance" "ec2_vpc1_private_subnet1" {
provider = aws.aws_region
ami = var.ami_id
instance_type = "t2.micro"
subnet_id = var.vpc1_private_subnet1_id
key_name = var.key_name
vpc_security_group_ids = [aws_security_group.vpc1_sg.id]
tags = {
Name = "private-subnet-vm1"
Env = "Production"
}
}
# VPC1: EC2 Instance in Private Subnet 2
resource "aws_instance" "ec2_vpc1_private_subnet2" {
provider = aws.aws_region
ami = var.ami_id
instance_type = "t2.micro"
subnet_id = var.vpc1_private_subnet2_id
key_name = var.key_name
vpc_security_group_ids = [aws_security_group.vpc1_sg.id]
tags = {
Name = "private-subnet-vm2"
Env = "Production"
}
}
# VPC2: EC2 Instance in Public Subnet
resource "aws_instance" "ec2_vpc2_public_subnet" {
provider = aws.aws_region
ami = var.ami_id
instance_type = "t2.micro"
subnet_id = var.vpc2_public_subnet_id
key_name = var.key_name
vpc_security_group_ids = [aws_security_group.vpc2_sg.id]
tags = {
Name = "public-subnet-vm"
Env = "Production"
}
}
# VPC2: EC2 Instance in Private Subnet 1
resource "aws_instance" "ec2_vpc2_private_subnet1" {
provider = aws.aws_region
ami = var.ami_id
instance_type = "t2.micro"
subnet_id = var.vpc2_private_subnet1_id
key_name = var.key_name
vpc_security_group_ids = [aws_security_group.vpc2_sg.id]
tags = {
Name = "private-subnet-vm1"
Env = "Production"
}
}
# VPC2: EC2 Instance in Private Subnet 2
resource "aws_instance" "ec2_vpc2_private_subnet2" {
provider = aws.aws_region
ami = var.ami_id
instance_type = "t2.micro"
subnet_id = var.vpc2_private_subnet2_id
key_name = var.key_name
vpc_security_group_ids = [aws_security_group.vpc2_sg.id]
tags = {
Name = "private-subnet-vm2"
Env = "Production"
}
}
Security Groups #
- security-groups.tf
# VPC1: Security Group for SSH Access and Ping
resource "aws_security_group" "vpc1_sg" {
provider = aws.aws_region
name = "VPC1-SG"
description = "Security group for SSH access and ping"
vpc_id = var.vpc1_id
ingress {
description = "Allow SSH from anywhere"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "Allow ping"
from_port = 8
to_port = -1
protocol = "icmp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
description = "Allow all outbound traffic"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "VPC1-SG"
Env = "Production"
}
}
# VPC1: Security Group for SSH Access and Ping
resource "aws_security_group" "vpc2_sg" {
provider = aws.aws_region
name = "VPC2-SG"
description = "Security group for SSH access and ping"
vpc_id = var.vpc2_id
ingress {
description = "Allow SSH from anywhere"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "Allow ping"
from_port = 8
to_port = -1
protocol = "icmp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
description = "Allow all outbound traffic"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "VPC2-SG"
Env = "Production"
}
}
Outputs #
- outputs.tf
# VPC1: Security Group ID
output "VPC1_SecurityGroup_ID" {
description = "Security Group ID for SSH Access"
value = aws_security_group.vpc1_sg.id
}
# VPC2: Security Group ID
output "VPC2_SecurityGroup_ID" {
description = "Security Group ID for SSH Access"
value = aws_security_group.vpc2_sg.id
}
#### EC2 Instance IDs
# VPC1: EC2 Instance ID: VM in Public Subnet
output "VPC1_Public_Instance_ID" {
description = "EC2 instance ID in public subnet"
value = aws_instance.ec2_vpc1_public_subnet.id
}
# VPC1: EC2 Instance ID: VM in Private Subnet 1
output "VPC1_Private_Instance1_ID" {
description = "EC2 instance ID in private subnet 1"
value = aws_instance.ec2_vpc1_private_subnet1.id
}
# VPC1: EC2 Instance ID: VM in Private Subnet 2
output "VPC1_Private_Instance2_ID" {
description = "EC2 instance ID in private subnet 2"
value = aws_instance.ec2_vpc1_private_subnet2.id
}
# VPC2: EC2 Instance ID: VM in Public Subnet
output "VPC2_Public_Instance_ID" {
description = "EC2 instance ID in public subnet"
value = aws_instance.ec2_vpc2_public_subnet.id
}
# VPC2: EC2 Instance ID: VM in Private Subnet 1
output "VPC2_Private_Instance1_ID" {
description = "EC2 instance ID in private subnet 1"
value = aws_instance.ec2_vpc2_private_subnet1.id
}
# VPC2: EC2 Instance ID: VM in Private Subnet 2
output "VPC2_Private_Instance2_ID" {
description = "EC2 instance ID in private subnet 2"
value = aws_instance.ec2_vpc2_private_subnet2.id
}
#### EC2 IPs
# VPC1: EC2 Instance public IP (VM in Public Subnet)
output "VPC1_Public_Instance_Public_IP" {
description = "Public IP address of the EC2 instance in the public subnet"
value = aws_instance.ec2_vpc1_public_subnet.public_ip
}
# VPC1: EC2 Instance private IP (VM in Public Subnet)
output "VPC1_Public_Instance_Private_IP" {
description = "Private IP address of the EC2 instance in the public subnet"
value = aws_instance.ec2_vpc1_public_subnet.private_ip
}
# VPC1: EC2 Instance private IP (VM1 in Private Subnet)
output "VPC1_Private_Instance1_Private_IP" {
description = "Private IP address of the EC2 instance in private subnet 1"
value = aws_instance.ec2_vpc1_private_subnet1.private_ip
}
# VPC1: EC2 Instance private IP (VM2 in Private Subnet)
output "VPC1_Private_Instance2_Private_IP" {
description = "Private IP address of the EC2 instance in private subnet 2"
value = aws_instance.ec2_vpc1_private_subnet2.private_ip
}
# VPC2: EC2 Instance public IP (VM in Public Subnet)
output "VPC2_Public_Instance_Public_IP" {
description = "Public IP address of the EC2 instance in the public subnet"
value = aws_instance.ec2_vpc2_public_subnet.public_ip
}
# VPC2: EC2 Instance private IP (VM in Public Subnet)
output "VPC2_Public_Instance_Private_IP" {
description = "Private IP address of the EC2 instance in the public subnet"
value = aws_instance.ec2_vpc2_public_subnet.private_ip
}
# VPC2: EC2 Instance private IP (VM1 in Private Subnet)
output "VPC2_Private_Instance1_Private_IP" {
description = "Private IP address of the EC2 instance in private subnet 1"
value = aws_instance.ec2_vpc2_private_subnet1.private_ip
}
# VPC2: EC2 Instance private IP (VM2 in Private Subnet)
output "VPC2_Private_Instance2_Private_IP" {
description = "Private IP address of the EC2 instance in private subnet 2"
value = aws_instance.ec2_vpc2_private_subnet2.private_ip
}
Configuration Deployment #
Initialize Terraform Project #
This will download and install the AWS Terraform provider defined in the terraform.tf file with “hashicorp/aws”, as well as setting up the configuration files in the project directory.
# Initialize the Terraform project
terraform init
Validate Configuration Files #
# Validates the syntax and structure of Terraform configuration files
terraform validate
# Shell output:
Success! The configuration is valid.
Plan the Deployment #
# Dry run / preview changes before applying them
terraform plan
Apply the Configuration #
# Create network stack
terraform apply -auto-approve
# Shell output:
Apply complete! Resources: 8 added, 0 changed, 0 destroyed.
Outputs:
VPC1_Private_Instance1_ID = "i-0337dec4a7f564228"
VPC1_Private_Instance1_Private_IP = "10.10.1.74"
VPC1_Private_Instance2_ID = "i-086381ca8e0cb18f6"
VPC1_Private_Instance2_Private_IP = "10.10.2.247"
VPC1_Public_Instance_ID = "i-0d322d55f384e6562"
VPC1_Public_Instance_Private_IP = "10.10.0.68"
VPC1_Public_Instance_Public_IP = "107.22.158.22"
VPC1_SecurityGroup_ID = "sg-067689d193fd48d59"
VPC2_Private_Instance1_ID = "i-0f4bae0fa16d95736"
VPC2_Private_Instance1_Private_IP = "10.20.1.146"
VPC2_Private_Instance2_ID = "i-02305f3aa005e7740"
VPC2_Private_Instance2_Private_IP = "10.20.2.184"
VPC2_Public_Instance_ID = "i-0e71a6c1b7348741f"
VPC2_Public_Instance_Private_IP = "10.20.0.202"
VPC2_Public_Instance_Public_IP = "54.81.194.94"
VPC2_SecurityGroup_ID = "sg-0453abde66fee0462"
Verfiy Network Connectivity #
VPC1 to VPC2 #
# SSH into the VM in the public subnet
ssh -i /home/ubuntu/.ssh/us-east-1-pc-le.pem ubuntu@107.22.158.22
# Ping the VM in the private subnet 1
ping 10.10.1.74
# Shell output:
PING 10.10.1.74 (10.10.1.74) 56(84) bytes of data.
64 bytes from 10.10.1.74: icmp_seq=1 ttl=64 time=1.75 ms
64 bytes from 10.10.1.74: icmp_seq=2 ttl=64 time=0.754 ms
# Ping the VM in the private subnet 2
ping 10.10.2.247
# Shell output:
PING 10.10.2.247 (10.10.2.247) 56(84) bytes of data.
64 bytes from 10.10.2.247: icmp_seq=1 ttl=64 time=1.87 ms
64 bytes from 10.10.2.247: icmp_seq=2 ttl=64 time=1.01 ms
# Ping the VM in the private subnet 1 in VPC2
ping 10.20.1.146
# Shell output:
PING 10.20.1.146 (10.20.1.146) 56(84) bytes of data.
64 bytes from 10.20.1.146: icmp_seq=1 ttl=63 time=4.10 ms
64 bytes from 10.20.1.146: icmp_seq=2 ttl=63 time=0.829 ms
# Ping the VM in the private subnet 2 in VPC2
ping 10.20.0.202
# Shell output:
PING 10.20.0.202 (10.20.0.202) 56(84) bytes of data.
64 bytes from 10.20.0.202: icmp_seq=1 ttl=63 time=2.67 ms
64 bytes from 10.20.0.202: icmp_seq=2 ttl=63 time=1.14 m
VPC2 to VPC1 #
# SSH into the VM in the public subnet
ssh -i /home/ubuntu/.ssh/us-east-1-pc-le.pem ubuntu@54.81.194.94
# Ping the VM in the private subnet 1
ping 10.20.1.146
# Shell output:
PING 10.20.1.146 (10.20.1.146) 56(84) bytes of data.
64 bytes from 10.20.1.146: icmp_seq=1 ttl=64 time=2.62 ms
64 bytes from 10.20.1.146: icmp_seq=2 ttl=64 time=0.450 ms
# Ping the VM in the private subnet 2
ping 10.20.0.202
# Shell output:
PING 10.20.0.202 (10.20.0.202) 56(84) bytes of data.
64 bytes from 10.20.0.202: icmp_seq=1 ttl=64 time=0.013 ms
64 bytes from 10.20.0.202: icmp_seq=2 ttl=64 time=0.027 ms
# Ping the VM in the private subnet 1 in VPC2
ping 10.10.1.74
# Shell output:
PING 10.10.1.74 (10.10.1.74) 56(84) bytes of data.
64 bytes from 10.10.1.74: icmp_seq=1 ttl=63 time=2.31 ms
64 bytes from 10.10.1.74: icmp_seq=2 ttl=63 time=1.70 ms
# Ping the VM in the private subnet 2 in VPC2
ping 10.10.2.247
# Shell output:
PING 10.10.2.247 (10.10.2.247) 56(84) bytes of data.
64 bytes from 10.10.2.247: icmp_seq=1 ttl=63 time=3.17 ms
64 bytes from 10.10.2.247: icmp_seq=2 ttl=63 time=1.08 ms
Links #
# GitHub Repository
https://github.com/jueklu/terraform-aws-multi-vpc-network
# AWS Transit Gateway Documentation
https://docs.aws.amazon.com/vpc/latest/tgw/tgw-vpc-attachments.html