Docker, Jenkins and You

Going over the workflow from code commit to built and tested docker image ready for deployment

Kyle Rockman
View slides in real time:

http://slides.com/rocktavious/docker-jenkins-and-you/live

Who am I?

  • Infrastructure Team
  • Build & Release Engineer
  • krockman@underarmour.com
  • @rockman on slack

Talk Overview

  • Give overview of current buildflow used with docker
  • Introduce the latest changes made to our buildflow 
  • Dive deeper into the "Jenkins Workflow" plugin and how it facilitates our new buildflow
  • Q&A (time permitting)

Terminology

Pipeline/Buildflow

Workflow

The highlevel concept of the organization of phases taken in a CI/CD world

A Jenkins DSL of the discrete actions taken to achieve the buildflow

Artifact

The bundle/package which is produced from your project generally during its "Build" phase

build - test - publish - release

Build, Test, Publish & Release

KEY POINT:

The Artifact that was tested is the same artifact that was released

Buildflow Overviews

Docker V1 Buildflow

This is the most common buildflow currently in use by almost all of the MMF projects using docker containers

PROJECT_ROOT
├─── DockerFile
├─── docker
│    ├─── Build
│    │    ├─── *.yml
│    │    └─── *.properties
│    ├─── Test
│    │    ├─── *.yml
│    │    └─── *.properties
│    └─── Release
│         ├─── *.yml
│         └─── *.properties
└─── ...

It has problems

  • Infrastructure manages the changes to the buildflow
  • Changes apply across all projects, slowing down the pace of changes or making sweeping changes difficult to push through
  • Project owners can only change limited aspects about their buildflow.
  • How does gitflow fit into this?

Docker V2 Buildflow

Only two changes from v1

PROJECT_ROOT
├─── DockerFile
├─── docker
│    ├─── Build
│    │    ├─── *.yml
│    │    └─── *.properties
│    ├─── Test
│    │    ├─── *.yml
│    │    └─── *.properties
│    └─── Release
│         ├─── *.yml
│         └─── *.properties
└─── ...
  • Gitlab instead of Gerrit
  • Specific branch names trigger events
  • review/*  -> verify
  • release/*  -> publish

It's a stopgap

  • Allows current v1 users to switch to centralized git repo (currently gitlab - https://gitlab.mfpaws.com) without changing their buildflow too
  • Allows project owners to control when verify/publish builds happen
  • LIMITATION: Only allows for docker image publishes to happen for only the "master" tag

Workflow Buildflow

  • Major simplification
  • Leverages the Jenkins "Workflow" plugin
  • Puts the concept of "buildflow" back into the hands of the developer
  • No more need for Infrastructure to change your buildflow
  • Enables infrastructure to just manage the Jenkins infrastructure
  • Will still consult on complex buildflows and best practices where/when needed
  • A single job in jenkins that runs the workflow for every branch in gitlab when changes happen
  • LIMITATION: Polling every minute

What you need?

  • A request to the infrastructure team to assign your project to a jenkins master that supports workflow and to get your job generated there
  • A "Jenkinsfile" in your project defining your buildflow using the DSL
PROJECT_ROOT
├─── Jenkinsfile
└─── ...

Jenkins Workflow

Documentation: http://bit.ly/1TLbSQt

Common Concepts

node() {
    sh 'ls -al'
}

Workflow Steps:

  • node
  • sh
  • withEnv
  • retry
  • parallel
  • ... etc

Its just groovy with some special jenkins objects/methods mixed in

def tests = [:]
for ( i = 0 ; i < test_configs.size() ; i++ ) {
    def test_config = test_configs[i][1]
    tests["parallel_${i}"] = {
        node('docker') {
            git url: 'https://github.com/ua/sample.git'
            println test_config
        }
    }
}
parallel tests
node('docker'){
    retry(5){
        sh 'docker pull my_image_name'
    }
    withEnv("NAMESPACE=my_namespace") {
        try {
            sh 'docker-compose -p "${NAMESPACE}" run unittest'
        } finally {
            sh 'docker-compose -p "${NAMESPACE}" stop || true'
            sh 'docker-compose -p "${NAMESPACE}" rm --force || true'
        }
    }
}
stage "Build"
node('docker'){
    checkout scm
    sh 'docker build --force-rm --pull=true -t "${DOCKER_REGISTRY_HOST}/parlour/parlour:example" -f "./parlour.dockerfile" .'
    retry(5) {
        sh 'docker push "${DOCKER_REGISTRY_HOST}/parlour/parlour:example"'
    }    
}
stage "Test"
node('docker'){
    checkout scm
    sh 'sed "s|CIVERSION|`git rev-parse HEAD`|g" "./docker-compose.yml" > ./docker-compose-ci.yml'
    retry(5) {
        sh 'docker-compose -f "./docker-compose-ci.yml" pull'
    }
    def extra_env = "BUILD_NAMESPACE=" + env["BRANCH_NAME"].replaceAll(/[\W-]/, "_").toLowerCase() + "_" + env["BUILD_NUMBER"]
    withEnv(extra_env) {
        try {
            sh 'docker-compose -f "./docker-compose-ci.yml" -p "${BUILD_NAMESPACE}" run -d -T ci-unittest > CONTAINER_NAME.txt'
            sh 'docker logs -f `cat CONTAINER_NAME.txt`'
            sh '(docker cp `cat CONTAINER_NAME.txt`:/out/. ./out)|| true
            sh 'docker wait `cat CONTAINER_NAME.txt`'
        } finally {
            sh 'docker-compose -f "./docker-compose-ci.yml" -p "${BUILD_NAMESPACE}" stop || true'
            sh 'docker-compose -f "./docker-compose-ci.yml" -p "${BUILD_NAMESPACE}" rm --force || true'
        }
    }
    step([$class: 'JUnitResultArchiver', testResults: 'out/TEST-*.xml'])
}

Parlour's Workflow Buildflow

  • Provide an example of Docker V1
  • Make improvements
  • Forked by convention
  • SRE's should run wild with customization if needs be, otherwise stick to the examples to get up to speed quickly, then deviate when necessary
  • Known future need for sharing

Parlour

PROJECT_ROOT
├─── parlour.dockerfile
├─── Jenkinsfile
├─── docker-compose.yaml
└─── ...
docker-compose run unittest
...
docker-compose run pep8
...
docker-compose run smoketest
  • Decided it was best to use a single dockerfile and docker-compose.yml
  • docker-compose targets for local testing and for ci testing using inheritance in the same docker-compose.yml
  • Using an entrypoint and shell script to allow single docker image to standup as parlour http server or celery worker via environment variable
  • Use the power of groovy to make the buildflow parameterized to support easy configuration and encapsulation of repeatable / parallel steps
docker-compose run ci-unittest
...
docker-compose run ci-pep8
...
docker-compose run ci-smoketest

CI Runs Like

Local Runs Like

Questions?

Docker, Jenkins and You

By Kyle Rockman

Docker, Jenkins and You

  • 1,562