How to run a "sidecar" container in Jenkins Blue Ocean?

Viewed 2470

I am fairly new to Jenkins and CI/CD in general, but believe that I have searched long enough to conclude things are not as I expect.

I want to do some frontend tests on my website and just as in real life I want to test with the site in one Docker container and the database in another container. Jenkins has this documented as "sidecar" containers which can be part of a pipeline.

Their example:

node {
    checkout scm
    /*
     * In order to communicate with the MySQL server, this Pipeline explicitly
     * maps the port (`3306`) to a known port on the host machine.
     */
    docker.image('mysql:5').withRun('-e "MYSQL_ROOT_PASSWORD=my-secret-pw" -p 3306:3306') { c ->
        /* Wait until mysql service is up */
        sh 'while ! mysqladmin ping -h0.0.0.0 --silent; do sleep 1; done'
        /* Run some tests which require MySQL */
        sh 'make check'
    }
}

The thing is that I do not have a 'traditional' Jenkins pipeline, but I am running Jenkins Blue Ocean instead. This gives me a fancy pipeline editor, but also my pipeline code (Jenkinsfile) looks really different from the example:

pipeline {
  agent {
    docker {
      image 'php'
    }

  }
  stages {
    stage('Build') {
      steps {
        sh 'composer --version'
        sh 'composer install'
      }
    }

    stage('Tests') {
      steps {
        echo 'Do test'
      }
    }

  }
}

So how would I be spawning (and tearing down) these "sidecar" containers in a Blue Ocean pipeline? Currently the Pipeline editor has no available options if I want to add a step related to Docker. Can I still use docker.image? I do have the Docker Pipeline plugin installed.

no available steps.

2 Answers

The example provided by Jenkins in the link is actually a fully functional pipeline, with one exception. You need to comment out checkout scm if you provide the pipeline script directly into Jenkins.

node {
    // checkout scm
    docker.image('mysql:5').withRun('-e "MYSQL_ROOT_PASSWORD=my-secret-pw"') { c ->
        docker.image('mysql:5').inside("--link ${c.id}:db") {
            /* Wait until mysql service is up */
            sh 'while ! mysqladmin ping -hdb --silent; do sleep 1; done'
        }
        docker.image('centos:7').inside("--link ${c.id}:db") {
            /*
             * Run some tests which require MySQL, and assume that it is
             * available on the host name `db`
             */
            sh 'make check'
        }
    }
}

What it may be confusing to you is that the code style in the example above is very different than the one generated by Blue Ocean pipeline editor. That is because the script is written in the Scripted Pipeline and Blue Ocean has generated a Declarative Pipeline. Both are fully supported in Jenkins and both use the same engine underneath, but the syntax differences may lead to confusion at start.

You can use the Scripted Pipeline example above just fine, but if you want to keep the Declarative Pipeline, you can run the scripted part inside the script step. In both cases you need to change the docker images and executed commands according to your needs.

pipeline {
    agent any
    stages {
        stage('Build and test') {
            steps {
                script {
                    node {
                        docker.image('mysql:5').withRun('-e "MYSQL_ROOT_PASSWORD=my-secret-pw"') { c ->
                            docker.image('mysql:5').inside("--link ${c.id}:db") {
                                /* Wait until mysql service is up */
                                sh 'while ! mysqladmin ping -hdb --silent; do sleep 1; done'
                            }
                            docker.image('centos:7').inside("--link ${c.id}:db") {
                                /*
                                * Run some tests which require MySQL, and assume that it is
                                * available on the host name `db`
                                */
                                sh 'make check'
                            }
                        }
                    }
                }
            }
        }
    }
}

Please note:

  1. Docker container link feature used in this example is a legacy feature, it may eventually be removed.
  2. The pipeline will fail at make check, as make is not provided in centos:7 image.

More then half a year later I finally figured out it was much simpler than I thought. It can be done with docker-compose.

You need to make sure that your Jenkins has access to docker-compose. So if you are running Jenkins as a docker container ensure it has access to the Docker socket. Also Jenkins is not likely to ship with docker-compose included (JENKINS-51898) so you will have to build your own blue ocean image to install docker-compose.

Rather than copying below file, check https://docs.docker.com/compose/install/ for the latest version!

# Dockerfile
FROM jenkinsci/blueocean

USER root

RUN curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose && \
    chmod +x /usr/local/bin/docker-compose
    
USER jenkins

Once you have Jenkins and Docker up and running you can deploy a test version of your application with a regular docker-compose file, including all database and other containers you might need. You can install dependencies and start the tests by using docker-compose exec to execute commands inside containers started with docker-compose.

Note that docker-compose -f docker-compose.test.yml exec -T php composer-install executes the composer-install command in the container that was defined as the php service in the docker-compose file.

In the end, no matter the outcome of the test, all containers and associated volumes (-v flag) are shutdown and removed.

# Jenkinsfile
pipeline {
  agent any
  stages {
    stage('Start') {
      steps {
        sh 'docker-compose -f docker-compose.test.yml up -d'
      }
    }
    stage('Composer install') {
      steps {
        sh 'docker-compose -f docker-compose.test.yml exec -T php composer install --no-interaction --no-progress --optimize-autoloader'
      }
    }
    stage('Test') {
      steps {
        sh 'docker-compose -f docker-compose.test.yml exec -T php <run test script>'
      }
    }
  }
  post {
    always {
      sh 'docker-compose -f docker-compose.test.yml down -v'
    }
  }
}
Related