Jenkins ala Terraform (with Docker)

The target of this entry is explain how to setup a local Jenkins server to test your pipelines during their development stage, I think that could be quicker to use a local Jenkins server when you’re developing your pipelines or even your Groovy common libraries. To this lab we’re going to use Terraform with its Docker provider.

Let’s define first the Jenkins container requirements:

  • 8080 port to access to Jenkins web interface
  • Local directory to be used as Jenkins Home directory
  • To attach build slave servers through JNLP (Java Web Start): map the port: -p 50000:50000
  • Environment variables:
    • –env JAVA_OPTS=”-Dhudson.footerURL=http://acme.com”

Getting Terraform configuration

$ git clone https://github.com/neklaf/lab-jenkins-terraform-docker
$ cd lab-jenkins-terraform-docker

Launching Jenkins container

$ terraform init

Initializing the backend...

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "docker" (terraform-providers/docker) 2.7.0...

...

* provider.docker: version = "~> 2.7"

Terraform has been successfully initialized!
...
$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # docker_container.jenkins will be created
  + resource "docker_container" "jenkins" {
      + attach           = false
      + bridge           = (known after apply)
      + command          = (known after apply)
      + container_logs   = (known after apply)
      + entrypoint       = (known after apply)
      + env              = [
          + "JAVA_OPTS=-Dhudson.footerURL=http://acme.com",
        ]
      + exit_code        = (known after apply)
      + gateway          = (known after apply)
      + hostname         = (known after apply)
      + id               = (known after apply)
      + image            = "jenkins/jenkins:lts"
      + ip_address       = (known after apply)
      + ip_prefix_length = (known after apply)
      + ipc_mode         = (known after apply)
      + log_driver       = "json-file"
      + logs             = false
      + must_run         = true
      + name             = "jenkins"
      + network_data     = (known after apply)
      + network_mode     = "jenkins_net"
      + read_only        = false
      + restart          = "always"
      + rm               = false
      + shm_size         = (known after apply)
      + start            = true

      + labels {
          + label = (known after apply)
          + value = (known after apply)
        }

      + mounts {
          + source = "jenkins_home"
          + target = "/var/jenkins_home"
          + type   = "volume"
        }

      + ports {
          + external = 8080
          + internal = 8080
          + ip       = "0.0.0.0"
          + protocol = "tcp"
        }
    }

  # docker_image.jenkins will be created
  + resource "docker_image" "jenkins" {
      + id     = (known after apply)
      + latest = (known after apply)
      + name   = "jenkins/jenkins:lts"
    }

  # docker_network.jenkins_net will be created
  + resource "docker_network" "jenkins_net" {
      + driver      = (known after apply)
      + id          = (known after apply)
      + internal    = (known after apply)
      + ipam_driver = "default"
      + name        = "jenkins_net"
      + options     = (known after apply)
      + scope       = (known after apply)

      + ipam_config {
          + aux_address = (known after apply)
          + gateway     = (known after apply)
          + ip_range    = (known after apply)
          + subnet      = (known after apply)
        }
    }

  # docker_volume.jenkins_home will be created
  + resource "docker_volume" "jenkins_home" {
      + driver     = (known after apply)
      + id         = (known after apply)
      + mountpoint = (known after apply)
      + name       = (known after apply)
    }

Plan: 4 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

Let’s apply the plan:

$ terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # docker_container.jenkins will be created
  + resource "docker_container" "jenkins" {
      + attach           = false
      + bridge           = (known after apply)
      + command          = (known after apply)
      + container_logs   = (known after apply)
      + entrypoint       = (known after apply)
      + env              = [
          + "JAVA_OPTS=-Dhudson.footerURL=http://acme.com",
        ]
      + exit_code        = (known after apply)
      + gateway          = (known after apply)
      + hostname         = (known after apply)
      + id               = (known after apply)
      + image            = "jenkins/jenkins:lts"
      + ip_address       = (known after apply)
      + ip_prefix_length = (known after apply)
      + ipc_mode         = (known after apply)
      + log_driver       = "json-file"
      + logs             = false
      + must_run         = true
      + name             = "jenkins"
      + network_data     = (known after apply)
      + network_mode     = "jenkins_net"
      + read_only        = false
      + restart          = "always"
      + rm               = false
      + shm_size         = (known after apply)
      + start            = true

      + labels {
          + label = (known after apply)
          + value = (known after apply)
        }

      + mounts {
          + source = "jenkins_home"
          + target = "/var/jenkins_home"
          + type   = "volume"
        }

      + ports {
          + external = 8080
          + internal = 8080
          + ip       = "0.0.0.0"
          + protocol = "tcp"
        }
    }

  # docker_image.jenkins will be created
  + resource "docker_image" "jenkins" {
      + id     = (known after apply)
      + latest = (known after apply)
      + name   = "jenkins/jenkins:lts"
    }

  # docker_network.jenkins_net will be created
  + resource "docker_network" "jenkins_net" {
      + driver      = (known after apply)
      + id          = (known after apply)
      + internal    = (known after apply)
      + ipam_driver = "default"
      + name        = "jenkins_net"
      + options     = (known after apply)
      + scope       = (known after apply)

      + ipam_config {
          + aux_address = (known after apply)
          + gateway     = (known after apply)
          + ip_range    = (known after apply)
          + subnet      = (known after apply)
        }
    }

  # docker_volume.jenkins_home will be created
  + resource "docker_volume" "jenkins_home" {
      + driver     = (known after apply)
      + id         = (known after apply)
      + mountpoint = (known after apply)
      + name       = (known after apply)
    }

Plan: 4 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

docker_volume.jenkins_home: Creating...
docker_image.jenkins: Creating...
docker_network.jenkins_net: Creating...
docker_container.jenkins: Creating...
docker_volume.jenkins_home: Creation complete after 0s [id=ab0fc7406a03bc9f60abba369480e6001e6d40aadc1e4cd77b42f2dda81f2354]
docker_network.jenkins_net: Creation complete after 2s [id=300c0c7d88a63bf78a5e2f758b69317f647a60c050010da21170f8cb65ca9c2c]
docker_image.jenkins: Still creating... [10s elapsed]
docker_container.jenkins: Still creating... [10s elapsed]
docker_image.jenkins: Still creating... [20s elapsed]
docker_container.jenkins: Still creating... [20s elapsed]
docker_image.jenkins: Still creating... [30s elapsed]
docker_container.jenkins: Still creating... [30s elapsed]
docker_image.jenkins: Still creating... [40s elapsed]
docker_container.jenkins: Still creating... [40s elapsed]
docker_image.jenkins: Still creating... [50s elapsed]
docker_container.jenkins: Still creating... [50s elapsed]
docker_image.jenkins: Still creating... [1m0s elapsed]
docker_container.jenkins: Still creating... [1m0s elapsed]
docker_image.jenkins: Still creating... [1m10s elapsed]
docker_container.jenkins: Still creating... [1m10s elapsed]
docker_image.jenkins: Still creating... [1m20s elapsed]
docker_container.jenkins: Still creating... [1m20s elapsed]
docker_image.jenkins: Creation complete after 1m23s [id=sha256:b2428543fab000c6b93cf4ac8d27ea243a4f798ca4e3ae09789630b93786ef31jenkins/jenkins:lts]
docker_container.jenkins: Creation complete after 1m24s [id=cd83d4f4a60a3943bd755c1fbb10b972093bfd935159f931df292862824d3efc]

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

Verification

The first step and the easiest one should be to browse to this URL http://localhost:8080:

Also some additional verifications from our side using the command line:

$ docker ps --filter name=jenkins
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                               NAMES
52053dff01d2        jenkins/jenkins:lts   "/sbin/tini -- /usr/…"   16 minutes ago      Up 16 minutes       127.0.0.1:8080->8080/tcp, 127.0.0.1:50000->50000/tcp   jenkins

$ docker inspect jenkins | jq -r '.[].Config.Env[]' | grep -i JAVA_OPTS
JAVA_OPTS=-Dhudson.footerURL=http://acme.com

$ docker inspect jenkins | jq -r '.[].HostConfig.RestartPolicy.Name'
always

$ docker inspect jenkins | jq -r '.[].HostConfig.NetworkMode'
$ docker inspect jenkins | jq -r '.[].Mounts[].Name'
$ docker inspect jenkins | jq -r '.[].Mounts[].Type'

Cleaning up

$ terraform destroy
docker_image.jenkins: Refreshing state... [id=sha256:b2428543fab000c6b93cf4ac8d27ea243a4f798ca4e3ae09789630b93786ef31jenkins/jenkins:lts]
docker_volume.jenkins_home: Refreshing state... [id=ab0fc7406a03bc9f60abba369480e6001e6d40aadc1e4cd77b42f2dda81f2354]
docker_network.jenkins_net: Refreshing state... [id=300c0c7d88a63bf78a5e2f758b69317f647a60c050010da21170f8cb65ca9c2c]
docker_container.jenkins: Refreshing state... [id=cd83d4f4a60a3943bd755c1fbb10b972093bfd935159f931df292862824d3efc]
...
Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

docker_image.jenkins: Destroying... [id=sha256:b2428543fab000c6b93cf4ac8d27ea243a4f798ca4e3ae09789630b93786ef31jenkins/jenkins:lts]
docker_volume.jenkins_home: Destroying... [id=ab0fc7406a03bc9f60abba369480e6001e6d40aadc1e4cd77b42f2dda81f2354]
docker_network.jenkins_net: Destroying... [id=300c0c7d88a63bf78a5e2f758b69317f647a60c050010da21170f8cb65ca9c2c]
docker_container.jenkins: Destroying... [id=cd83d4f4a60a3943bd755c1fbb10b972093bfd935159f931df292862824d3efc]
docker_container.jenkins: Destruction complete after 1s
docker_volume.jenkins_home: Destruction complete after 2s
docker_network.jenkins_net: Destruction complete after 2s

Error: Unable to remove Docker image: Error response from daemon: conflict: unable to delete b2428543fab0 (cannot be forced) - image is being used by running container cd83d4f4a60a

$ docker rmi jenkins/jenkins:lts
Untagged: jenkins/jenkins:lts
Untagged: jenkins/jenkins@sha256:7ea1d29c621a10d1e231013f62e32f0eb726dde15a4c219e5010564a6766daa8
...

Reference links:


“It is the mark of an educated mind to be able to entertain a thought without accepting it.”
Aristotle, Metaphysics

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s