AWS Three-Tier Architecture Explained: VPC, EC2, ALB, RDS, and More
projectIn today's cloud-driven world, building scalable and resilient applications is crucial for ensuring high availability and seamless user experiences. In this blog, I'll walk you through the deployment of a three-tier web application architecture on Amazon Web Services (AWS). This project integrates essential AWS services such as VPC for secure networking, EC2 instances for the application servers, Route 53 for domain management, CloudFront for content delivery, an Application Load Balancer (ALB) for routing traffic, and RDS with a standby instance for a robust MySQL database.
The three-tier architecture consists of:
Presentation Tier: React application hosted on NGINX, distributed via CloudFront for fast, global delivery.
Application Tier: Node.js backend, hosted on EC2 instances, managed behind an ALB for fault tolerance and load balancing.
Database Tier: Amazon RDS (MySQL) with a standby instance for automated failover and high availability.
Throughout this guide, we’ll explore how to set up and manage these components to create a scalable, highly available, and secure application. Stay tuned for a deep dive into the configuration, deployment, and monitoring setup!
Prerequisites:
- Clone project using url: https://github.com/shubzz-t/aws-three-tier-app-deployment.git
1. Creating VPC and its Components:
The foundation of our AWS three-tier architecture begins with setting up a Virtual Private Cloud (VPC). We'll configure the VPC with two Availability Zones (AZs) to ensure high availability and fault tolerance. Here's how it's done:
VPC: Create a custom VPC that will house all our resources, isolating them in a secure virtual network. I used ap-south-1 Mumbai Region.
Subnets: We'll create 6 subnets:
4 private subnets (2 per Availability Zone): These will host the application servers and the database for security purposes.
2 public subnets (1 per Availability Zone): These will host public-facing resources like the ALB (Application Load Balancer) and NAT Gateway.
After creating the public subnets, go to Subnet settings and enable auto-assign public IP by clicking on the public subnet and selecting the option.
NAT Gateway: Set up a NAT Gateway in one of the public subnets. This allows instances in the private subnets to connect to the internet (for updates and patches) without exposing them to inbound internet traffic.
2. Creating Security Groups:
Bastion Host Security Group:
Name: bastion-host-sg
Inbound Rule: Allow SSH (Port 22) access from your specific IP address for secure connection.
Presentation Tier ALB Security Group:
Name: presentation-alb-sg
Inbound Rule: Allow HTTP (Port 80) traffic from anywhere (
0.0.0.0/0
).
Presentation Tier EC2 Security Group:
Name: presentation-ec2-sg.
Inbound Rules:
SSH (Port 22): Allow traffic from the Bastion Host's Security Group (i.e. bastion-host-sg).
HTTP (Port 80): Allow traffic from the Load Balancer's Security Group (i.e. presentation-alb-sg).
Application Tier ALB Security Group:
Name: application-alb-sg.
Inbound Rules:
- HTTP (Port 80): Allow traffic only from the Presentation Tier EC2 Instances' Security Group (i.e. presentation-ec2-sg).
Application Tier EC2 Security Group:
Name: application-ec2-sg.
Inbound Rules:
SSH (Port 22): Allow traffic from the Bastion Host Security Group (i.e.,
bastion-host-sg
).Custom TCP (Port 3200): Allow traffic from the Application Tier Load Balancer Security Group (i.e.
application-alb-sg
).
Database Tier EC2 Security Group:
Name: database-ec2-sg
Inbound Rules:
MySQL/Aurora (Port 3306): Allow traffic from Bastion Host Security Group(i.e. bastion-host-sg)
MySQL/Aurora (Port 3306): Allow traffic from Application Tier EC2 Instance SG (i.e. application-ec2-sg).
3. Creating Bastion Host
Bastion Host Creation:
Launch EC2 Instance:
- Go to the EC2 Dashboard and click Launch Instance.
Select AMI and Instance Type:
- Choose Ubuntu Server and select instance type (e.g.,
t2.micro
).
- Choose Ubuntu Server and select instance type (e.g.,
Configure Instance:
- Select your VPC and a Public Subnet. Enable Auto-assign Public IP.
Security Group:
Select bastion-host-sg (ensure it allows SSH from your IP). Connect Bastion Host
ssh -i /path/to/your-key.pem ec2-user@your-public-ip
4. Setting RDS MySQL:
We will create an RDS MySQL instance configured as the primary database with a standby instance for high availability. The standby instance will replicate data from the primary, ensuring redundancy and automatic failover in case of an outage.
Creating Subnet Group:
Select VPC: Choose our created VPC where your database instances will be hosted.
Choose Availability Zones: Select the availability zones where you want your database instances to be deployed.
Select Private Subnets: Choose the private subnets within the selected VPC and availability zones for the subnet group. Ensure that these subnets provide adequate resources and redundancy for your RDS instances.
Creating RDS Instance:
Create Database Instance: Choose MySQL as the database engine.
Select Template: Choose the "Dev/Test" template for a non-production environment.
Multi-AZ Deployment: Ensure the Multi-AZ instance option is selected for high availability.
DB Instance Identifier: Enter a name for the DB instance, such as
dev-db-instance
.Master Username: Set the master username as
admin
.Credentials Management: Choose "Self-managed" and enter a secure password for the master user.
Leave Default Settings: Keep the default settings for other configuration options.
Select VPC: Choose the VPC you created earlier.
Select Subnet Group: Select the previously created subnet group for the RDS instances.
Select Security Group: Choose the
database-ec2-sg
security group to control access.Create: Click on the "Create Database" button to provision the MySQL database instance.
Access RDS EC2 from Bastion Host:
SSH into the Bastion host using its public IP:
ssh -i /path/to/key.pem ec2-user@<Bastion-Host-IP>
Install MySQL client:
sudo dnf update -y sudo dnf install mariadb105-server
Connect to RDS:
mysql -h <RDS-Endpoint> -P 3306 -u admin -p
Enter the master password when prompted.
Run the following SQL queries to create the database, user, and grant privileges
CREATE DATABASE react_node_app; CREATE USER 'appuser'@'%' IDENTIFIED BY 'learnIT02#'; GRANT ALL PRIVILEGES ON react_node_app.* TO 'appuser'@'%'; FLUSH PRIVILEGES;
Log out from the current session.
Log in again using the newly created user.
mysql -h <RDS-Endpoint> -P 3306 -u appuser -p
Enter the password
learnIT02#
when prompted.Once logged in as the appuser run the below sql scripts:
CREATE TABLE `author` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `birthday` date NOT NULL, `bio` text NOT NULL, `createdAt` date NOT NULL, `updatedAt` date NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; CREATE TABLE `book` ( `id` int NOT NULL AUTO_INCREMENT, `title` varchar(255) NOT NULL, `releaseDate` date NOT NULL, `description` text NOT NULL, `pages` int NOT NULL, `createdAt` date NOT NULL, `updatedAt` date NOT NULL, `authorId` int DEFAULT NULL, PRIMARY KEY (`id`), KEY `FK_66a4f0f47943a0d99c16ecf90b2` (`authorId`), CONSTRAINT `FK_66a4f0f47943a0d99c16ecf90b2` FOREIGN KEY (`authorId`) REFERENCES `author` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
5. Presentation Tier:
Create a launch template for the presentation layer EC2 instances:
Navigate to Launch Templates: Go to the EC2 Dashboard, and in the left-hand menu, click on "Launch Templates."
Create Launch Template:
Template Name: Enter a name (e.g., presentation-tier-template).
AMI: Choose Amazon Linux 2 AMI (HVM).
Instance Type: Select the instance type (e.g.,
t2.micro
).Key Pair: Choose an existing key pair or create a new one.
Security Groups: Select the Presentation EC2 security group, allowing HTTP (port 80) for alb and SSH (port 22 from bastion).
Do Not Specify Subnet: Leave the subnet field blank, as this will be handled by the Auto Scaling group later for proper distribution across Availability Zones.
User Data:
#!/bin/bash # Update the package list and install NGINX sudo yum update -y sudo yum install nginx -y # Start and enable NGINX sudo systemctl start nginx sudo systemctl enable nginx # Fetch metadata token TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") # Fetch instance details using IMDSv2 INSTANCE_ID=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" "http://169.254.169.254/latest/meta-data/instance-id") AVAILABILITY_ZONE=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" "http://169.254.169.254/latest/meta-data/placement/availability-zone") PUBLIC_IP=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" "http://169.254.169.254/latest/meta-data/public-ipv4") # Create a simple HTML page displaying instance details sudo bash -c "cat > /usr/share/nginx/html/index.html <<EOF <h1>Instance Details</h1> <p><b>Instance ID:</b> $INSTANCE_ID</p> <p><b>Availability Zone:</b> $AVAILABILITY_ZONE</p> <p><b>Public IP:</b> $PUBLIC_IP</p> EOF" # Restart NGINX to ensure changes are applied sudo systemctl restart nginx
Create Target Group:
Navigate to Target Groups: Go to the EC2 Dashboard and click on Target Groups in the left-hand mEC2 Dashboard and click on Target Groupsenu.
Create Target Group:
Choose Target Type: Select Instances.
Target Group Name: Enter
presentation-tg
.Protocol: Select HTTP.
Port: Set to 80.
VPC: Select your created VPC.
Health Check Settings:
Path: Enter
/
for the health check endpoint.Leave other health check settings as default.
Create Target Group: Click Create Target Group to complete the setup.
Creating Application Load Balancer:
Navigate to Load Balancers: Go to the EC2 Dashboard and select Load Balancers.
Create Load Balancer:
Name: Enter
presentation-alb
.Scheme: Select Internet-facing.
Availability Zones: Internet-facingChoose the Availability Zones and their corresponding public subnets.
Assign Security Group:
- Select the security group created i.e presentation-alb-sg.
Configure Routing:
- Target Group: Associate the ALB with the target group
presentation-tg
.
- Target Group: Associate the ALB with the target group
Create Load Balancer: Click Create Load Balancer to complete the process.
Create Auto Scaling Group:
Name the Auto Scaling Group: Enter the name as
presentation-asg
.Select Launch Template: Choose the launch template we created earlier.
VPC and Subnets: Select our VPC and the public subnets where the instances will be launched.
Attach Load Balancer:
Choose Attach to an existing load balancer.
Select our target group
presentation-tg
.
Enable Health Checks: Turn on load balancer health checks.
Group Metrics: Enable group metrics collection in CloudWatch.
Desired Capacity:
- Set Desired Capacity to 3, Minimum 2, Maximum
4
.
- Set Desired Capacity to 3, Minimum 2, Maximum
Scaling Policy:
Select Target tracking policy.
Set Average CPU Utilization to
50%
.
6. Application Tier:
Create a launch template for the presentation layer EC2 instances:
Navigate to Launch Templates: Go to the EC2 Dashboard, and in the left-hand menu, click on "Launch Templates."
Create Launch Template:
Template Name: Enter a name (e.g.,
application-tier-template
).AMI: Choose Amazon Linux 2 AMI (HVM).
Instance Type: Select the instance type (e.g.,
t2.micro
).Key Pair: Choose an existing key pair or create a new one.
Security Groups: Select the Application EC2 security group, allowing HTTP (port 80) and SSH (port 22 from bastion).
Do Not Specify Subnet: Leave the subnet field blank, as this will be handled by the Auto Scaling group later for proper distribution across Availability Zones.
User Data:
#!/bin/bash # Update package list and install required packages sudo yum update -y sudo yum install -y git # Install Node.js (use NodeSource for the latest version) curl -fsSL https://rpm.nodesource.com/setup_18.x | sudo bash - sudo yum install -y nodejs # Install PM2 globally sudo npm install -g pm2 # Define variables REPO_URL="https://github.com/learnItRightWay01/react-node-mysql-app.git" BRANCH_NAME="feature/add-logging" REPO_DIR="/home/ec2-user/react-node-mysql-app/backend" ENV_FILE="$REPO_DIR/.env" # Clone the repository cd /home/ec2-user sudo -u ec2-user git clone $REPO_URL cd react-node-mysql-app # Checkout to the specific branch sudo -u ec2-user git checkout $BRANCH_NAME cd backend # Define the log directory and ensure it exists LOG_DIR="/home/ec2-user/react-node-mysql-app/backend/logs" mkdir -p $LOG_DIR sudo chown -R ec2-user:ec2-user $LOG_DIR # Append environment variables to the .env file echo "LOG_DIR=$LOG_DIR" >> "$ENV_FILE" echo "DB_HOST=\"<rds-instance.end.point.region.rds.amazonaws.com>\"" >> "$ENV_FILE" echo "DB_PORT=\"3306\"" >> "$ENV_FILE" echo "DB_USER=\"<db-user>\"" >> "$ENV_FILE" echo "DB_PASSWORD=\"<db-user-password>\"" >> "$ENV_FILE" # Replace with actual password echo "DB_NAME=\"<db-name>\"" >> "$ENV_FILE" # Install Node.js dependencies as ec2-user sudo -u ec2-user npm install # Start the application using PM2 as ec2-user sudo -u ec2-user npm run serve # Ensure PM2 restarts on reboot as ec2-user sudo -u ec2-user pm2 startup systemd sudo -u ec2-user pm2 save
Create Target Group:
Navigate to Target Groups: Go to the EC2 Dashboard and click on Target Groups in the left-hand menu.
Create Target Group:
Choose Target Type: Select Instances.
Target Group Name: Enter
application-tg
.Protocol: Select HTTP.
Port: Set to 3200.
VPC: Select your created VPC.
Health Check Settings:
Path: Enter
/
for the health check endpoint.Leave other health check settings as default.
Create Target Group: Click Create Target Group to complete the setup.
Creating Application Load Balancer:
Navigate to Load Balancers: Go to the EC2 Dashboard and select Load Balancers.
Create Load Balancer:
Name: Enter
application-alb
.Scheme: Select internal.
Availability Zones: Choose the Availability Zones and their corresponding private subnets.
Assign Security Group:
- Select the security group created for the application tier ALB.
Configure Routing:
- Target Group: Associate the ALB with the target group
application-tg
.
- Target Group: Associate the ALB with the target group
Create Load Balancer: Click Create Load Balancer to complete the process.
Create Auto Scaling Group:
Name the Auto Scaling Group: Enter the name as
application-asg
.Select Launch Template: Choose the launch template we created earlier.
VPC and Subnets: Select our VPC and the public subnets where the instances will be launched.
Attach Load Balancer:
Choose Attach to an existing load balancer.
Select our target group
application-tg
.
Enable Health Checks: Turn on load balancer health checks.
Group Metrics: Enable group metrics collection in CloudWatch.
Desired Capacity:
- Set Desired Capacity to 3, Minimum 2, Maximum
4
.
- Set Desired Capacity to 3, Minimum 2, Maximum
Scaling Policy:
Select Target tracking policy.
Set Average CPU Utilization to
50%
.
7. Modify Presentation Tier:
- Modify Launch Template:
Edit the user data section in the existing launch template.
Add necessary commands for configuring the presentation layer (e.g. installing NGINX, pulling the React app). Replace user data with content in file https://github.com/shubzz-t/AWS-THREE-TIER-APP/blob/main/config.txt
Save changes and create a new version of the launch template.
Navigate to the Auto Scaling Group. Edit the configuration and select the latest version of the launch template to ensure that the updated version is used for instance launches.
8. CloudWatch Monitoring:
We will now monitor the application using CloudWatch by collecting logs from the application tier EC2 instances. The logs are located at react-node-mysql-app/backend/logs
, and we'll move all the instance logs to CloudWatch. Here's a quick breakdown of the steps:
Create a CloudWatch Log Group:
Go to the CloudWatch Console.
Navigate to Log Groups, fill in the details, and create a new log group.
Create IAM Role for EC2:
Create a new IAM role with the following policies:
CloudWatchLogsFullAccess
CloudWatchAgentServerPolicy
Attach this IAM role to the application tier launch template.
Modify the launch template version, and under Advanced Details, select the created IAM role under the IAM profile.
Enable Detailed CloudWatch Monitoring:
- Ensure detailed monitoring is enabled for CloudWatch.
Add User Data Snippet:
- Update the user data script in the launch template with the snippet for CloudWatch Agent, configuring it to forward logs to the created log group.
Create New Version:
- Save changes and create a new version of the launch template.
Update Auto Scaling Group:
Navigate to the Auto Scaling Group.
Edit the configuration and select the latest version of the launch template to ensure that the updated version is used for instance launches.
9. Cloudfront and Route 53:
Cloudfront:
Create CloudFront Distribution:
Go to the CloudFront Console.
Click on Create Distribution.
Configure Origin:
Origin Domain Name: Select the domain name of your presentation tier ALB.
Origin Protocol Policy: Choose HTTP Only to ensure communication is established over HTTP.
Viewer Protocol Policy:
- Set the Viewer Protocol Policy to Redirect HTTP to HTTPS. This will ensure that all HTTP requests are redirected to HTTPS for better security.
Web Application Firewall (WAF):
- Disable WAF by not associating it with the distribution for now.
Alternate Domain Names:
- In the Alternate Domain Names (CNAMEs) field, insert any custom domain names you wish to use for the distribution.
Create Distribution:
- Review your configurations and click on Create Distribution to finalize the setup.
Route 53:
Create Record:
In the Hosted Zones page, select the hosted zone you just created.
Click on Create record.
Configure Record Settings:
Record name: Leave this field empty for the root domain or enter a subdomain (e.g.,
www
).Record type: Choose A – IPv4 address.
Alias: Select Yes.
Alias Target: In the dropdown, find and select your CloudFront distribution from the list.
Routing Policy:
- Choose the routing policy (Simple Routing is usually appropriate).
TTL (Time to Live):
- Set the TTL as desired (default is typically 300 seconds).
Create Record:
- Click on Create records to finalize the setup.
Ready to access you three tier web application.
Conclusion
In this blog, we successfully deployed a robust three-tier application architecture on AWS, leveraging services like VPC, EC2, RDS, and CloudFront to ensure high availability, security, and performance. The integration of Route 53 for domain management, including creating a hosted zone and alias records for our CloudFront distribution, enables seamless access to our application.
By implementing Auto Scaling and Load Balancing, we created a flexible and scalable solution capable of handling varying traffic loads. Additionally, CloudWatch monitoring allows us to track application performance and maintain operational health effectively. The inclusion of a bastion host enhances security by limiting direct access to our resources.
This deployment strategy, along with the best practices discussed, provides a strong foundation for building and managing production-ready applications in the cloud.
For more insightful content on technology, AWS, and DevOps, make sure to follow me for the latest updates and tips. If you have any questions or need further assistance, feel free to reach out—I’m here to help!
Streamline, Deploy, Succeed-- Devops Made Simple!☺️
Subscribe to my newsletter
Read articles from Shubham Taware directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Shubham Taware
Shubham Taware
👨💻 Hi, I'm Shubham Taware, a Systems Engineer at Cognizant with a passion for all things DevOps. While my current role involves managing systems, I'm on an exciting journey to transition into a career in DevOps by honing my skills and expertise in this dynamic field. 🚀 I believe in the power of DevOps to streamline software development and operations, making the deployment process faster, more reliable, and efficient. Through my blog, I'm here to share my hands-on experiences, insights, and best practices in the DevOps realm as I work towards my career transition. 🔧 In my day-to-day work, I'm actively involved in implementing DevOps solutions, tackling real-world challenges, and automating processes to enhance software delivery. Whether it's CI/CD pipelines, containerization, infrastructure as code, or any other DevOps topic, I'm here to break it down, step by step. 📚 As a student, I'm continuously learning and experimenting, and I'm excited to document my progress and share the valuable lessons I gather along the way. I hope to inspire others who, like me, are looking to transition into the DevOps field and build a successful career in this exciting domain. 🌟 Join me on this journey as we explore the world of DevOps, one blog post at a time. Together, we can build a stronger foundation for successful software delivery and propel our careers forward in the exciting world of DevOps. 📧 If you have any questions, feedback, or topics you'd like me to cover, feel free to get in touch at shubhamtaware15@gmail.com. Let's learn, grow, and DevOps together!