Publié le 09/06/2021 Par Meher Hedhli

Avec les outils actuels, mettre en place une simple chaîne d’intégration et de livraison continue n’est pas un exercice difficile. Le faire, même pour un petit projet personnel, est un excellent moyen d’apprendre beaucoup de choses sur les pratiques DevOps. À cet effet, Docker, GitLab et Traefik sont des composants très intéressants à utiliser afin de mettre en place un pipeline CI/CD pour un projet informatique.

Même le plus petit projet mérite d’avoir son pipeline CI/CD

Dans cet article, je vais vous présenter comment déployer une application en utilisant Docker avec les services d’intégration et de livraison continue proposés par l’offre GitLab CI/CD.

L’objectif de la première partie de cet article est de mettre en avant les différents besoins et intérêts qui motivent l’application de ces techniques DevOps (pour un rappel sur ce qu’est le DevOps, c’est par ici). Y sont également présentés les outils et les technologies dont nous aurons besoin afin de mettre en place le premier lot de notre pipeline CI/CD permettant de créer et de publier le livrable de notre projet.

La seconde partie quant à elle nous donnera l’occasion d’améliorer notre système d’intégration continue en lui rajoutant la livraison continue. Nous pourrons ainsi déployer automatiquement notre application dès qu’un “Merge Request” est validé sur la branche “master”.

Enfin, nous verrons comment permettre à chaque nouvelle feature branche poussée sur GitLab d’avoir son propre environnement et éventuellement son propre DNS pour accéder à l’application grâce à l’outil Traefik couplé avec Docker.

L’objectif de la première partie de cet article est de mettre en avant les différents besoins et intérêts qui motivent l’application de ces techniques DevOps (pour un rappel sur ce qu’est le DevOps, c’est par ici). Y sont également présentés les outils et les technologies dont nous aurons besoin afin de mettre en place le premier lot de notre pipeline CI/CD permettant de créer et de publier le livrable de notre projet.

La seconde partie quant à elle nous donnera l’occasion d’améliorer notre système d’intégration continue en lui rajoutant la livraison continue. Nous pourrons ainsi déployer automatiquement notre application dès qu’un “Merge Request” est validé sur la branche “master”.

Enfin, nous verrons comment permettre à chaque nouvelle feature branche poussée sur GitLab d’avoir son propre environnement et éventuellement son propre DNS pour accéder à l’application grâce à l’outil Traefik couplé avec Docker.

Pourquoi GitLab ?

La livraison continue permet de tester votre application sur un environnement dédié, quasiment en temps réel. Le déploiement de votre application est alors automatisé et entièrement piloté par GitLab afin que :

• Il n’y ait pas de tâches manuelles lorsque vous voulez mettre une nouvelle version de votre application en production (donc pas d’erreur manuelle).
• Vous puissiez tester votre application dans un environnement proche de la production avant de la mettre en production.
• Le coût de déploiement en production soit considérablement réduit. On pourrait, en effet, effectuer autant de déploiements que dans une application sans une chaîne CD.
• Chaque feature branche puisse disposer de son propre environnement afin de pouvoir tester facilement le résultat des demandes de « Merge Request ».
• Le PO et l’équipe QA puissent valider la réalisation d’une User Story en testant la feature branche correspondante sur son propre environnement d’une manière indépendante sans attendre qu’elle soit livrée avec d’autres User Stories dans un environnement UAT.

Les étapes de déploiement

Dans ce qui suit, nous allons déployer une application en Spring Boot et Angular 7. Il s’agit d’une simple application qui servirait comme POC « Proof of Concept » pour cet article.

GitLab Pipeline Diagram

Nous allons ainsi : 

• Builder le projet et créer le livrable de l’application
• Créer une image Docker contenant l’application
• Stocker l’image créée dans le registry du GitLab
• Déployer l’application depuis le registry sur le serveur qui va l’héberger en utilisant Docker

 

Est-ce de l’état de l’art ?

Soyons clairs, non, ce n’est pas l’état de l’art. Si nous voulons être plus exigeants et malins 😎, nous utiliserions clairement l’intégration continue de GitLab avec Kubernetes pour déployer nos applications directement dans le cloud. Dans ce cas, nous la déploierons plutôt sur un seul serveur dédié.

En effet, en utilisant Kubernetes, nous pourrions faire :
• L’équilibrage de charge
• La haute disponibilité
• L’autoscalabilité
• Etc.

Alors, pourquoi déployer sur un seul serveur ? Eh bien… parce que c’est moins cher !

Au moment d’écrire cet article, il est possible d’avoir un VPS (Virtual Private Server) auprès de l’un des fournisseurs d’infrastructure et services cloud (AWS, OVH…) pour quelques euros par mois. J’ai donc pris une machine Ubuntu avec 1G de RAM / 25G SSD pour 5$/mois chez Digital Ocean (je les passerai en note de frais à Meritis 😄). La même configuration dans un environnement Kubernetes me coûterait environ 4 fois plus.

Pour être tout à fait clair : je n’aurais pas utilisé cette technique pour une application critique, mais pour l’exemple développé dans cet article avec une petite application du PoC, c’est parfait ! 😄

Plus tard, nous pourrons refaire ce même exercice avec la technologie Kubernetes dès que j’aurai pu développer mes compétences en DevOps, 😉. Pour l’instant, je garde ma casquette de développeur Java full-stack 😊 !

 

De quoi avons-nous besoin ?

Alors, pour pouvoir commencer, nous avons besoin de :
• Une application à déployer ! Dans cet exemple, je vais déployer cette simple application en Spring Boot et Angular. Vous pouvez obtenir son code source ici.
• Un compte GitLab (à créer si vous n’en avez pas)
• Un repo GitLab avec le service CI/CD activé dessus
• Un serveur dédié (je suppose qu’il serait sous la dernière LTS Ubuntu 18.04 mais n’importe quel OS Linux suffirait)

GitLab CI/CD

Pour ceux qui ne le connaissent pas, GitLab CI/CD va vous permettre d’automatiser les builds, les tests, les déploiements… de vos applications. L’ensemble de vos tâches peuvent être divisées en étapes et l’ensemble de vos tâches et étapes constituent un pipeline.
Chaque tâche est exécutée par des runners qui fonctionnent grâce à un projet open source nommé GitLab Runner écrit en GO.
Vous pouvez avoir vos propres runners directement sur votre machine.
GitLab propose également des runners publics qui vous épargnent une installation. Mais attention, il y a des quotas suivant le type de compte dont vous disposez. En compte gratuit, vous avez le droit à 2 000 minutes de temps de pipeline par mois. Les runners publics de gitlab.com sont exécutés sur AWS.
Et de la même manière, comme on est radins, nous allons nous servir d’un runner public pour builder et déployer notre petite appli gratuitement.
Pour la suite, je suppose que vous avez une connaissance de base sur GitLab CI, Docker et l’incontournable couple Java / Maven.
Comme vous le verrez, ce n’est pas vraiment difficile, mais il faut franchir plusieurs étapes afin d’arriver au bout de la chaîne de livraison continue. Si vous voulez l’essayer, attendez-vous à y consacrer au moins un jour de travail pour que cela fonctionne.

Comment réaliser le Build du projet avec GitLab CI/CD ?

Nous partons d’un projet ayant une structure Maven multimodule permettant de modulariser séparément le code du backend et celui du frontend. Il est constitué ainsi de deux modules enfants héritant d’un module Maven parent comme suit :

• Backend : contenant le code Java de l’application Spring Boot.
• Frontend : contenant le code source de l’application Angular.

Pour construire le livrable du projet en local, exécutez la commande «mvn clean install » au niveau de la racine du projet (le pom parent).

Le process du build est alors lancé et Maven va donc faire son travail pour nous fournir le livrable attendu. Cela se fait en plusieurs étapes :

• La première consiste à builder le projet frontend en Angular grâce à son plugin frontend-maven-plugin afin de générer les ressources de l’application web via les commandes « npm install » et « npm run build » dans le répertoire frontend-app/dist.
• Puis il s’agit de copier les ressources générées suite au build du module frontend dans le répertoire backend-app/target/classes/ resources. Cela permettra d’inclure ces ressources lors de la création du livrable .jar.
• Enfin, vous devez builder le projet backend-app et créer l’artifact .jar qui constitue notre livrable projet.

Ensuite, vous pouvez alors lancer l’application en exécutant la commande :

$ java -jar backend-app/target/backend-app-0.0.1-SNAPSHOT.jar

L’application serait alors accessible sur http://localhost:8080.

 

Comment builder le projet avec GitLab CI/CD ?

Maintenant que nous savons comment builder notre projet en local, voyons comment le faire avec GitLab CI/CD.
Pour que la CI/CD fonctionne sur GitLab, il nous faut un manifeste .gitlab-ci.yml à la racine du projet.
Dans ce manifeste, nous allons définir les différentes étapes de notre pipeline CI/CD telles que la compilation et le build du projet, l’exécution des tests unitaires, la création d’une image Docker, la connexion à distance à notre serveur pour déployer l’application, etc.

Nous allons donc créer le manifeste GitLab pour notre projet et y déclarer la première étape (ou stage) ainsi que la première tâche de notre pipeline qui va nous permettre de builder le projet à chaque fois que nous poussons une branche.

Voilà donc à quoi ressemble le contenu de notre fichier .gitlab-ci.yml :

image: maven:latest 
services: 
- docker:dind 
cache: 
paths: 
- .m2/repository 
- frontend-app/node_modules/ 
variables: 
MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository" # Ici on déclare toutes nos étapes 
stages: 
- build 
project-build: 
# On déclare que ce `job` fait partie de l'étape build 
stage: build 
script: 
- mvn clean install 
artifacts: 
paths: 
- backend-app/target/*.jar

Le cache sert à se souvenir de l’état de votre code entre les stages. Cela signifie qu’on ne téléchargera pas les dépendances Maven et celles de node_modules à chaque fois que nous poussons un commit. Il est ainsi intéressant de spécifier une liste de fichiers et de répertoires à mettre en cache tout au long de notre pipeline.

Les “artifacts” sont les résultats d’une étape que nous souhaitons reporter à d’autres étapes. Dans ce cas, on demande que tous les livrables .jar générés lors du build du projet backend soient intacts pour les prochaines étapes.

Comment créer et sauvegarder l’image Docker ?

Maintenant, “dockérisons” l’application Spring Boot que nous venons de créer. Cette manipulation s’effectue avec un simple fichier texte standard Docker qui inclut les commandes Docker natives utilisées pour décrire notre image.

Pour ce faire, créons un fichier nommé “Dockerfile” au niveau de la racine du projet backend. Son contenu devrait ressembler à ceci :

FROM java:8-jre-alpine 
VOLUME /tmp 
ADD /target/backend-app-0.0.1-SNAPSHOT.jar app.jar 

RUN sh -c 'touch /app.jar' 
CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-Dspring.profiles.active=prod", "-jar", "/app.jar"] 

EXPOSE 8888

Pour comprendre chacune de ces commandes en détail, je vous invite à consulter la documentation Docker.

Construisons alors l’image Docker en accédant au répertoire du module backend et exécutons la commande suivante :

$ docker build -t demo-pipeline-app .

Comme lors de l’étape précédente, une fois que tout est correctement réalisé en local, nous refaisons les mêmes étapes avec GitLab CI/CD. Nous allons donc modifier notre fichier .gitlab-ci.yml pour y ajouter une nouvelle étape permettant de prendre notre livrable .jar et d’en faire une image Docker que nous stockerons dans notre registre privé GitLab.
Avant d’ajouter cette nouvelle étape, il est nécessaire d’utiliser des variables d’environnement pour déclarer le tag et le nom que nous souhaitons donner à l’image Docker à créer. Ici, elle portera un tag équivalent à la valeur de la variable globale fournie par GitLab CI, ${CI_COMMIT_REF_SLUG} qui correspond au nom de la branche actuelle. Par ailleurs, son nom serait composé des deux variables “$CI_REGISTRY_IMAGE” et “$CI_COMMIT_REF_SLUG”.

variables: 
MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository" 
DOCKER_HOST: tcp://docker:2375 
IMAGE_NAME: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG 
IMAGE_TAG: ${CI_COMMIT_REF_SLUG}

Donc, pour la branche master, l’image à créer aura le tag “master” et le nom “registry.gitlab.com/meherhedhli/demo-pipeline-gitlab-cd:master

La nouvelle étape de notre pipeline est décrite comme suit :

release: 
image: docker:git 
services: 
- docker:dind 
variables: 
DOCKER_DRIVER: overlay 
stage: release 
before_script: 
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY 
script: 
- docker build --tag=$IMAGE_TAG ./backend-app --pull -t $IMAGE_NAME 
- docker push $IMAGE_NAME 
only: 
- master

Une commande importante qui est :

- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY

Elle sert à se connecter à GitLab registry en utilisant les variables d’environnement fournies par GitLab. Nous utilisons alors la variable $CI_JOB_TOKEN, qui est un token fourni par GitLab, pour autoriser la connexion au registre à partir de l’environnement CI.

Une fois connecté, nous buildons l’image et nous la poussons dans le registre GitLab.

La propriété « only » définit alors quelle branche est concernée par l’exécution de ce travail. Dans notre cas, il s’agit de la branche master.

Pour l’instant, nous nous contentons de recréer une image relative à la branche master dès lors qu’un merge request est validé. Plus tard, il s’agira de créer une image Docker par feature branche.

CONCLUSION

À ce stade, nous pouvons affirmer que nous sommes à mi-chemin de l’objectif à atteindre. Notre actuel pipeline nous offre un système d’intégration continue avec des tâches automatisées assurant la compilation, les tests unitaires, le build de l’artifact projet, la production et la publication de l’image Docker.

Dans la prochaine partie, nous allons développer notre système de livraison continue afin de pouvoir déployer l’application dans un serveur dédié. Tâche qui sera également automatisée dans notre chaîne CI/CD de GitLab. En attendant, nous pouvons continuer à déployer notre application d’une manière traditionnelle et manuelle. 😄
Découvrez la suite de l’article. 

Vous avez aimé cet article ? Partagez-le et laissez vos commentaires 😉
Découvrez également notre expertise DevOps

bannière Meritis

Vos commentaires

  1. Par mokhtar ammar, le 27 Jan 2020

    merci pour l’explication. 😀 simple et efficace.

  2. Par Dupeyrat Kévin, le 30 Juin 2020

    Très bon article, clair et précis
    Un pas de plus vers une double casquette Dev Full Stack et DevOps.

    Bonne continuation camarade

  3. Par Anthony, le 26 Oct 2021

    Salut. Merci pour le tuto. J’ai fait à peu près la même chose, à l’exception que le langage est python (je précise, je ne suis pas dev). Je veux savoir comment est-ce que tu crées une image par branche. J’ai installé kubernetes (avec Kubeadm), j’ai crée un secret docker-registry pour récupérer l’image de mon gitlab et j’ai crée des variables d’environnement via (configmap et secret).

  4. Par Marcel, le 15 Juil 2024

    Super pour ce tuto, effectivement simple et efficace bravo !

Publier un commentaire

Auteur

Meher Hedhli

Issu d'une formation d’ingénieur en informatique complétée par un Master 2 MIAGE de l'Université Paris Est-Créteil, Meher cumule plus de 3 ans d'expérience dans la conception et le développement des applications web. Il est ainsi capable d'intervenir aussi bien sur le backend que le frontend des projets en développement autour des technologies Java et Angular. Il découvre également les technologies de containérisation Docker et se forme sur les pratiques DevOps.