Construire une image Docker dans une intégration continue

17 Aug 2015

Je voulais voir s'il était possible de construire une image Docker à la suite d'un build réussi en utilisant des outils disponibles sur le net. J'ai enfin réussi à faire ce que je voulais et je présente ici ce que j'ai fait.

Objectif

Mon objectif était de construire une image Docker à la suite d'un build CI réussi. Le build CI est déclenché à chaque push. De plus je voulais pouvoir construire l'image sur mon poste pour des tests local.

Outils

Les outils que je vais utiliser sont

Gradle-docker

Il s'agit d'un plugin Gradle permettant de construire une image Docker à partir du script de build. On peut fournir un fichier Dockerfile au plugin ou en générer un à partir du script.

Shippable

Shippable est une plateforme d'intégration continue. Par rapport à Travis cette plateforme se base sur Docker : l'environnement créé pour le build est une image Docker. Il est possible d'utiliser les images Docker par défaut mais on peut aussi fournir notre propre image. De plus, il est possible de construire une image Docker pendant le build.

L'option gratuite permet de lancer qu'un seul build à la fois.

Tutum.co

Tutum est une plateforme qui permet la gestion de déploiement de containers Docker dans le cloud. Le produit permet de déployer des conteneurs dans différents cloud comme Amazon, Digital Ocean, ...

L’inscription est gratuite pour l’instant et le restera par la suite lors de la fin de la beta.

Construction de l'image Docker en local

L'image Docker est construite par Gradle grâce au plugin gradle-docker. La déclaration dans le fichier build.gradle

buildscript {
    ...
    dependencies {
        ...
        classpath 'se.transmode.gradle:gradle-docker:1.2'
    }
}
...
apply plugin: 'docker'
...

task buildDocker(type: Docker, dependsOn: build) {
    project.group = "tutum.co/jgiovaresco"
    applicationName = jar.baseName
    dockerfile = file('Dockerfile')
    doFirst {
        copy {
            from jar
            into "${stageDir}/buildoutput"
        }
    }
}

Une nouvelle tâche buildDocker est ajoutée et celle-ci dépend de la tâche build. Pour construire l'image Docker, le plugin va d'abord copier le fichier Dockerfile fournit dans le répertoire de travail build/docker. Le plugin lance ensuite la commande docker build depuis ce répertoire.

L'image à construire doit contenir le jar de l'application construit par Gradle. Ce jar est donc copié avant de construire l'image grâce au bloc doFirst{}. Le jar est copié dans un sous répertoire buildoutput pour que la construction avec Shippable fonctionne.

On construit l'image sur son poste avec gradle buildDocker

Build avec Shippable

On commence par lier notre compte Shippable avec notre compte Tutum pour que l'image Docker soit publiée dans le registry privé. Le doc décrit la démarche : Use a Docker registry

La création d'un build est aussi très bien expliquée dans la documentation : Run A Build With Test And Coverage Reports. Pour rappel, le process de build fournit par Shippable se trouve ici.

Pour construire un image Docker dans un build, il est nécessaire de configurer le projet dans Shippable. Comme le décrit la documentation Docker build, il y a 2 workflows :

  • Pre-CI :

    • Construction de l'image Docker en utilisant le fichier Dockerfile présent à la racine du repository.
    • Récupération du code depuis GitHub/Bitbucket et lance les tests dans le conteneur issue de l'image construite précédemment.
    • Pousse le conteneur dans le registry (Docker Hub, GCR, Quay.io, Private Registry)
  • Post-CI :

    • Récupération de l'image Docker spécifiée dans la configuration du projet depuis le registry (Dockery Hub, GCR, Quay.io)
    • Récupération du code depuis GitHub/Bitbucket et lance les tests dans le conteneur issue de l'image récupéré précédemment.
    • Si le build réussi, construit le conteneur Docker depuis le fichier Dockerfile présent à la racine du repository.
    • Pousse le conteneur dans le registry (Docker Hub, GCR, Quay.io, Private Registry)

J'ai utilisé le 2ème workflow. L'image utilisée pour lancer mon build est une image fournie par Shippable shippableimages/ubuntu1404_java. Dans un second temps, je pense que je vais créer ma propre image dans laquelle je pourrais installer les outils nécessaires pour accélérer le build en simplifiant la phase de préparation.

En utilisant l'image par défaut, il faut commencer par configurer la version de Java à utiliser. Pour cela on ajoute dans le bloc before_script les lignes suivantes :

before_script:
  - if [[ $SHIPPABLE_JDK_VERSION == "oraclejdk8" ]] ; then export JAVA_HOME="/usr/lib/jvm/java-8-oracle"; export PATH="$PATH:/usr/lib/jvm/java-8-oracle/bin"; export java_path="/usr/lib/jvm/java-8-oracle/jre/bin/java"; fi
  - update-alternatives --set java $java_path
  - java -version

Pour inclure le jar construit dans l'image, il est nécessaire de le copier dans un répertoire particulier shippable/buildoutput. Il faut donc créer ce répertoire dans la phase before_script et copier le jar construit dans la phase after_script. La construction de l'image Docker se fera automatiquement à la fin du build.

Pour finir je rappel le contenu du fichier shippable.yml que j'utilise

language: java

jdk:
  - oraclejdk8

before_script:
  - if [[ $SHIPPABLE_JDK_VERSION == "oraclejdk8" ]] ; then export JAVA_HOME="/usr/lib/jvm/java-8-oracle"; export PATH="$PATH:/usr/lib/jvm/java-8-oracle/bin"; export java_path="/usr/lib/jvm/java-8-oracle/jre/bin/java"; fi
  - update-alternatives --set java $java_path
  - java -version
  - mkdir -p shippable/buildoutput
  - mkdir -p shippable/testresults
  - mkdir -p shippable/codecoverage
  - wget -O shippable/cover2cover.py https://raw.githubusercontent.com/rix0rrr/cover2cover/master/cover2cover.py

script:
  - ./gradlew build

after_script:
  - cp ./build/test-results/*.xml ./shippable/testresults
  - cp ./build/libs/sm-identityaccess.jar ./shippable/buildoutput

after_success:
  - ./gradlew jacocoTestReport
  - python shippable/cover2cover.py ./build/reports/jacoco/test/jacocoTestReport.xml src/main/java > shippable/codecoverage/coverage.xml

On peut trouver un exemple sur mon Github