Deploy an EC2 instance with Jenkins installed using Terraform (2024)

Deploy an EC2 instance with Jenkins installed using Terraform (3)

Welcome Back, everyone! 👋🏽 In this project write-up, I will cover deploying an EC2 into a new VPC with Jenkins installed using Terraform. I will also demonstrate how to create a private S3 bucket for our Jenkins artifacts and allow access from our EC2 using an Instance Profile.

Terraform is a popular DevOps IaC tool that allows you to build, manage, and automate on-premise and cloud resources declaratively with a configuration file. It leverages a provider platform API to create and manage resources for the respective platform (e.g., AWS, Azure, GCP, Kubernetes, etc.). What I love ❤️most about Terraform is that we only have to worry about defining WHAT we want to deploy and not HOW it should be deployed. Terraform takes care of all of the underlying resources and dependencies for us!

On the other hand, Jenkins is an open-source automation server that can serve as a simple continuous integration tool or a continuous delivery hub for your software projects. It is primarily used to build and test the integration of new code in your project by executing a set of instructions found in the “Jenkinsfile”. I will not be covering Jenkins in-depth in this article. I will only bootstrap an EC2 with Jenkins software and confirm it has been installed successfully.

Now, without further ado, let’s begin!

  • Create a main.tf, providers.tf, variables.tf, and outputs.tf to manage your Terraform deployment.
  • Deploy 1 EC2 Instance (Amazon Linux 2) into a new VPC.
  • Bootstrap the EC2 instance with a script that will install and start Jenkins.
  • Create and assign a Security Group to the Jenkins Server that allows traffic on port 22 from your Public IP and allows traffic from port 8080.
  • Create an S3 bucket for your Jenkins Artifacts that is not open to the public.
  • Create an Instance Profile allowing S3 write access for the Jenkins Server and assign the role to your Jenkins Server EC2 instance.
  • AWS account with Administrator Access permissions
  • AWS CLI installed and configured with your programmatic access credentials
  • Terraform installed (version ≥ 1.3.0)

📝NOTE: I am using the WSL terminal in this demonstration, but you can follow along using any terminal supporting the abovementioned prerequisites. ☝🏽

In this step, we will create our Terraform configuration files: main.tf, providers.tf, variables.tf, and outputs.tf. The naming conventions of the tf files are only significant in letting us know the purpose of each configuration file. 💡

Within your terminal, create another directory and CD into it:

mkdir deploy-jenkins-ec2-with-s3-access
cd deploy-jenkins-ec2-with-s3-access

Now, using your favorite text editor, create the following four tf files in this directory and enter the code below for each file.

main.tf

/* This Terraform deployment creates the following resources:
VPC, Subnet, Internet Gateway, Default Route, IAM instance profile with S3 access,
Security Group, SSH Key, and EC2 with userdata script installing Jenkins */

# Create VPC Resources

resource "aws_vpc" "vpc" {
cidr_block = var.vpc_cidr

tags = {
Name = "${var.environment}-vpc"
}
}

resource "aws_subnet" "subnet" {
vpc_id = aws_vpc.vpc.id
cidr_block = var.subnet_cidr
map_public_ip_on_launch = true

tags = {
Name = "${var.environment}-subnet"
}
}

resource "aws_internet_gateway" "internet_gateway" {
vpc_id = aws_vpc.vpc.id

tags = {
Name = "${var.environment}-internet-gateway"
}
}

resource "aws_default_route_table" "default_route" {
default_route_table_id = aws_vpc.vpc.default_route_table_id

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

# Create S3 Bucket and IAM Policies

resource "aws_iam_role" "ec2_iam_role" {
name = "${var.environment}-ec2-iam-role"
assume_role_policy = var.ec2-trust-policy
}

resource "aws_iam_instance_profile" "ec2_instance_profile" {
name = "${var.environment}-ec2-instance-profile"
role = aws_iam_role.ec2_iam_role.id
}

resource "aws_iam_role_policy" "ec2_role_policy" {
name = "${var.environment}-ec2-role-policy"
role = aws_iam_role.ec2_iam_role.id
policy = var.ec2-s3-permissions
}

resource "aws_s3_bucket" "s3" {
bucket = var.bucket_name
force_destroy = true

tags = {
Name = "${var.environment}-s3-bucket"
}
}

# Obtain User's Local Public IP

data "external" "myipaddr" {
program = ["bash", "-c", "curl -s 'https://ipinfo.io/json'"]
}

# Create EC2 Security Group and Security Rules

resource "aws_security_group" "jenkins_security_group" {
name = "${var.environment}-jenkins-security-group"
description = "Apply to Jenkins EC2 instance"
vpc_id = aws_vpc.vpc.id

ingress {
description = "Allow SSH from MY Public IP"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["${data.external.myipaddr.result.ip}/32"]
}

ingress {
description = "Allow access to Jenkis from My IP"
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["${data.external.myipaddr.result.ip}/32"]
}

egress {
description = "Allow All Outbound"
protocol = -1
from_port = 0
to_port = 0
cidr_blocks = ["0.0.0.0/0"]
}

tags = {
Name = "${var.environment}-jenkins-security-group"
}
}

# Lookup Amazon Linux Image
data "aws_ami" "amazon_linux_2" {
most_recent = true

filter {
name = "name"
values = ["amzn2-ami-hvm*"]
}

filter {
name = "owner-alias"
values = ["amazon"]
}
}

# Create SSH Keys for EC2 Remote Access
resource "tls_private_key" "generated" {
algorithm = "RSA"
}

resource "local_file" "private_key_pem" {
content = tls_private_key.generated.private_key_pem
filename = "${var.ssh_key}.pem"
file_permission = "0400"
}

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

# Create EC2 Instance
resource "aws_instance" "jenkins_server" {
ami = data.aws_ami.amazon_linux_2.id
instance_type = var.instance_type
key_name = aws_key_pair.generated.key_name
subnet_id = aws_subnet.subnet.id
security_groups = [aws_security_group.jenkins_security_group.id]
iam_instance_profile = aws_iam_instance_profile.ec2_instance_profile.id
user_data = var.ec2_user_data
connection {
user = "ec2-user"
private_key = tls_private_key.generated.private_key_pem
host = self.public_ip
}

tags = {
Name = "${var.environment}-jenkins-server"
}
}

providers.tf

# Terraform Providers

terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~>4.55.0"
}
local = {
source = "hashicorp/local"
version = "~>2.3.0"
}
tls = {
source = "hashicorp/tls"
version = "~>4.0.0"
}
}

required_version = "~>1.3.0"
}

provider "aws" {
region = var.aws_region
}

variables.tf

# Terraform Variables

variable "environment" {
description = "Environment name for deployment"
type = string
default = "demo"
}

variable "aws_region" {
description = "AWS region resources are deployed to"
type = string
default = "us-east-1"
}

# VPC Variables

variable "vpc_cidr" {
description = "VPC cidr block"
type = string
default = "10.0.0.0/16"
}

variable "subnet_cidr" {
description = "Subnet cidr block"
type = string
default = "10.0.0.0/24"
}

# IAM Role Variables

variable "ec2-trust-policy" {
description = "sts assume role policy for EC2"
type = string
default = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sts:AssumeRole"
],
"Principal": {
"Service": [
"ec2.amazonaws.com"
]
}
}
]
}
EOF
}

variable "ec2-s3-permissions" {
description = "IAM permissions for EC2 to S3"
type = string
default = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "*"
}
]
}
EOF
}

# S3 Variables

variable "bucket_name" {
description = "S3 bucket name"
type = string
default = "terraform1demo1s3bucket2023"
}

# EC2 Variables

variable "ssh_key" {
description = "ssh key name"
type = string
default = "my-ssh-key"
}

variable "instance_type" {
description = "Jenkins EC2 instance type"
type = string
default = "t2.micro"
}

variable "ec2_user_data" {
description = "User data shell script for Jenkins EC2"
type = string
default = <<EOF
#!/bin/bash
# Install Jenkins and Java
sudo wget -O /etc/yum.repos.d/jenkins.repo \
https://pkg.jenkins.io/redhat-stable/jenkins.repo
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key
sudo yum upgrade -y
# Add required dependencies for the jenkins package
sudo amazon-linux-extras install -y java-openjdk11
sudo yum install -y jenkins
sudo systemctl daemon-reload

# Start Jenkins
sudo systemctl enable jenkins
sudo systemctl start jenkins

# Firewall Rules
if [[ $(firewall-cmd --state) = 'running' ]]; then
YOURPORT=8080
PERM="--permanent"
SERV="$PERM --service=jenkins"

firewall-cmd $PERM --new-service=jenkins
firewall-cmd $SERV --set-short="Jenkins ports"
firewall-cmd $SERV --set-description="Jenkins port exceptions"
firewall-cmd $SERV --add-port=$YOURPORT/tcp
firewall-cmd $PERM --add-service=jenkins
firewall-cmd --zone=public --add-service=http --permanent
firewall-cmd --reload
fi
EOF
}

⚠️ IF you plan on deploying resources to a region besides “us-east-1”, you can modify the region value in the “aws_region” variable block. ⚠️

Deploy an EC2 instance with Jenkins installed using Terraform (4)

⚠️ In the “bucket_name” variable block, you must change the default name value since S3 bucket names must be unique across AWS. ⚠️

Deploy an EC2 instance with Jenkins installed using Terraform (5)

outputs.tf

# Terraform Outputs

output "ec2_remote_access" {
description = "EC2 Remote Access"
value = "ssh -i ${local_file.private_key_pem.filename} ec2-user@${aws_instance.jenkins_server.public_ip}"
}

output "instance_public_ip" {
description = "Public IP address of the Jenkins EC2 instance"
value = "Jenkins Server Public IP: ${aws_instance.jenkins_server.public_ip}"
}

output "s3_bucket_uri" {
description = "S3 bucket URI"
value = "S3 Bucket URI: s3://${aws_s3_bucket.s3.id}"
}

output "s3_bucket_url" {
description = "S3 Bucket URL"
value = "S3 Bucket URL: https://${aws_s3_bucket.s3.bucket_domain_name}"
}

output "user_local_public_IP" {
description = "User's local public IP"
value = "User Local Public IP: ${data.external.myipaddr.result.ip}"
}

Once you have created the files above and added the Terraform configuration, you should have the following files in your directory.

ls
Deploy an EC2 instance with Jenkins installed using Terraform (6)

In this step, we will finally deploy our AWS resources using Terraform. Before deploying your infrastructure, we must run “terraform init” to initialize the working directory where the Terraform configuration files are located.

terraform init
Deploy an EC2 instance with Jenkins installed using Terraform (7)

Next, to stay in line with best practices, we will execute the “terraform plan” command to preview the changes Terraform plans to make on our behalf.

terraform plan
Deploy an EC2 instance with Jenkins installed using Terraform (8)
Deploy an EC2 instance with Jenkins installed using Terraform (9)

As we can see from our returned output, Terraform will create 13 resources in AWS.

Now, let’s deploy our resources:

terraform apply
Deploy an EC2 instance with Jenkins installed using Terraform (10)

You will be prompted to confirm that you want to perform these actions. Enter “yes”.

Deploy an EC2 instance with Jenkins installed using Terraform (11)

Once the command completes, you should see an output similar to below:

Deploy an EC2 instance with Jenkins installed using Terraform (12)

We will verify the successful state of our deployment in a few ways. First, let’s start with running “terraform show”. The “terraform show” command will output the current infrastructure state.

terraform show
Deploy an EC2 instance with Jenkins installed using Terraform (13)

This command returns the output from our “terraform.tfstate” file in a human-readable format. The “terraform.tfstate” file is used by Terraform to map real-world resources to your configuration, keep track of metadata, and improve performance for large infrastructures.

We can view a lot of information about our deployment from this command. Another more simple command is “terraform state list”.

terraform state list
Deploy an EC2 instance with Jenkins installed using Terraform (14)

“terraform state list” can list all the resources managed in our state file. We can take it a step further and use “terraform state show <resource_name>” to retrieve information for a specific resource.

terraform state show <resource_name>
Deploy an EC2 instance with Jenkins installed using Terraform (15)

Now, I know what you are probably thinking. How do we know Jenkins is installed? What about S3 write access from our EC2 instance? If those are your exact thoughts, don’t worry. We will verify S3 access and the Jenkins installation with the upcoming steps. 😊

terraform output
Deploy an EC2 instance with Jenkins installed using Terraform (16)

Attempt to curl the returned output for the bucket_domain_name. You should receive an access denied message confirming that Public Access to the bucket isn’t possible.

curl <s3_bucket_url>
Deploy an EC2 instance with Jenkins installed using Terraform (17)

Next, enter the value returned from the instance_public_ip in your browser with “:8080” appended to the end.

Deploy an EC2 instance with Jenkins installed using Terraform (18)

You should have successfully reached the Jenkins “Getting Started” page that is displayed on every new Jenkins installation. 👍🏽

If you haven’t noticed, this Terraform code creates an SSH key in your current working directory that we can use to SSH into your EC2 instance!

Deploy an EC2 instance with Jenkins installed using Terraform (19)

Now let’s use the output returned for “ec2_remote_access” and ssh into our EC2 instance.

ssh -i <ssh-key-pem-file> ec2-user@<instance_public_ip>
Deploy an EC2 instance with Jenkins installed using Terraform (20)

Create a test file.

touch testfile

Copy/Write the file to the S3 bucket URI.

aws s3 cp testfile <s3_bucket_uri>
Deploy an EC2 instance with Jenkins installed using Terraform (21)

You should have received a successful “upload” message and confirmed write access to the S3 bucket. 👍🏽

Don’t forget to enter the exit command to return to your main terminal.

Deploy an EC2 instance with Jenkins installed using Terraform (22)

Now it’s time to clean up our resources. This may be the only time you hear me say this, but I love cleaning up behind myself (in Terraform)! Let’s execute “terraform destroy” to tear down our infrastructure with one command.

terraform destroy
Deploy an EC2 instance with Jenkins installed using Terraform (23)

Enter “yes” to approve destroying all resources.

Deploy an EC2 instance with Jenkins installed using Terraform (24)

Once complete, all of your resources should be removed.

Deploy an EC2 instance with Jenkins installed using Terraform (25)

More content at PlainEnglish.io.

Sign up for our free weekly newsletter. Follow us on Twitter, LinkedIn, YouTube, and Discord.

Build awareness and adoption for your tech startup with Circuit.

Deploy an EC2 instance with Jenkins installed using Terraform (2024)

References

Top Articles
Latest Posts
Article information

Author: Sen. Emmett Berge

Last Updated:

Views: 5553

Rating: 5 / 5 (60 voted)

Reviews: 83% of readers found this page helpful

Author information

Name: Sen. Emmett Berge

Birthday: 1993-06-17

Address: 787 Elvis Divide, Port Brice, OH 24507-6802

Phone: +9779049645255

Job: Senior Healthcare Specialist

Hobby: Cycling, Model building, Kitesurfing, Origami, Lapidary, Dance, Basketball

Introduction: My name is Sen. Emmett Berge, I am a funny, vast, charming, courageous, enthusiastic, jolly, famous person who loves writing and wants to share my knowledge and understanding with you.