Terraform Hands-On: Deploying a Web Application on AWS with Terraform.

In this tutorial, we'll walk through the steps to deploy a web application on AWS using Terraform. We'll cover the setup of a Virtual Private Cloud (VPC), public subnets, an Internet Gateway, Application Load Balancer (ALB), Auto Scaling Group, IAM roles, and necessary security groups.

Prerequisites

  1. An AWS account

  2. Terraform installed on VM, Visual Studio Code or Code-space.

Project Structure

Here's the structure of our Terraform project:

Provider Configuration

First, we need to configure the AWS provider with the region we want to deploy our resources in provider.tf file.

# Configure the AWS provider
provider "aws" {
  region = var.aws_region
}

Variables Configuration

We'll define all our variables in a separate file to make the configuration reusable and clean. Create a variables.tf file and copy the contents.

variable "aws_region" {
  description = "AWS region where resources will be provisioned"
  default     = "us-east-1"
}

variable "vpc_cidr_block" {
  description = "CIDR block for the VPC"
  default     = "10.0.0.0/16"
}

variable "subnet_cidr_blocks" {
  description = "CIDR blocks for the subnets"
  default     = ["10.0.1.0/24", "10.0.2.0/24"]
}

variable "instance_type" {
  description = "EC2 instance type"
  default     = "t2.nano"
}

variable "ami_id" {
  description = "Amazon Machine Image (AMI) ID for the EC2 instances"
  default     = "ami-0b72821e2f351e396"  # Replace with your AMI ID
}

variable "alb_listener_port" {
  description = "Port on which ALB listens for incoming traffic"
  default     = 80
}

variable "desired_capacity" {
  description = "Desired number of instances in the Auto Scaling Group"
  default     = 2
}

Main Configuration

Now, let's define the main configuration in main.tf. This includes the creation of VPC, subnets, Internet Gateway, ALB, Auto Scaling Group, IAM roles, and security groups.

# Create a VPC
resource "aws_vpc" "main" {
  cidr_block = var.vpc_cidr_block

  tags = {
    Name = "MainVPC"
  }
}

# Create public subnets
resource "aws_subnet" "public" {
  count             = length(var.subnet_cidr_blocks)
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.subnet_cidr_blocks[count.index]
  availability_zone = element(data.aws_availability_zones.available.names, count.index % length(data.aws_availability_zones.available.names))  # Replace with your preferred availability zone

  tags = {
    Name = "PublicSubnet-${count.index + 1}"
  }
}

#Data Source for AWS AZ 
data "aws_availability_zones" "available" {}

# Create an Internet Gateway
resource "aws_internet_gateway" "gw" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "InternetGateway"
  }
}

# Attach the Internet Gateway to the VPC
/* resource "aws_internet_gateway_attachment" "gw_attach" {
  vpc_id       = aws_vpc.main.id
  internet_gateway_id = aws_internet_gateway.gw.id
}*/

# Create a Route Table for public subnets
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.gw.id
  }

  tags = {
    Name = "PublicRouteTable"
  }
}

# Associate public subnets with the public route table
resource "aws_route_table_association" "public" {
  count          = length(var.subnet_cidr_blocks)
  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}

# Create IAM role for EC2 instances
resource "aws_iam_role" "ec2_role" {
  name               = "ec2-role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect    = "Allow"
        Principal = {
          Service = "ec2.amazonaws.com"
        }
        Action    = "sts:AssumeRole"
      }
    ]
  })
}

# Create IAM Instance Profile 
resource "aws_iam_instance_profile" "test_profile" {
  name = "test_profile"
  role = aws_iam_role.ec2_role.name
}

# Attach AmazonEC2RoleforSSM managed policy to the IAM role
resource "aws_iam_role_policy_attachment" "ec2_ssm_policy_attachment" {
  role       = aws_iam_role.ec2_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM"
}

# Create a Launch Configuration for the Auto Scaling Group
resource "aws_launch_configuration" "example" {
  name_prefix                 = "example-config"
  image_id             = var.ami_id
  instance_type        = var.instance_type
  security_groups      = [aws_security_group.instance.id]
  iam_instance_profile = aws_iam_instance_profile.test_profile.id
  user_data            = <<-EOF
                          #!/bin/bash
                          echo "Hello, World!" > index.html
                          EOF

  lifecycle {
    create_before_destroy = true
  }
}

# Create an Application Load Balancer (ALB)
resource "aws_lb" "example" {
  name               = "example-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.alb.id]
  subnets            = aws_subnet.public[*].id

  tags = {
    Name = "ExampleALB"
  }
}

# Create a target group
resource "aws_lb_target_group" "example" {
  name     = "example-tg"
  port     = 80
  protocol = "HTTP"
  vpc_id   = aws_vpc.main.id

  health_check {
    path                = "/"
    protocol            = "HTTP"
    healthy_threshold   = 2
    unhealthy_threshold = 2
    timeout             = 3
    interval            = 30
  }
}

# Create a listener for the ALB
resource "aws_lb_listener" "example" {
  load_balancer_arn = aws_lb.example.arn
  port              = var.alb_listener_port
  protocol          = "HTTP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.example.arn
  }
}

# Create an Auto Scaling Group (ASG)
resource "aws_autoscaling_group" "example" {
  desired_capacity     = var.desired_capacity
  max_size             = 5
  min_size             = 1
  launch_configuration = aws_launch_configuration.example.name
  vpc_zone_identifier  = aws_subnet.public[*].id

  tag {
    key                 = "Name"
    value               = "ExampleASG"
    propagate_at_launch = true
  }
}

# Create a security group for EC2 instances
resource "aws_security_group" "instance" {
  name        = "example-instance-sg"
  description = "Security group for EC2 instances"

  vpc_id = aws_vpc.main.id

  ingress {
    from_port   = 80
    to_port     = 80
    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 = "ExampleInstanceSG"
  }
}

# Create a security group for ALB
resource "aws_security_group" "alb" {
  name        = "example-alb-sg"
  description = "Security group for ALB"

  vpc_id = aws_vpc.main.id

  ingress {
    from_port   = var.alb_listener_port
    to_port     = var.alb_listener_port
    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 = "ExampleALBSG"
  }
}

Executing the Code from Terraform Terminal

Terraform Initialization (terraform init)

The terraform init command initializes a Terraform working directory by downloading necessary providers and modules defined in your configuration files (main.tf, variables.tf, etc.).

terraform init

Terraform Plan (terraform plan)

The terraform plan command creates an execution plan detailing the actions Terraform will take to achieve the desired state as defined in your configuration files.

terraform plan

Terraform Apply (terraform apply)

The terraform apply command executes the actions proposed in the execution plan generated by terraform plan, provisioning and managing infrastructure according to your Terraform configuration.

 terraform apply

Terraform Destroy (terraform destroy)

The terraform destroy command deletes the resources made by the apply command.

 terraform destroy

Conclusion

These commands (terraform init, terraform plan, terraform apply) are fundamental to managing infrastructure with Terraform. They help you initialize your environment, plan changes before applying them, and apply those changes to your cloud infrastructure efficiently and safely.

By using these commands in sequence, you can effectively deploy and manage infrastructure as code using Terraform, ensuring consistency and reproducibility across your environments.

AWS Console Infrastructure:

AWS VPC Dashboard

This screenshot shows the VPCs created in AWS. The highlighted VPC, named "MainVPC," was provisioned using Terraform to host our web application infrastructure.

AWS Subnets

Here, you can see the public and private subnets created within our VPC. These subnets are strategically placed across multiple Availability Zones (AZs) to ensure high availability for our application components.

AWS Internet Gateway

The Internet Gateway screenshot confirms the attachment to our VPC, enabling internet access for resources like our Application Load Balancer (ALB) and EC2 instances.

AWS EC2 Instances

This screenshot displays the EC2 instances managed by our Auto Scaling Group. These instances automatically scale based on demand to maintain application performance and availability.

AWS Load Balancer

The Application Load Balancer (ALB) screenshot illustrates how traffic is distributed across EC2 instances within our VPC. It ensures even load distribution and high availability for our web application.

AWS Auto Scaling Group

Here, you can view the configuration of our Auto Scaling Group. It dynamically adjusts the number of EC2 instances based on traffic patterns, optimizing costs and performance.

Assume Role