Curious About a 3-Tier Architecture?

Jerry Karran
6 min readJul 7, 2023

For a scalable and reliable e-commerce platform, deploying a 3-tier architecture is what engineers advise getting.

The web tier would serve the static content to end users and handle incoming requests.

The application tier would handle the work behind the scenes such as processing transactions, handling payments, and interacting with the database.

The database tier would, you guessed it, store and manage all of the data.

This three-tier architecture provides a scalable and reliable platform that could be deployed quickly and easily with minimal required maintenance.

Pre-requisites:
AWS account with VPC and EC2 credentials.

Create the following-

Web Tier:
1. 2 public subnets
2. A minimum of 2 EC2 instances
3. EC2 Web server security group with inbound internet permissions
4. Custom AMI with a static web page
5. Public route table associated with the 2 public subnets

Application Tier:
1. 2 private subnets
2. A minimum of 2 EC2 instances
3. EC2 Application server security group with inbound permissions from the web server security group
4. Private route table and associate with the 2 private subnets
5. Create a NAT gateway in the public subnet and update the private route table

Database Tier:
1. Use a free tier MySql RDS database
2. Create a database security group with inbound traffic for MySQL from the application server security group
3. 2 private subnets
4. Associate with private route table

Step 1: Create a VPC with an Internet Gateway

First, we’ll create a VPC and Internet Gateway. Then, we’ll attach it to the VPC.

# Jerry Karran 3-tier architecture
AWSTemplateFormatVersion: 2010-09-09
Parameters:
KeyPair:
Type: 'AWS::EC2::KeyPair::KeyName'
Description: A list of Keypairs for the Auto Scaling group
DBMasterUsername:
Type: String
NoEcho: "true"
DBMasterPassword:
Type: String
NoEcho: "true"
Resources:
# create our VPC name VPC-jk with a CIDR block of 10.10.0.0/16
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.10.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: "VPC-jk"
# add an internet gateway so we have internet access
InternetGateway:
Type: AWS::EC2::InternetGateway
DependsOn: VPC
Properties:
Tags:
- Key: Name
Value: "Internet Gateway - jk"
# attach your internet gateway to your vpc
AttachGatewaytoVpc:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway

Notice we’ve created parameters for choosing the keypair, entering the database username, and entering the database password.

Step 2: Create the Web Tier

For this step, we will create 2 public subnets, a public route table with necessary associations, a web security group (allowing inbound permission from the internet), set up an autoscaling group, and set up a load balancer.

# create our 2 public subnets
publicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.10.1.0/24
AvailabilityZone: "us-east-1a"
Tags:
- Key: Name
Value: Public-1
publicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.10.2.0/24
AvailabilityZone: "us-east-1b"
Tags:
- Key: Name
Value: Public-2
# create a new route table
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: Public Route Table
# create route entry to route table
newRoute:
Type: AWS::EC2::Route
DependsOn: AttachGatewaytoVpc
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
# associate our subnets with our route table
PublicRouteTableAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref publicSubnet1
RouteTableId: !Ref PublicRouteTable
PublicRouteTableAssociation2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref publicSubnet2
RouteTableId: !Ref PublicRouteTable
# create a security group
WebServerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref VPC
GroupDescription: Enable HTTP and SSH access
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: Web Server Security Group
# create a launch template for auto scaling group to use the already created templated ami
WebServerLaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: !Sub ${AWS::StackName}-web-server-launch-template
LaunchTemplateData:
ImageId: ami-055864024342e0e66
InstanceType: t2.micro
KeyName: !Ref KeyPair
NetworkInterfaces:
- DeviceIndex: 0
AssociatePublicIpAddress: true
Groups:
- !Ref WebServerSecurityGroup
DeleteOnTermination: true
# create autoscaling group
WebServerAutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
LaunchTemplate:
LaunchTemplateId: !Ref WebServerLaunchTemplate
Version: !GetAtt WebServerLaunchTemplate.LatestVersionNumber
MaxSize: '1'
MinSize: '1'
DesiredCapacity: '1'
VPCZoneIdentifier:
- !Ref publicSubnet1
- !Ref publicSubnet2
TargetGroupARNs:
- !Ref WebTargetGroup
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-Cloudformation Web Server Auto Scaling Group
PropagateAtLaunch: true
PropagateAtLaunch: true
# create my load balancer listener
WebListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
Protocol: HTTP
Port: 80
DefaultActions:
- Type: forward
TargetGroupArn: !Ref WebTargetGroup
LoadBalancerArn: !Ref WebLoadBalancer
# create load balancer target group
WebTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckIntervalSeconds: 30
HealthCheckProtocol: HTTP
HealthCheckTimeoutSeconds: 15
HealthyThresholdCount: 5
Matcher:
HttpCode: '200'
IpAddressType: ipv4
Port: 80
Name: web-target-group
Protocol: HTTP
ProtocolVersion: HTTP1
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: '20'
VpcId: !Ref VPC
Tags:
- Key: "Name"
Value: !Sub ${AWS::StackName}-My Web Target Group
TargetType: instance
VpcId: !Ref VPC
# create our application load balancer
WebLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
DependsOn: InternetGateway
Properties:
Type: application
Scheme: internet-facing
IpAddressType: ipv4
Subnets:
- !Ref publicSubnet1
- !Ref publicSubnet2
SecurityGroups:
- !Ref WebServerSecurityGroup
Name: Web-Load-Balancer
Tags:
- Key: "Name"
Value: Web Load Balancer

Step 3: Create the App Tier

Next, we will create 2 private subnets, a private route table with the necessary associations, an application security group (allowing inbound permission from the Web Server Security Group), set up an autoscaling group, elastic IP, a NAT gateway, and set up a load balancer.

privateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.10.11.0/24
AvailabilityZone: "us-east-1a"
Tags:
- Key: Name
Value: Private-1
privateSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.10.12.0/24
AvailabilityZone: "us-east-1b"
Tags:
- Key: Name
Value: Private-2
# create a new private route table
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: Private Route Table
# create a route entry for our private route table to the nat gateway
natRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway
# associate our private subnets with our private route table
PrivateRouteTableAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref privateSubnet1
RouteTableId: !Ref PrivateRouteTable
PrivateRouteTableAssociation2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref privateSubnet2
RouteTableId: !Ref PrivateRouteTable
# create security group for application server
applicationServerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref VPC
GroupDescription: Application Server Security Group
SecurityGroupIngress:
- IpProtocol: -1
SourceSecurityGroupId: !Ref WebServerSecurityGroup
Tags:
- Key: Name
Value: "Application Server Security Group"
# create a launch template for private instances
applicationServerLaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: !Sub ${AWS::StackName}-application-server-launch-template
LaunchTemplateData:
ImageId: ami-055864024342e0e66
InstanceType: t2.micro
KeyName: !Ref KeyPair
NetworkInterfaces:
- DeviceIndex: 0
Groups:
- !Ref applicationServerSecurityGroup
DeleteOnTermination: true
# create autoscaling group for private instances
applicationServerAutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
LaunchTemplate:
LaunchTemplateId: !Ref applicationServerLaunchTemplate
Version: !GetAtt applicationServerLaunchTemplate.LatestVersionNumber
MaxSize: '1'
MinSize: '1'
DesiredCapacity: '1'
VPCZoneIdentifier:
- !Ref privateSubnet1
- !Ref privateSubnet2
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-Cloudformation Application Server Auto Scaling Group
PropagateAtLaunch: true
# create an elastic ip
elasticIp:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
# create a nat gateway for private subnets to gain access to the internet for updates
NatGateway:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt elasticIp.AllocationId
SubnetId: !Ref publicSubnet1
# create my load balancer listener
AppListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
Protocol: HTTP
Port: 80
DefaultActions:
- Type: forward
TargetGroupArn: !Ref AppTargetGroup
LoadBalancerArn: !Ref AppLoadBalancer
# create load balancer target group
AppTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckIntervalSeconds: 30
HealthCheckProtocol: HTTP
HealthCheckTimeoutSeconds: 15
HealthyThresholdCount: 5
Matcher:
HttpCode: '200'
IpAddressType: ipv4
Port: 80
Name: app-target-group
Protocol: HTTP
ProtocolVersion: HTTP1
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: '20'
VpcId: !Ref VPC
Tags:
- Key: "Name"
Value: App Target Group
TargetType: instance
VpcId: !Ref VPC
# create our application load balancer
AppLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
DependsOn: InternetGateway
Properties:
Type: application
Scheme: internet-facing
IpAddressType: ipv4
Subnets:
- !Ref privateSubnet1
- !Ref privateSubnet2
SecurityGroups:
- !Ref applicationServerSecurityGroup
Name: App-Load-Balancer
Tags:
- Key: "Name"
Value: App Load Balancer

Step 4: Create the Database Tier

Last, we will create a database security group, 2 private database subnets (associated with the private route table), and the RDS database.

We will also create a database subnet group and associate our 2 private subnets.

We must create this in order for the RDS database instance to exist in our custom VPC.

  # create a database security group
DatabaseSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref VPC
GroupDescription: Database Security Group
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 3306
ToPort: 3306
SourceSecurityGroupId: !Ref applicationServerSecurityGroup
Tags:
- Key: Name
Value: "Database Security Group"
# create 2 private subnets
privateDbSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.10.21.0/24
AvailabilityZone: "us-east-1a"
Tags:
- Key: Name
Value: DB-Private-1
privateDbSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.10.22.0/24
AvailabilityZone: "us-east-1b"
Tags:
- Key: Name
Value: DB-Private-2
# associate subnets with route table
DatabaseRouteTableAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref privateDbSubnet1
RouteTableId: !Ref PrivateRouteTable
DatabaseRouteTableAssociation2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref privateDbSubnet2
RouteTableId: !Ref PrivateRouteTable
# create database subnet group
DBSubnetGroup:
Type: "AWS::RDS::DBSubnetGroup"
Properties:
DBSubnetGroupDescription: group to associate db to subnets
SubnetIds:
- !Ref privateDbSubnet1
- !Ref privateDbSubnet2
Tags:
-
Key: Name
Value: DB Subnet Group
# create the database
DatabaseInstance:
Type: "AWS::RDS::DBInstance"
Properties:
MasterUsername: !Ref DBMasterUsername
MasterUserPassword: !Ref DBMasterPassword
VPCSecurityGroups:
- !GetAtt DatabaseSecurityGroup.GroupId
DBSubnetGroupName: !Ref DBSubnetGroup
DBInstanceClass: "db.t3.micro"
Engine: "MySQL"
AllocatedStorage: 5

Congrats!

You’ve just used the power of AWS CloudFormation to easily set up a 3-tier architecture.

Thanks for following along.

Feel free to follow me at:
https://medium.com/@jerrykarran

Connect with me on LinkenIn:
https://www.linkedin.com/in/jerry-karran/

--

--