Set up Jenkins to use Docker containers

The goal is to have MacOS as Docker host, create a container with Jenkins and set it up so that it would be able to create child containers for workers dynamically. See the diagram below for logical relationships.

Prerequisites

  1. MacOS
  2. Docker installed locally

Steps (manual)

First we will perform all configuration steps manually to figure out details not mentioned in any guides. I have found out that the tutorials available online take a lot of things for granted - they assume that you already know everything about Docker and Jenkins.

(1). Pull the official docker image form the DockerHub

docker pull jenkins/jenkins:lts

(2). Create a local directory for this exercise, and inside create one more directory called jenkins_data

mkdir jenkins001 && cd ./jenkins001
mkdir jenkins_data

(3). Run a container with this folder set as a volume. Another requirement is to mount -v /var/run/docker.sock to allow master container to spawn child containers for worker nodes.

docker run -d --rm --name jmaster -v $PWD/jenkins_data:/var/jenkins_home -v /var/run/docker.sock:/var/run/docker.sock -p 8080:8080 -p 50000:50000 jenkins/jenkins:lts && docker ps --format "{{.ID}}"

(4). Get the admin default password (you can use it and unlock Jenkins manually without messing with groovy scripts in the next steps)

docker container exec -it (docker ps -a --no-trunc --filter name=jmaster --format "{{.ID}}") cat /var/jenkins_home/secrets/initialAdminPassword

(5). If you want to start Jenkins without activating it manually with password from /var/jenkins_home/secrets/initialAdminPassword, add these variables at container start.
The default user is testci:testci (it is better to use docker secrets instead of env variables, but that is for next time).

docker run -d --rm --name jmaster \
    -v $PWD/jenkins_data:/var/jenkins_home \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -p 8080:8080 \
    -p 50000:50000 \
    -e JAVA_OPTS="-Djenkins.install.runSetupWizard=false -Djava.awt.headless=true" \
    -e JENKINS_USER=testci \
    -e JENKINS_PASS=testci \
    jenkins/jenkins:lts \
    && docker ps --format "{{.ID}}"
    # Check that the variable is set correctly
    docker exec (docker ps -a --no-trunc --filter name=jmaster --format "{{.ID}}") env

(6). Place the following file basic.security.groovy into /usr/share/jenkins/ref/init.groovy.d/ directory to disable initial setup wizard at first run.

```groovy
#!groovy
import static jenkins.model.Jenkins.instance as jenkins
import jenkins.install.InstallState
if (!jenkins.installState.isSetupComplete()) {
  println '--> Preventing startWizard from showing'
  InstallState.INITIAL_SETUP_COMPLETED.initializeState()
}
```

```bash
docker cp ./basic.security.groovy jmaster:/usr/share/jenkins/ref/init.groovy.d/
# This step appears to be unnecessary for the present
# docker exec (docker ps -a --no-trunc --filter name=jmaster --format "{{.ID}}") chown -R jenkins:jenkins /var/jenkins_home/init.groovy.d/
```

(7). Set a default user with this file: basic.security002.groovy and place it in the same directory

#!groovy
import jenkins.model.*
import hudson.security.*
def instance = Jenkins.getInstance()
def env = System.getenv()
def hudsonRealm = new HudsonPrivateSecurityRealm(false)
hudsonRealm.createAccount(env.JENKINS_USER, env.JENKINS_PASS)
instance.setSecurityRealm(hudsonRealm)
instance.save()
docker cp ./basic.security002.groovy jmaster:/usr/share/jenkins/ref/init.groovy.d/

(8). Set csrf protection with this file: csrf.groovy and place it in the same directory

import hudson.security.csrf.DefaultCrumbIssuer
import jenkins.model.Jenkins
def instance = Jenkins.instance
instance.setCrumbIssuer(new DefaultCrumbIssuer(true))
instance.save()
docker cp ./csrf.groovy jmaster:/usr/share/jenkins/ref/init.groovy.d/

(9). Copy the list of necessary plugins (plugins.txt) to Jenkins plugins folder

docker cp ./plugins_list.txt jmaster:/usr/share/jenkins/ref/

So far this file has only one plugin: docker-plugin

docker-plugin

(10). Restart the container to make Jenkins run the scripts at startup

docker restart jmaster

(11). Run the plugin installation script (this will not work in fish, only in bash)

docker exec it $(docker ps -a --no-trunc --filter name=jmaster --format "{{.ID}}") bash 
/usr/local/bin/install-plugins.sh $(cat /var/jenkins_home/plugins_list.txt)

There’s no combination of parentheses and/or escaping to make this line work in fish shell yet.

(12). Get the list of installed plugins

curl -s -k "http://testci:testci@127.0.0.1:8080/pluginManager/api/json?depth=1" | jq -r '.plugins[].shortName'

(13). Log in into the running container as root to perform any other maintenance actions.

docker exec -i -t --user root (docker ps -a --no-trunc --filter name=jmaster --format "{{.ID}}") /bin/bash

Allow access to docker.sock

Sometimes Jenkins might not be able to access host Docker daemon because of permissions. Log in to the container as root and run the following command to allow all users to access /var/run/docker.sock This is not very secure, but will allow to create something working quickly

docker exec --user root -it $(docker ps -a --no-trunc --filter name=jmaster --format "{{.ID}}") bash
chmod 777 /var/run/docker.sock

A better practice would be to add jenkins user ro docker group and set permissions for docker.sock to 644

Manual configuration with GUI

Add a docker cloud

  1. Go to http://127.0.0.1:8080/configure section and scroll down to Cloud section

  2. Click Add new cloud* > ***Docker***

  3. Enter any descriptive name and click Cloud details to continue setup

  4. Enter you docker host address (unix:///var/run/docker.sock) into the corresponding field and click the button to test connection.


    You might get a permissions error

    in this case you need to open access to /var/run/docker.sock from inside the container
    If all goes well, you will see this:
    image-20191016115152159.png

  5. Enable the new cloud with the folowing checkbox.

  6. Apply the changes.

  7. Set Jenkins Location>Jenkins URL to http://127.0.0.1:8080/
    This might trigger the reverse proxy notification from Jenkins, which you can dismiss for now.

Add Docker agent templates

  1. After you add docker cloud to your Jenkins instance you need to specify slave templates. Click Docker Agent templates… -> Add Docker Template button to start
  2. Configure a minimal set of parameters as shown below:

Create a build project

  1. Click create new jobs link to create a new project
  2. Give the new project a name, mark it as freestyle project and click OK button
  3. Give a build description and click Save button to finish test build configuration.

Test-run the project

Open the build project and click Build Now link to test your configuration

If all goes well, you should see this:

image-20191016141004594.png

At this stage our Jenkins instance with test project does not do anything useful.

In the next part we will recreate the manual steps above with Docker Compose and secrets

November 6, 2019   (v.16c40e0)