End-to-End Web Application Deployment Using Terraform

End-to-End Web Application Deployment Using Terraform


Pre-deployment Procedures

1. Install Terraform

  • Ensure Terraform is installed on your local machine. You can download it from the Terraform official site.

sudo apt-get update && sudo apt-get install -y gnupg software-properties-common
wget -O- https://apt.releases.hashicorp.com/gpg | \
gpg --dearmor | \
sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg > /dev/null
gpg --no-default-keyring \
--keyring /usr/share/keyrings/hashicorp-archive-keyring.gpg \
--fingerprint

echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update
sudo apt-get install terraform -y        

2. Configure AWS Credentials

  • Terraform needs access to your AWS account to deploy resources. You can configure credentials by using AWS CLI or by specifying them directly in Terraform:

curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
aws --version        

3. Write Terraform Configuration Files

  • Create a new directory for your project and write configuration files (*.tf). You will define resources like VPCs, EC2 instances, RDS, etc.

4. Initialize Terraform

  • Run terraform init in the directory containing your Terraform files. This command initializes the working directory and downloads necessary provider plugins (like AWS).

terraform init        

5. Format and Validate Configuration

  • Before applying your configuration, you should format the code and check for any syntax errors. Format the configuration:

terraform validate        

6. Generate and Review an Execution Plan

  • Run terraform plan to generate a preview of what Terraform will do when you apply the changes. It shows you the resources that will be created, modified, or destroyed.
  • Review the output carefully to ensure it matches your expectations.

terraform plan        

7. Apply the Configuration

  • Use terraform apply to deploy the infrastructure. Terraform will prompt for confirmation before making any changes.
  • Once confirmed with "yes", terraform will proceed to create the resources and display their status in real time.

terraform apply        


?Architecture Components:

1. VPC (Virtual Private Cloud):

? ? - Create a VPC to logically isolate the application environment.

?? - Cidr block This block determines the IP range available for subnets and other resources in the VPC

?? - Subnets:

???? - Public Subnets for web servers and load balancers (allowing internet access).

???? - Private Subnets for RDS (isolating database traffic).

?? - Internet Gateway for outbound traffic from web servers.

?? - NAT Gateway for private subnet instances (like RDS) to access the internet for updates.

vpc.tf

?
data "aws_availability_zones" "available" {
  state = "available"
}

# Main  vpc
resource "aws_vpc" "levelup_vpc" {
  cidr_block       = var.LEVELUP_VPC_CIDR_BLOC
  enable_dns_support = "true"
  enable_dns_hostnames = "true"
  tags = {
    Name = "${var.ENVIRONMENT}-vpc"
  }
}

# Public subnets

#public Subnet 1
resource "aws_subnet" "levelup_vpc_public_subnet_1" {
  vpc_id     = aws_vpc.levelup_vpc.id
  cidr_block = var.LEVELUP_VPC_PUBLIC_SUBNET1_CIDR_BLOCK
  availability_zone = data.aws_availability_zones.available.names[0]
  map_public_ip_on_launch = "true"
  tags = {
    Name = "${var.ENVIRONMENT}-levelup-vpc-public-subnet-1"
  }
}
#public Subnet 2
resource "aws_subnet" "levelup_vpc_public_subnet_2" {
  vpc_id     = aws_vpc.levelup_vpc.id
  cidr_block = var.LEVELUP_VPC_PUBLIC_SUBNET2_CIDR_BLOCK
  availability_zone = data.aws_availability_zones.available.names[1]
  map_public_ip_on_launch = "true"
  tags = {
    Name = "${var.ENVIRONMENT}-levelup-vpc-public-subnet-2"
  }
}

# private subnet 1
resource "aws_subnet" "levelup_vpc_private_subnet_1" {
  vpc_id     = aws_vpc.levelup_vpc.id
  cidr_block = var.LEVELUP_VPC_PRIVATE_SUBNET1_CIDR_BLOCK
  availability_zone = data.aws_availability_zones.available.names[0]
  tags = {
    Name = "${var.ENVIRONMENT}-levelup-vpc-private-subnet-1"
  }
}
# private subnet 2
resource "aws_subnet" "levelup_vpc_private_subnet_2" {
  vpc_id     = aws_vpc.levelup_vpc.id
  cidr_block = var.LEVELUP_VPC_PRIVATE_SUBNET2_CIDR_BLOCK
  availability_zone = data.aws_availability_zones.available.names[1]
  tags = {
    Name = "${var.ENVIRONMENT}-levelup-vpc-private-subnet-2"
  }
}

# internet gateway
resource "aws_internet_gateway" "levelup_igw" {
  vpc_id = aws_vpc.levelup_vpc.id

  tags = {
    Name = "${var.ENVIRONMENT}-levelup-vpc-internet-gateway"
  }
}

# ELastic IP for NAT Gateway
resource "aws_eip" "levelup_nat_eip" {
  domain      = "vpc"  # This should be a string value
  depends_on = [aws_internet_gateway.levelup_igw]
}

# NAT gateway for private ip address
resource "aws_nat_gateway" "levelup_ngw" {
  allocation_id = aws_eip.levelup_nat_eip.id
  subnet_id     = aws_subnet.levelup_vpc_public_subnet_1.id
  depends_on = [aws_internet_gateway.levelup_igw]
  tags = {
    Name = "${var.ENVIRONMENT}-levelup-vpc-NAT-gateway"
  }
}

# Route Table for public Architecture
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.levelup_vpc.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.levelup_igw.id
  }

  tags = {
    Name = "${var.ENVIRONMENT}-levelup-public-route-table"
  }
}

# Route table for Private subnets
resource "aws_route_table" "private" {
  vpc_id = aws_vpc.levelup_vpc.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_nat_gateway.levelup_ngw.id
  }

  tags = {
    Name = "${var.ENVIRONMENT}-levelup-private-route-table"
  }
}

# Route Table association with public subnets
resource "aws_route_table_association" "to_public_subnet1" {
  subnet_id      = aws_subnet.levelup_vpc_public_subnet_1.id
  route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "to_public_subnet2" {
  subnet_id      = aws_subnet.levelup_vpc_public_subnet_2.id
  route_table_id = aws_route_table.public.id
}

# Route table association with private subnets
resource "aws_route_table_association" "to_private_subnet1" {
  subnet_id      = aws_subnet.levelup_vpc_private_subnet_1.id
  route_table_id = aws_route_table.private.id
}
resource "aws_route_table_association" "to_private_subnet2" {
  subnet_id      = aws_subnet.levelup_vpc_private_subnet_2.id
  route_table_id = aws_route_table.private.id
}

provider "aws" {
  region     = var.AWS_REGION
}

#Output Specific to Custom VPC
output "my_vpc_id" {
  description = "VPC ID"
  value       = aws_vpc.levelup_vpc.id
}

output "private_subnet1_id" {
  description = "Subnet ID"
  value       = aws_subnet.levelup_vpc_private_subnet_1.id
}

output "private_subnet2_id" {
  description = "Subnet ID"
  value       = aws_subnet.levelup_vpc_private_subnet_2.id
}

output "public_subnet1_id" {
  description = "Subnet ID"
  value       = aws_subnet.levelup_vpc_public_subnet_1.id
}

output "public_subnet2_id" {
  description = "Subnet ID"
  value       = aws_subnet.levelup_vpc_private_subnet_2.id
}        

?variable.tf

variable "AWS_REGION" {
    type        = string
    default     = "us-east-2"
}

variable "LEVELUP_VPC_CIDR_BLOC" {
  description = "The CIDR block for the VPC"
  type        = string
  default     = "10.0.0.0/16"
}

variable "LEVELUP_VPC_PUBLIC_SUBNET1_CIDR_BLOCK" {
  description = "The CIDR block for the VPC"
  type        = string
  default     = "10.0.101.0/24"
}

variable "LEVELUP_VPC_PUBLIC_SUBNET2_CIDR_BLOCK" {
  description = "The CIDR block for the VPC"
  type        = string
  default     = "10.0.102.0/24"
}

variable "LEVELUP_VPC_PRIVATE_SUBNET1_CIDR_BLOCK" {
  description = "The CIDR block for the VPC"
  type        = string
  default     = "10.0.1.0/24"
}

variable "LEVELUP_VPC_PRIVATE_SUBNET2_CIDR_BLOCK" {
  description = "The CIDR block for the VPC"
  type        = string
  default     = "10.0.2.0/24"
}

variable "ENVIRONMENT" {
  description = "AWS VPC Environment Name"
  type        = string
  default     = "Development"
}

        






2. RDS (Relational Database Service):

?? - A primary RDS instance with Multi-AZ replication for high availability and disaster recovery.

?? - Place the RDS in private subnets to enhance security.

?? - Create security groups allowing access only from web servers and specific port access (e.g., MySQL on port 3306).

rds.tf


#Call VPC Module First to get the Subnet IDs
module "levelup-vpc" {
    source      = "../vpc"

    ENVIRONMENT = var.ENVIRONMENT
    AWS_REGION  = var.AWS_REGION
}

#Define Subnet Group for RDS Service
resource "aws_db_subnet_group" "levelup-rds-subnet-group" {

    name          = "${var.ENVIRONMENT}-levelup-db-snet"
    description   = "Allowed subnets for DB cluster instances"
    subnet_ids    = [
      "${module.levelup-vpc.private_subnet1_id}",
      "${module.levelup-vpc.private_subnet2_id}",
    ]
    tags = {
        Name         = "${var.ENVIRONMENT}_levelup_db_subnet"
    }
}

#Define Security Groups for RDS Instances
resource "aws_security_group" "levelup-rds-sg" {

  name = "${var.ENVIRONMENT}-levelup-rds-sg"
  description = "Created by LevelUp"
  vpc_id = module.levelup-vpc.my_vpc_id
 
  ingress {
    from_port = 3306
    to_port = 3306
    protocol = "tcp"
    cidr_blocks = ["${var.RDS_CIDR}"]

  }

  egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

    tags = {
    Name = "${var.ENVIRONMENT}-levelup-rds-sg"
   }
}

resource "aws_db_instance" "levelup-rds" {
  identifier = "${var.ENVIRONMENT}-levelup-rds"
  allocated_storage = var.LEVELUP_RDS_ALLOCATED_STORAGE
  storage_type = "gp2"
  engine = var.LEVELUP_RDS_ENGINE
  engine_version = var.LEVELUP_RDS_ENGINE_VERSION
  instance_class = var.DB_INSTANCE_CLASS
  backup_retention_period = var.BACKUP_RETENTION_PERIOD
  publicly_accessible = var.PUBLICLY_ACCESSIBLE
  username = var.LEVELUP_RDS_USERNAME
  password = var.LEVELUP_RDS_PASSWORD
  vpc_security_group_ids = [aws_security_group.levelup-rds-sg.id]
  db_subnet_group_name = aws_db_subnet_group.levelup-rds-subnet-group.name
  multi_az = "false"
}

output "rds_prod_endpoint" {
  value = aws_db_instance.levelup-rds.endpoint
}        

variable.tf

variable "AWS_REGION" {
    type        = string
    default     = "us-east-2"
}

variable "BACKUP_RETENTION_PERIOD" {
    default = "7"
}

variable "PUBLICLY_ACCESSIBLE" {
    default = "true"
}

variable "LEVELUP_RDS_USERNAME" {
    default = "testdb"
}

variable "LEVELUP_RDS_PASSWORD" {
    default = "testdb12345"
}

variable "LEVELUP_RDS_ALLOCATED_STORAGE" {
    type = string
    default = "20"
}

variable "LEVELUP_RDS_ENGINE" {
    type = string
    default = "mysql"
}

variable "LEVELUP_RDS_ENGINE_VERSION" {
    type = string
    default = "8.0.35" 

}

variable "DB_INSTANCE_CLASS" {
    type = string
    default = "db.t3.micro"
}

variable "RDS_CIDR" {
  description = "The CIDR block for the VPC"
  type        = string
  default     = "0.0.0.0/0"
}

variable "ENVIRONMENT" {
  description = "AWS VPC Environment Name"
  type        = string
  default     = "Development"
}

variable "vpc_private_subnet1" {
  description = "AWS VPC Environment Name"
  type        = string
  default     = ""
}

variable "vpc_private_subnet2" {
  description = "AWS VPC Environment Name"
  type        = string
  default     = ""
}

variable "vpc_id" {
  description = "AWS VPC Environment Name"
  type        = string
  default     = ""
}

        


3. Web Servers (EC2 with NGINX):

?? - EC2 instances running NGINX in an Auto Scaling Group to handle traffic spikes.

?? - Instances should be placed in public subnets, with access managed by security groups to allow HTTP/HTTPS traffic.

?? - Use User Data to install and configure NGINX on EC2 at launch.

instance.tf

module "levelup-vpc" {
    source      = "../module/vpc"

    ENVIRONMENT = var.ENVIRONMENT
    AWS_REGION  = var.AWS_REGION
}

module "levelup-rds" {
    source      = "../module/rds"

    ENVIRONMENT = var.ENVIRONMENT
    AWS_REGION  = var.AWS_REGION
    vpc_private_subnet1 = var.vpc_private_subnet1
    vpc_private_subnet2 = var.vpc_private_subnet2
    vpc_id = var.vpc_id
}

resource "aws_security_group" "levelup_webservers"{
  tags = {
    Name = "${var.ENVIRONMENT}-levelup-webservers"
  }
  
  name          = "${var.ENVIRONMENT}-levelup-webservers"
  description   = "Created by Levelup"
  vpc_id        = var.vpc_id

  ingress {
    from_port = 22
    to_port = 22
    protocol = "tcp"
    cidr_blocks = ["${var.SSH_CIDR_WEB_SERVER}"]

  }

  ingress {
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]

  }
  
  ingress {
    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"]
  }
}

#Resource key pair
resource "aws_key_pair" "level_key" {
  key_name      = "level_key"
  public_key    = file(var.public_key_path)
}

resource "aws_launch_configuration" "launch_config_webserver" {
  name   = "launch_config_webserver"
  image_id      = lookup(var.AMIS, var.AWS_REGION)
  instance_type = var.INSTANCE_TYPE
  user_data = "#!/bin/bash\napt-get update\napt-get -y install net-tools nginx\nMYIP=`ifconfig | grep -E '(inet 10)|(addr:10)' | awk '{ print $2 }' | cut -d ':' -f2`\necho 'Hello Team\nThis is my IP: '$MYIP > /var/www/html/index.html"
  security_groups = [aws_security_group.levelup_webservers.id]
  key_name = aws_key_pair.level_key.key_name
  
  root_block_device {
    volume_type = "gp2"
    volume_size = "20"
  }
}

resource "aws_autoscaling_group" "levelup_webserver" {
  name                      = "levelup_WebServers"
  max_size                  = 2
  min_size                  = 1
  health_check_grace_period = 30
  health_check_type         = "EC2"
  desired_capacity          = 1
  force_delete              = true
  launch_configuration      = aws_launch_configuration.launch_config_webserver.name
  vpc_zone_identifier       = ["${var.vpc_public_subnet1}", "${var.vpc_public_subnet2}"]
  target_group_arns         = [aws_lb_target_group.load-balancer-target-group.arn]
}

#Application load balancer for app server
resource "aws_lb" "levelup-load-balancer" {
  name               = "${var.ENVIRONMENT}-levelup-lb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.levelup_webservers_alb.id]
  subnets            = ["${var.vpc_public_subnet1}", "${var.vpc_public_subnet2}"]

}

# Add Target Group
resource "aws_lb_target_group" "load-balancer-target-group" {
  name     = "load-balancer-target-group"
  port     = 80
  protocol = "HTTP"
  vpc_id   = var.vpc_id
}

# Adding HTTP listener
resource "aws_lb_listener" "webserver_listner" {
  load_balancer_arn = aws_lb.levelup-load-balancer.arn
  port              = "80"
  protocol          = "HTTP"

  default_action {
    target_group_arn = aws_lb_target_group.load-balancer-target-group.arn
    type             = "forward"
  }
}

output "load_balancer_output" {
  value = aws_lb.levelup-load-balancer.dns_name
}
        

variable.tf

variable "SSH_CIDR_WEB_SERVER" {
    type = string
    default = "0.0.0.0/0"
}

variable "INSTANCE_TYPE" {
  default = "t2.micro"
}

variable "AMIS" {
    type = map
    default = {
        us-east-1 = "ami-0f40c8f97004632f9"
        us-east-2 = "ami-05692172625678b4e"
        us-west-2 = "ami-02c8896b265d8c480"
        eu-west-1 = "ami-0cdd3aca00188622e"
    }
}

variable "AWS_REGION" {
    type        = string
    default     = "us-east-2"
}

variable "ENVIRONMENT" {
  description = "AWS VPC Environment Name"
  type        = string
  default     = "Development"
}

variable "public_key_path" {
  description = "Public key path"
  default = "~/.ssh/level_key.pub"
}

variable "vpc_private_subnet1" {
  description = "AWS VPC Environment Name"
  type        = string
  default     = ""
}

variable "vpc_private_subnet2" {
  description = "AWS VPC Environment Name"
  type        = string
  default     = ""
}

variable "vpc_id" {
  description = "AWS VPC Environment Name"
  type        = string
  default     = ""
}

  
variable "vpc_public_subnet1" {
  description = "AWS VPC Environment Name"
  type        = string
  default     = ""
}

variable "vpc_public_subnet2" {
  description = "AWS VPC Environment Name"
  type        = string
  default     = ""
}        



4. Load Balancers:

?? - An Application Load Balancer (ALB) distributes traffic across web servers, ensuring high availability.

?? - The ALB should be in public subnets with proper routing to EC2 instances within the Auto Scaling Group.

alb.tf

resource "aws_security_group" "levelup_webservers_alb" {
  tags = {
    Name = "${var.ENVIRONMENT}-levelup-webservers-ALB"
  }
  name = "${var.ENVIRONMENT}-levelup-webservers-ALB"
  description = "Created by levelup"
  vpc_id      = var.vpc_id 

  ingress {
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]

  }

  ingress {
    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"]
  }
}
        


5. Security Groups:

?? - Each resource (EC2, RDS, ALB) requires a dedicated Security Group.

?? - Web server SG: Allows HTTP (80) and HTTPS (443) inbound.

?? - RDS SG: Only allows traffic from web server SG over the database port (e.g., 3306)


?

Clean Up the Deployment

  • Execute the following command

terraform destroy        

  • This will identify and destroy all resources defined in your Terraform state.
  • Terraform will prompt you to confirm the destruction. Type yes to proceed.

要查看或添加评论,请登录

Augustine Tetteh Ozor的更多文章

社区洞察

其他会员也浏览了