Automate Your EC2 Deployments with Terraform: A Step-by-Step Guide
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
.
- Inbound ports:
Custom Software Installations:
Instance 1: AWS CLI, Terraform, and Kubectl.
Instance 2: SonarQube.
Instance 3: Nexus.
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:
Terraform installed on your local machine.
AWS CLI Configured: Set up your AWS credentials with
aws configure
.An AWS Key Pair: Create one in the AWS Management Console and save the
.pem
file.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:
Initialize Terraform:
terraform init
Plan the Infrastructure:
terraform plan
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! ๐