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_PROGRESStoCREATE_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