Three-Tier-Architecture-Using-Terraform in aws

koya sivakrishna
8 min readOct 8, 2023

--

Prerequisites:

· AWS Account

· VS Code

· Terraform Installed

· Configure AWS CLI in the local system

IAC:

IAC stands for Infrastructure As Code, Is a practice that involves managing and provisioning infrastructure resources using machine-readable configuration files or scripts.

Terraform:

Terraform is an open-source Infrastructure as a Code tool by HashiCorp. It enables us to provision, manage, and version infrastructure resources across various cloud providers and infrastructure platforms.

In this project, we will create a Three-tier architecture using terraform.

In the web tier, we will create two public subnets to provision an EC2 server, a Nat Gateway, an internet-facing load balancer to direct the traffic to an autoscaling group, and to be publicly available to the user in the web tier, to maintain the application more secure.

In the Application tier, we will create two private subnets backed with an Internal facing load balancer that directs the traffic to the autoscaling group

In the database tier, we will create two private subnets, for the database

STEP 1:

Create main.tf file and enter the code to provision the network necessary to our architecture, VPC, Public Subnets, Private Subnets, Route Tables, Internet Gateway, Nat Gateway, Security Groups, and subnet groups.

provider "aws" {
region = "us-east-1"
}
# create a VPC
resource "aws_vpc" "three-tier-vpc" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
}
# check the az
data "aws_availability_zones" "myazs" {
}
# create the igw, nat-gw and eip
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.three-tier-vpc.id
}
# create 1 public and 2 private subnets
resource "aws_subnet" "pb_sn" {
count = var.pb_sn_count
vpc_id = aws_vpc.three-tier-vpc.id
cidr_block = "10.0.${10 + count.index}.0/24"
map_public_ip_on_launch = true
availability_zone = data.aws_availability_zones.myazs.names[count.index]
}
# create the rt
resource "aws_route_table" "pb_rt" {
vpc_id = aws_vpc.three-tier-vpc.id
}
resource "aws_route" "def_public_route" {
route_table_id = aws_route_table.pb_rt.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
resource "aws_route_table_association" "pb_rt_asc" {
count = var.pb_sn_count
route_table_id = aws_route_table.pb_rt.id
subnet_id = aws_subnet.pb_sn.*.id[count.index]
}
resource "aws_eip" "eip" {
vpc = true
}
resource "aws_nat_gateway" "nat" {
allocation_id = aws_eip.eip.id
subnet_id = aws_subnet.pb_sn[0].id
}resource "aws_subnet" "app_pr_sn" {
count = var.app_pr_sn_count
vpc_id = aws_vpc.three-tier-vpc.id
availability_zone = data.aws_availability_zones.myazs.names[count.index]
cidr_block = "10.0.${20 + count.index}.0/24"
map_public_ip_on_launch = false
}resource "aws_route_table" "app_pr_rt" {
vpc_id = aws_vpc.three-tier-vpc.id
}
resource "aws_route" "def_pr_route" {
route_table_id = aws_route_table.app_pr_rt.id
destination_cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.nat.id
}resource "aws_route_table_association" "app_pr_rt_asc" {
count = var.app_pr_sn_count
route_table_id = aws_route_table.app_pr_rt.id
subnet_id = aws_subnet.app_pr_sn.*.id[count.index]
}
resource "aws_subnet" "db_pr_sn" {
count = var.db_pr_sn_count
vpc_id = aws_vpc.three-tier-vpc.id
cidr_block = "10.0.${30 + count.index}.0/24"
map_public_ip_on_launch = false
availability_zone = data.aws_availability_zones.myazs.names[count.index]
}
resource "aws_route_table" "db_pr_sn_rt" {
vpc_id = aws_vpc.three-tier-vpc.id
}
resource "aws_route" "def_db_route" {
route_table_id = aws_route_table.db_pr_sn_rt.id
destination_cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.nat.id
}
resource "aws_route_table_association" "db_pr_sn_asc" {
count = var.db_pr_sn_count
route_table_id = aws_route_table.db_pr_sn_rt.id
subnet_id = aws_subnet.db_pr_sn.*.id[count.index]
}
# Web-tier lb sg
resource "aws_security_group" "web_lb_sg" {
name = "web_lb_sg"
vpc_id = aws_vpc.three-tier-vpc.id
egress {
protocol = "-1"
to_port = 0
from_port = 0
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
protocol = "tcp"
to_port = 80
from_port = 80
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
protocol = "tcp"
to_port = 22
from_port = 22
cidr_blocks = ["0.0.0.0/0"]
}
}
# web-tier sg
resource "aws_security_group" "web_sg" {
name = "web_sg"
egress {
protocol = "-1"
to_port = 0
from_port = 0
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.web_lb_sg.id]
}
ingress {
to_port = 22
from_port = 22
protocol = "tcp"
security_groups = [aws_security_group.web_lb_sg.id]
}
}
#app-tier lb sg
resource "aws_security_group" "app_lb_sg" {
vpc_id = aws_vpc.three-tier-vpc.id
name = "app_lb_sg"
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.web_lb_sg.id]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = [aws_security_group.web_lb_sg.id]
}
}
#app-tier sg
resource "aws_security_group" "app_sg" {
name = "app_sg"
vpc_id = aws_vpc.three-tier-vpc.id
egress {
to_port = 0
from_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.app_lb_sg.id]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = [aws_security_group.app_lb_sg.id]
}
}
resource "aws_security_group" "db_sg" {
name = "db_sg"
vpc_id = aws_vpc.three-tier-vpc.id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
to_port = 3306
from_port = 3306
protocol = "tcp"
security_groups = [aws_security_group.app_sg.id]
}
}
# create the db subnet group2
resource "aws_db_subnet_group" "db_sn_group" {
name = "db_sn_group"
subnet_ids = [aws_subnet.db_pr_sn[0].id,aws_subnet.db_pr_sn[1].id]
}

Create the resources with the above-mentioned configurations.

Below are the output and variables file for the networking part.

output "vpc_id" {
value = aws_vpc.three-tier-vpc.id
}
output "db_subnet_group_name" {
value = aws_db_subnet_group.db_sn_group.*.name
}
output "rds_db_subnet_group" {
value = aws_db_subnet_group.db_sn_group.*.id
}
output "rds_sg" {
value = aws_security_group.db_sg.id
}
output "frontend_app_sg" {
value = aws_security_group.web_sg.id
}
output "frontend_lb_sg" {
value = aws_security_group.web_lb_sg.id
}
output "app_sg" {
value = aws_security_group.app_sg.id
}
output "app_lb_sg" {
value = aws_security_group.app_lb_sg.id
}
output "public_subnets" {
value = aws_subnet.pb_sn.*.id
}
output "app_subnets" {
value = aws_subnet.app_pr_sn.*.id
}
output "db_subnets" {
value = aws_subnet.db_pr_sn.*.id
}
variable "pb_sn_count" {}
variable "app_pr_sn_count" {}variable "db_pr_sn_count" {}variable "azs" {}variable "my_ip" {}variable "availabilityzone" {}

LOADBALANCER:

Here we will create two load balancers, one is internet-facing and another one is internal facing

# internet facing loadbalancer
resource "aws_lb" "web_lb" {
name = "web-lb"
security_groups = [var.frontend_lb_sg]
subnets = var.public_subnets
idle_timeout = 300
}
resource "aws_lb_target_group" "web_tg" {
name = "web-tg"
port = 80
protocol = "tcp"
vpc_id = var.vpc_id
}
resource "aws_lb_listener" "web_listener" {
load_balancer_arn = aws_lb.web_lb.arn
port = 80
protocol = "tcp"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.web_tg.arn
}
}# internal facing loadbalancer
resource "aws_lb" "app_lb" {
name = "app-lb"
subnets = var.private_subnets
security_groups = [var.app_lb_sg]
idle_timeout = 300
}
resource "aws_lb_target_group" "app_tg" {
name = "app-tg"
port = 80
protocol = "tcp"
vpc_id = var.vpc_id
}
resource "aws_lb_listener" "app_listener" {
port = 80
protocol = "tcp"
load_balancer_arn = aws_lb.app_lb.arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app_tg.arn
}
}

In this main.tf file we will create Load balancers, Target Groups, and listeners.

Below are the variables.tf and outputs.tf files for Loadbalancer

variable "frontend_lb_sg" {}
variable "public_subnets" {}variable "private_subnets" {}variable "vpc_id" {}variable "app_lb_sg" {}variable "myazs" {}
output "app_alb_dns" {
value = aws_lb.app_lb.dns_name
}
output "app_alb_endpoint" {
value = aws_lb.app_lb.arn
}
output "app_tg_name" {
value = aws_lb_target_group.app_tg.name
}
output "app_tg" {
value = aws_lb_target_group.app_tg.arn
}
output "web_alb_dns" {
value = aws_lb.web_lb.dns_name
}
output "web_alb_endpoint" {
value = aws_lb.web_lb.dns_name
}
output "web_tg_name" {
value = aws_lb_target_group.web_tg.name
}
output "web_tg" {
value = aws_lb_target_group.web_tg.arn
}

COMPUTE:

In the main.tf file we will start with getting the latest AMI using the AWS SSM parameter, create the launch template and ASG, then attach it to the loadbalacer target group.

data "aws_ssm_parameter" "three_tier_ami" {
name = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
}
#aws launch template for web-tier
resource "aws_launch_template" "web_tier_instance" {
name_prefix = "web_tier_instance"
instance_type = var.instance_type
image_id = data.aws_ssm_parameter.three_tier_ami.value
vpc_security_group_ids = [var.frontend_app_sg]
}
resource "aws_autoscaling_group" "web_tier_asg" {
name = "web-tier_asg"
vpc_zone_identifier = var.public_subnets
min_size = 2
max_size = 2
desired_capacity = 2
launch_template {
id = aws_launch_template.web_tier_instance.id
version = "$Latest"
}
}
resource "aws_autoscaling_attachment" "web_asg_attach" {
autoscaling_group_name = aws_autoscaling_group.web_tier_asg.id
lb_target_group_arn = var.web_tg
}
#Lauch template for app-tier
resource "aws_launch_template" "app_tier_instance" {
name_prefix = "app_tier_instance"
instance_type = var.instance_type
image_id = data.aws_ssm_parameter.three_tier_ami.value
vpc_security_group_ids = [var.app_sg]
}
resource "aws_autoscaling_group" "app_tier_asg" {
name = "app_tier_asg"
vpc_zone_identifier = var.app_subnets
min_size = 2
max_size = 2
desired_capacity = 2
launch_template {
id = aws_launch_template.app_tier_instance.id
version = "$latest"
}
}
resource "aws_autoscaling_attachment" "app_asg_attach" {
autoscaling_group_name = aws_autoscaling_group.app_tier_asg.id
lb_target_group_arn = var.app_tg
}

Here are the variables.tf and outputs.tf files.

variable "frontend_app_sg" {}
variable "app_sg" {}
variable "public_subnets" {}
variable "app_subnets" {}
variable "web_tg" {}
variable "web_tg_name" {}
variable "app_tg" {}
variable "instance_type" {}
variable "app_tg_name" {}
output "web_asg" {
value = aws_autoscaling_group.web_tier_asg.arn
}

The database tier, In the main.tf we will be creating a MySQL db.

resource "aws_db_instance" "rds_db" {
allocated_storage = var.db_storage
engine = "mysql"
engine_version = var.db_engine_version
instance_class = var.db_instance_class
db_name = var.db_name
username = var.dbuser
password = var.dbpassword
db_subnet_group_name = var.rds_db_subnet_group
identifier = var.db_identifier
skip_final_snapshot = true
vpc_security_group_ids = [var.rds_sg]
}

And Finally, comes to the root module, which will combine all other modules we have created (Compute, Loadbalancer, Network, Database)

provider "aws" {
region = local.location
}
locals {
instance_type = "t2.micro"
location = "us-east-1"
}module "network" {
source = "../modules/network"
my_ip = var.my_ip
pb_sn_count = 2
app_pr_sn_count = 2
db_pr_sn_count = 2
azs = 2
availabilityzone = "us-east-1a"
}
module "loadbalancer" {
source = "../modules/loadbalancer"
frontend_lb_sg = module.network.frontend_lb_sg
app_lb_sg = module.network.app_lb_sg
vpc_id = module.network.vpc_id
myazs = 2
public_subnets = module.network.public_subnets
private_subnets = module.network.app_subnets
}
module "db" {
source = "../modules/database"
db_storage = 10
db_engine_version = "8.0.30"
db_identifier = "my-3-tier-rds-db"
db_instance_class = "db.t2.micro"
rds_db_subnet_group = module.network.rds_db_subnet_group[0]
rds_sg = module.network.rds_sg
dbpassword = var.dbpassword
dbuser = var.dbuser
db_name = var.db_name
}
module "compute" {
source = "../modules/compute"
frontend_app_sg = module.network.frontend_app_sg
app_sg = module.network.app_sg
web_tg = module.loadbalancer.web_tg
web_tg_name = module.loadbalancer.web_tg_name
app_tg = module.loadbalancer.app_tg
app_tg_name = module.loadbalancer.app_tg_name
instance_type = "t2.micro"
app_subnets = module.network.app_subnets
public_subnets = module.network.public_subnets
}

And here are the variables.tf, outputs.tf files for our root module

variable "dbuser" {
type = string
sensitive = true
}
variable "dbpassword" {
type = string
sensitive = true
}
variable "db_name" {
type = string
}
variable "my_ip" {}
output "load_balancer_endpoint" {
value = module.loadbalancer.web_alb_endpoint
}
output "database_endpoint" {
value = module.db.rds_db_endpoint
}
my_ip      = "0.0.0.0/0"
dbname = "*********"
dbpassword = "*********"
dbuser = "*********"

Finally, let’s test the configuration we created using the command terraform plan. Before that initialize our working directory.

Thanks For Reading

--

--