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
An AWS account
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