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 2: CloudFormation Version

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

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



VPC and Subnet Stack
#

Create Template File
#

# Create CloudFormation template
vi vpc-subnet-scheme.yml

CloudFormation Template
#

AWSTemplateFormatVersion: '2010-09-09'
Description: VPC and Subnet scheme in us-east-1

Resources:
# VPC: us-east-1 / "10.10.0.0/16"
  vpcprod:
    Type: AWS::EC2::VPC
    Properties: 
      CidrBlock: 10.10.0.0/16
      Tags:
        - Key: Name
          Value: vpcprod
      EnableDnsSupport: true
      EnableDnsHostnames: true

# Subnet 1: Public / us-east-1a / "10.10.0.0/24"
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref vpcprod
      CidrBlock: 10.10.0.0/24
      AvailabilityZone: us-east-1a
      MapPublicIpOnLaunch: true # Automatically assign public IP to EC2 instances
      Tags:
        - Key: Name
          Value: vpcprod_publicsubnet1

# Subnet 2: Private / us-east-1a / "10.10.1.0/24"
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref vpcprod
      CidrBlock: 10.10.1.0/24
      AvailabilityZone: us-east-1a
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: vpcprod_privatesubnet1

# Subnet 3: Private / us-east-1b / "10.10.2.0/24"
  PrivateSubnet3:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref vpcprod
      CidrBlock: 10.10.2.0/24
      AvailabilityZone: us-east-1b
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: vpcprod_privatesubnet3


# Public Routing Table
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref vpcprod
      Tags:
        - Key: Name
          Value: vpcprod_public-route-table

# Private Routing Table
  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref vpcprod
      Tags:
        - Key: Name
          Value: vpcprod_private-route-table


  # Routing Table Association for Public Subnet
  PublicRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet1
      RouteTableId: !Ref PublicRouteTable

  # Routing Table Association for Private Subnet 2
  PrivateRouteTableAssociation2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet1
      RouteTableId: !Ref PrivateRouteTable

  # Routing Table Association for Private Subnet 3
  PrivateRouteTableAssociation3:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet3
      RouteTableId: !Ref PrivateRouteTable


# Internet Gateway
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: vpcprod_ig

  # Attach Internet Gateway to VPC
  AttachInternetGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref vpcprod
      InternetGatewayId: !Ref InternetGateway

  # Route for Public Route Table
  PublicRouteToInternet:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway


# Elastic IP for NAT Gateway
  ElasticIPForNAT:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: vpcprod_nat-gateway-eip

# NAT Gateway
  NATGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      SubnetId: !Ref PublicSubnet1
      AllocationId: !GetAtt ElasticIPForNAT.AllocationId
      Tags:
        - Key: Name
          Value: nat-gateway

  # Route for Private Route Table
  PrivateRouteToNAT:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NATGateway




Outputs:
# VPC
  VpcId:
    Description: The ID of the VPC
    Value: !Ref vpcprod

# Subnets
  PublicSubnet1:
    Description: The ID Subnet 1
    Value: !Ref PublicSubnet1

  PrivateSubnet1:
    Description: The ID Subnet 2
    Value: !Ref PrivateSubnet1

  PrivateSubnet3:
    Description: The ID Subnet 3
    Value: !Ref PrivateSubnet3

# Routing Tables
  PublicRouteTableId:
    Description: The ID of the Public Route Table
    Value: !Ref PublicRouteTable

  PrivateRouteTableId:
    Description: The ID of the Private Route Table
    Value: !Ref PrivateRouteTable

# Internet Gateway
  InternetGatewayId:
    Description: The ID of the Internet Gateway
    Value: !Ref InternetGateway

  PublicRouteId:
    Description: The ID of the public route to the Internet Gateway
    Value: !Ref PublicRouteToInternet

# NAT Gateway
  ElasticIPId:
    Description: The Allocation ID of the Elastic IP for the NAT Gateway
    Value: !GetAtt ElasticIPForNAT.AllocationId

  NATGatewayId:
    Description: The ID of the NAT Gateway
    Value: !Ref NATGateway

  PrivateRouteId:
    Description: The ID of the private route to the NAT Gateway
    Value: !Ref PrivateRouteToNAT

Validate Template
#

# Validate the template
aws cloudformation validate-template --template-body file://vpc-subnet-scheme.yml

# Shell output:
{
    "Parameters": [],
    "Description": "VPC and Subnet scheme in us-east-1"
}

Deploy Stack
#

# Deploy the stack from the template
aws cloudformation create-stack --stack-name vpc-subnet-scheme --template-body file://vpc-subnet-scheme.yml

# Shell output:
{
    "StackId": "arn:aws:cloudformation:us-east-1:012345678912:stack/vpc-subnet-scheme/7e615610-bbf3-11ef-ad6d-1207e77f631b"
}

Monitor the Deployment
#

# Monitor the deployment
aws cloudformation describe-stacks \
  --stack-name vpc-subnet-scheme \
  --no-cli-pager

# Shell output:
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:us-east-1:012345678912:stack/vpc-subnet-scheme/7e615610-bbf3-11ef-ad6d-1207e77f631b",
            "StackName": "vpc-subnet-scheme",
            "Description": "VPC and Subnet scheme in us-east-1",
            "CreationTime": "2024-12-16T21:19:54.404000+00:00",
            "RollbackConfiguration": {},
            "StackStatus": "CREATE_COMPLETE", # Check the status
            "DisableRollback": false,
            "NotificationARNs": [],
            "Outputs": [
                {
                    "OutputKey": "InternetGatewayId",
                    "OutputValue": "igw-004dcdad354404aa5",
                    "Description": "The ID of the Internet Gateway"
                },
                {
                    "OutputKey": "NATGatewayId",
                    "OutputValue": "nat-0418d6ee8df9c44a2",
                    "Description": "The ID of the NAT Gateway"
                },
                {
                    "OutputKey": "PrivateSubnet1",
                    "OutputValue": "subnet-0183988c1ad6b2b2e",
                    "Description": "The ID Subnet 2"
                },
                {
                    "OutputKey": "PrivateSubnet3",
                    "OutputValue": "subnet-06f3c9f6512ee8cb4",
                    "Description": "The ID Subnet 3"
                },
                {
                    "OutputKey": "VpcId",
                    "OutputValue": "vpc-0cbb0a63524b5b9ae",
                    "Description": "The ID of the VPC"
                },
                {
                    "OutputKey": "PrivateRouteTableId",
                    "OutputValue": "rtb-08de34840f3c30a35",
                    "Description": "The ID of the Private Route Table"
                },
                {
                    "OutputKey": "PrivateRouteId",
                    "OutputValue": "rtb-08de34840f3c30a35|0.0.0.0/0",
                    "Description": "The ID of the private route to the NAT Gateway"
                },
                {
                    "OutputKey": "PublicRouteId",
                    "OutputValue": "rtb-0d1aa8970230acb54|0.0.0.0/0",
                    "Description": "The ID of the public route to the Internet Gateway"
                },
                {
                    "OutputKey": "ElasticIPId",
                    "OutputValue": "eipalloc-0b5600ad76a7e38d0",
                    "Description": "The Allocation ID of the Elastic IP for the NAT Gateway"
                },
                {
                    "OutputKey": "PublicSubnet1",
                    "OutputValue": "subnet-0e824512942cf0439",
                    "Description": "The ID Subnet 1"
                },
                {
                    "OutputKey": "PublicRouteTableId",
                    "OutputValue": "rtb-0d1aa8970230acb54",
                    "Description": "The ID of the Public Route Table"
                }
            ],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}
  • Wait till the “StackStatus” changes from CREATE_IN_PROGRESS to CREATE_COMPLETE
# Output only the "StackStatus"
aws cloudformation describe-stacks --stack-name vpc-subnet-scheme \
    --query "Stacks[0].StackStatus" --output text

# Shell output:
CREATE_COMPLETE

List the Stack Events
#

Optional, for troubleshooting list the Stack events:

# List the Stack events
aws cloudformation describe-stack-events \
  --stack-name vpc-subnet-scheme \
  --no-cli-pager

List Stack Output Variables
#

# List the Stack output variables
aws cloudformation describe-stacks --stack-name vpc-subnet-scheme \
    --query "Stacks[0].Outputs" --output table

# Shell output:
-----------------------------------------------------------------------------------------------------------------------
|                                                   DescribeStacks                                                    |
+----------------------------------------------------------+----------------------+-----------------------------------+
|                        Description                       |      OutputKey       |            OutputValue            |
+----------------------------------------------------------+----------------------+-----------------------------------+
|  The ID of the Internet Gateway                          |  InternetGatewayId   |  igw-004dcdad354404aa5            |
|  The ID of the NAT Gateway                               |  NATGatewayId        |  nat-0418d6ee8df9c44a2            |
|  The ID Subnet 2                                         |  PrivateSubnet1      |  subnet-0183988c1ad6b2b2e         |
|  The ID Subnet 3                                         |  PrivateSubnet3      |  subnet-06f3c9f6512ee8cb4         |
|  The ID of the VPC                                       |  VpcId               |  vpc-0cbb0a63524b5b9ae            |
|  The ID of the Private Route Table                       |  PrivateRouteTableId |  rtb-08de34840f3c30a35            |
|  The ID of the private route to the NAT Gateway          |  PrivateRouteId      |  rtb-08de34840f3c30a35|0.0.0.0/0  |
|  The ID of the public route to the Internet Gateway      |  PublicRouteId       |  rtb-0d1aa8970230acb54|0.0.0.0/0  |
|  The Allocation ID of the Elastic IP for the NAT Gateway |  ElasticIPId         |  eipalloc-0b5600ad76a7e38d0       |
|  The ID Subnet 1                                         |  PublicSubnet1       |  subnet-0e824512942cf0439         |
|  The ID of the Public Route Table                        |  PublicRouteTableId  |  rtb-0d1aa8970230acb54            |
+----------------------------------------------------------+----------------------+-----------------------------------+



Security Group and EC2 Stack
#

Create Template File
#

# Create CloudFormation template
vi sg-ec2.yml

CloudFormation Template
#

AWSTemplateFormatVersion: '2010-09-09'
Description: Security Group and EC2 instances for testing VPC connectivity


Mappings:
  SubnetMapping:
    us-east-1:
      PublicSubnet1: subnet-0e824512942cf0439 # Define Public Subnet 1 ID
      PrivateSubnet1: subnet-0183988c1ad6b2b2e # Define Private Subnet 1 ID
      PrivateSubnet2: subnet-06f3c9f6512ee8cb4 # Define Private Subnet 2 ID
  VPCMapping:
    us-east-1:
      VPCId: vpc-0cbb0a63524b5b9ae # Define VPC ID

Resources:
# Security Group for SSH Access
  SSHSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for SSH access
      VpcId: !FindInMap [VPCMapping, us-east-1, VPCId]
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: vpc-us-east-1-prod-sg


# EC2 Instance in Public Subnet 1
  EC2PublicSubnet1:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-0e2c8caa4b6378d8c
      InstanceType: t2.micro
      SubnetId: !FindInMap [SubnetMapping, us-east-1, PublicSubnet1]
      SecurityGroupIds:
        - !Ref SSHSecurityGroup
      KeyName: us-east-1-pc-le
      Tags:
        - Key: Name
          Value: public-subnet-vm

# EC2 Instance in Private Subnet 1
  EC2PrivateSubnet1:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-0e2c8caa4b6378d8c
      InstanceType: t2.micro
      SubnetId: !FindInMap [SubnetMapping, us-east-1, PrivateSubnet1]
      SecurityGroupIds:
        - !Ref SSHSecurityGroup
      KeyName: us-east-1-pc-le
      Tags:
        - Key: Name
          Value: private-subnet-vm1

# EC2 Instance in Private Subnet 2
  EC2PrivateSubnet2:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-0e2c8caa4b6378d8c
      InstanceType: t2.micro
      SubnetId: !FindInMap [SubnetMapping, us-east-1, PrivateSubnet2]
      SecurityGroupIds:
        - !Ref SSHSecurityGroup
      KeyName: us-east-1-pc-le
      Tags:
        - Key: Name
          Value: private-subnet-vm2



Outputs:
  SecurityGroupId:
    Description: Security Group ID for SSH Access
    Value: !Ref SSHSecurityGroup

  EC2Id1:
    Description: EC2 instance ID in public subnet
    Value: !Ref EC2PublicSubnet1

  EC2Id2:
    Description: EC2 instance ID in private subnet 1
    Value: !Ref EC2PrivateSubnet1

  EC2Id3:
    Description: EC2 instance ID in private subnet 2
    Value: !Ref EC2PrivateSubnet2

Validate Template
#

# Validate the template
aws cloudformation validate-template --template-body file://sg-ec2.yml

# Shell output:
{
    "Parameters": [],
    "Description": "Security Group and EC2 instances for testing VPC connectivity"
}

Deploy Stack
#

# Deploy the stack from the template
aws cloudformation create-stack --stack-name sg-ec2 --template-body file://sg-ec2.yml

# Shell output:
{
    "StackId": "arn:aws:cloudformation:us-east-1:012345678912:stack/sg-ec2/f143dc40-bbf6-11ef-b202-12fb3e550959"
}

Monitor the Deployment
#

# Monitor the deployment
aws cloudformation describe-stacks \
  --stack-name sg-ec2 \
  --no-cli-pager

# Shell output:
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:us-east-1:012345678912:stack/sg-ec2/f143dc40-bbf6-11ef-b202-12fb3e550959",
            "StackName": "sg-ec2",
            "Description": "Security Group and EC2 instances for testing VPC connectivity",
            "CreationTime": "2024-12-16T21:44:35.640000+00:00",
            "RollbackConfiguration": {},
            "StackStatus": "CREATE_COMPLETE",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Outputs": [
                {
                    "OutputKey": "EC2Id3",
                    "OutputValue": "i-045acb4469ec207b7",
                    "Description": "EC2 instance ID in private subnet 2"
                },
                {
                    "OutputKey": "EC2Id1",
                    "OutputValue": "i-06b826049001de339",
                    "Description": "EC2 instance ID in public subnet"
                },
                {
                    "OutputKey": "EC2Id2",
                    "OutputValue": "i-0708a09456a6c1076",
                    "Description": "EC2 instance ID in private subnet 1"
                },
                {
                    "OutputKey": "SecurityGroupId",
                    "OutputValue": "sg-0dbe64a4f59a939a3",
                    "Description": "Security Group ID for SSH Access"
                }
            ],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}
# Output only the "StackStatus"
aws cloudformation describe-stacks --stack-name vpc-subnet-scheme \
    --query "Stacks[0].StackStatus" --output text

# Shell output:
CREATE_COMPLETE

List the Stack Events
#

Optional, for troubleshooting list the Stack events:

# List the Stack events
aws cloudformation describe-stack-events \
  --stack-name sg-ec2 \
  --no-cli-pager

List Stack Output Variables
#

# List the Stack output variables
aws cloudformation describe-stacks --stack-name sg-ec2 \
    --query "Stacks[0].Outputs" --output table

# Shell output:
-----------------------------------------------------------------------------------------------------
|                                          DescribeStacks                                           |
+-------------------------------------------------------+------------------+------------------------+
|                      Description                      |    OutputKey     |      OutputValue       |
+-------------------------------------------------------+------------------+------------------------+
|  EC2 instance ID in private subnet 2                  |  EC2Id3          |  i-045acb4469ec207b7   |
|  EC2 instance ID in public subnet                     |  EC2Id1          |  i-06b826049001de339   |
|  EC2 instance ID in private subnet 1                  |  EC2Id2          |  i-0708a09456a6c1076   |
|  Security Group ID for SSH Access                     |  SecurityGroupId |  sg-0dbe64a4f59a939a3  |
+-------------------------------------------------------+------------------+------------------------+



SSH into VMs
#

List VM IP Addresses
#

# Export variables
VM1=i-06b826049001de339
VM2=i-0708a09456a6c1076
VM3=i-045acb4469ec207b7
# Output the IP addresses of VM1
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.90     |
|  3.84.146.55    |
+-----------------+
# Output the IP addresses of VM2
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.96     |
|  None           |
+-----------------+
# Output the IP addresses of VM3
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.93     |
|  None           |
+-----------------+

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@3.84.146.55:~/.ssh/

# SSH into the VM in the public subnet
ssh -i /home/ubuntu/.ssh/us-east-1-pc-le.pem ubuntu@3.84.146.55
  • 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.1.96 


# Verify the VM can reach the internet
ping www.google.com

# Shell output:
PING www.google.com (142.251.16.99) 56(84) bytes of data.
64 bytes from bl-in-f99.1e100.net (142.251.16.99): icmp_seq=1 ttl=55 time=2.74 ms
64 bytes from bl-in-f99.1e100.net (142.251.16.99): icmp_seq=2 ttl=55 time=2.08 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.93 


# Verify the VM can reach the internet
ping www.google.com

# Shell output:
PING www.google.com (142.251.163.104) 56(84) bytes of data.
64 bytes from wv-in-f104.1e100.net (142.251.163.104): icmp_seq=1 ttl=57 time=3.53 ms
64 bytes from wv-in-f104.1e100.net (142.251.163.104): icmp_seq=2 ttl=57 time=2.73 ms



Cleanup
#

Delete Security Group and EC2 Stack
#

# Delete the Security Group and EC2 Stack
aws cloudformation delete-stack --stack-name sg-ec2
# Verify the Stack is deleted
aws cloudformation describe-stacks --stack-name sg-ec2

# Shell output: (Wait till the stack does not exist)
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:us-east-1:012345678912:stack/sg-ec2/f143dc40-bbf6-11ef-b202-12fb3e550959",
            "StackName": "sg-ec2",
            "Description": "Security Group and EC2 instances for testing VPC connectivity",
            "CreationTime": "2024-12-16T21:44:35.640000+00:00",
            "DeletionTime": "2024-12-16T21:55:18.147000+00:00",
            "RollbackConfiguration": {},
            "StackStatus": "DELETE_IN_PROGRESS",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Outputs": [
                {
                    "OutputKey": "EC2Id3",
                    "OutputValue": "i-045acb4469ec207b7",
                    "Description": "Instance ID of the EC2 instance in private subnet 2"
                },
                {
                    "OutputKey": "EC2Id1",
                    "OutputValue": "i-06b826049001de339",
                    "Description": "Instance ID of the EC2 instance in the public subnet"
                },
                {
                    "OutputKey": "EC2Id2",
                    "OutputValue": "i-0708a09456a6c1076",
                    "Description": "Instance ID of the EC2 instance in private subnet 1"
                },
                {
                    "OutputKey": "SecurityGroupId",
                    "OutputValue": "sg-0dbe64a4f59a939a3",
                    "Description": "Security Group ID for SSH Access"
                }
            ],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}

# Shell output:
An error occurred (ValidationError) when calling the DescribeStacks operation: Stack with id sg-ec2 does not exist

Delete VPC and Subnet Stack
#

# Delete the VPC and Subnet Stack
aws cloudformation delete-stack --stack-name vpc-subnet-scheme
AWS-VPC - This article is part of a series.
Part 2: This Article