Déploiement continu avec Docker entre Gitlab et Rancher

Avec la progression de l’utilisation des containers d’application (Docker, AWS, etc …) favorisant les architectures microservices, nous divisons nos programmes en un ensemble de petits programmes communiquant les uns avec les autres. Cette division engendre une multiplication croissante du nombre de machines qui, avec le temps, deviennent de plus en plus difficiles à gérer. Par conséquent, il est nécessaire d'utiliser un gestionnaire de containers ainsi que l'élaboration de procédures de déploiement. Et comme par hasard, mon article parle de ça !

Thumbnail

Présentation

La procédure que je vous propose est plutôt simple : à partir d'un simple commit sur notre serveur Git fourni par Gitlab, nous allons construire notre machine, lancer nos tests (Oui, car c'est important!) et si tout se passe bien : on balance le tout en prod’. Pour faciliter la compréhension, car il y a beaucoup de choses à assimiler, j’ai préparé pour vous un projet sous Symfony3 pour que vous puissiez le faire de votre côté sans trop vous prendre la tête avec le superflu.

Ah mince, j’ai oublié d’introduire mon plan … On commencera par configurer notre projet Symfony, puis par configurer Gitlab et Rancher. Bien entendu, si vous ne connaissez pas Docker, Rancher et Gitlab, je vous laisse le soin d’apprendre ces prérequis de votre côté et revenir lire l’article. Pour les plus motivés, deux petits diagrammes pour avoir une vue d'ensemble de ce qu'on s'apprête à faire.

Objectif
Diagramme d'activité
Note
Dans la suite de l’article, vous devrez changer par vos propres valeurs la variable CONTAINER_TEST_IMAGE dans le fichier .gitlab-ci.yml et l’image à monter de votre projet Symfony dans l'interface Rancher.

Projet


Configuration de Symfony

Comme annoncé dans la présentation, vous pouvez directement utiliser le projet d'exemple disponible en ligne qui vous permettra de suivre plus facilement l'article. Nous allons devoir configurer Symfony, créer l’image Docker contenant Nginx et PHP FPM avec son entrypoint et configurer le fichier .gitlab-ci.yml pour Gitlab.

Sachant que nous allons bien faire les choses, c’est à dire lancer nos tests unitaires avant de déployer notre image, nous allons tout d’abord installer PhpUnit. Bien entendu, si vous ne souhaitez pas lancer les tests, c'est à vous que revient d'adapter la suite du post.

Via Composer, nous installons PhpUnit et ainsi nous pourrons lancer nos tests unitaires à l’avenir. Pour l’installer, voici la commande à exécuter à la racine de votre projet Symfony.


$ composer require phpunit/phpunit
                                    

Ensuite, nous définissons nos constantes pour nos bases de données : une pour la prod et une pour les tests. Prêtez bien attention aux noms des HOST (“prod-mysql” et “test-mysql”) : ils nous seront utiles bientôt.


# app/config/parameters.yml.dist
database_host:     prod-mysql
database_port:     ~
database_name:     symfony
database_user:     root
database_password: symfony
test_database_host:     test-mysql
test_database_port:     ~
test_database_name:     database_test
test_database_user:     database_test
test_database_password: database_test
                                    

Pour finir, nous demandons gentiment à Symfony de configurer notre base de données de tests (Oui car nous ne souhaitons pas foutre le bordel dans notre base de données de prod).


# app/config/config_test.yml
doctrine:
   dbal:
       driver:   pdo_mysql
       host:     "%test_database_host%"
       port:     "%test_database_port%"
       dbname:   "%test_database_name%"
       user:     "%test_database_user%"
       password: "%test_database_password%"
       charset:  UTF8
                                    

Jusqu’ici, c’est plutôt simple : Symfony utilisera nos constantes pour initialiser nos bases de données selon l’environnement souhaité et nous avons Phpunit dans notre projet pour lancer nos tests unitaires.


Image Docker

J’avais prévenu : je parlerais peu de Docker. Pour faire rapide, on va avoir besoin de configurer notre serveur ainsi que son point d'entrée (l’entrypoint) qui donnera l’ordre à notre image d'exécuter un traitement désiré.

Pour l’image, une configuration plutôt simple pour un serveur web basé sur Debian Jessie : PHP-FPM, Nginx, Composer (qui lui-même aura besoin de curl et git pour les dépendances) et Supervisor. Ce dernier s’occupera de lancer Nginx et PHP-FPM car Docker ne permet de lancer qu’un seul processus. Or, nous avons besoin d'en lancer deux. Supervisor sert à ça ! Bien entendu, nous balançons dans notre image nos configurations pour Nginx et Supervisor, notre entrypoint ainsi que les sources de notre projet Symfony. Ne pas oublier d'ouvrir les ports pour que notre site soit accessible depuis l’internet mondial !

Je n'entre pas dans les détails de la configuration du serveur, vous avez tout ce qu’il vous faut dans le projet d’exemple pour décortiquer avec exactitude comment ça fonctionne.


# Dockerfile
FROM debian:jessie

MAINTAINER Anthony K GROSS

WORKDIR /src

RUN apt-get update -y && \
	apt-get upgrade -y && \
	apt-get install -y wget && \
	apt-get install -y php5-common php5-cli php5-fpm php5-mcrypt \
	                    php5-mysql php5-apcu php5-gd php5-imagick \
	                    php5-curl php5-intl php5-sqlite && \
	apt-get install -y curl git && \
	php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \
    php composer-setup.php && \
    php -r "unlink('composer-setup.php');" && \
    chmod a+x composer.phar && \
    mv composer.phar /usr/local/bin/composer && \
    rm -rf /var/lib/apt/lists/* && \
    apt-get autoremove -y --purge

RUN apt-get update -y && \
	apt-get upgrade -y && \
	apt-get install -y supervisor nginx && \
    rm -rf /var/lib/apt/lists/* && \
    apt-get autoremove -y --purge && \
    usermod -u 1000 www-data

COPY entrypoint.sh /entrypoint.sh
COPY conf/supervisor /etc/supervisor/conf.d
COPY conf/nginx /etc/nginx
COPY src /src

RUN chmod 777 /src -Rf && \
    mkdir -p /logs && \
    chmod 777 /logs -Rf && \
    chmod +x /entrypoint.sh && \
    sh /entrypoint.sh install && \
    rm web/app_dev.php

EXPOSE 80
EXPOSE 443

ENTRYPOINT ["/entrypoint.sh"]
CMD ["run"]
                                

Le coeur de la tomme : l’entrypoint. Il faut savoir que notre image Docker se lance par un point d'entrée qu’on peut redéfinir. Si vous regardez le Dockerfile à gauche, nous avons défini que ce point d'entrée serait notre fichier entrypoint.sh.

Nous souhaitons que, selon le paramètre que nous envoyons à notre fichier entrypoint.sh, l'image exécute une fonction particulière. Vous suivez ? Autrement dit, qu’il "install”, “tests” ou “run” notre serveur. Je recapitule : si je fais “/entrypoint.sh install”: ça m’installe les dépendances de Composer, “/entrypoint.sh tests”: ça me lance les tests unitaires et “/entrypoint.sh run” me lance Supervisor, c'est à dire Nginx et PHP FPM. Je sens que je vais en perdre quelques-uns d’entre vous là …


# entrypoint.sh
#!/bin/bash
set -e

install() {
    composer install
    php bin/console assets:install
}

tests() {
    php vendor/bin/phpunit -c /src/
}

run() {
    supervisord
}

case "$1" in
"install")
    echo "Install"
    install
    ;;
"tests")
    echo "Tests"
    tests
    ;;
"run")
    echo "Run"
    run
    ;;
*)
    echo "Custom command : $@"
    exec "$@"
    ;;
esac
                            

Remarque : avez-vous remarqué que je lance “/entrypoint.sh install” dans le Dockerfile ? Le but étant que lors de la construction de notre image, sachant qu’elle possède les sources de l’application, Docker m’installe les dépendances de Composer directement. L'intérêt est que de cette manière, mon application est complète, prête à être lancée, avec tous ses prérequis.


Configuration de GitlabCI

Pfiou, si vous êtes encore d’attaque on va faire la suite : le fichier .gitlab-ci.yml. Ce fichier va permettre à notre Gitlab de savoir que nous voulons profiter de son serveur d'intégration continue (GitlabCI), c’est à dire, une machine qui lancera les traitements demandés à chaque fois que nous lui envoyions des modifications de notre projet.

Le fichier décrit ci-dessous demande à Gitlab que nous souhaitons, pour ce repository seulement, un serveur d'intégration continue utilisant Docker et que nous souhaitons utiliser du Docker dans cette machine (DinD signifie “Docker in Docker”).

GitlabCI fournit des variables par défaut comme $CI_BUILD_TOKEN, $CI_REGISTRY et $CI_BUILD_REF_NAME. Cette dernière est le nom de la branche git en cours. Quant aux autres, elles sont utiles pour éviter de définir manuellement notre serveur de stockage d’images Docker (Registry) et le mot de passe pour s'y connecter.

Notre procédure de déploiement est divisée en deux tâches : Build (Construction) et Deploy (Déploiement = mise en production). Nous nous occuperons que de la partie Build pour le moment.

Dans un premier temps, nous demandons à GitlabCI de créer notre image Docker à partir du fichier Dockerfile présent à la racine de notre projet. Une fois générée, nous créons une base de données MySQL de test ainsi que les identifiants de tests identiques à ceux de notre fichier de paramètres Symfony. Ensuite, on lie la base de données de test à notre image précédemment construite et nous demandons à Symfony de générer le schéma de la base de données. Vient le tour de lancer les tests unitaires. Pour finir, si les tests sont passés avec succès, nous nous connectons à notre Registry et poussons notre nouvelle image fraîchement créée sur celui-ci.


# .gitlab-ci.yml
image: docker:latest
services:
  - docker:dind

variables:
  # Ici vous mettez VOTRE image, pas la mienne :) !
  CONTAINER_TEST_IMAGE: registry.gitlab.com/anthonykgross/sample-continuous-deployment:$CI_BUILD_REF_NAME

stages:
  - build
  - deploy

build:
  stage: build
  script:
    - docker build --file="Dockerfile" --tag="$CONTAINER_TEST_IMAGE" .
    - docker run --name gitlab-mysql -e MYSQL_DATABASE=database_test -e MYSQL_USER=database_test -e MYSQL_PASSWORD=database_test -e MYSQL_ROOT_PASSWORD=root -d mysql:5.5.44
    - docker run --link gitlab-mysql:test-mysql $CONTAINER_TEST_IMAGE php bin/console doctrine:schema:update --force --env=test
    - docker run --link gitlab-mysql:test-mysql $CONTAINER_TEST_IMAGE tests
    - docker login -u "gitlab-ci-token" -p "$CI_BUILD_TOKEN" $CI_REGISTRY
    - docker push $CONTAINER_TEST_IMAGE

deploy:
  stage: deploy
  image: cdrx/rancher-gitlab-deploy
  only:
    - master
  script:
    - echo "Deployment not configured"
                            

Une fois terminées, vous pouvez envoyer vos modifications sur Gitlab via Git. Si vous avez été un élève assidu, le service GitlabCI a dû se déclencher et l'image Docker est en cours de construction.


Registry

Du coup, rendez vous dans votre projet sur votre Gitlab et allez dans Pipeline. Si tout s’est bien passé, vous verrez que celui-ci s’est déroulé avec succès et vous retrouvez votre image prête à l’utilisation dans Registry. Dans le cas contraire, je n’ai pas dû être assez clair et vous avez manqué un truc, ou vos tests unitaires sont faux. Dans les deux cas … débrouillez vous ! <3 Ou bien, précisez votre problème en commentaire que je puisse affiner la qualité de mon article.


Le build s'est bien passé
Notre image Docker dans le registry

Rancher


Services

MySQL
Détails du container MySQL
Symfony3

Si vous ne le connaissez pas encore, Rancher est le programme qui va gérer nos containers en production. Si vous ne savez pas l’installer, vous avez la procédure d’installation en Liens Utiles. Admettons qu’il reste des survivants qui liront la suite de cet article, nous allons paramétrer notre machine. Tout d’abord, nous allons créer notre base de données de prod, configurer le container de notre projet et générer les identifiants de l’API Rancher pour la partie finale du post.

Connectez vous sur votre serveur Rancher (http://yourdomain.com:8080 pour l'exemple), puis allez dans Infrastructure > Registries et Add Registry. Ici, nous ajoutons notre registry Gitlab pour que Rancher puisse avoir accès à nos images Docker.


Address : registry.gitlab.com # Ou l'adresse de votre registry
Email : Sérieusement ? débrouillerez vous ! :)
Username : Votre login Gitlab
Password : Votre mot de passe Gitlab
                                    

Ensuite, allez dans Environment > Default et Add Service. Comme dans la première capture, nous allons installer MySQL. La configuration à suivre :


Name : mysql
Image : mysql-5.5.44
Port : 3306 > 3306
                                    

Si vous connaissez Docker, vous savez à quoi correspondent ces champs. Si ce n’est pas le cas, je vous incite à revoir votre copie.

Une fois le container démarré, cliquez sur l’instance et dans l’onglet Container vous retrouvez l'état de la machine. Dans le Menu, recherchez Execute Shell : vous êtes désormais loggé dans votre container. Créez les identifiants MySQL pour votre base de données de prod; vous vous rappelez ? Dans le fichier parameters.yml.dist de notre projet Symfony. Les mêmes oui ! Note : n'oubliez pas de mettre l'accès à votre base de données depuis l'extérieur à votre utilisateur ! ("%" au lieu de "localhost")

La suite sera de créer un second container, celui de notre application Symfony.


Name : symfony3_app
# Ici vous mettez VOTRE image, pas la mienne :) !
Image : registry.gitlab.com/anthonykgross/sample-continuous-deployment:master
Port : 80 > 80
Liens : Mysql > prod-mysql
                                        

Rancher téléchargera l’image de votre Registry, créera le container à partir de votre image et liera l’instance MySQL nommé “mysql” au container de notre projet sous le nom de “prod-mysql” (parameters.yml.dist).


API

On arrive au bout les copains ! Sous Rancher, cliquez sur API et générez une Access Key et une Secret Key. Aucun prérequis à renseigner mais conservez vos identifiants, on va s’en servir tout de suite.


Gitlab

Dans votre projet sous Gitlab, trouvez la section Variables. Nous allons définir des constantes pour pouvoir les utiliser dans le fichier .gitlab-ci.yml. Pourquoi définir des variables au lieu d'écrire directement nos valeurs dans le fichier en question ? Tout simplement car si vous devez travailler avec plusieurs personnes sur le projet, vous ne serez pas contraints de rendre public les accès au serveur de production. S'ils sont écrits en clair dans le fichier .gitlab-ci.yml, tous vos collaborateurs pourraient avoir accès à vos clés. En tant que variables Gitlab, seul le serveur d'intégration continue connait leurs valeurs.


Variables

Définissez les variables comme ci-dessous :

Ajout des variables dans Gitlab

RANCHER_ACCESS_KEY : MY_RANCHER_ACCESS_KEY
RANCHER_ENV : Default
RANCHER_SECRET_KEY : MY_RANCHER_SECRET_KEY
RANCHER_SERVICE : symfony3_app
RANCHER_STACK : Default
RANCHER_URL : http://yourdomain.com:8080
                                    

Pour ce qui est de RANCHER_ACCESS_KEY et de RANCHER_SECRET_KEY vous l'aurez compris : elles serviront à se connecter à l’API de Rancher. La variable RANCHER_URL est l’adresse et le port d'accès à votre serveur, RANCHER_ENV et RANCHER_STACK sont vraiment spécifiques à notre gestionnaire de containers et à votre configuration. Rappelez vous, après l’ajout de notre Registry, nous avons ajouté notre machine Mysql dans l’environnement Default. Sachez que si vous utilisez Rancher avec la configuration par défaut, la valeur de ces deux variables sont “Default”. Sinon, à vous de savoir comment remplir ces champs. Enfin, RANCHER_SERVICE c’est le nom de la machine que vous voulez mettre à jour après chacune des mises à jour de nos sources; ici “symfony3_app” créée juste avant la génération des identifiants API.


Dernières modifications


Finalisation de la configuration de GitlabCI

Ça y est, on est à la fin ! Nous n’avons plus qu’à éditer notre fichier .gitlab-ci.yml et à ajouter dans l'étape “deploy” la commande pour remplacer notre image Docker par celle que nous venons juste de créer. Vous savez quoi ? C'est terminé =D


# .gitlab-ci.yml
...
deploy:
  stage: deploy
  image: cdrx/rancher-gitlab-deploy
  only:
    - master
  script:
    - upgrade --environment $RANCHER_ENV --stack $RANCHER_STACK --service $RANCHER_SERVICE --no-start-before-stopping --no-wait-for-upgrade-to-finish
                                    

Pour vérifier, ouvrez votre serveur Rancher et allez dans la liste des services en cours. Une autre fenêtre dans votre navigateur est nécessaire pour visualiser le lancement de GitlabCI dans votre Gitlab (dans l'onglet Pipeline). Modifiez les sources de votre projet et poussez les modifications sur la branche Master. Gitlab reçoit votre commit, lance vos tests unitaires puis signale à Rancher que celui-ci doit mettre à jour le service dont le nom est “Symfony3_app”. Regardez sur Rancher, votre service est en cours de mise à jour, puis de redémarrage et votre nouvelle image est en production. Bien entendu, si vous avez correctement configuré votre image Docker, vos noms de domaines, etc … (Tout ce qui vous est propre), vous devriez voir la nouvelle version de votre site en ligne.


Conclusion

Je sais ... Je me suis embarqué dans l'écriture d'un article plutôt indigeste mais il fallait absolument que vous sachiez ! Désormais, vous êtes en mesure de pouvoir faire du déploiement continue automatique sans aucune intervention humaine : directement du producteur au consommateur, et tout ça GRATUITEMENT ! N'hésitez à me le faire savoir si vous avez besoin de davantage d'informations, si vous avez un autre workflow à proposer, toussa toussa ! Sur ce, j'ai garé ma baleine en double file. Tchou !



  Le 14 Novembre 2016

Restez en contact

J'accepte régulièrement de nouveaux clients. C'est avec plaisir que je vous servirais.

Vous avez un projet à estimer, une demande de devis ou vous souhaitez être formé sur un domaine précis ? N'hésitez pas à me contacter et je vous répondrais avec plaisir dès que possible. Ne soyez pas timide ! Vous pouvez me rejoindre ci-après sur la plupart des réseaux sociaux si vous souhaitez me connaitre davantage.