Infrastructure provisioning for Deploying Three-Tier-Architecture.


WHY?
There is a way of manually setting up resources and deploying applications on to cloud providers like AWS by following all required security and availability concerns, but there are few disadvantages or drawbacks of doing so.
Drawbacks of setting up resources manually:
Errors can occur easily.
Time consuming
Difficulty in tracking
Security risks
Disaster recovery problem
HOW?
So, how to eliminate or reduce these kind of drawbacks?
The answer to this question is Infrastructure as Code by using tools like Terraform. One can spin up resources by writing code. Instead of manually clicking and provisioning resources on cloud providers, we can write code to set them up. Read this to learn more.
In this article we will explore how to write code in order to set up resources to deploy an three tier application.
Resources defined using Terraform and Prerequisites to understand better.
Availability Zones
VPC
Subnets [Public and Private]
Security Groups
Route tables
Internet Gateway
NAT gateways
S3 Bucket
IAM Role
Before getting into Resource Definition, let’s understand the below architecture.
It consists of one VPC with CIDR block 10.0.0.0/16 which gives us 2^16 IP addresses. In VPC there are two availability zones [us-east-1a, us-east-1b] for high availability and disaster recovery. In each availability there exists one public subnet and two private subnets, public subnet is defined for WEB-SERVER and one of the two private subnets is for APP-TIER and other is for DATABASE-TIER. External Load Balancer is for routing traffic to any one of the WEB-TIERs available in us-east-1a and us-east-1b. Internal Load Balancer for routing traffic to any one of the APP-TIERs available in us-east-1a and us-east-1b. Internet gateway is defined to allow internet access to WEB-TIER-Subnets, Route table is associated with Internet gateway to allow HTTP and TCP routes to WEB-TIER-Subnets. NAT Gateway is defined to establish connection between WEB-TIER-Subnet[Public] and APP-TIER-Subnet[Private]. Route table is associated with NAT Gateway to allow all routes from WEB-TIER to APP-TIER. An S3 Bucket is created to store code of the application. Finally an IAM Role is defined with specific policies to access the S3 Bucket by resources like ec2.
To get the complete code of this project visit my GitHub.
The entire project is divided into modules for better organization of code.
Module-1
VPC
resource "aws_vpc" "Three-Tier-VPC-Resource" {
cidr_block = "10.0.0.0/16"
instance_tenancy = "default"
enable_dns_hostnames = true
enable_dns_support = true
tags={
Name="${var.Project_Name}-VPC"
}
}
#This code is inside the repository InfraAsCode/modules/vpc/main.tf
This block of code creates a VPC with 2^16 IP ranges which can be further used by the resources inside the VPC with name Three-Tier-VPC.
Internet Gateway
resource "aws_internet_gateway" "Three-tier-IGW-Resource" {
vpc_id = aws_vpc.Three-Tier-VPC-Resource.id
tags = {
Name="${var.Project_Name}-IGW"
}
}
#This code is inside the repository InfraAsCode/modules/vpc/main.tf
This resource definition creates Internet Gateway with name Three-Tier-IGW in the VPC which was defined earlier[Three-Tier-VPC].
Route table for Internet Gateway
resource "aws_route_table" "Three-Tier-Public-RT-Resource" {
vpc_id = aws_vpc.Three-Tier-VPC-Resource.id
route {
cidr_block="0.0.0.0/0"
gateway_id=aws_internet_gateway.Three-tier-IGW-Resource.id
}
tags = {
Name="Public-RT"
}
}
#This code is inside the repository InfraAsCode/modules/vpc/main.tf
These lines of code creates an Route table with name Public-RT in the VPC[Three-Tier-VPC], it has one route which allows internet from Internet gateway[Three-Tier-IGW] to which ever resource under this route table.
Get Availability Zones
data "aws_availability_zones" "available_zones" {
}
#This code is inside the repository InfraAsCode/modules/vpc/main.tf
These lines fetch you the total number of availability zones in the region and stores in data named available_zones.
Web-Tier-Public-Subnet-1
resource "aws_subnet" "Web_tier-Subnet-1-Pub" {
vpc_id = aws_vpc.Three-Tier-VPC-Resource.id
cidr_block = var.Web-Tier-1a-cidr
availability_zone = data.aws_availability_zones.available_zones.names[0]
map_public_ip_on_launch = true
tags = {
Name="Web-Tier-Subnet-1a-Pub"
}
}
#This code is inside the repository InfraAsCode/modules/vpc/main.tf
This block creates a Public subnet with name Web-Tier-Subnet-1a-Pub inside the VPC[Three-Tier-VPC], within the CIDR block 10.0.0.1.0/24, in one of the available_zones i.e us-east-1a.
Web-Tier-Public-Subnet-2
resource "aws_subnet" "Web_tier-Subnet-2-Pub" {
vpc_id = aws_vpc.Three-Tier-VPC-Resource.id
cidr_block = var.Web-Tier-2a-cidr
availability_zone = data.aws_availability_zones.available_zones.names[1]
map_public_ip_on_launch = true
tags = {
Name="Web-Tier-Subnet-1b-Pub"
}
}
#This code is inside the repository InfraAsCode/modules/vpc/main.tf
This block creates a Public subnet with name Web-Tier-Subnet-1b-Pub inside the VPC[Three-Tier-VPC], within the CIDR block 10.0.0.4.0/24, in one of the available_zones i.e us-east-1b.
App-Tier-Subnet-1-Private
resource "aws_subnet" "App_tier-Subnet-1-Private" {
vpc_id = aws_vpc.Three-Tier-VPC-Resource.id
cidr_block = var.App-Tier-1a-cidr
availability_zone = data.aws_availability_zones.available_zones.names[0]
map_public_ip_on_launch = false
tags = {
Name="App-Tier-Subnet-1a-Pri"
}
}
#This code is inside the repository InfraAsCode/modules/vpc/main.tf
This block creates a Private subnet with name App-Tier-Subnet-1a-Pri inside the VPC[Three-Tier-VPC], within the CIDR block 10.0.0.2.0/24, in one of the available_zones i.e us-east-1a.
App-Tier-Subnet-2-Private
resource "aws_subnet" "App_tier-Subnet-2-Private" {
vpc_id = aws_vpc.Three-Tier-VPC-Resource.id
cidr_block = var.App-Tier-1b-cidr
availability_zone = data.aws_availability_zones.available_zones.names[1]
map_public_ip_on_launch = false
tags = {
Name="App-Tier-Subnet-1b-Pri"
}
}
#This code is inside the repository InfraAsCode/modules/vpc/main.tf
This block creates a Private subnet with name App-Tier-Subnet-1b-Pri inside the VPC[Three-Tier-VPC], within the CIDR block 10.0.0.5.0/24, in one of the available_zones i.e us-east-1b.
DB-Tier-Subnet-1-Private
resource "aws_subnet" "DB_tier-Subnet-1-Private" {
vpc_id = aws_vpc.Three-Tier-VPC-Resource.id
cidr_block = var.DB-Tier-1a-cidr
availability_zone = data.aws_availability_zones.available_zones.names[0]
map_public_ip_on_launch = false
tags = {
Name="DB-Tier-Subnet-1a-Pri"
}
}
#This code is inside the repository InfraAsCode/modules/vpc/main.tf
This block creates a Private subnet with name DB-Tier-Subnet-1a-Pri inside the VPC[Three-Tier-VPC], within the CIDR block 10.0.0.3.0/24, in one of the available_zones i.e us-east-1a.
DB-Tier-Subnet-2-Private
resource "aws_subnet" "DB_tier-Subnet-2-Private" {
vpc_id = aws_vpc.Three-Tier-VPC-Resource.id
cidr_block = var.DB-Tier-1b-cidr
availability_zone = data.aws_availability_zones.available_zones.names[1]
map_public_ip_on_launch = false
tags = {
Name="DB-Tier-Subnet-1b-Pri"
}
}
#This code is inside the repository InfraAsCode/modules/vpc/main.tf
This block creates a Private subnet with name DB-Tier-Subnet-1b-Pri inside the VPC[Three-Tier-VPC], within the CIDR block 10.0.0.6.0/24, in one of the available_zones i.e us-east-1a.
Assign public subnets to public route table
resource "aws_route_table_association" "Web_tier-Subnet-1-Pub-RTA" {
subnet_id = aws_subnet.Web_tier-Subnet-1-Pub.id
route_table_id = aws_route_table.Three-Tier-Public-RT-Resource.id
}
resource "aws_route_table_association" "Web_tier-Subnet-2-Pub-RTA" {
subnet_id = aws_subnet.Web_tier-Subnet-2-Pub.id
route_table_id = aws_route_table.Three-Tier-Public-RT-Resource.id
}
#This code is inside the repository InfraAsCode/modules/vpc/main.tf
This block of code associates the Route table [Public-RT] with the public subnets namely Web-Tier-Subnet-1a-Pub and Web-Tier-Subnet-1b-Pub for internet access.
Module-2-Security Groups
Security Group[External] for Web-Tier-Instances
resource "aws_security_group" "alb_sg" {
name = "ALB security group"
description = "enable http/https access on port 80/443"
vpc_id = var.vpc_id
ingress {
description = "Http access"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "https access"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = -1
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name="ALB-sg"
}
}
#This code is inside the repository InfraAsCode/modules/securitygroup/main.tf
This code snippet creates security group with name ALB-sg for Web-TIer-Servers that allows 80 and 443 ports inside the server and every port to outside world.
Security Group[Internal] for App-Tier-Instances
resource "aws_security_group" "internal-sg" {
name = "Internal Security group"
description = "Allow access from alb sg"
vpc_id = var.vpc_id
ingress{
description = "https access"
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.alb_sg.id]
}
egress{
from_port = 0
to_port = 0
protocol = -1
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name="Internal-sg"
}
}
#This code is inside the repository InfraAsCode/modules/securitygroup/main.tf
This code snippet creates a security group named Internal-sg for App-Tier servers. It allows traffic on port 80 only from the ALB-sg, which was defined earlier, and permits all outbound traffic to the outside world.
Module-3-NAT
Elastic IP for NAT Gateway in us-east-1a
resource "aws_eip" "EIP_for_NAT1" {
vpc = true
}
#This code is inside the repository InfraAsCode/modules/nat/main.tf
This bit of code creates an elastic IP, which has to be attached to the NAT gateway.
NAT Gateway for Web-Tier1
resource "aws_nat_gateway" "NAT_for_Web_Tier_1" {
subnet_id = var.Web_Tier_Subnet1_id
allocation_id = aws_eip.EIP_for_NAT1.id
tags = {
Name = "NAT_for_Web_Tier_1"
}
}
#This code is inside the repository InfraAsCode/modules/nat/main.tf
This code defines a NAT Gateway with name NAT_for_Web_Tier_1 for the Web-Tier-Subnet-1-Public and associates an elastic IP to it.
Route Table for NAT_for_Web_Tier_1
resource "aws_route_table" "RT_for_Web_Tier_1" {
vpc_id = var.vpc_id
route{
cidr_block ="0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.NAT_for_Web_Tier_1.id
}
tags = {
Name="RT_for_Web_Tier_1"
}
}
#This code is inside the repository InfraAsCode/modules/nat/main.tf
It creates an Route Table with name RT_for_Web_Tier_1 in the same VPC where all the other resources reside, It allows all the routes from internet to NAT_for_Web_Tier_1.
Route Table Association to App-Tier-Subnet1
resource "aws_route_table_association" "RTA_for_RT_Web_Tier_1" {
subnet_id = var.App_Tier_Subnet1_id
route_table_id = aws_route_table.RT_for_Web_Tier_1.id
}
#This code is inside the repository InfraAsCode/modules/nat/main.tf
This code snippet associates the route table RT_for_Web_Tier_1 with App_Tier_Subnet1_Private.
Elastic IP for NAT Gateway in us-east-1b
resource "aws_eip" "EIP_for_NAT2" {
vpc = true
}
#This code is inside the repository InfraAsCode/modules/nat/main.tf
This bit of code creates an elastic IP, which has to be attached to the NAT gateway.
NAT Gateway for Web-Tier2
resource "aws_nat_gateway" "NAT_for_Web_Tier_2" {
subnet_id = var.Web_Tier_Subnet2_id
allocation_id = aws_eip.EIP_for_NAT2.id
tags = {
Name = "NAT_for_Web_Tier_2"
}
}
#This code is inside the repository InfraAsCode/modules/nat/main.tf
This code defines a NAT Gateway with name NAT_for_Web_Tier_2 for the Web-Tier-Subnet-2-Public and associates an elastic IP to it.
Route Table for NAT_for_Web_Tier_2
resource "aws_route_table" "RT_for_Web_Tier_2" {
vpc_id = var.vpc_id
route{
cidr_block ="0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.NAT_for_Web_Tier_2.id
}
tags = {
Name="RT_for_Web_Tier_2"
}
}
#This code is inside the repository InfraAsCode/modules/nat/main.tf
It creates an Route Table with name RT_for_Web_Tier_2 in the same VPC where all the other resources reside, It allows all the routes from internet to NAT_for_Web_Tier_2.
Route Table Association to App-Tier-Subnet1
resource "aws_route_table_association" "RTA_for_RT_Web_Tier_2" {
subnet_id = var.App_Tier_Subnet2_id
route_table_id = aws_route_table.RT_for_Web_Tier_2.id
}
#This code is inside the repository InfraAsCode/modules/nat/main.tf
This code snippet associates the route table RT_for_Web_Tier_2 with App_Tier_Subnet2_Private.
Module-4-S3-Bucket
resource "aws_s3_bucket" "Bucket_for_code" {
bucket = "Bucket_for_code"
tags={
Name="Bucket_for_code"
}
}
Here’s how you create an S3 bucket with name Bucket_for_code to store the code of the application.
Module-5-IAM
IAM
resource "aws_iam_role" "RoleForBucketAccess" {
name = "RoleForBucketAccess"
# Terraform's "jsonencode" function converts a
# Terraform expression result to valid JSON syntax.
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Sid = ""
Principal = {
Service = "ec2.amazonaws.com"
}
},
]
})
tags = {
tag-key = "RoleForBucketAccess"
}
}
#This code is inside the repository InfraAsCode/modules/iam/main.tf
This code snippet creates an IAM Role with name RoleForBucketAccess, so that it can be used by other resources like ec2 to get limited access to the bucket.
IAM Policies
resource "aws_iam_role_policy_attachment" "ssm_policy_attachment" {
role = aws_iam_role.RoleForBucketAccess.name
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
resource "aws_iam_role_policy_attachment" "s3_read_only_attachement" {
role = aws_iam_role.RoleForBucketAccess.name
policy_arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
}
#This code is inside the repository InfraAsCode/modules/iam/main.tf
This code attaches two policies, one is SSMManagedInstanceCore and other is S3ReadOnlyAccess.
SSMManagedInstanceCore- It gives ec2 instance a permission to work with Amazon Systems Manager.
S3ReadOnlyAccess- It gives read-only access to all S3 buckets in the AWS account to the resource the role is assigned to.
All the resources depicted in the architecture diagram are created and connected to each other in the way required, we can utilize them to deploy our application with high security, availability, recovery.
Note: If you have followed along, please don’t forget to delete the created resources using ‘terraform destroy‘, also check for elastic IPs and delete if found.
Subscribe to my newsletter
Read articles from Kasturi Nithin directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Kasturi Nithin
Kasturi Nithin
Exploring something new.