In this post, i am going to show you, how to design a 3-Tier architecture, to host your application by following Best Practices. This option offers better security, HA (High Availability), Elasticity and Scalability, it’s fault tolerant and easier to manage/perform updates.
Some of the advantages are:
- Decoupling – Breaking down your application
- Development teams can work on different tiers and also is easier to recover from an issue as you know which tier is responsible for it. Your team can update servers in one layer without impacting the operation of the others. For example, a change to business logic, won’t impact the presentation layer.
- High Availability as you are going to host your application across different Availability zones. (We are going to use 3 AZs but in the design diagram below i have used 2, for simplicity)
- Each tier can scale when needed or when there are traffic spikes
- The presentation (Web) tier of the infrastructure will be in a private subnets with no public IP assigned to the instances. Users can only reach the Web servers through the application load balancer.
- All assets/images can be stored in a S3 bucket and retrieved by the App.
- Databases can be Multi AZ with Read replicas and also use caching if required.
- Traffic will be controlled by using Security Groups and ACLs and only the required ports will be open.
In a 3 Tier architecture we break down the application into 3 logical tiers or layers as they called.
- Presentation Tier
- This the tier where the Web Servers are going to be (Private Subnets)
- Application Tier
- This can also be referenced as Logic Tier. It’s where the application servers live, and it contains the business logic for the application.
- Data Tier
- This is where we place the Databases required for the application. The only way to access those databases is by connecting to them from the application layer.
To deploy a three-tier Virtual Private Cloud (VPC) using AWS CloudFormation, you will need to create a CloudFormation template in JSON or YAML. The template should define all of the resources needed for your VPC, including the VPC itself, subnets, route tables, internet gateway, and any other resources required by your application.
Here are the basic steps for deploying a three-tier VPC using CloudFormation:
- Create a new CloudFormation stack or modify an existing stack.
- Define the VPC, subnets, and internet gateway resources in your CloudFormation template.
- Define the route tables and routes for each subnet.
- Optionally, add resources for your application, such as EC2 instances, load balancers, and security groups.
- Validate your template to ensure it is correct.
- Review the changes that will be made by the CloudFormation stack and acknowledge them.
- Wait for the stack to reach the
CREATE_COMPLETE
orUPDATE_COMPLETE
state.
You can use the AWS Management Console, AWS CLI, or SDKs to create and deploy your CloudFormation stack (see below)
AWSTemplateFormatVersion: '2010-09-09'
Description: '3 Tier VPC'
Parameters:
ApplicationName:
Description: 'Enter your app name:'
Type: String
MaxLength: '50'
MinLength: '3'
ConstraintDescription: Please input your app name.
Resources:
myVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
InstanceTenancy: default
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: environment
Value: my3tier-vpc
- Key: Name
Value: !Join ['-', [myVPC, !Ref 'ApplicationName']]
PublicSubnetA:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.0.0/24
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: !Ref AWS::Region
MapPublicIpOnLaunch: true
VpcId: !Ref 'myVPC'
Tags:
- Key: environment
Value: my3tier-vpc
- Key: Name
Value: !Join ['-', [PublicSubnetA, !Ref 'ApplicationName']]
PublicSubnetB:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.1.0/24
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: !Ref AWS::Region
MapPublicIpOnLaunch: true
VpcId: !Ref 'myVPC'
Tags:
- Key: environment
Value: my3tier-vpc
- Key: Name
Value: !Join ['-', [PublicSubnetB, !Ref 'ApplicationName']]
PublicSubnetC:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.2.0/24
AvailabilityZone:
Fn::Select:
- 2
- Fn::GetAZs: !Ref AWS::Region
MapPublicIpOnLaunch: true
VpcId: !Ref 'myVPC'
Tags:
- Key: environment
Value: my3tier-vpc
- Key: Name
Value: !Join ['-', [PublicSubnetC, !Ref 'ApplicationName']]
PrivateSubnetA:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.3.0/24
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: !Ref AWS::Region
VpcId: !Ref 'myVPC'
Tags:
- Key: environment
Value: my3tier-vpc
- Key: Name
Value: !Join ['-', [PrivateSubnetA, !Ref 'ApplicationName']]
PrivateSubnetB:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.4.0/24
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: !Ref AWS::Region
VpcId: !Ref 'myVPC'
Tags:
- Key: environment
Value: my3tier-vpc
- Key: Name
Value: !Join ['-', [PrivateSubnetB, !Ref 'ApplicationName']]
PrivateSubnetC:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.5.0/24
AvailabilityZone:
Fn::Select:
- 2
- Fn::GetAZs: !Ref AWS::Region
VpcId: !Ref 'myVPC'
Tags:
- Key: environment
Value: my3tier-vpc
- Key: Name
Value: !Join ['-', [PrivateSubnetC, !Ref 'ApplicationName']]
myIGW:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: environment
Value: my3tier-vpc
- Key: Name
Value: !Join ['-', [IGW, !Ref 'ApplicationName']]
myNetworkACL:
Type: AWS::EC2::NetworkAcl
Properties:
VpcId: !Ref 'myVPC'
Tags:
- Key: environment
Value: my3tier-vpc
- Key: Name
Value: !Join ['-', [NACL, !Ref 'ApplicationName']]
myRoutePublic:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref 'myVPC'
Tags:
- Key: environment
Value: my3tier-vpc
- Key: Name
Value: !Join ['-', [PublicRoute, !Ref 'ApplicationName']]
myRoutePrivate:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref 'myVPC'
Tags:
- Key: environment
Value: my3tier-vpc
- Key: Name
Value: !Join ['-', [PrivateRoute, !Ref 'ApplicationName']]
myLT:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: myLT
LaunchTemplateData:
DimybleApiTermination: true
ImageId: ami-012345678790
UserData:
InstanceType: t2.micro
SecurityGroupIds:
- Ref: mySGapp
myASG:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
VPCZoneIdentifier:
- !Ref PrivateSubnetA
- !Ref PrivateSubnetB
- !Ref PrivateSubnetC
LaunchTemplate:
LaunchTemplateId: !Ref myLT
Version: !GetAtt myLT.LatestVersionNumber
MaxSize: '3'
MinSize: '2'
TargetGroupARNs:
- !Ref myALBTG
myelb:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: myelb
Scheme: internet-facing
Subnets:
- !Ref PublicSubnetA
- !Ref PublicSubnetB
- !Ref PublicSubnetC
SecurityGroups: [!Ref 'mySGELB']
Type: application
Tags:
- Key: environment
Value: my3tier-vpc
- Key: Name
Value: !Join ['-', [ELB, !Ref 'ApplicationName']]
ALBHTTPListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: "redirect"
RedirectConfig:
Protocol: "HTTPS"
Port: 443
Host: "#{host}"
Path: "/#{path}"
Query: "#{query}"
StatusCode: "HTTP_301"
LoadBalancerArn: !Ref myelb
Port: 80
Protocol: HTTP
ALBHTTPSListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref myALBTG
LoadBalancerArn: !Ref myelb
Port: 443
Protocol: HTTPS
Certificates:
- CertificateArn: arn:aws:acm:eu-west-1:123456789012:certificate/12345678-1234-1234-1234-123456789012 #Replace this with your certficate ARN
SslPolicy: ELBSecurityPolicy-TLS-1-2-2017-01
myALBTG:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckIntervalSeconds: 30
HealthCheckPath: /index.html
HealthCheckProtocol: HTTP
HealthCheckPort: traffic-port
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 2
Matcher:
HttpCode: '200'
Name: myInstances
Port: 80
Protocol: HTTP
VpcId: !Ref 'myVPC'
Tags:
- Key: Name
Value: myALB-TG
mySGELB:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: myapp - ELB security group
VpcId: !Ref 'myVPC'
SecurityGroupIngress:
- IpProtocol: tcp
ToPort: 80
FromPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
ToPort: 80
FromPort: 443
CidrIp: 0.0.0.0/0
Tags:
- Key: environment
Value: my3tier-vpc
- Key: Name
Value: ELBSecurityGroup
mySGapp:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: myapp - App server security group
VpcId: !Ref 'myVPC'
SecurityGroupIngress:
- IpProtocol: tcp
ToPort: 80
FromPort: 80
SourceSecurityGroupId: !Ref 'mySGELB'
Tags:
- Key: environment
Value: my3tier-vpc
- Key: Name
Value: AppServerSecurityGroup
myNACLEntry1:
Type: AWS::EC2::NetworkAclEntry
Properties:
CidrBlock: 0.0.0.0/0
Egress: true
Protocol: -1
RuleAction: allow
RuleNumber: 100
NetworkAclId: !Ref 'myNetworkACL'
myNACLEntry2:
Type: AWS::EC2::NetworkAclEntry
Properties:
CidrBlock: 0.0.0.0/0
Protocol: -1
RuleAction: allow
RuleNumber: 100
NetworkAclId: !Ref 'myNetworkACL'
subnetacl1:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
NetworkAclId: !Ref 'myNetworkACL'
SubnetId: !Ref 'PublicSubnetA'
subnetacl2:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
NetworkAclId: !Ref 'myNetworkACL'
SubnetId: !Ref 'PublicSubnetB'
subnetacl3:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
NetworkAclId: !Ref 'myNetworkACL'
SubnetId: !Ref 'PublicSubnetC'
subnetacl4:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
NetworkAclId: !Ref 'myNetworkACL'
SubnetId: !Ref 'PrivateSubnetA'
subnetacl5:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
NetworkAclId: !Ref 'myNetworkACL'
SubnetId: !Ref 'PrivateSubnetB'
subnetacl6:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
NetworkAclId: !Ref 'myNetworkACL'
SubnetId: !Ref 'PrivateSubnetC'
myIGWAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref 'myVPC'
InternetGatewayId: !Ref 'myIGW'
subnetRoutePublicA:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref 'myRoutePublic'
SubnetId: !Ref 'PublicSubnetA'
subnetRoutePublicB:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref 'myRoutePublic'
SubnetId: !Ref 'PublicSubnetB'
subnetRoutePublicC:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref 'myRoutePublic'
SubnetId: !Ref 'PublicSubnetC'
subnetRoutePrivateA:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref 'myRoutePrivate'
SubnetId: !Ref 'PrivateSubnetA'
subnetRoutePrivateB:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref 'myRoutePrivate'
SubnetId: !Ref 'PrivateSubnetB'
subnetRoutePrivateC:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref 'myRoutePrivate'
SubnetId: !Ref 'PrivateSubnetC'
publicroute:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
RouteTableId: !Ref 'myRoutePublic'
GatewayId: !Ref 'myIGW'
privateroute:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
RouteTableId: !Ref 'myRoutePrivate'
NatGatewayId: !Ref 'NATGW'
NATEIP:
Type: AWS::EC2::EIP
Properties:
Domain: !Ref 'myVPC'
NATGW:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NATEIP.AllocationId
SubnetId: !Ref 'PublicSubnetA'
Outputs:
LoadBalancerDNSName:
Description: The DNS Name of the load balancer
Value: !GetAtt myelb.DNSName
Once done, you can also enable/configure a number of AWS services to increase security, like Security Hub, GuardDuty, CloudTrail, AWS config, AWS Inspector.
AWS Well-Architected framework helps us, cloud architects, build secure, high-performing, resilient, and efficient infrastructure for a variety of applications and workloads.
For your reference these are the Pillars and we are going to discuss them in more details, in another post
- Operational Excellence
- Security
- Reliability
- Performance Efficiency
- Cost Optimization
- Sustainability