Install Jenkins with terraform and ansible on a Linode VPS

02, February 2022

In this guide Jenkins will be installed on a Linode VPS using Terraform and Ansible.

Terraform will be used to provision the Linode VPS and Ansible will be used to install Jenkins and configure a firewall. This guide won't explain how to configure Jenkins once it is installed.

Setup

This guide assumes the following things:

  • Ansible is installed locally
  • Jenkins is installed locally
  • You have a Linode account
  • You have a Linode access token
  • You have a SSH key

The first thing to do is to create the following folder structure.

ansible/
└───roles/
    └───jenkins/
        └───tasks/
    └───firewall/
        └───tasks/
    └───upgrade/
        └───tasks/
terraform/
└───modules/
    └───site/
└───templates/

Ignore files from Git

Create a .gitignore file in the root directory with the following

# IDE
.idea

# Ansible
ansible/hosts
ansible/.vault_pass.txt

# Terraform
terraform/.terraform.lock.hcl
terraform/.terraform
terraform/terraform.tfvars
terraform/terraform.tfstate
terraform/.terraform.tfstate.lock.info
terraform/terraform.tfstate.backup

Create a vault password

Create the file ansible/.vault_pass.txt with a random vault password. This will be used later in the Ansible steps.

SomeRandomString

Create a Linode VPS with Terraform

Create the file terraform/terraform.tfvars and add the following variables

token="<YOUR_LINODE_TOKEN>"
root_pass="<YOUR_LINODE_ROOT_PASSWORD"
ssh_key_pub="<PATH_TO_YOUR_SSH_KEY>"

Create Terraform inputs

Create the file terraform/inputs.tf and add the following inputs

variable "token" {
  type = string
  description = "Environment in which to deploy application"
  sensitive = true
}
variable "root_pass" {
  type = string
  description = "Site Root Password"
  sensitive = true
}

variable "ssh_key_pub" {
  type = string
  description = "SSH KEY"
  sensitive = true
}

Create Terraform outputs

Create the file terraform/outputs.tf with the following output variables

output "webserver_id" {
  value = module.site.jenkins-site-id
}
output "webserver_ip" {
  value = module.site.jenkins-site-public-ip
}
output "webserver_private_ip" {
  value = module.site.jenkins-site-private-ip
}

Create a template for the Ansible hosts

Create a file in terraform/templats/hosts.tpl with the following template

all:
  children:
    webservers:
      hosts:
        webserver_1:
          ansible_host: ${webserver_ip}
          private_ip: ${webserver_private_ip}

Create Terraform main

Create the file terraform/main.tf and add the following Terraform code

This will do the following:

  • Install the Linode provider using version 1.18.0
  • Pass the Linode token to the Linode provider
  • Require the site module and provide it the root_pass and ssh_key_pub variables
  • Create a file in the ansible directory with jenkins private and public IPs
terraform {
  required_providers {
    linode = {
      source = "linode/linode"
      version = "1.18.0"
    }
  }
}

provider "linode" {
  token = var.token
}

module "site" {
  source = "./modules/site"
  root_pass = var.root_pass
  ssh_key_pub = var.ssh_key_pub
  label = "jenkins"
}

resource "local_file" "hosts" {
  depends_on = [
    module.site
  ]
  content = templatefile("./templates/hosts.tpl",
    {
      webserver_ip = module.site.jenkins-site-public-ip
      webserver_private_ip = module.site.jenkins-site-private-ip
    }
  )
  filename = "../ansible/hosts"
}

Create the inputs for the site module

Create the file terraform/modules/site/inputs.tf and add the following terraform code

variable "root_pass" {}

variable "ssh_key_pub" {}

variable "label" {}

Create the local variables for the site module

Create the file terraform/modules/site/locals.tf and add the following terraform code

locals {
  region = "eu-west"
  site_label = "jenkinsSite"
  site_type = "g6-nanode-1"
  image = "linode/ubuntu18.04"
}

Create the main file for the site module

Create the file terraform/modules/site/main.tf with the following terraform code.

This will do the following:

  • Use the linode provider
  • Create a linode_sshkey resource
  • Create a lindoe_instance resource
  • Upgrade the VPS and install Python
terraform {
  required_providers {
    linode = {
      source = "linode/linode"
      version = "1.18.0"
    }
  }
}

resource "linode_sshkey" "jenkins" {
  label = var.label
  ssh_key = chomp(file(var.ssh_key_pub))
}

resource "linode_instance" "jenkins" {
  label = var.label
  group = local.site_label
  image = local.image
  region = local.region
  type = local.site_type
  private_ip = true
  root_pass = var.root_pass
  authorized_keys = [
    linode_sshkey.jenkins.ssh_key
  ]

  provisioner "remote-exec" {
    inline = [
      "sudo apt-get update && sudo apt-get upgrade -y",
      "sudo apt-get -qq upgrade -y",
      "sudo apt-get -qq install python -y",
    ]
  }
}


Create the output file for the site module

Create the file terraform/modules/site/outputs.tf with the following terraform code.

This will do the following:

  • Output the VPS ID
  • Output the public IP address
  • Output the Private IP address
output "jenkins-site-id" {
  value = linode_instance.jenkins.id
}
output "jenkins-site-public-ip" {
  value = linode_instance.jenkins.ip_address
}
output "jenkins-site-private-ip" {
  value = linode_instance.jenkins.private_ip_address
}

Provision the Linode VPS

In a terminal run the following inside the terraform folder

$ terraform init

Then apply the terraform plan

$ terraform apply

Once the VPS is provisioned the output variables should be shown in the terminsal like so

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Outputs:

webserver_id = "<VPS_ID>"
webserver_ip = "<VPS_PUBLIC_IP>"
webserver_private_ip = "<VPS_PRIVATE_IP>"

There should also be a hosts file in the ansible directory with the populated IP addresses. It should look similar to the following

all:
  children:
    webservers:
      hosts:
        webserver_1:
          ansible_host: <VPS_PUBLIC_IP>
          private_ip: <VPS_PRIVATE_IP>

Install software with Ansible

Create the file ansible/ansible.cfg that will hold the Ansible configuration

[defaults]
inventory = hosts
host_key_checking = false
vault_password_file = .vault_pass.txt

Create an update Ansible role

Create the file ansible/roles/upgrade/tasks/main.yml with the following yaml code which will update the package cache on the Linode VPS

- name: Update and upgrade apt packages
  become: yes
  apt:
    upgrade: yes
    update_cache: yes
    cache_valid_time: 86400

Create a firewall Ansible role

Create the file ansible/roles/firewall/tasks/main.yml with the following yaml code which will use ufw to lock down the webserver.

This will do the following:

  • Allow SSH on port 22
  • Allow HTTP requests on port 80
  • Allow HTTPS requests on port 443
  • Set the default policy to deny
  • Enable firewall logging
- name: Allow SSH
  ufw:
    rule: allow
    port: "22"
    proto: tcp

- name: Allow Port 80
  ufw:
    rule: allow
    port: "80"
    proto: tcp

- name: Allow Port 443
  ufw:
    rule: allow
    port: "443"
    proto: tcp

- name: Set firewall default policy
  ufw:
    state: enabled
    policy: deny

- name: Set logging
  ufw:
    logging: on

Run the Ansible playbook

To run the playbook, move into the ansible directory and run the following command.

Command arguments

  • -i = host inventory file. This has automatically been created by Terraform
  • -u = The user that will run the commands
$ ansible-playbook -i ./hosts playbook.yaml -u root
Peter Fisher Contract Web Developer

Peter Fisher is a UK based PHP contractor with 20 years experience in web development He can be hired to work with PHP using Symfony or Laravel or Flask and Django with Python.