In this detailed blog, we will walk you through a comprehensive project on creating a real-time AWS Virtual Private Cloud (VPC) suitable for production environments. This project leverages various AWS services, including EC2, Auto Scaling Groups, Load Balancers, NAT Gateways, and Bastion Hosts, to build a highly resilient and secure network architecture.
Project Overview
The goal of this project is to design and deploy an AWS VPC with the following features:
High availability across two Availability Zones (AZs)
Auto Scaling Group for dynamic scalability
Load Balancer for traffic distribution
Enhanced security with private subnets and NAT Gateways
Secure access via Bastion Host
Architecture
The architecture we will implement is illustrated below:
VPC: The main container for all our resources.
Subnets: Two public and two private subnets across two AZs.
Route Tables: Custom route tables for controlling traffic within the VPC.
NAT Gateway: Deployed in each AZ to enable outbound internet access for instances in private subnets.
Auto Scaling Group: Ensures the right number of EC2 instances are running to handle the load.
Load Balancer: Distributes incoming traffic across EC2 instances.
Bastion Host: Provides secure access to instances in private subnets.
This is the architecture that we will be following throughout this project execution.
Step-by-Step Implementation
Setting Up the VPC
Create VPC:
Navigate to the VPC service in the AWS Console.
Click on "Create VPC".
Select "VPC and more" so that other components of VPC can be created along with VPC creation like Subnets, Internet Gateway, NAT Gateway, Route Tables, etc.
Set the project name as "VPC_Project".
Specify the IPv4 CIDR block (e.g., 10.0.0.0/16).
Choose two Availability Zones.
Create two public subnets and two private subnets.
Set the NAT Gateway as 1 per AZ and select "None" for VPC endpoints.
Click on Create VPC and so the VPC along with all its components will be successfully created!
Provisioning EC2 Instances
Launch Template:
Create a Launch Template specifying the Launch Template name along with the description.
Specify the AMI (e.g., Ubuntu 24.04) and instance type (e.g., t2.micro). This project is for demonstration purposes only. You can select the AMI and the instance type based on your needs.
Configure network settings to use the VPC and appropriate subnets.
Define security group rules (Inbound rules) for SSH access and application traffic at Port 22 and 8000 respectively.
Click on Create Launch Template and the Launch Template will be successfully created.
Auto Scaling Group:
Head over to the EC2 dashboard and on the left panel click on Auto Scaling Groups. Create an Auto Scaling Group using the Launch Template.
In the Networks section, select the VPC that we just created and the 2 private subnets, and then click on Next.
Specify the desired, minimum, and maximum instance count.
Click on Next -> Click on Create Auto Scaling Group
Now, let’s head over to the EC2 dashboard and see if the instances indeed got created or not.
Two instances are successfully created inside the private subnet using the Auto Scaling Group. One instance was created in us-east-1a and the other in us-east-1b.
Creating the Bastion Host
Launch Bastion Host:
Now, it's time to create a bastion host that will enable us to log in to our EC2 instance inside the private subnet and deploy the application on port 8000.
Create an EC2 instance in one of the public subnets.
Ensure it has a public IP address.
Configure its security group to allow SSH access only from Anywhere and click on Launch Instance.
The bastion-Host instance is successfully created!
Deploying the Application
Now, before doing SSH into the bastion-host instance, we need to copy the key-pair file that we used while creating the Launch template for the EC2 instance from our machine into the bastion host. This will allow us to successfully log in to the application instances from the bastion host.
Command template for the same:
scp -i /path/to/your/local/key-pair-file.pem /path/to/your/local-file-to-copy username@bastionhost-public-ip:/path/to/destination-directory/
Command I used (sample):
scp -i "C:\Users\kouln\Downloads\test.pem" "C:\Users\kouln\Downloads\test.pem" ubuntu@ec2-44203-163-89.compute-1.amazonaws.com:/home/ubuntu
SSH into the Bastion Host.
ssh -i "Your key-pair here" ubuntu@"Public_IP_Address_here" ls
We can see that the key pair file “test.pem” exists on the bastion-host instance as well!
Next, we’ll log in to the EC2 instance where we intend to deploy our Python application. So, copy any of the instance’s private IP and execute the command:
ssh –i “key-pair filename here” ubuntu@"Private_IP_Address"
We have successfully logged in to the EC2 instance inside the private subnet!
After logging into the ec2 instance, execute the command vim index.html and write down a basic HTML file that will be displayed as the application.
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>
<h1>AWS VPC Project Demonstration/h1>
</body>
</html>
Save and quit from the file and execute this command to start an HTTP server for this application at Port 8000.
python3 –m http.server 8000
The HTTP server has successfully started!
Setting Up the Load Balancer
Now comes the final step which is to create an Application Load Balancer at the public subnet level. We installed a Python application on just one instance to effectively demonstrate the working of the Load Balancer.
We will see the working of health checks that despite having both instances available for load balancing, the load balancer will only route traffic to the healthy one i.e. which has the application running.
Create Application Load Balancer:
Come to the EC2 dashboard and on the left panel click on Create Load Balancer
Click on Create Application Load Balancer. Set the name of the Load Balancer, and select the Scheme as “Internet facing” as we want the Load balancer to direct traffic from the Internet to the instances inside the private subnet.
In the Network Mapping section, select the VPC we are using for this project and select the public subnets that our Load Balancer should reside in.
Select the Security Group. If the Security Group rules don’t match the desired requirements, we can change them later as well.
Next comes the Listeners and the routing part.
Creating Target Group:
We need to create a Target Group that will have both our instances. Click on Create Target Group -> Open Link in the new tab.
Set the Group Details as Instances.
Set the target group’s name and the Protocol: Port. Set the port as 8000 because our application is running on the port 8000.
Select the “VPC_Project”. Click on Next.
Select the instances which we want to include in the target group.
Click on Create Target Group.
Now go back and add this Target Group to the Load Balancer.
Click on Create Load Balancer.
The load balancer has been successfully created but there is one issue under Listeners and Rules.
It says that the listener port which we specified to be 80 is unavailable which means we need to make some changes to our Security Group rules that we attached with the load balancer.
Come to Security Groups and select “aws-prod-sg” (this is the security group we used while creating the load balancer). This is the security group we attached with the load balancer. Click on Inbound Rules -> Edit Inbound Rules. Add the HTTP:80 rule from Anywhere IPv4.
Accessing the Application
Now come back to the Load Balancer dashboard. Copy the DNS name and paste it into the browser.
We can successfully access our application!
Conclusion
By following these steps, you have successfully set up a production-grade AWS VPC with high availability, scalability, and enhanced security. This setup ensures that your application is resilient, cost-efficient, and secure, making it suitable for real-time deployment in production environments.