Overview #
Flowchart #
Public Subnet
us-east-1a"] %% Routing Tables subgraph RoutingTables["Routing Tables"] PublicRT["Public Routing Table"] PrivateRT["Private Routing Table"] end %% Subnets subgraph Subnets["Subnets"] Subnet1["Public Subnet 10.10.0.0/24
us-east-1a"] Subnet2["Private Subnet 10.10.1.0/24
us-east-1a"] Subnet3["Private Subnet 10.10.2.0/24
us-east-1b"] end PublicRT -->|Route: 0.0.0.0/0 to IGW| IGW PrivateRT -->|Route: 0.0.0.0/0 to NAT| NAT Subnet1 -->|Associated| PublicRT Subnet2 -->|Associated| PrivateRT Subnet3 -->|Associated| PrivateRT NAT -->|Outbound Internet Traffic| IGW end %% Dashed Border classDef BoarderDash stroke-dasharray:5 5; class Subnets,RoutingTables,VPC BoarderDash %% Node Colors style Subnet1 fill:#d1f7d6,stroke:#333,stroke-width:2px style Subnet2 fill:#f7d6d6,stroke:#333,stroke-width:2px style Subnet3 fill:#f7d6d6,stroke:#333,stroke-width:2px style PublicRT fill:#d1f7d6,stroke:#333,stroke-width:2px style PrivateRT fill:#f7d6d6,stroke:#333,stroke-width:2px style NAT fill:#d6f7f7,stroke:#333,stroke-width:2px
Network Elements #
Internet Gateway (IGW):
-
Allows resources in a public subnet to connect to the internet and receive inbound connections if security rules permit it
-
Public subnets must have a route to the IGW for external connectivity
NAT Gateway (NGW):
-
Allows private subnets to access the internet while keeping the resources inaccessible from external sources
-
Private subnets must have a route to the NAT Gateway, which routes traffic through the IGW
-
It only handles outbound traffic and does not allow direct inbound traffic from the internet
Routing & Traffic Flow #
Routing Tables:
-
The public subnet (Subnet 1) routes internet-bound traffic directly via the internet gateway
-
The private subnets (Subnet 2 & 3) route internet-bound traffic via the NAT gateway
Traffic Flow:
-
Public subnet (Subnet 1): Traffic → Internet gateway → Internet
-
Private subnets (Subnet 2 & 3): Traffic → NAT gateway → Internet gateway → Internet
Security:
- The private subnets (Subnet 2 & 3) remain inaccessible from the internet
AWS VPC Network Scheme #
Create VPC #
# Create a new VPC with "10.10.0.0/16" (10.10.0.1 - 10.10.255.254) CIDR block
VPC_ID=$(aws ec2 create-vpc \
--region us-east-1 \
--cidr-block 10.10.0.0/16 \
--tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=vpc-us-east-1-prod}]' \
--output text \
--query Vpc.VpcId)
Verify the VPC #
# Verify the VPC state is "available"
aws ec2 describe-vpcs \
--vpc-ids $VPC_ID \
--region us-east-1
# Shell output:
{
"Vpcs": [
{
"OwnerId": "073526172187",
"InstanceTenancy": "default",
"CidrBlockAssociationSet": [
{
"AssociationId": "vpc-cidr-assoc-0b0d13b435dd00330",
"CidrBlock": "10.10.0.0/16",
"CidrBlockState": {
"State": "associated"
}
}
],
"IsDefault": false,
"Tags": [
{
"Key": "Name",
"Value": "vpc-us-east-1-prod"
}
],
"BlockPublicAccessStates": {
"InternetGatewayBlockMode": "off"
},
"VpcId": "vpc-032cca98b7e240fdc",
"State": "available",
"CidrBlock": "10.10.0.0/16",
"DhcpOptionsId": "dopt-0494309f299f154e5"
}
]
}
Create Subnets #
Create Subnet 1 (Public) #
# Create Subnet 1
SUBNET_ID_1=$(aws ec2 create-subnet \
--region us-east-1 \
--availability-zone us-east-1a \
--vpc-id $VPC_ID \
--cidr-block 10.10.0.0/24 \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=vpc-us-east-1-prod}]' \
--output text \
--query Subnet.SubnetId)
Create Subnet 2 (Private) #
# Create Subnet 2
SUBNET_ID_2=$(aws ec2 create-subnet \
--region us-east-1 \
--availability-zone us-east-1a \
--vpc-id $VPC_ID \
--cidr-block 10.10.1.0/24 \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=vpc-us-east-1-prod}]' \
--output text \
--query Subnet.SubnetId)
Create Subnet 3 (Private) #
# Create Subnet 3
SUBNET_ID_3=$(aws ec2 create-subnet \
--region us-east-1 \
--availability-zone us-east-1b \
--vpc-id $VPC_ID \
--cidr-block 10.10.2.0/24 \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=vpc-us-east-1-prod}]' \
--output text \
--query Subnet.SubnetId)
Create Routing Tables #
Create Routing Table for the Public Subnet #
# Create a Routing Table for the public Subnet in the new VPC
PUBLIC_ROUTE_TABLE_ID=$(aws ec2 create-route-table \
--region us-east-1 \
--vpc-id $VPC_ID \
--tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=vpc-us-east-1-prod}]' \
--output text \
--query RouteTable.RouteTableId)
Create Routing Table for the Private Subnets #
# Create a Routing Table for the Private subnet in the new VPC
PRIVATE_ROUTE_TABLE_ID=$(aws ec2 create-route-table \
--region us-east-1 \
--vpc-id $VPC_ID \
--tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=vpc-us-east-1-prod}]' \
--output text \
--query RouteTable.RouteTableId)
Associate the Route Tables with the Subnets #
# Associate the Public Routing Table with Subnet 1
aws ec2 associate-route-table \
--region us-east-1 \
--route-table-id $PUBLIC_ROUTE_TABLE_ID \
--subnet-id $SUBNET_ID_1
# Associate the Private Routing table with subnet 2
aws ec2 associate-route-table \
--region us-east-1 \
--route-table-id $PRIVATE_ROUTE_TABLE_ID \
--subnet-id $SUBNET_ID_2
# Associate the Private Routing table with subnet 3
aws ec2 associate-route-table \
--region us-east-1 \
--route-table-id $PRIVATE_ROUTE_TABLE_ID \
--subnet-id $SUBNET_ID_3
Internet Gateway #
Create an Internet Gateway #
# Create an Internet Gateway
IGW_ID=$(aws ec2 create-internet-gateway \
--region us-east-1 \
--tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=vpc-us-east-1-prod}]' \
--query InternetGateway.InternetGatewayId \
--output text)
Attach Internet Gateway to VPC #
# Attach Internet Gateway to the VPC to provide internet access for the NAT Gateway
aws ec2 attach-internet-gateway \
--region us-east-1 \
--internet-gateway-id $IGW_ID \
--vpc-id $VPC_ID
Add Route to Public Routing Table #
- Add a route to the public routing table for all internet-bound traffic (0.0.0.0/0) to go through the internet gateway:
# Add a route to the public Routing Table
aws ec2 create-route \
--region us-east-1 \
--route-table-id $PUBLIC_ROUTE_TABLE_ID \
--destination-cidr-block 0.0.0.0/0 \
--gateway-id $IGW_ID
# Shell output:
{
"Return": true
}
NAT Gateway #
Allocate an Elastic IP (EIP) #
# Allocate an Elastic IP (EIP) for the NAT gateway
EIP_ID=$(aws ec2 allocate-address \
--region us-east-1 \
--query AllocationId \
--output text)
Create a NAT Gateway #
- The NAT gateway must be in the public subnet (a subnet with a route to the internet gateway)
# Create a NAT Gateway
NAT_GATEWAY_ID=$(aws ec2 create-nat-gateway \
--region us-east-1 \
--subnet-id $SUBNET_ID_1 \
--allocation-id $EIP_ID \
--query NatGateway.NatGatewayId \
--output text)
Verify the NAT Gateway #
Verify the NAT Gateway is available, this can take several minutes:
# Verify the NAT Gateway is in the "available" state
aws ec2 describe-nat-gateways \
--region us-east-1 \
--nat-gateway-ids $NAT_GATEWAY_ID \
--query "NatGateways[0].State" \
--output text
# Shell output:
available
Add Route to Private Routing Table #
-
Wait for the NAT gateway to become available
-
Add a route to the NAT gateway for internet-bound traffic from private subnets
# Add route to the private Routing Table
aws ec2 create-route \
--region us-east-1 \
--route-table-id $PRIVATE_ROUTE_TABLE_ID \
--destination-cidr-block 0.0.0.0/0 \
--nat-gateway-id $NAT_GATEWAY_ID
# Shell output:
{
"Return": true
}
List & Save Variable Values #
List Variable Values #
# Output the values of the used variables:
echo "VPC_ID=$VPC_ID"
echo "SUBNET_ID_1=$SUBNET_ID_1"
echo "SUBNET_ID_2=$SUBNET_ID_2"
echo "SUBNET_ID_3=$SUBNET_ID_3"
echo "PUBLIC_ROUTE_TABLE_ID=$PUBLIC_ROUTE_TABLE_ID"
echo "PRIVATE_ROUTE_TABLE_ID=$PRIVATE_ROUTE_TABLE_ID"
echo "IGW_ID=$IGW_ID"
echo "NAT_GATEWAY_ID=$NAT_GATEWAY_ID"
echo "EIP_ID=$EIP_ID"
# Shell output:
VPC_ID=vpc-032cca98b7e240fdc
SUBNET_ID_1=subnet-0111260e67ffb71b8
SUBNET_ID_2=subnet-0f34d61c2cf7f4d0a
SUBNET_ID_3=subnet-003ab011d2f9184bb
PUBLIC_ROUTE_TABLE_ID=rtb-0dea155dcaeaed485
PRIVATE_ROUTE_TABLE_ID=rtb-022dcd0648a27d3e5
IGW_ID=igw-0d3e9aef3648fd601
NAT_GATEWAY_ID=nat-037ff4fc5f637e71d
EIP_ID=eipalloc-05487e5f1c9e1edff
Save Variable Values to a File #
Save the variable values into a file, to use them in different shell session:
# Save the variables into a file
echo "VPC_ID=$VPC_ID" > aws-variables.sh
echo "SUBNET_ID_1=$SUBNET_ID_1" >> aws-variables.sh
echo "SUBNET_ID_2=$SUBNET_ID_2" >> aws-variables.sh
echo "SUBNET_ID_3=$SUBNET_ID_3" >> aws-variables.sh
echo "PUBLIC_ROUTE_TABLE_ID=$PUBLIC_ROUTE_TABLE_ID" >> aws-variables.sh
echo "PRIVATE_ROUTE_TABLE_ID=$PRIVATE_ROUTE_TABLE_ID" >> aws-variables.sh
echo "IGW_ID=$IGW_ID" >> aws-variables.sh
echo "NAT_GATEWAY_ID=$NAT_GATEWAY_ID" >> aws-variables.sh
echo "EIP_ID=$EIP_ID" >> aws-variables.sh
# Reload Variables in a New Session
source aws-variables.sh
Verify the Network #
Create a security group and a VM in each of the subnets to verify the network connectivity.
Create Security Group #
# Create a security group that allows SSH access
SG_ID=$(aws ec2 create-security-group \
--group-name vpc-us-east-1-prod-sg \
--description "Security group for SSH access" \
--vpc-id $VPC_ID \
--region us-east-1 \
--query "GroupId" \
--output text)
# Add the rule to allow SSH access from anywhere
aws ec2 authorize-security-group-ingress \
--group-id $SG_ID \
--protocol tcp \
--port 22 \
--cidr 0.0.0.0/0 \
--region us-east-1
Create VMs #
Create VM in Public Subnet #
# Create a VM in the public subnet
VM1=$(aws ec2 run-instances \
--region us-east-1 \
--image-id ami-0e2c8caa4b6378d8c \
--count 1 \
--instance-type t2.micro \
--subnet-id $SUBNET_ID_1 \
--security-group-ids $SG_ID \
--key-name us-east-1-pc-le \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=public-subnet-vm}]' \
--associate-public-ip-address \
--no-cli-pager \
--query "Instances[0].InstanceId" \
--output text)
# Output the IP addresses of the VM
aws ec2 describe-instances \
--region us-east-1 \
--instance-ids $VM1 \
--query "Reservations[0].Instances[0].[PrivateIpAddress, PublicIpAddress]" \
--output table
# Shell output:
-------------------
|DescribeInstances|
+-----------------+
| 10.10.0.138 |
| 52.91.236.149 |
+-----------------+
Create VMs in Private Subnets #
# Create a VM in the private Subnet 1
VM2=$(aws ec2 run-instances \
--region us-east-1 \
--image-id ami-0e2c8caa4b6378d8c \
--count 1 \
--instance-type t2.micro \
--subnet-id $SUBNET_ID_2 \
--security-group-ids $SG_ID \
--key-name us-east-1-pc-le \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=private-subnet-vm1}]' \
--no-cli-pager \
--query "Instances[0].InstanceId" \
--output text)
# Output the private IP addresse of the VM
aws ec2 describe-instances \
--region us-east-1 \
--instance-ids $VM2 \
--query "Reservations[0].Instances[0].[PrivateIpAddress, PublicIpAddress]" \
--output table
# Shell output:
-------------------
|DescribeInstances|
+-----------------+
| 10.10.1.249 |
| None |
+-----------------+
# Create a VM in the private Subnet 2
VM3=$(aws ec2 run-instances \
--region us-east-1 \
--image-id ami-0e2c8caa4b6378d8c \
--count 1 \
--instance-type t2.micro \
--subnet-id $SUBNET_ID_3 \
--security-group-ids $SG_ID \
--key-name us-east-1-pc-le \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=private-subnet-vm2}]' \
--no-cli-pager \
--query "Instances[0].InstanceId" \
--output text)
# Output the private IP addresse of the VM
aws ec2 describe-instances \
--region us-east-1 \
--instance-ids $VM3 \
--query "Reservations[0].Instances[0].[PrivateIpAddress, PublicIpAddress]" \
--output table
# Shell output:
-------------------
|DescribeInstances|
+-----------------+
| 10.10.2.115 |
| None |
+-----------------+
SSH into VMs #
SSH into Public Subnet VM #
- SSH into the first VM in the public subnet via it’s public IPv4 address
# Copy the private SSH key to the VM in the public subnet
scp -i /home/ubuntu/.ssh/us-east-1-pc-le.pem /home/ubuntu/.ssh/us-east-1-pc-le.pem ubuntu@52.91.236.149:~/.ssh/
# SSH into the VM in the public subnet
ssh -i /home/ubuntu/.ssh/us-east-1-pc-le.pem ubuntu@52.91.236.149
- Use the first VM to SSH it into the VMs in the private subnets
SSH into Private Subnet VM 1 #
# SSH into the VM in the private subnet 1
ssh -i /home/ubuntu/.ssh/us-east-1-pc-le.pem ubuntu@10.10.0.138
# Verify the VM can reach the internet
ping www.google.com
# Shell output:
PING www.google.com (142.251.163.105) 56(84) bytes of data.
64 bytes from wv-in-f105.1e100.net (142.251.163.105): icmp_seq=1 ttl=104 time=1.74 ms
64 bytes from wv-in-f105.1e100.net (142.251.163.105): icmp_seq=2 ttl=104 time=1.92 ms
# Exit the VM in the private subnet 1
exit
SSH into Private Subnet VM 2 #
# SSH into the VM in the private subnet 2
ssh -i /home/ubuntu/.ssh/us-east-1-pc-le.pem ubuntu@10.10.2.115
# Verify the VM can reach the internet
ping www.google.com
# Shell output:
PING www.google.com (172.253.63.104) 56(84) bytes of data.
64 bytes from bi-in-f104.1e100.net (172.253.63.104): icmp_seq=1 ttl=104 time=3.38 ms
64 bytes from bi-in-f104.1e100.net (172.253.63.104): icmp_seq=2 ttl=104 time=2.91 ms
Cleanup #
Delete VMs #
# Delete VM1
aws ec2 terminate-instances \
--region us-east-1 \
--instance-ids $VM1 \
--no-cli-pager
# Delete VM2
aws ec2 terminate-instances \
--region us-east-1 \
--instance-ids $VM2 \
--no-cli-pager
# Delete VM3
aws ec2 terminate-instances \
--region us-east-1 \
--instance-ids $VM3 \
--no-cli-pager
Delete Security Group #
Wait till the EC2 Instances are deleted, this can take a minute:
# Delete the Security Group
aws ec2 delete-security-group \
--region us-east-1 \
--group-id $SG_ID
Delete Routes in the Routing Tables #
Remove the routes pointing to the NAT Gateway or Internet Gateway:
# Delete route from public routing table
aws ec2 delete-route \
--region us-east-1 \
--route-table-id $PUBLIC_ROUTE_TABLE_ID \
--destination-cidr-block 0.0.0.0/0
# Delete route from private routing table
aws ec2 delete-route \
--region us-east-1 \
--route-table-id $PRIVATE_ROUTE_TABLE_ID \
--destination-cidr-block 0.0.0.0/0
Delete the NAT Gateway #
# Delete the NAT Gateway
aws ec2 delete-nat-gateway \
--region us-east-1 \
--nat-gateway-id $NAT_GATEWAY_ID
Disassociate the Elastic IP #
# List allocated Elastic IPs
aws ec2 describe-addresses \
--region us-east-1 \
--query "Addresses[*].[AllocationId,InstanceId,NetworkInterfaceId]" \
--output table
# Shell output:
-----------------------------------------------------------------
| DescribeAddresses |
+-----------------------------+-------+-------------------------+
| eipalloc-05487e5f1c9e1edff | None | eni-00d665546f2b5542c |
+-----------------------------+-------+-------------------------+
# Release the Elastic IP
aws ec2 release-address \
--region us-east-1 \
--allocation-id eipalloc-05487e5f1c9e1edff
Detach and Delete the Internet Gateway #
# Detach the Internet Gateway from the VPC
aws ec2 detach-internet-gateway \
--region us-east-1 \
--internet-gateway-id $IGW_ID \
--vpc-id $VPC_ID
# Delete the Internet Gateway
aws ec2 delete-internet-gateway \
--region us-east-1 \
--internet-gateway-id $IGW_ID
Delete the Subnets #
# The delete public subnet
aws ec2 delete-subnet \
--region us-east-1 \
--subnet-id $SUBNET_ID_1
# The delete private subnet 1
aws ec2 delete-subnet \
--region us-east-1 \
--subnet-id $SUBNET_ID_2
# The delete private subnet 2
aws ec2 delete-subnet \
--region us-east-1 \
--subnet-id $SUBNET_ID_3
Delete the Routing Tables #
# Delete the public Routing Table
aws ec2 delete-route-table \
--region us-east-1 \
--route-table-id $PUBLIC_ROUTE_TABLE_ID
# Delete the private Routing Table
aws ec2 delete-route-table \
--region us-east-1 \
--route-table-id $PRIVATE_ROUTE_TABLE_ID
Delete the VPC #
# Delete the VPC
aws ec2 delete-vpc \
--region us-east-1 \
--vpc-id $VPC_ID