Automate Your EC2 Deployments with Terraform: A Step-by-Step Guide

ยท

6 min read

When setting up a DevOps environment on AWS, it's often necessary to create multiple EC2 instances, each with different software requirements. In this blog, we will walk through how to create multiple EC2 instances using Terraform to set up a DevOps environment with various software packages like AWS CLI, Terraform, Kubectl, SonarQube, Nexus, Jenkins, Docker, Trivy, and more. Each instance will have its unique configuration, tailored to its role.

By the end of this blog, youโ€™ll not only know how to create your setup but also how to tear it down in a flash! โšก

Ready? Let's roll! ๐Ÿ˜Ž


Why Terraform?

Terraform is an Infrastructure as Code (IaC) tool that allows us to define and provision infrastructure using configuration files. By automating infrastructure management with Terraform, you can ensure consistency, scalability, and repeatability in your cloud setups.


๐ŸŽฏ Objective

Weโ€™re creating four EC2 instances with the following setup:

  • Ubuntu 24.02 LTS (AMI).

  • Instance Type: t2.medium.

  • Key Pair: DevOps.

  • Region: ap-south-1.

  • Security Group Rules:

    • Inbound ports: 22, 25, 80, 443, 465, 6443, 2000-11000.
  • Custom Software Installations:

    1. Instance 1: AWS CLI, Terraform, and Kubectl.

    2. Instance 2: SonarQube.

    3. Instance 3: Nexus.

    4. Instance 4: Java 17, Jenkins, Docker, Trivy, and Kubectl.

Finally, we'll show you how to tear it all down in a click! ๐Ÿ—‘๏ธ


Prerequisites

Before we begin, make sure you have the following:

  1. Terraform installed on your local machine.

  2. AWS CLI Configured: Set up your AWS credentials with aws configure.

  3. An AWS Key Pair: Create one in the AWS Management Console and save the .pem file.

  4. Ubuntu AMI: Find the latest Ubuntu AMI ID for your region (e.g., ap-south-1).


Step-by-Step Guide

1. Folder Structure

To keep things organized, create the following folder structure:

terraform-ec2/
โ”œโ”€โ”€ main.tf
โ”œโ”€โ”€ variables.tf
โ”œโ”€โ”€ outputs.tf
โ”œโ”€โ”€ ec2.tf
โ”œโ”€โ”€ security-group.tf
โ””โ”€โ”€ key-pair.tf

2. main.tf - Setting Up the Provider

First, define the provider in the main.tf file. This tells Terraform which cloud provider to interact with (in this case, AWS).

provider "aws" {
  region = var.aws_region
}

provider "tls" {}

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    tls = {
      source  = "hashicorp/tls"
      version = "~> 4.0"
    }
  }

  required_version = ">= 1.3.0"
}

3. variables.tf - Declaring Variables

Weโ€™ll define variables like the AWS region, AMI ID, instance types, and the security group configuration.

variable "aws_region" {
  default = "ap-south-1"
}

variable "ami_id" {
  default = "ami-00bb6a80f01f03502" # Replace with Ubuntu AMI ID for ap-south-1
}

variable "instance_type" {
  default = "t2.medium"
}

variable "key_name" {
  default = "DevOps"
}

variable "root_volume_size" {
  default = 15
  description = "Size of the root volume in GiB"
}

variable "root_volume_type" {
  default = "gp3"
  description = "Type of the root volume (e.g., gp2, gp3, io1)"
}

variable "security_group_name" {
  default = "devops-sg"
}

variable "inbound_ports" {
  default = [
    22,  # SSH
    25,  # SMTP
    80,  # HTTP
    443, # HTTPS
    2000, # Custom Port
    11000, # Custom Port
    6443, # Kubernetes API Server
    465,  # SMTP (SSL) - Email over SSL
    "2000-11000" # Range of Ports
  ]
}

variable "instance_names" {
  default = {
    Server      = "Install AWS CLI, Terraform, Kubectl"
    SonarQube   = "SonarQube"
    Nexus       = "Nexus"
    Jenkins     = "Java 17, Jenkins, Docker, Trivy, Kubectl"
  }
}

4. security-group.tf - Configuring Security Group

We need to create a security group that allows inbound traffic on specific ports like 22 for SSH, 80 for HTTP, and others for different applications.

resource "aws_security_group" "devops_sg" {
  name_prefix = var.security_group_name
  description = "Security group for DevOps setup"

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

  dynamic "ingress" {
    for_each = var.inbound_ports
    content {
      from_port   = ingress.value
      to_port     = ingress.value
      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. ec2.tf - Provisioning EC2 Instances

Now, letโ€™s define the EC2 instances using the aws_instance resource. Each instance will have a unique user_data to install the necessary software.

resource "aws_instance" "ec2" {
  count         = length(var.instance_names)
  ami           = var.ami_id
  instance_type = var.instance_type
  key_name      = aws_key_pair.devops_key.key_name   # Referencing the new key pair
  security_groups = [aws_security_group.devops_sg.name]
  tags = {
    Name = var.instance_names[count.index]
  }

  root_block_device {
    volume_size           = var.root_volume_size    # Size in GiB
    volume_type           = var.root_volume_type    # Type of volume (e.g., gp3)
    delete_on_termination = true                    # Automatically delete root volume on instance termination
  }

  user_data = <<-EOT
  #!/bin/bash
  case "${var.instance_names[count.index]}" in
    "Install AWS CLI, Terraform, Kubectl")
      sudo apt update -y
      sudo apt install -y awscli unzip
      curl -o terraform.zip https://releases.hashicorp.com/terraform/1.5.0/terraform_1.5.0_linux_amd64.zip
      unzip terraform.zip
      sudo mv terraform /usr/local/bin/
      curl -LO "https://dl.k8s.io/release/v1.27.0/bin/linux/amd64/kubectl"
      chmod +x kubectl
      sudo mv kubectl /usr/local/bin/
      ;;
    "SonarQube")
      sudo apt update -y
      sudo apt install -y openjdk-17-jdk wget
      wget https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-9.9.0.65499.zip
      sudo apt install -y unzip
      unzip sonarqube-9.9.0.65499.zip
      ;;
    "Nexus")
      sudo apt update -y
      sudo apt install -y openjdk-17-jdk wget
      wget https://download.sonatype.com/nexus/3/latest-unix.tar.gz
      tar -xvf latest-unix.tar.gz
      ;;
    "Java 17, Jenkins, Docker, Trivy, Kubectl")
      sudo apt update -y
      sudo apt install -y openjdk-17-jdk docker.io curl
      curl -fsSL https://get.docker.com | sh
      curl -fsSL https://trivy.io/install.sh | sh
      curl -LO "https://dl.k8s.io/release/v1.27.0/bin/linux/amd64/kubectl"
      chmod +x kubectl
      sudo mv kubectl /usr/local/bin/
      ;;
  esac
  EOT
}

6. outputs.tf - Output the EC2 Instance IPs

We can output the public IP addresses of the EC2 instances after they are created.

output "ec2_instance_ips" {
  value = {
    Server = aws_instance.Server.public_ip
    SonarQube = aws_instance.SonarQube.public_ip
    Nexus     = aws_instance.Nexus.public_ip
    Jenkins   = aws_instance.Jenkins.public_ip
  }
  description = "Public IPs of the EC2 instances"
}

output "key_pair_path" {
  value = "${path.module}/DevOps.pem"
  description = "Path to the DevOps PEM key file"
}

7. key-pair.tf - Key pair creation

This file will handle the creation and download of the PEM key.

resource "tls_private_key" "devops_key" {
  algorithm = "RSA"
  rsa_bits  = 2048
}

resource "local_file" "devops_key" {
  content  = tls_private_key.devops_key.private_key_pem
  filename = "${path.module}/DevOps.pem"
  provisioner "local-exec" {
    command = "chmod 400 ${path.module}/DevOps.pem"
  }
}

resource "aws_key_pair" "devops_key" {
  key_name   = var.key_name
  public_key = tls_private_key.devops_key.public_key_openssh
}

This will create the PEM key pair named DevOps and automatically download it.


Running the Terraform Configuration

Once you've set up the files, you can now run the following Terraform commands:

  1. Initialize Terraform:

     terraform init
    
  2. Plan the Infrastructure:

     terraform plan
    
  3. Apply the Configuration:

     terraform apply --auto-approve
    

Terraform will create the resources and provide the public IPs of the EC2 instances.


๐Ÿงน Tear Down in One Go!

To delete the entire setup, run:

terraform destroy

This will ensure all your instances and resources are cleaned up, leaving no trace. ๐Ÿšฎ


Conclusion

By using Terraform to provision EC2 instances, weโ€™ve created a scalable and repeatable process to set up a DevOps environment. Each EC2 instance has been configured for a specific purpose, and the necessary software packages have been installed using user_data scripts. This setup can be expanded or modified as needed for different use cases.

If you found this blog helpful, feel free to drop a comment below, share it, and share your thoughts! ๐ŸŒŸ

Happy provisioning! ๐Ÿš€


ย