Day-5-terraform

Understanding Terraform Provisioners: A Practical Guide for DevOps Engineers

TLDR: This blog post explores the concept of Terraform provisioners through a practical demonstration, detailing how DevOps engineers can automate the deployment of applications on AWS EC2 instances. It covers the setup of a Terraform project, the use of provisioners for file copying and command execution, and the integration of CI/CD practices to streamline development workflows.

In this blog post, we will dive deep into the concept of Terraform provisioners, a crucial tool for DevOps engineers. We will start with a practical implementation, followed by a theoretical explanation of how provisioners work and their significance in real-world scenarios. This approach will help you understand how to automate common tasks in DevOps using Terraform.

provider "aws" {
  region = "us-east-1"  # Replace with your desired AWS region.
}

variable "cidr" {
  default = "10.0.0.0/16"
}

resource "aws_key_pair" "example" {
  key_name   = "terraform-demo-abhi"  # Replace with your desired key name
  public_key = file("~/.ssh/id_rsa.pub")  # Replace with the path to your public key file
}

resource "aws_vpc" "myvpc" {
  cidr_block = var.cidr
}

resource "aws_subnet" "sub1" {
  vpc_id                  = aws_vpc.myvpc.id
  cidr_block              = "10.0.0.0/24"
  availability_zone       = "us-east-1a"
  map_public_ip_on_launch = true               
}

resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.myvpc.id
}

resource "aws_route_table" "RT" {
  vpc_id = aws_vpc.myvpc.id

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

resource "aws_route_table_association" "rta1" {
  subnet_id      = aws_subnet.sub1.id
  route_table_id = aws_route_table.RT.id
}

resource "aws_security_group" "webSg" {
  name   = "web"
  vpc_id = aws_vpc.myvpc.id

  ingress {
    description = "HTTP from VPC"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  ingress {
    description = "SSH"
    from_port   = 22
    to_port     = 22
    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 = "Web-sg"
  }
}

resource "aws_instance" "server" {
  ami                    = "ami-0261755bbcb8c4a84"
  instance_type          = "t2.micro"
  key_name      = aws_key_pair.example.key_name
  vpc_security_group_ids = [aws_security_group.webSg.id]
  subnet_id              = aws_subnet.sub1.id

  connection {
    type        = "ssh"
    user        = "ubuntu"  # Replace with the appropriate username for your EC2 instance
    private_key = file("~/.ssh/id_rsa")  # Replace with the path to your private key
    host        = self.public_ip
  }

  # File provisioner to copy a file from local to the remote EC2 instance
  provisioner "file" {
    source      = "app.py"  # Replace with the path to your local file
    destination = "/home/ubuntu/app.py"  # Replace with the path on the remote instance
  }

  provisioner "remote-exec" {
    inline = [
      "echo 'Hello from the remote instance'",
      "sudo apt update -y",  # Update package lists (for ubuntu)
      "sudo apt-get install -y python3-pip",  # Example package installation
      "cd /home/ubuntu",
      "sudo pip3 install flask",
      "sudo python3 app.py &",
    ]
  }
}
main.tf → code structure
provider → aws → [region]
variable → cidr → [default] (ip range)
resource → "aws_key_pair" "example" → [key_name , public_key ] ——> ssh-keygen -t rsa → to get public key
resource → "aws_vpc" "myvpc" → [cidr_block = var.cidr]
resource → "aws_subnet" "sub1" →[ vpc_id, cidr_block, availability_zone, map_public_ip_on_launch]
resource → "aws_internet_gateway" "igw" → [vpc_id]
resource → "aws_route_table" "RT" → [vpc_id, route [cidr_block,gateway_id] ]
resource → "aws_route_table_association" "rta1" → [subnet_id, route_*table_*id ]
resource → "aws_security_group" "webSg" → [name, vpc_id, ingress [description, from_port, to_port, protocol, cidr_blocks], ingress [description,from_port,to_port,protocol,cidr_blocks] , egress [from_port,to_port,protocol,cidr_blocks], tags [Name]]
resource → "aws_security_group" "webSg" → [ ami, instance_type, key_name, vpc_security_group_ids, subnet_id]
command
terraform init
terraform plan
terraform apply
ssh -i ~/.ssh/id_rsa ubuntu@<public-ip-add>
ls
pwd
python3
Sudo ps -ef
sudo python3 app.py
provisioner
provisioner → copy files + execute commands → during creation / destruction of any resource
3 type:
File → copy files from local to created resource
Remote-exec→ execute commands
Local-exec → print statement to console terminal

Recap of Previous Lessons

Before we begin, let’s quickly recap what we learned in the previous session:

  • Terraform State Management: We discussed the importance of the state file, how to integrate it with version control systems, and the challenges associated with it. We also covered the concept of remote backends, specifically using S3 buckets, and the locking mechanism with DynamoDB.

Today's Task Overview

Today, we will tackle a common task faced by DevOps engineers. Imagine a development team, XYZ, that has a Python Flask application named app.py. Whenever they modify this file, they require the DevOps team to create a Terraform project that:

  1. Sets up a Virtual Private Cloud (VPC).

  2. Creates a public subnet within that VPC.

  3. Deploys the app.py file onto an EC2 instance and exposes it to the internet.

This task, if done manually, could take a significant amount of time, especially with multiple developers and projects. By automating this process with Terraform, we can save countless hours.

Setting Up the Terraform Project

Step 1: Project Structure

We will create a Terraform project that includes:

  • A VPC

  • A public subnet

  • An EC2 instance

  • Security groups to allow traffic

Step 2: Writing the Terraform Configuration

  1. Provider Configuration: We start by defining the AWS provider and the region.

  2. VPC Creation: Define the CIDR block for the VPC.

  3. Subnet Creation: Create a public subnet and associate it with the VPC.

  4. Route Table and Internet Gateway: Set up a route table and associate it with the internet gateway to ensure the subnet is public.

  5. EC2 Instance: Configure the EC2 instance, including the AMI, instance type, and security group settings.

Step 3: Deploying the Application with Provisioners

Instead of using user data scripts, we will utilize Terraform provisioners to deploy the application. Provisioners allow us to execute scripts or copy files to the created resources during the provisioning process.

  1. File Provisioner: This will copy the app.py file to the EC2 instance.

  2. Remote Exec Provisioner: This will execute commands on the EC2 instance to install necessary packages and run the application.

Practical Demonstration

Step 1: Initialize Terraform

Before running our Terraform scripts, we need to initialize the project:

terraform init

Step 2: Plan and Apply the Configuration

Next, we will create a plan to see what resources will be created:

tf plan

If everything looks good, we will apply the configuration:

tf apply

Step 3: Verify the Deployment

Once the resources are created, we can log into the EC2 instance to verify that the application is running. We can check the status of the application by accessing the public IP address assigned to the EC2 instance.

Understanding Provisioners

What are Provisioners?

Provisioners in Terraform are used to execute scripts or copy files to a resource at creation or destruction time. They help automate tasks that Terraform alone cannot handle, such as installing software or configuring applications.

Types of Provisioners

  1. File Provisioner: Used to copy files to the resource.

  2. Remote Exec Provisioner: Executes commands on the resource after it has been created.

  3. Local Exec Provisioner: Executes commands on the machine running Terraform, useful for logging or notifications.

Use Cases for Provisioners

Provisioners are particularly useful when you need to:

  • Install software on an instance after it has been created.

  • Copy configuration files or application code to an instance.

  • Execute scripts that configure the environment.

Conclusion

In this blog post, we explored the practical implementation of Terraform provisioners through a common DevOps task. By automating the deployment of applications on AWS EC2 instances, we can significantly reduce the time and effort required for manual configurations. Understanding how to effectively use provisioners is essential for any DevOps engineer looking to streamline their workflows and enhance productivity.

Feel free to try out the project on your own and share your experiences or any challenges you encounter in the comments below. Happy Terraforming!