Architecture SI Développement applicatif Développement applicatif DevOps

Même le plus petit projet mérite d’avoir son pipeline CI/CD (Partie 3) : le déploiement continu

Publié le 08/08/2021 Par Meher Hedhli

Dans le précédent article, nous avons vu comment automatiser le déploiement de notre application à la suite d’un commit sur la branche master de notre dépôt Gitlab. Il serait alors utile et intéressant de pouvoir le généraliser à toutes les branches. Voilà ce que nous allons voir dans cette 3e partie de l’article.

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

Retrouvez la 1er partie et la 2ème partie de cet article

L’idée désormais est d’obtenir une image Docker par feature branche. Ceci implique de demander au serveur de démarrer un containeur à chaque fois qu’une branche est poussée sur Gitlab. Dans ce cas, nous avons besoin d’un moyen permettant au serveur de router les requêtes HTTP vers le conteneur approprié. Par exemple, les requêtes adressées à l’URL master.blog.meritis.fr doivent se rediriger vers le conteneur principal (celui de la branche master), tandis que les requêtes adressées à feat-ci-config.blog.meritis.fr doivent être redirigées vers le conteneur relatif à la branche feat-ci-config.

 

Effectuer ce type de routage est le rôle d’un Reverse Proxy. Et il en existe un excellent adapté pour Docker : il s’appelle Traefik !

Comment faire la mise en place de Traefik pour une pipelines CI/CD ?

Puisque nous utilisons Docker, nous déploierons Traefik sur notre VM à l’aide de Docker également. Nous allons tout mettre dans le répertoire /opt/traefik.

$ sudo mkdir -p /opt/traefik

Afin que Traefik puisse fonctionner correctement, nous avons besoin d’un réseau Docker dédié. Créons-le et appelons-le « Web » :

$ docker network create web

Créons maintenant un fichier nommé docker-compose.yml dans notre répertoire Traefik dans lequel nous pouvons copier le contenu ci-dessous :

/opt/traefik/docker-compose.yml

version: "3"
services:
    traefik:
        image: traefik
        container_name: traefik
        restart: always
        networks:
          - web
        ports:
          - "80:80"
          - "8080:8080"
        volumes:
          - /var/run/docker.sock:/var/run/docker.sock
          - ./traefik.toml:/traefik.toml
    networks:
        web:
            external: true

Vous devez changer le nom de domaine (mydomain.fr) dans votre configuration. Pensez également à installer Docker Compose si vous ne l’avez pas.

C’est fait ? Démarrons alors Traefik !

$ cd /opt/traefik
$ docker-compose up -d

Pour vérifier que tout va bien, accédez à l’URL http://{your_vm_ip_address_or_dns}:8080 pour voir le dashboard Traefik. Nous y retournerons une fois que nous aurons déployé un nouveau containeur.

dashboard Traefik

Si vous souhaitez en apprendre davantage, consultez la documentation Traefik.

Rediriger les requêtes HTTP vers le bon conteneur Docker

Les requêtes HTTP provenant de Traefik doivent être acheminées vers le conteneur approprié. Cela se fait en fonction du nom de domaine.

Comme indiqué précédemment, les demandes adressées à master.mydomain.fr doivent être redirigées au conteneur principal (celui de la branche master), tandis que les requêtes sur feat-ci-config.mydomain.fr doivent être adressées à la branche feat-ci-config.

Pour y parvenir, vous devez configurer votre DNS afin que mydomain.fr et *.mydomain.fr pointent sur la même adresse IP.

Utiliser xip.io

Si vous ne disposez pas d’un nom de domaine et que vous avez la flemme de chercher à en acheter un puis de le configurer, ne vous inquiétez pas, j’ai une petite astuce pour vous qui ne coûte rien 😉

Nous allons utiliser xip.io. De quoi s’agit-il ? Xip.io est un nom de domaine magique 😀qui fournit un DNS générique pour toute adresse IP dans le monde.

Par exemple, si l’adresse IP de mydomain.fr est 220.20.30.20 alors 220.20.30.20.xip.io pointe sur « 220.20.30.20« . Mieux encore : master.220.20.30.20.xip.io pointe également sur « 220.20.30.20« .

Ainsi, pour notre branche feat-ci-config, on peut créer et déployer un conteneur, et configurer Traefik pour router toutes les requêtes adressées à feat-ci-config.[my-ip-adress].xip.io vers le conteneur ayant le tag feat-ci-config.

Et voilà, nous avons fini ! Vraiment ? Mais non, cela ne suffit pas. Il faut en effet désormais informer nos containeurs docker de l’arrivée de leur nouvel ami Traefik !

Comment configurer les containeurs Docker avec Traefik ?

Traefik fonctionne avec les étiquettes (ou labels) Docker. Lorsqu’un conteneur démarre, il analyse ses labels et achemine les requêtes vers ce conteneur en fonction de ses labels.

Ces étiquettes peuvent être ajoutées directement lors du démarrage du conteneur ou être intégrées à l’image Docker lors de sa création.

Nous allons mettre des labels dans l’image Docker et également dans la commande permettant de démarrer le conteneur pour tout ce qui relève des labels dynamiques (ceux qui seront créés par Gitlab CI/CD).

Les labels sont à ajouter dans le fichier Dockerfile.

LABEL traefik.docker.network=web traefik.enable=true traefik.port=8888 traefik.default.protocol=http

Ici, nous avons rajouté 4 labels :

  • traefik.docker.network=web : c’est le nom du réseau Docker à utiliser pour accéder à ce conteneur
  • traefik.enable=true : explicite
  • traefik.port=8888 : le conteneur écoute sur le port 8888
  • traefik.default.protocol=http : le conteneur utilise le protocole HTTP

Nous allons ajouter deux autres labels au niveau du job Gitlab permettant de déployer notre application. Plus précisément, dans la commande permettant de démarrer un containeur :

# start new container

- ssh $DEPLOYER_USER@$DEPLOYMENT_SERVER_IP "docker run --label traefik.backend=${IMAGE_NAME} --label traefik.frontend.rule=Host:${TAG_IMAGE}.${DNS} --name ${IMAGE_NAME} --network=web -d ${CONTAINER_IMAGE}"
  • traefik.backend=${IMAGE_NAME} : c’est tout simplement le « nom » du conteneur cible dans Traefik. Il pourrait avoir n’importe quelle valeur mais devrait être unique pour notre conteneur (sinon nous devrons faire de l’équilibrage de charge).
  • traefik.frontend.rule=Host:${TAG_IMAGE}.${DNS} : c’est la règle utilisée pour router les requêtes destinées à ce conteneur. Ici, la règle est Host:${TAG_IMAGE}.${DNS} qui est la concaténation de deux variables que nous avons déclarées dans notre manifeste Gitlab CI (voir la première partie de l’article). La valeur serait alors une URL qui pointe sur un sous-domaine de notre DNS ayant comme nom celui de la branche actuelle.

Déploiement automatique de nos feature branches (pipelines CI/CD)

Avec Traefik configuré et nos conteneurs intégrant les bons labels, nous sommes prêts à déployer une version de notre application par feature branche.

Nous allons donc éditer notre fichier .gitlab-ci.yml pour activer les étapes « release » et « deploy-staging » à toutes les branches commençant par « feat- » et qui auparavant étaient seulement activées pour la branche master.

Comment procéder ? Grâce à la directive « only » permettant de mettre en place des contraintes sur l’exécution d’une tâche. Nous pouvons dire qu’une tâche s’exécutera uniquement sur l’événement d’un push sur master ou d’une branche préfixée par la valeur « feat-« .

L’expression régulière /^(feat-|master).*$/ permettrait de matérialiser cette condition au niveau du job où nous voulons l’appliquer.

Voilà alors le contenu de notre manifeste Gitlab après ces changements :

image: maven:latest
services:
  - docker:dind
cache:
    paths:
      - .m2/repository
      - frontend-app/node_modules/
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}
    DNS: ${DEPLOYMENT_SERVER_IP}."xip.io"
stages:
  - build
  - release
  - deploy
project-build:
    stage: build
    script:
      - mvn clean install
    artifacts:
    paths:
      - backend-app/target/*.jar
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:
      - /^(feat-|master).*$/
deploy-staging:
    stage: deploy
    image: gitlab/dind:latest
    cache: {}
    services:
      - docker:dind
    variables:
      DOCKER_DRIVER: overlay
    before_script:
    # add the server as a known host
      - mkdir -p ~/.ssh
      - echo "$SSH_PRIVATE_KEY" | tr -d 'r' > ~/.ssh/id_rsa
      - chmod 600 ~/.ssh/id_rsa
      - eval "$(ssh-agent -s)"
      - ssh-add ~/.ssh/id_rsa
      - ssh-keyscan -H $DEPLOYMENT_SERVER_IP >> ~/.ssh/known_hosts
    script:
      - ssh $DEPLOYER_USER@$DEPLOYMENT_SERVER_IP "docker login -u ${CI_REGISTRY_USER} -p ${CI_REGISTRY_PASSWORD} ${CI_REGISTRY}"
      # stop container, remove image.
      - ssh $DEPLOYER_USER@$DEPLOYMENT_SERVER_IP "docker stop ${IMAGE_TAG}" || true
      - ssh $DEPLOYER_USER@$DEPLOYMENT_SERVER_IP "docker rm ${IMAGE_TAG}" || true
      - ssh $DEPLOYER_USER@$DEPLOYMENT_SERVER_IP "docker rmi -f ${IMAGE_NAME}" || true

# start new container

      - ssh $DEPLOYER_USER@$DEPLOYMENT_SERVER_IP "docker run --label traefik.backend=${IMAGE_NAME} --label traefik.frontend.rule=Host:${TAG_IMAGE}.${DNS} --name ${IMAGE_NAME} --network=web -d ${CONTAINER_IMAGE}"
    only:
      - /^(feat-|master).*$/

Et voilà notre pipeline CI/CD permet dorénavant de déployer une nouvelle version de notre application à chaque fois qu’une branche est créée et poussée 😃. Cela est indiqué dans le dashboard Traefik où nous pouvons voir qu’une nouvelle règle de routage a été ajoutée pour cette branche afin de pointer sur un containeur Docker qui lui a été dédié.

dashboard Traefik

L’application relative à la feature branche feat-ci-config est également accessible sur l’URL feat-ci-config.{ip-address}.xip.io

environnements Gitlab

Les environnements Gitlab pour pipelines CI/CD

Mais ce n’est pas encore fini, il reste encore un petit souci ! 🙁

Jusqu’à présent, nous n’avons aucun moyen de supprimer l’application et le containeur associé lorsqu’une branche est détruite (dans le cas où il n’est plus nécessaire de l’utiliser). À force de créer plus de branches, nous empilons des containeurs Docker sur notre serveur jusqu’à ce que celui-ci soit saturé et ne démarre plus.

Il faudrait ainsi trouver un moyen, depuis l’espace Gitlab ou lors de la suppression d’une branche, d’arrêter les containeurs inutiles et de supprimer les images Dockers associées. C’est là que les environnements Gitlab entrent en jeu 😀

En attendant de les voir dans la prochaine et dernière partie de cet article, je vous présente la manipulation à effectuer afin d’arrêter et de supprimer un containeur.

Connectez-vous en SSH sur le serveur puis, avec le nom du containeur et celui de l’image à supprimer, exécutez ces commandes :

$ docker stop container_name

$ docker rm image_name

Conclusion

Nous verrons aussi comment répondre à des problématiques d’optimisation du temps de build du projet ainsi que celui de son déploiement. Des nouveaux ajustements bonus seront également apportés à notre pipeline.
Découvrez notre expertise DevOps

 

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

bannière Meritis

Pas encore de commentaires

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.