Le but de ce tutoriel sera de mettre en place le déploiement continu & automatique d’une application avec Jenkins sur une architecture Docker. Le déploiement continu & automatique est l’un des concepts clés du mouvement DevOps. Le but de la manœuvre est de favoriser l’industrialisation d’une application en permettant à un système : de tester l’application, de construire l’image du conteneur et de mettre celui-ci en production.

Prérequis :

  • Docker
  • Docker-compose

Fonctionnement

Notre système de déploiement utilisera 4 couches pour mettre en production l’application :

  • Gogs : Plateforme d’hébergement privé de projet git (se présente sous la forme d’une interface web très proche de celle de GitHub). Gogs peut être remplacé par n’importe quel serveur git, en effet, nous l’utiliserons uniquement comme un dépôt.
  • Jenkins : Jenkins utilisera un script appelé « Jenkins file » pour builder l’image du conteneur applicatif.
  • Registre docker privé : Contiendra les images des conteneurs internes à notre système.
  • WatchTower : Un worker va de manière régulière chercher à installer une image plus récente des conteneurs déployés.

Pour lancer un déploiement, il suffira de pousser du code sur gogs. Ce dernier enverra une alerte à Jenkins au travers d’un webhook. Jenkins va alors lancer un build de type « pipeline ». Il s’agit d’un script qui se découpe en plusieurs étapes (contenus dans le fichier Jenkinsfile). Nous créerons alors trois étapes, la première qui construira l’image docker du projet (à l’aide d’un fichier Dockerfile), la seconde lancera un conteneur temporaire avec l’image construite à l’étape précédente dans le but d’exécuter les tests unitaires et l’étape finale est de pousser l’image sur notre registre docker privé. L’étape finale est d’attendre que Watchtower détecte le changement sur l’image et remplace l’ancien conteneur par un conteneur mis à jour.

Mise en place de l'architecture Docker

Nous utiliserons un docker-compose pour mettre en place l’architecture dont nous aurons besoin.

Il nous faudra créer un Dockerfile (nous le placerons dans le fichier "/home/user/jenkins/Dockerfil"). Il aura pour but de créer une image d’un conteneur applicatif Jenkins modifié pour avoir accès à l’API Docker :

FROM jenkins
MAINTAINER Flavien JOURDREN <[email protected]>

USER root
RUN apt-get update -qq
RUN apt-get install -qqy apt-transport-https ca-certificates
RUN apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
RUN echo "deb https://apt.dockerproject.org/repo debian-jessie main" > /etc/apt/sources.list.d/docker.list
RUN apt-get update -qq
RUN apt-get install -qqy docker-engine

RUN usermod -aG docker jenkins

USER jenkins

Contenu du fichier docker-compose.yml :

version: '2'

services:

  mysql:
    image: mysql
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=aCoolPassword
      - MYSQL_DATABASE=gogs
      - MYSQL_USER=root
      - MYSQL_PASSWORD=aCoolPassword
    volumes:
      - "/containersVolume/database:/var/lib/mysql"

  gogs:
    image: gogs/gogs
    restart: always
    links:
      - "mysql:db"
    ports:
      - "10022:22"
      - "10080:3000"
    volumes:
      - "/containersVolume/gogs:/data"

  jenkins:
    build: /home/user/jenkins
    restart: always
    ports:
      - "8080:8080"
      - "50000:50000"
    volumes:
      - "/containersVolume/jenkins:/var/jenkins_home"
      - "/var/run/docker.sock:/var/run/docker.sock"

  registry:
    image: registry
    restart: always
    ports:
      - "127.0.0.1:5000:5000"
    environment:
        REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
    volumes:
       - "/containersVolume/registry:/data"

  watchtower:
    image: v2tec/watchtower
    restart: always
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
    command: --interval 30 --cleanup

  application:
    image: localhost:5000/application
    restart: always

Exécution du fichier docker-compose.yml :

sudo docker-compose up -d --build

Configuration de Gogs

Gogs est un projet OpenSource développé en Go dont l’objectif est de créer un clone de github. L’intérêt est de pouvoir héberger des projets git sur un serveur interne à notre système. Par la suite, gogs nous permettra aussi de mettre en place des webhooks pour lancer des builds Jenkins automatiquement lors d’un push git.

Après l’installation de gogs, on crée ensuite le projet git privé qui contiendra les sources de l’application.

Configuration de Jenkins

Jenkins nécessitera au moins les plugins suivants : gogs, git, Docker et Docker Pipeline.

Pour récupérer le code source présent sur Gogs, nous devrons configurer des "credentials". Il s’agit d’un élément qui permet d’indiquer à Jenkins la façon de se connecter à un service externe, ici Jenkins se connectera avec notre compte gogs. Configuration du credential : Global credentials, Username with password.

On crée un projet jenkins de type Pipeline en ajoutant l’URL du projet git et en associant le credential.

Configuration du webhook

Le webhook va nous permettre de lancer un "build" Jenkins au moment où un push aura lieu sur le projet git. Pour ce faire il faut se rendre sur Gogs dans les paramètres du projet puis dans "webhook". Ensuite, il suffit d’insérer l’URL du hook Jenkins : http://<Jenkins server>:8080/<nom projet jenkins>-webhook/

Configuration de WatchTower

WatchTower est un projet OpenSource dont le but est de mettre à jour les images de conteneurs docker de manière automatique. Cela fonctionne avec un worker qui vérifie à intervalle réguliers’il existe une image plus récente dans le registre Docker associé.

L’ensemble des configurations possibles de Watchtower est disponible sur la page github : https://github.com/v2tec/watchtower

En réalité, l’intervalle de traitement et le nettoyage des images est déjà configuré, en effet nous l’avons déjà mis dans le docker-compose :

command: --interval 30 --cleanup

Création des fichiers Jenkinsfile et Dockerfile

Le fichier Jenkinsfile est le fichier exécuté par Jenkins lors de l’exécution du pipeline. Dans notre cas, il créera un conteneur qui servira à exécuter le Dockerfile de notre application, à effectuer des tests sur ce dernier et à pousser l’image sur le registre Docker.

Le Dockerfile permet d’automatiser la création d’image Docker. Il permet ainsi d’installer toutes les dépendances nécessaires au bon fonctionnement de l’application et à l’installation de cette dernière. De plus un Dockerfile peut hériter d’un autre Dockerfile ce qui permet de se baser sur des scripts déjà créés par la communauté.

Les fichier Jenkinsfile et Dockerfile seront poussés eux aussi sur le dépôt git et seront à la racine du projet.

L’exemple porte sur le déploiement d’une simple API en Javascript.

Jenkinsfile :

node {

	def app
	currentBuild.result = "SUCCESS"

	try {
		stage('Clone repository') {
			checkout scm
		}

		stage('Build image') {
			app = docker.build("test")
		}

		stage('Test image'){
			app.inside {
				echo 'test'
			}
		}

		stage('Push image') {
			docker.withRegistry('https://127.0.0.1:5000') {
				app.push("latest")
			}
		}
	}
	catch (err) {
		currentBuild.result = "FAILURE"
		throw err
	}

}

Dockerfile :

FROM node
MAINTAINER Flavien JOURDREN <[email protected] >

ADD . /home/node/app
WORKDIR /home/node/app

RUN npm install

CMD npm run start
EXPOSE 3000

Ce que le Dockerfile fait :

  • « FROM node » : Hérite de l’image « node » créée par la communauté du registre publique docker hub.
  • « ADD . /home/node/app » : Ajoute les fichiers de l’application dans le répertoire /home/node/app du conteneur.
  • « RUN npm install » : Installe les dépendances npm.
  • « CMD npm run start » : Démarre l’application à chaque démarrage d’un conteneur de cette image.
  • « EXPOSE 3000 » : Expose le port 3000 du conteneur.

Mise en place final et tests

  1. Pousser le code source à déployer sur la branche « production » du dépôt git distant.
  2. Vérifier sur l’interface web de jenkins que le build a bien était lancé au travers du webhook.
  3. Une fois le build de l’application terminé, on vérifie que l’image a bien été poussée sur le docker registry en consommant son API (pour vérifier : http://ipRegistry:5000/v2/_catalog).
  4. Démarrage des conteneurs avec docker-compose.
  5. Vérifier que les conteneurs déployés fonctionnent bien.
  6. Repousser une nouvelle version sur la branche « production ».
  7. Cette fois-ci, WatchTower sera capable une fois la construction de l’image terminée et celle-ci mise à jour sur le registre docker, de détecter les modifications et de mettre à jour les conteneurs de cette image.
  8. Tester les dernières modifications de l’application.