2-tier application - aws using terraform

khalid kifayatkhalid kifayat
15 min read

Deploy a 2 -tier application on aws using terraform

Step 1: The provider.tf file in Terraform tells Terraform which cloud provider (like AWS, Azure, Google Cloud, etc.) you want to use for your infrastructure. It's like picking a toolbox for a specific job.

Inside this file, you specify the details for connecting to that cloud provider, such as credentials and settings. It's like giving Terraform the keys to access and manage resources on your behalf in that cloud.

In essence, it sets the stage for Terraform to work with a particular cloud provider, allowing you to create, update, or delete resources like virtual machines, databases, and more in that cloud environment.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "4.67.0"
    }
  }
}

provider "aws" {
  region  = "us-east-1"
}

Step 2: network_resources.tf

# VPC
resource "aws_vpc" "two-tier-vpc" {
  cidr_block = "10.0.0.0/16"
  tags = {
    Name = "two-tier-vpc"
  }
}

# Public Subnets 
resource "aws_subnet" "two-tier-pub-sub-1" {
  vpc_id            = aws_vpc.two-tier-vpc.id
  cidr_block        = "10.0.0.0/18"
  availability_zone = "ap-southeast-1a"
  map_public_ip_on_launch = "true"

  tags = {
    Name = "two-tier-pub-sub-1"
  }
}

resource "aws_subnet" "two-tier-pub-sub-2" {
  vpc_id            = aws_vpc.two-tier-vpc.id
  cidr_block        = "10.0.64.0/18"
  availability_zone = "ap-southeast-1b"
  map_public_ip_on_launch = "true"
  tags = {
    Name = "two-tier-pub-sub-2"
  }
}

# Private Subnets
resource "aws_subnet" "two-tier-pvt-sub-1" {
  vpc_id                  = aws_vpc.two-tier-vpc.id
  cidr_block              = "10.0.128.0/18"
  availability_zone       = "ap-southeast-1a"
  map_public_ip_on_launch = false
  tags = {
    Name = "two-tier-pvt-sub-1"
  }
}
resource "aws_subnet" "two-tier-pvt-sub-2" {
  vpc_id                  = aws_vpc.two-tier-vpc.id
  cidr_block              = "10.0.192.0/18"
  availability_zone       = "ap-southeast-1b"
  map_public_ip_on_launch = false
  tags = {
    Name = "two-tier-pvt-sub-2"
  }
}

# Internet Gateway
resource "aws_internet_gateway" "two-tier-igw" {
  tags = {
    Name = "two-tier-igw"
  }
  vpc_id = aws_vpc.two-tier-vpc.id
}

# Route Table
resource "aws_route_table" "two-tier-rt" {
  tags = {
    Name = "two-tier-rt"
  }
  vpc_id = aws_vpc.two-tier-vpc.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.two-tier-igw.id
  }
}

# Route Table Association
resource "aws_route_table_association" "two-tier-rt-as-1" {
  subnet_id      = aws_subnet.two-tier-pub-sub-1.id
  route_table_id = aws_route_table.two-tier-rt.id
}

resource "aws_route_table_association" "two-tier-rt-as-2" {
  subnet_id      = aws_subnet.two-tier-pub-sub-2.id
  route_table_id = aws_route_table.two-tier-rt.id
}

# Create Load balancer
resource "aws_lb" "two-tier-lb" {
  name               = "two-tier-lb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.two-tier-alb-sg.id]
  subnets            = [aws_subnet.two-tier-pub-sub-1.id, aws_subnet.two-tier-pub-sub-2.id]

  tags = {
    Environment = "two-tier-lb"
  }
}

resource "aws_lb_target_group" "two-tier-lb-tg" {
  name     = "two-tier-lb-tg"
  port     = 80
  protocol = "HTTP"
  vpc_id   = aws_vpc.two-tier-vpc.id
}

# Create Load Balancer listener
resource "aws_lb_listener" "two-tier-lb-listner" {
  load_balancer_arn = aws_lb.two-tier-lb.arn
  port              = "80"
  protocol          = "HTTP"
  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.two-tier-lb-tg.arn
  }
}

# Create Target group
resource "aws_lb_target_group" "two-tier-loadb_target" {
  name       = "target"
  depends_on = [aws_vpc.two-tier-vpc]
  port       = "80"
  protocol   = "HTTP"
  vpc_id     = aws_vpc.two-tier-vpc.id

}

resource "aws_lb_target_group_attachment" "two-tier-tg-attch-1" {
  target_group_arn = aws_lb_target_group.two-tier-loadb_target.arn
  target_id        = aws_instance.two-tier-web-server-1.id
  port             = 80
}
resource "aws_lb_target_group_attachment" "two-tier-tg-attch-2" {
  target_group_arn = aws_lb_target_group.two-tier-loadb_target.arn
  target_id        = aws_instance.two-tier-web-server-2.id
  port             = 80
}

# Subnet group database
resource "aws_db_subnet_group" "two-tier-db-sub" {
  name       = "two-tier-db-sub"
  subnet_ids = [aws_subnet.two-tier-pvt-sub-1.id, aws_subnet.two-tier-pvt-sub-2.id]
}

1. Create a VPC (aws_vpc):

  • A Virtual Private Cloud (VPC) is created with the specified CIDR block (10.0.0.0/16).

  • Tags are added for identification.

2. Create Public Subnets (aws_subnet):

  • Two public subnets are created within the VPC.

  • These subnets have public IP mapping enabled, allowing instances launched in them to have public IP addresses.

  • Each subnet is associated with an availability zone in the ap-southeast-1 region.

  • Tags are added for identification.

3. Create Private Subnets (aws_subnet):

  • Two private subnets are created within the VPC.

  • Public IP mapping is disabled, ensuring that instances in these subnets do not have direct public internet access.

  • Like the public subnets, these are also associated with availability zones and tagged for identification.

4. Create an Internet Gateway (aws_internet_gateway):

  • An internet gateway is created and attached to the VPC.

  • This allows resources within the VPC to connect to the internet and vice versa.

5. Create a Route Table (aws_route_table):

  • A route table is created and associated with the VPC.

  • It is defined with a default route (0.0.0.0/0) via the internet gateway.

  • This route table will be used by the public subnets to route traffic to the internet.

6. Associate Route Tables with Public Subnets (aws_route_table_association):

  • The route table created in the previous step is associated with the public subnets.

  • This ensures that instances in the public subnets use the defined route table for internet-bound traffic.

7. Create a Load Balancer (aws_lb):

  • An Application Load Balancer (ALB) is created.

  • It is given a name and defined as non-internal (accessible from the internet).

  • Security groups are attached to control inbound and outbound traffic.

  • Subnets are specified to distribute incoming traffic across the two public subnets.

  • Tags are added for identification.

8. Create a Target Group (aws_lb_target_group):

  • A target group is created for routing requests to backend instances.

  • It listens on port 80 and uses the HTTP protocol.

  • It's associated with the VPC.

9. Create a Load Balancer Listener (aws_lb_listener):

  • An ALB listener is created to handle incoming HTTP traffic on port 80.

  • It forwards incoming requests to the target group created earlier.

10. Create a Target Group for Database Subnet (aws_lb_target_group): - Another target group is created for potential database instances. - While this target group doesn't have any attachments in this configuration, it's set up for future use if needed.

11. Attach EC2 Instances to the Target Group (aws_lb_target_group_attachment): - Instances can be attached to the target group for the ALB. - Two instances (aws_instance.two-tier-web-server-1 and aws_instance.two-tier-web-server-2) are assumed to exist and are attached to this target group. - These instances would typically represent the web servers in your architecture.

12. Create a Database Subnet Group (aws_db_subnet_group): - A database subnet group is created, defining which private subnets are available for RDS instances. - It includes both private subnets created earlier.

Please note that while this Terraform configuration sets up the network infrastructure for a 2-tier architecture, it assumes the existence of EC2 instances for web servers and database instances (RDS) that are not defined in this configuration. You would typically define those resources separately and configure security groups, IAM roles, and other details based on your application's specific requirements.

Step 3: security_resources.tf

# Security Group for EC2 instances
resource "aws_security_group" "two-tier-ec2-sg" {
  name        = "two-tier-ec2-sg"
  description = "Allow traffic from VPC"
  vpc_id      = aws_vpc.two-tier-vpc.id
  depends_on = [
    aws_vpc.two-tier-vpc
  ]

  # Ingress rules (allow incoming traffic)
  ingress {
    from_port = "0"
    to_port   = "0"
    protocol  = "-1" # Allow all incoming traffic
  }
  ingress {
    from_port   = "80"
    to_port     = "80"
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"] # Allow HTTP traffic from anywhere
  }
  ingress {
    from_port   = "22"
    to_port     = "22"
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"] # Allow SSH traffic from anywhere (CAUTION: This is not recommended for production)
  }

  # Egress rules (allow outgoing traffic)
  egress {
    from_port   = "0"
    to_port     = "0"
    protocol    = "-1" # Allow all outgoing traffic
    cidr_blocks = ["0.0.0.0/0"] # Allow traffic to anywhere
  }

  tags = {
    Name = "two-tier-ec2-sg"
  }
}

# Security Group for Load Balancer
resource "aws_security_group" "two-tier-alb-sg" {
  name        = "two-tier-alb-sg"
  description = "Load balancer security group"
  vpc_id      = aws_vpc.two-tier-vpc.id
  depends_on = [
    aws_vpc.two-tier-vpc
  ]

  # Ingress rules (allow incoming traffic)
  ingress {
    from_port   = "0"
    to_port     = "0"
    protocol    = "-1" # Allow all incoming traffic
    cidr_blocks = ["0.0.0.0/0"] # Allow traffic from anywhere (CAUTION: This is not recommended for production)
  }

  # Egress rules (allow outgoing traffic)
  egress {
    from_port   = "0"
    to_port     = "0"
    protocol    = "-1" # Allow all outgoing traffic
    cidr_blocks = ["0.0.0.0/0"] # Allow traffic to anywhere
  }

  tags = {
    Name = "two-tier-alb-sg"
  }
}

# Security Group for Database Tier
resource "aws_security_group" "two-tier-db-sg" {
  name        = "two-tier-db-sg"
  description = "Allow traffic from the internet"
  vpc_id      = aws_vpc.two-tier-vpc.id

  # Ingress rules (allow incoming traffic)
  ingress {
    from_port       = 3306 # MySQL port
    to_port         = 3306 # MySQL port
    protocol        = "tcp"
    security_groups = [aws_security_group.two-tier-ec2-sg.id] # Allow traffic from EC2 instances
    cidr_blocks     = ["0.0.0.0/0"] # Allow MySQL traffic from anywhere (CAUTION: This is not recommended for production)
  }

  ingress {
    from_port       = 22 # SSH port
    to_port         = 22 # SSH port
    protocol        = "tcp"
    security_groups = [aws_security_group.two-tier-ec2-sg.id] # Allow SSH traffic from EC2 instances
    cidr_blocks     = ["10.0.0.0/16"] # Allow SSH traffic from a specific IP range within the VPC
  }

  # Egress rules (allow outgoing traffic)
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1" # Allow all outgoing traffic
    cidr_blocks = ["0.0.0.0/0"] # Allow traffic to anywhere
  }
}

Certainly, let's break down each step in the provided security_resources.tf file:

1. Security Group for EC2 instances (aws_security_group.two-tier-ec2-sg):

  • A security group named "two-tier-ec2-sg" is defined.

  • It is associated with the VPC defined earlier (aws_vpc.two-tier-vpc.id).

  • Dependencies are specified to ensure the VPC is created before this security group.

Ingress Rules (Inbound Traffic):

  • The security group allows all incoming traffic on all ports (from_port = "0", to_port = "0", protocol = "-1"). This is typically not recommended for production and should be configured more restrictively.

  • Port 80 (HTTP) is allowed for incoming traffic from anywhere (cidr_blocks = ["0.0.0.0/0"]).

  • Port 22 (SSH) is allowed for incoming traffic from anywhere. This is not recommended for production as SSH access should be restricted to trusted IP addresses.

Egress Rules (Outbound Traffic):

  • The security group allows all outgoing traffic to any destination (from_port = "0", to_port = "0", protocol = "-1").

2. Security Group for Load Balancer (aws_security_group.two-tier-alb-sg):

  • A security group named "two-tier-alb-sg" is defined.

  • It is associated with the same VPC as above (aws_vpc.two-tier-vpc.id).

  • Dependencies are specified to ensure the VPC is created before this security group.

Ingress Rules (Inbound Traffic):

  • The security group allows all incoming traffic on all ports from anywhere (from_port = "0", to_port = "0", protocol = "-1", cidr_blocks = ["0.0.0.0/0"]). This is generally not recommended for production as it exposes the load balancer to all traffic.

Egress Rules (Outbound Traffic):

  • The security group allows all outgoing traffic to any destination (from_port = "0", to_port = "0", protocol = "-1").

3. Security Group for Database Tier (aws_security_group.two-tier-db-sg):

  • A security group named "two-tier-db-sg" is defined.

  • It is associated with the same VPC as above (aws_vpc.two-tier-vpc.id).

Ingress Rules (Inbound Traffic):

  • Port 3306 (MySQL) is allowed for incoming traffic from anywhere (from_port = 3306, to_port = 3306, protocol = "tcp", cidr_blocks = ["0.0.0.0/0"]). This allows MySQL traffic from anywhere, which is generally not recommended for production. You should restrict this to trusted sources.

  • Port 22 (SSH) is allowed for incoming traffic from a specific IP range within the VPC (from_port = 22, to_port = 22, protocol = "tcp", security_groups = [aws_security_group.two-tier-ec2-sg.id], cidr_blocks = ["10.0.0.0/16"]). This restricts SSH access to a specific range of IP addresses within the VPC, providing better security.

Egress Rules (Outbound Traffic):

  • The security group allows all outgoing traffic to any destination (from_port = "0", to_port = "0", protocol = "-1").

Please note that while this configuration provides a basic setup for security groups, you should adjust the rules according to your application's security requirements and best practices. Exposing services like SSH and databases to the public internet without proper restrictions can pose security risks, so it's important to follow the principle of least privilege and restrict access as much as possible.

Step 4: ec2-resources.tf

# Public subnet EC2 instance 1
resource "aws_instance" "two-tier-web-server-1" {
  ami           = "ami-064eb0bee0c5402c5"
  instance_type = "t2.micro"
  security_groups = [aws_security_group.two-tier-ec2-sg.id]
  subnet_id     = aws_subnet.two-tier-pub-sub-1.id
  key_name      = "two-tier-key"

  tags = {
    Name = "two-tier-web-server-1"
  }

  user_data = <<-EOF
#!/bin/bash
sudo yum update -y
sudo amazon-linux-extras install nginx1 -y 
sudo systemctl enable nginx
sudo systemctl start nginx
EOF
}

# Public subnet EC2 instance 2
resource "aws_instance" "two-tier-web-server-2" {
  ami           = "ami-064eb0bee0c5402c5"
  instance_type = "t2.micro"
  security_groups = [aws_security_group.two-tier-ec2-sg.id]
  subnet_id     = aws_subnet.two-tier-pub-sub-2.id
  key_name      = "two-tier-key"

  tags = {
    Name = "two-tier-web-server-2"
  }

  user_data = <<-EOF
#!/bash/bin
sudo yum update -y
sudo amazon-linux-extras install nginx1 -y 
sudo systemctl enable nginx
sudo systemctl start nginx
EOF
}

# Allocate Elastic IPs (EIPs) for EC2 instances
resource "aws_eip" "two-tier-web-server-1-eip" {
  vpc = true

  instance   = aws_instance.two-tier-web-server-1.id
  depends_on = [aws_internet_gateway.two-tier-igw]
}

resource "aws_eip" "two-tier-web-server-2-eip" {
  vpc = true

  instance   = aws_instance.two-tier-web-server-2.id
  depends_on = [aws_internet_gateway.two-tier-igw]
}

1. Public Subnet EC2 Instance 1 (aws_instance.two-tier-web-server-1):

  • An EC2 instance named "two-tier-web-server-1" is defined.

  • It uses the specified Amazon Machine Image (AMI) ID (ami-064eb0bee0c5402c5) for the operating system.

  • The instance type is set to t2.micro, which is a low-cost, general-purpose instance type.

  • The instance is associated with the security group defined earlier (aws_security_group.two-tier-ec2-sg.id).

  • It is placed in the public subnet two-tier-pub-sub-1 using subnet_id.

  • A key pair named "two-tier-key" is used to allow SSH access.

  • Tags are added to identify the instance.

User Data Script:

  • A Bash script is provided in the user_data section. This script does the following:

    • Updates the system packages (sudo yum update -y).

    • Installs NGINX (sudo amazon-linux-extras install nginx1 -y).

    • Enables NGINX to start on boot (sudo systemctl enable nginx).

    • Starts NGINX (sudo systemctl start nginx).

2. Public Subnet EC2 Instance 2 (aws_instance.two-tier-web-server-2):

  • Similar to the first instance, a second EC2 instance named "two-tier-web-server-2" is defined with the same configurations but placed in the public subnet two-tier-pub-sub-2.

3. Elastic IPs (EIPs) for EC2 Instances:

  • Elastic IPs (aws_eip) are allocated for each EC2 instance.

  • EIP 1 (aws_eip.two-tier-web-server-1-eip) is associated with two-tier-web-server-1.id.

  • EIP 2 (aws_eip.two-tier-web-server-2-eip) is associated with two-tier-web-server-2.id.

  • These EIPs provide static public IP addresses to the EC2 instances, making them accessible from the internet.

Each step represents the creation and configuration of resources required for running EC2 instances in public subnets. The user data scripts are used to initialize the instances, and the Elastic IPs ensure that the instances have static public IP addresses.

Remember to customize the configurations, such as the AMI ID, instance type, key name, and user data script, to match your application's specific requirements. Additionally, consider security best practices and follow the principle of least privilege when configuring security groups and access.

Step 5: db-resources.tf


# RDS MySQL database
resource "aws_db_instance" "two-tier-db-1" {
  allocated_storage           = 5
  storage_type                = "gp2"
  engine                      = "mysql"
  engine_version              = "5.7"
  instance_class              = "db.t2.micro"
  db_subnet_group_name        = "two-tier-db-sub"  # Reference to the DB subnet group defined elsewhere
  vpc_security_group_ids      = [aws_security_group.two-tier-db-sg.id]  # Reference to the DB security group
  parameter_group_name        = "default.mysql5.7"
  db_name                     = "two_tier_db1"  # Name of the initial database
  username                    = "admin"  # Master username
  password                    = "password"  # Master password
  allow_major_version_upgrade = true  # Allow major version upgrades
  auto_minor_version_upgrade  = true  # Enable automatic minor version upgrades
  backup_retention_period     = 35  # Retention period for automated backups in days
  backup_window               = "22:00-23:00"  # Preferred backup window (UTC)
  maintenance_window          = "Sat:00:00-Sat:03:00"  # Maintenance window for RDS updates
  multi_az                    = false  # Disable Multi-AZ deployment
  skip_final_snapshot         = true  # Skip final DB snapshot during deletion
}
  1. An RDS MySQL database instance (aws_db_instance) is defined with various configurations:

    • allocated_storage: Specifies the allocated storage for the database in GB.

    • storage_type: Sets the storage type (e.g., gp2, standard).

    • engine and engine_version: Define the database engine and version (MySQL 5.7 in this case).

    • instance_class: Specifies the instance class for the RDS instance.

    • db_subnet_group_name: References the name of the DB subnet group where the RDS instance will be placed.

    • vpc_security_group_ids: References the security group(s) for database access.

    • parameter_group_name: Specifies the parameter group for database settings.

    • db_name: Sets the name of the initial database.

    • username and password: Configure the master username and password for the RDS instance.

    • allow_major_version_upgrade and auto_minor_version_upgrade: Enable upgrades for the database engine.

    • backup_retention_period: Sets the retention period for automated backups in days.

    • backup_window: Defines the preferred backup window (in UTC).

    • maintenance_window: Specifies the maintenance window for RDS updates.

    • multi_az: Disables Multi-AZ deployment for this example.

    • skip_final_snapshot: Skips the final DB snapshot during deletion.

Make sure to customize the configuration values such as allocated_storage, instance_class, username, password, backup_retention_period, and other settings according to your application's requirements and security policies. Additionally, ensure that you've properly defined the referenced DB subnet group and security group resources elsewhere in your Terraform configuration.

Let's deploy!

Make sure you have already inserted your AWS credentials and are operating from the root directory before starting these Terraform commands.

  1. terraform init

The terraform init the command is used to initialize a new or existing Terraform configuration. This command downloads the required provider plugins and sets up the backend for storing state.

terraform init

2. terraform plan

The terraform plan the command is used to create an execution plan for the Terraform configuration. This command shows what resources Terraform will create, modify, or delete when applied.

terraform plan

3. terraform apply

The terraform apply the command is used to apply the Terraform configuration and create or modify resources in the target environment.

terraform apply

Deploying a 2-tier application on AWS using Terraform is a comprehensive project that involves provisioning the necessary infrastructure and deploying application components. In a 2-tier architecture, you typically have a web tier and a database tier, and Terraform can automate the provisioning of these resources. Below is a description of how to deploy a 2-tier application on AWS using Terraform:

Project Overview:

The goal of this project is to deploy a 2-tier application on AWS infrastructure using Terraform. This application will consist of the following components:

  1. Web Tier:

    • EC2 Instances: These will host the web application. You can configure them with a web server like Apache or Nginx.

    • Auto Scaling Group: To ensure high availability and scalability, you can use an Auto Scaling Group to manage multiple EC2 instances.

    • Elastic Load Balancer (ELB): The ELB will distribute incoming traffic across the EC2 instances in the Auto Scaling Group.

    • Security Groups: To control inbound and outbound traffic to the EC2 instances.

  2. Database Tier:

    • RDS (Relational Database Service): This managed database service will host your application's database. You can choose a database engine like MySQL, PostgreSQL, etc.

    • DB Subnet Group: To define which subnets the RDS instance should be placed in.

    • Security Groups: To control inbound and outbound traffic to the RDS instance.

Steps to Deploy:

  1. AWS Setup:

    • Create an AWS account if you don't already have one.

    • Set up AWS CLI and configure your access credentials.

  2. Terraform Configuration:

    • Set up a new Terraform project directory.

    • Create Terraform configuration files (e.g., main.tf, variables.tf, outputs.tf, and provider configuration in provider.tf) to define your AWS infrastructure and resources.

  3. Network Resources:

    • Create a Virtual Private Cloud (VPC) to isolate your resources.

    • Define subnets (public and private) within the VPC.

    • Set up an Internet Gateway to allow internet access.

    • Create route tables to route traffic between subnets and the internet.

  4. Security Resources:

    • Configure security groups for your EC2 instances, ELB, and RDS to control traffic.
  5. EC2 Instances:

    • Define EC2 instances for your web tier using an Auto Scaling Group.

    • Configure user data or cloud-init scripts to set up your web servers (e.g., installing Apache, Nginx, or application code).

  6. Elastic Load Balancer (ELB):

    • Create an ELB to distribute traffic to EC2 instances.

    • Configure listeners and health checks.

  7. RDS Database:

    • Provision an RDS instance with your chosen database engine.

    • Set up a DB subnet group for the RDS instance.

    • Configure database parameters, username, and password.

  8. Terraform Init and Apply:

    • Run terraform init to initialize your Terraform environment.

    • Run terraform apply to create your AWS resources.

  9. Testing and Scaling:

    • Test your web application to ensure it's working as expected.

    • Monitor your application's performance and consider scaling up or down based on demand.

  10. Cleanup:

    • When you're done with your project, run terraform destroy to tear down the AWS resources to avoid ongoing charges.

Conclusion:

Deploying a 2-tier application on AWS using Terraform allows you to automate the provisioning and management of your infrastructure, making it more scalable, reliable, and maintainable. This project can serve as a foundation for more complex applications and can be customized based on your specific requirements.

0
Subscribe to my newsletter

Read articles from khalid kifayat directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

khalid kifayat
khalid kifayat

Skillful Tech Engineer having working experience with creating & managing AI-NLP-Chatbots and Cloud computing Infrastructure deployment, testing, monitoring, scripting, automation, Version control, documentation & system's support.