Skip to main content

AWS Virtual Private Cloud (VPC): Create VPC and Build a Private and Public Subnet Scheme, Verify the Network Connectivity with EC2 Instances, Part 1: CLI Version, Mermaid Flowchart

1941 words·
AWS AWS CLI Virtual Private Cloud (VPC) Internet Gateway NAT Gateway Routing Tables EC2 Mermaid Flowchart
Table of Contents
AWS-VPC - This article is part of a series.
Part 1: This Article

Overview
#

Flowchart
#

graph TD %% VPC subgraph VPC["VPC: 10.10.0.0/16"] IGW["Internet Gateway"] NAT["NAT Gateway
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
AWS-VPC - This article is part of a series.
Part 1: This Article