Chez Mayasquad, lorsqu’il s’agit de développement logiciel, nous apportons une attention toute particulière à l’intégration et au déploiement continu.
On vous partage alors nos paroles d’experts qui vous permettront sans doute, d’y voir un peu plus clair !
CONTINUOUS INTEGRATION ET CONTINUOUS DEPLOYMENT ? KÉSAKO ?
Pour commencer, qu’est ce que la continuous integration et le continuous deployment ?
Nous développons nos applications en utilisant, bien évidemment, un gestionnaire de sources. Cela permet de partager une version centralisée du code entre les différents développeurs, de conserver son historique, la chronologie des ajouts à la base de code et de revenir rapidement à une version antérieure si besoin.
Cela n’est qu’une première étape, le concept d’intégration continu consiste à vérifier qu’à chaque ajout à la base de code, le développeur n’introduit pas de régressions à l’application et ceci de manière complètement automatisée.
Mais on peut aller plus loin !
Le déploiement continu consiste à détecter ces changements, vérifier leur bon fonctionnement à travers l’intégration continue puis:
👉 À construire un livrable de l’application
👉 À déployer, automatiquement, ces nouveaux livrables sur les environnements de recette ou de production
Un livrable, c’est la version exécutable, construite sur la base du code, de l’application.
Mais toutes les applications ne sont pas construites en livrables identiques, cela dépend beaucoup de la technologie employée.
Une application Java ou Node.js, par exemple, ne seront pas construites ni exécutées de la même façon.
Afin d’avoir une façon unifiée de gérer les différentes types d’application, nous avons fait le choix d’utiliser Docker et de créer des livrables sous la forme d’images Docker. Cela permet de faire abstraction de la technologie utilisée et d’avoir un processus de CI / CD unifié pour toutes nos applications.
Nous considérons qu’une CI / CD saine et efficace est indispensable, cela permet :
À nos clients
👉 D’avoir accès, en phase de développement itératif, à un produit quotidiennement et automatiquement mis à jour en fonction des avancés de nos développeurs
👉 De pouvoir, en production, mettre à jour en 2 minutes montre en main une nouvelle version de leur application
À nos développeurs
👉 D’avoir un cadre de travail agréable, simple et efficace
👉 D’avoir la garantie que les nouvelles versions de leurs applications n’entraînent pas de régressions fonctionnelles ou techniques
Pour notre CI/CD, nous avons choisis de nous reposer sur les épaules de géants: Docker Gitlab et Google Cloud Platform.
LA « BIG PICTURE »
Pour cet article nous prendrons l’exemple très classique d’un back-end Node.js (Express) que l’on appellera my-js-backend.
Dans les grandes lignes, voici le fonctionnement cible :
Nous organisons nos bases de code autour de deux branches principales :
- Staging : cette branche représente la version de l’application de “recette”. Nous utilisons cette branche pour déployer les versions de test de l’application afin de permettre à nos clients de valider les changement apportées à leur application.
- Master : cette branche représente la version de “production”, c’est la version qui est directement mise à disposition des utilisateurs finaux.
Notre base de code contient un Dockerfile permettant de construire l’image de l’application.
Notre intégration continue détecte toute activité sur les branches de notre base de code :
Si une activité est détectée sur une branche quelconque :
👉 On exécute tous les tests de l’application
👉 En cas de succès, on construit l’image de l’application
Si une activité est détectée sur la branch staging :
👉 On exécute tous les tests de l’application
👉 En cas de succès, on construit l’image de l’application
👉 En cas de succès, on déploie cette image sur l’environnement de recette
Si une activité est détectée sur la branch master
👉 On exécute tous les tests de l’application
👉 En cas de succès, on construit l’image de l’application
👉 En cas de succès, on déploie cette image sur l’environnement de production
Pour chacun de ces scénarios, on s’arrête dès qu’une étape rate ou si l’intégralité des étapes passent avec succès.
DOCKER
Nous ne décrirons pas ici en détail Docker mais la définition donnée par 451 research est assez parlante :
Docker est un outil qui peut empaqueter une application et ses dépendances dans un conteneur isolé, qui pourra être exécuté sur n’importe quel serveur.
En gros, Docker nous permet d’assimiler tout ce qu’il faut à l’application pour pouvoir tourner et qui pourra être lancé, sans aucune modification, sur n’importe quel serveur.
D’excellentes introductions sont disponibles sur le net, comme celle-ci.
DOCKERFILE ET SCRIPTS
Nous créons un dossier docker à la racine de notre application contenant:
👉 Un Dockerfile
👉 Un dossier scripts contenant
👉 run.sh responsable du lancement de l’application
👉 test.sh responsable du lancement des tests de l’application
De la façon suivante:
Docker
├── Dockerfile
└── scripts
├── run.sh
└── test.sh
Voici ici le Dockerfile de notre application Node.js, les commentaires expliquant chaque étape:
De façon optionnelle, vous pouvez ajouter un fichier .dockerignore à la racine de votre code base afin de spécifier des fichiers ou dossiers qui ne seront pas importer dans le container au moment de la commande copy.
Voici le contenu d’un run.sh :
Notre run.sh lance notre application Node.js, en mode production, via pm2 en mode cluster avec autant d’instances que de CPUs disponibles.
Ce run.sh sera donc lancé automatiquement au lancement du container (dernière ligne du Dockerfile).
Le test.sh doit, lui, lancer les tests :
Pour finir, pour créer notre image depuis ce Dockerfile la commande à lancer est :
docker build -f docker/Dockerfile -t my-js-backend .
Et vous pouvez tester votre application avec la commande :
docker run my-js-backend
GOOGLE CLOUD PLATFORM
1. Créez un nouveau projet (my-js-backend par exemple) dans votre console Google Cloud Platform et récupérez son PROJECT-ID sur votre dashboard, my-js-backend dans notre cas. (https://cloud.google.com/resource-manager/docs/creating-managing-projects#identifying_projects).
2. Puis dans le menu IAM & Admin, dans la partie Service account, créer un nouvel acompte de service.
Donnez lui un nom (ci-cd par exemple)
Lors de l’étape d’après, donnez les rôles suivant (ces permissions pourraient être surement plus fines que celles-ci, vous pouvez affiner si besoin)
- Service account user
- Cloud run admin
- Storage admin
Lors de l’étape suivante, cliquez sur “Create key” et sélectionnez “JSON”, téléchargez le fichier contenant la clé et conservez le !
3. Allez dans le menu Container Registry et cliquez sur activer les APIs
4. Dans le menu Cloud Run, cliquez sur Create service
- Cliquez sur Start using Google Cloud Run
- Créez deux services, l’un pour votre environnement de recette l’autre pour l’environnement de production
- Dans la partie Deployment platform, choisissez Cloud Run (fully managed)
- Choisissez votre région (nous prendrons europe-west1 pour le reste de l’article)
- Donner un nom à votre application (my-js-backend-staging et my-js-backend-prod par exemple)
- Choisissez un mode d’authentification pour votre application (Allow unauthenticated invocations dans le cas d’un site web ou d’API public)
- Validez
Lors de l’étape suivante, sélectionnez l’image de démo “hello”
Nous en avons fini pour la partie Google Cloud Platform !
GITLAB
Gitlab est un outils de gestion de sources décentralisé mais Gitlab offre beaucoup plus que la gestion de sources.
Nous fonctionnons sur tous nos projets, comme je vous l’expliquais tout à l’heure, avec une branche staging correspondant à la version de la base de code de recette et avec une branche master correspondant à la base de code de production.
Toutes les autres branches sont utilisées comme le veulent nos développeurs.
Gitlab offre un service de CI / CD intégré, dont le comportement est piloté via un fichier gitlab-ci.yml qui doit être situé à la racine de votre base de code.
SETTINGS
Pour commencer, aller dans le menu Settings – CI / CD et dans la partie variables ajoutez dénommée GCP_SA_KEY dont la valeur est le contenu du fichier JSON de clé de l’acompte de service que vous avez créé tout à l’heure.
GITLAB-CI.YML
Voici le .gitlab-ci.yml de notre application Node.js, les commentaires expliquant chaques étapes :
Nous ne rentrerons pas dans le détail du fonctionnement du .gitlab-ci.yml et de la CI de Gitlab mais voilà ce qui nous intéresse dans les grandes lignes :
- La partie variables indique les variables dont nous aurons besoin
- La partie before_script indique ce qu’il faut lancer avant toute chose
- L’authentification à la Docker registry de Gitlab
- L’authentification à la Docker registry de GCP
- La partie staging décrit des étapes de notre CI/CD
- Construction du livrable
- Lancement des tests
- Push de l’image vers le registry
- Déploiement de l’application
- La partie build-image et test-image se déclenchent pour tout commit sur n’importe quelle branche (par de mention only à la fin de la section comme nous en verrons plus tard)
- build-image construit l’image sur la base du Dockerfile présent dans le dossier docker de nos source puis la pousse dans la registry Gitlab
- test-image récupère l’image dans la registry Git[lab et la lance avec le script lançant les tests de l’application
- Puis nous avons deux blocs spécifiques aux branches master et staging de vos sources grâce aux options : only master et only staging
- push-image-master récupère l’image sur la registry Gitlab et la pousse sur la registry GCP
- deploy-master installe le SDK GCP et l’utilise pour déployer, sur le service Google Cloud Run de production, l’image que nous venons de pousser
- push-image-staging et deploy-staging font de même mais sur le service Google Cloud Run de staging
Cette configuration permet donc :
👉 De construire et de tester automatiquement tout ajout sur n’importe quelle branche de vos sources Gitlab
👉 De déployer automatiquement votre application en staging pour tout changement sur la branche staging
👉 De déployer votre automatiquement votre application en production pour tout changement sur la branche master