Running multiple Docker containers from a single Jenkinsfile

11,832

So this is what I ended up with. There are surely better solutions, but I have to move on. I hope to gather some (better) answers in time, I'll not mark this as "the solution" yet ;)

First, some credits to Stephen Kings slides (the title says "Declarative" but there are some nice examples regarding the scripted Pipeline): (Declarative) Jenkins Pipelines

Here is my gist on GitHub with the following snippet:

def docker_images = ["python:2.7.14", "python:3.5.4", "python:3.6.2"]

def get_stages(docker_image) {
    stages = {
        docker.image(docker_image).inside {
            stage("${docker_image}") {
                echo 'Running in ${docker_image}'
            }
            stage("Stage A") {
                switch (docker_image) {
                    case "python:2.7.14":
                        sh 'exit 123'  // for python 2.7.14 we force an error for fun
                        break
                    default:
                        sh 'sleep 10'  // for any other docker image, we sleep 10s
                }
                sh 'echo this is stage A'  // this is executed for all
            }
            stage("Stage B") {
                sh 'sleep 5'
                sh 'echo this is stage B'
            }
            stage("Stage C") {
                sh 'sleep 8'
                sh 'echo this is stage C'
            }

        }
    }
    return stages
}

node('master') {
    def stages = [:]

    for (int i = 0; i < docker_images.size(); i++) {
        def docker_image = docker_images[i]
        stages[docker_image] = get_stages(docker_image)
    }

    parallel stages
}

I tried to make it easy to use:

  • you add your Docker images in a list at the top and then you define the stages in the get_stages() function
  • add the common stages and steps
  • if any Docker image needs special treatment (like python:2.7.14 in my example), you can use a simple switch. This could also be realised with a double map for the special cases ('images'->'stage'='steps') and a fallback double map for defaults, but I'll leave it as an exercise for the reader. (to be honest, I could not figure out the correct, supported Groovy-lang syntax)

This is how it looks like when everything is fine in both the Classic and the Blue Ocean UIs (it's known that the Blue Ocean UI fails to display multiple stages in parallel runs, see JENKINS-38442):

Classic UI Classic UI - Build OK

Blue Ocean UI Blue Ocean UI - Build OK

And this is the output if Stage A in python:2.7.14 fails:

Classic UI Classic UI - Failed Stage A step

Blue Ocean UI Blue Ocean UI - Failed Stage A step

Share:
11,832
tamasgal
Author by

tamasgal

My name is Tamás Gál, I am an astroparticle physicist and working at Erlangen Centre for Astroparticle Physics (ECAP). I currently develop online monitoring and live reconstruction algorithms for the KM3NeT neutrino telescopes and maintain the IT services of KM3NeT and ECAP. My DevOps engineering skills include Docker (+Swarm), GitLab CI/CD, Jenkins, Xen, OpenVZ, Ansible and more than two decades of experience with Linux and BSD as system administrator. I spend most of my time with science (astroparticle physics), coding (Julia, Python, …) and electronics (both analog/digital); the rest preferably off the road with one of my motorbikes. I have an obsession to repair things and keep them alive as long as possible, no matter if it requires a gearbox restoration of my BMW R1100 GS or replacing a dead 0201 SMD capacitor on a MacBook logicboard. Furthermore, I love making music and spend a lot of time on my DIY modular synthesizer. #SOreadytohelp

Updated on June 18, 2022

Comments

  • tamasgal
    tamasgal almost 2 years

    So I spent the whole day trying to figure out how to configure a simple Jenkins Pipeline with multiple Docker images and I am not happy at all.

    I need a few stages (prepare, build, test, docs) executed on a couple of different docker containers (currently I just picked three standard Python containers). And it would be nice if those would run in parallel, but I only found this solution, which combines all stages into a single one (and thus creates a not so informative overview in the Blue Ocean UI): Jenkins Pipeline Across Multiple Docker Images

    So I ended up with the configuration below, which is ugly as hell (code repetition everywhere), but more or less creates an good looking overview in the classic UI:

    Classic UI stages

    A not so informative overview in the Blue Ocean UI

    Blue Ocean stages overview

    And an acceptable test overview from junit, which combines all the tests from each stage but if any test is failing, the corresponding "version" is shown:

    junit

    The most annoying thing however is, you cannot see which step has failed. If Python 2.7 fails, everything else is also marked as failed and you don't even see which stage failed.

    I tried so many different approaches and I am wondering how this should be done. This should be such a common thing to do with Jenkins, so I guess I have some general misunderstandings in this (for me absolutely new) pipeline/nodes/labels/stages/steps/declarative/scripted/groovy/blueocean stuff...

    It should be possible to define a list of docker containers some (maybe customisable stages/steps) for each of them and run them in parallel and having it displayed nicely in Blue Ocean and in Classic UI, shouldn't it?

    node {
        stage("Python 2.7.14") {
            checkout scm
            docker.image('python:2.7.14').inside {  // just a dummy for now
                stage("Prepare") { sh 'python --version' }
                stage("Build") { sh 'ls -al' }
            }
        }
        stage("Python 3.5.4") {
            checkout scm
            docker.image('python:3.5.4').inside {
                stage("Prepare") { sh 'python -m venv venv' }
                stage("Build") {
                    sh """
                        . venv/bin/activate
                        make install-dev
                    """
                }
                stage('Test') {
                    sh """
                        . venv/bin/activate
                        make test
                    """
                }
                stage('Docs') {
                    sh """
                        . venv/bin/activate
                        make doc-dependencies
                        cd docs
                        make html
                    """
                }
            }
        }
        stage("Python 3.6.4") {
            checkout scm
            docker.image('python:3.5.4').inside {
                stage("Prepare") { sh 'python -m venv venv' }
                stage("Build") {
                    sh """
                        . venv/bin/activate
                        make install-dev
                    """
                }
                stage('Test') {
                    sh """
                        . venv/bin/activate
                        make test
                    """
                }
                stage('Docs') {
                    sh """
                        . venv/bin/activate
                        make doc-dependencies
                        cd docs
                        make html
                    """
                }
            }
        }
    }
    

    Update: this is how it looks like in the Blue Ocean UI when a step fails, int this case "Test" in both Python 3.5.4 and 3.6.4 failed but it looks like everything has failed. Test step failed

    Also the Python 2.7.14 and 3.5.4 stages are collapsed and cannot be viewed separately. If I click on one of them, all the steps are shown in green although in this case . venv/bin/activate make test failed:

    Failed test step is shown in green

  • Happy Coder
    Happy Coder almost 6 years
    what can I do if my tests needs MySQL ?
  • tamasgal
    tamasgal almost 6 years
    @HappyCoder you can provide your own Dockerfile where mysql will be installed during the container setup