Créer sa première pipeline CICD avec GitLab
Nov 15, 2022Hello
On part d'une application que tu as. En python, en js ... peu importe
On va automatiser 4 étapes :
- Lancement des tests
- Build une image Docker
- Pousser cette image sur Docker Registry
- Déployer l'image sur un serveur
Pour ça, on va utiliser la CICD de Git Lab
Si tu as un doute sur ce qu'est la CICD, je te remets un article d'introduction
Pour mon exemple, j'ai une app Python, avec un Dockerfile de prêt, des tests et un serveur Ubuntu de dispo sur un cloud public.
Et bien sûr, mon code est déjà sur un dépôt Gitlab
C'est parti
On initialise la CICD dans Gitlab : tu ajoutes un fichier .gitlab-ci.yml à la racine de ton projet
Tu peux le faire par l'éditeur web de Gitlab directement.
Il faut avoir aussi quelques bases en YML, hein.
On a vu qu'on a 4 tâches à faire.
Chacune de ces tâches s'appelle un JOB. C'est le block dans la CI que l'on manipule le plus.
Pour lancer mes tests, en local, je dois faire "make test".
Donc je commence par écrire :
Ce job a :
- un nom : run_test (tu peux mettre ce que tu veux, mais il vaut mieux rester lisible)
- une section "script" : contient les commandes qui seront exécutées
- Un tiret par commande
Et voilà, la version la plus simple.
Sur mon poste pour que ça fonctionne "make test", j'ai besoin de Python, PIP et Make
Mais là, dans la CICD où va être exécutée ma commande ?
En fait, elle va être exécutée sur des Gitlab Runners
Ce sont des "server" Linux, Windows ou même MacOS géré par Gitlab directement
En fait, Gitlab utilise des conteneur Docker directement.
Donc nos commandes vont être exécutées à l'intérieur d'un conteneur.
Et un conteneur se base sur une image.
Et donc en fonction des images que tu utilises tu auras des outils déjà prêts à l'emploi dans le conteneur
Par défaut, Gitlab utilise l'image Ruby.
Pour nous, ça ne marchera pas, il faut un conteneur avec python et tout
On peut le faire dans notre YML. Pour chaque job, tu peux spécifier une image à utiliser pour le job.
On va pas s'embêter, on va prendre l'image Python directement (à chercher sur le Docker Hub)
Tu peux spécifier un tag "image: python:3.9-slim-buster"
C'est une bonne pratique de fixer la version de l'image que tu veux utiliser, pour garder le contrôle sur comment s'exécute ta CICD.
Si tu ne mets rien, Gitlab va toujours prendre la dernière dispo.
Avec ça, on a donc Python et PIP. Il me manque Make :
On ajoute une section "before_script" qui comme son nom l'indique s'exécute AVANT la partie script
On lance un APT pour installer un package sous Linux (toujours faire un update avant)
La syntaxe && est du shell. Si l'update fonctionne, alors il fera l'install
A ce stade, tu peux commit le YML.
A chaque commit, ta CICD va se déclencher.
Puis dans CI/CD -> Pipeline :
Tu peux voir ta 1ere pipeline exécutée :
En cliquant sur le job, tu accèdes à l'écran de log.
Tu peux voir tout ce qui a été fait par le job.
On a fini le 1er Job.
On passe au 2eme et 3eme : build l'image docker et push sur un repo d'image
Pour ça, tu peux te créer une compte gratuit sur Docker Hub.
Tu crée un repo privé
Une fois que c'est fait, sur la page du repo, tu as la commande à lancer pour push une image dessus
En l'occurence : docker push imranesubstack/demoapp:tagname
(bien entendu avec tes propres infos)
Comme sous gitlab, pour pouvoir push sur un repo docker privé, je dois m'authentifier
donc je dois faire un "docker login" AVANT
Sur ma machine, c'est facile. Là on doit donner à Gitlab mon login et mon password
C'est chaud, de mettre ça dans un fichier YML versionné comme ça à la vue de tous
Il faut surtout ne pas le faire.
Ca permet d'introduire une notion de Gitlab : Les rôles.
Ce sont des droits que tu accordes ou pas aux Dev de ton équipe.
Dans "settings" sur ton repo, tu peux gérer qu'est ce qui est visible ou pas :
Exemple : un Admin voit les settings, un Dev ne voit que le repo et peut écrire la CICD
Donc nous on va stocker le login/mdp Docker Hub sans que les dev ne les voient
Mais ils doivent pouvoir l'utiliser quand même dans la CICD.
Comment qu'on fait ?
En tant qu'admin, tu vas dans Setting -> CI/CD -> Variables
On va stocker ces informations en dehors du repo git pour qu'il ne soit visible que du runner
Tu cliques sur "Variables", "Add"
Dans le formulaire qui s'ouvre tu crées :
- en key : DOCKER_USER (c'est le nom de la variable)
- en value : "ton user docker hub" (c'est la valeur de la variable)
Tu crées une deuxième variable pour le mot de passe : DOCKER_PASSWD
Dans les deux cas, que tu cliques sur "Mask variable"
Cela permet de ne pas afficher la valeur de la variable dans l'écran de log (visible de tous les dev)
Ca aide à la sécurité.
Essayons : CI/CD -> Editor (pour continuer à modifier le fichier)
On continue à la suite notre nouveau job
On lance le build dans le dossier courant (la racine du projet qui contient le Dockerfile)
On met le -t pour lui donner le même que le repo Docker Hub et une version
Puis on push notre image vers Docker Hub
On se login sur docker hub en before_script, en utilisant le nom des variables que l'on a créées plus haut.
On est pas mal.
Mais on peut aussi utiliser les variables pour éviter des copier/coller (comme avec du code) :
Là, la variable est définie dans le job build_image et n'est donc utilisable que dans ce job.
Si tu mets en dehors, c'est utilisable globalement
Là, si tu as bien suivi, tu te dis il y a un problème.
On va utiliser la commande "docker" dans le runner gitlab qui lui même est une image docker.
Et comme on n'a pas spécifié une image, c'est l'image Ruby par défaut qui va être prise.
Ca ne marchera pas.
Il nous faut une image docker, avec docker dedans :o
C'est la notion de Docker In Docker (DIND)
Donc en fouillant la doc de gitlab, on voit ce qu'il faut faire :
Alors on ajoute l'utilisation d'une image docker. Ok.
Nouveauté : services
Ca permet de dire à Gitlab de lancer un deuxième conteneur en même que le runner lui-même
Exemple : pour lancer tes tests sur ton code, tu peux avoir besoin d'une bdd. Tu peux dire donc dans ta CICD, de lancer un conteneur MYSQL en même temps que ton job run_test
Gitlab s'assure que les deux peuvent communiquer
Ici pour que Docker fonctionne dans notre runner, il a besoin d'un Daemon Docker
Qu'on ajoute donc dans la section "services"
Petite subtilité qu'on trouve dans la doc, on a besoin d'une variable concernant les certificats pour que ca fonctionne correctement.
Et voilà, tu peux commit. Et ta pipeline va se déclencher de nouveau :
Quand c'est fini. Tu peux check ton repo docker hub et voir ton image si elle est bien présente
Problème : les deux jobs se lancent en parallèle !!
C'est pas ce que je veux.
Je veux d'abord les tests, puis, si c'est ok, le build de l'image. Sinon c'est pas bon
On ajoute une section globale : stages.
On défini les 2 étapes de notre pipeline : test et build
Puis on dit dans chaque job, à quelle stage il appartient.
On peut mettre donc plusieurs jobs dans une même stage.
Tous les jobs d'une stage se lancent ensemble.
Les jobs se lancent dans l'ordre des stages définies (ici test est en 1er)
Tu commit
Et tu vois les deux :
On a fini l'étape 1, 2 et 3.
Il ne manque plus que le déploiement sur un serveur : mon ubuntu sur le cloud
Bon comment faire depuis la CICD ?
Déjà comment j'aurais pu faire depuis mon poste ?
Bon là, faut connaitre un peu linux.
Pour me connecter sur mon linux, j'utilise une pair de clé SSH (privé et public)
Cela permet de se connecter sans utiliser de mot de passe (à saisir, c'est impossible dans un runner)
Il y a une article pour générer la paire de clé
On va en avoir besoin
On oublie pas d'installer Docker sur notre ubuntu
Une fois que c'est fait et qu'on arrive à s'authentifier par clé en ssh, on est bon
Avec un truc du genre : ssh -i ~/.ssh/myprivatekey root@IPDUSEVEUR
On commence par mettre la clé en Variable dans gitlab (comme tout à l'heure)
Sauf qu'on la stocke pas en tant que Variable, mais File
En effet, SSH a besoin d'un fichier Private key et non d'une chaine de caractère pour fonctionner
Gitlab, c'est puissant et ca permet de le faire.
On l'appelle SSH_KEY
On ajoute un stage "deploy"
On ajoute un job deploy dans cette stage
Dans la section script on se connecte en SSH
Petite subtilité le : -o StrickHostKeyChecking=no
Quand tu te connectes en SSH sur ton serveur, tu as toujours un message du type "add finger print Yes or No ?"
Quand tu es devant ton terminal, tu peux taper Yes or No. Dans un runner gitlab, pas possible. Avec cette option, ca propose pas le message.
Quand on se connecte en ssh, on lance une seule ligne de commande qui sera entre ""
Un simple docker run en disant de lier le port 5000 du serveur avec le port 5000 du conteneur
Le docker run fait un pull automatique du Docker Hub puis lancer le conteneur à partir de l'image
On oublie pas de se login avant : pas dans un before script cette fois-ci. On est sur mon serveur Ubuntu, donc y'a pas de before_script.
Attention au double && . C'est du shell, on demande donc à ssh d'exécuter une "grande" ligne de commande et on a besoin de ça pour séparer les différentes commandes.
Par contre, là, à chaque fois qu'on va lancer la CICD, on va faire un docker run.
Or la première fois ca va marcher.
Mais la deuxième fois, ca va planter. Car on a lancé le docker sur le serveur, donc le port 5000 est déjà utilisé.
Et à aucun moment on libère ce port. Donc au deuxième lancement, Ubuntu va nous dire : port 5000 déjà utilisé. Ca va failed.
C'est normal, notre conteneur tourne toujours et on lui a pas dit de s'arrêter avant de se lancer avec la nouvelle image.
Donc on va le faire avant de faire un run
Bon, c'est un peu technique :
- docker ps -aq : liste tous les conteneurs qui tournent en ne donnant que leur id
- xargs docker stop : prend le résultat de la commande juste avant (le ps -aq) et lance un docker stop pour chaque ligne
- xargs docker rm : pareil, mais on pense à enlever le conteneur complètement
Là, quand tu lances, ca marche pas.
La connexion SSH ne se passe pas.
En effet, on a utilisé un fichier KEY en variable.
Mais ssh est tatillon. Il veut que les droits sur le fichier Private Key soit dans un état spécifique -rw------- (juste les droits de lecture et écriture pour le owner)
Ca c'est des révisons de la gestion des droits sous linux :p
On fait un chmod 400 sur le fichier :
On ajoute le -d dans le docker run pour le mettre en mode background. Il se lance et tourne en fond.
Ca permet à la CICD de se terminer.
Tu commit et tu regardes ta pipeline :
Tu peux tester ton application.
Bravo !
Il y a plein de fonctionnalités sur Gitlab qu'on a pas vu :
- Les artifacts
- Le caching
- Job templates
- Avoir ses propres runners gitlab
- Utiliser le registry docker de gitlab au lieu de docker hub
- Exécuter la CICD que dans certaines conditions ou sur certaines branches uniquement
Sur la CICD, il y a aussi des cas d'utilisation plus avancés :
- Docker compose
- Kubernetes
- Microservices
- Multi-stage
- Versionner dynamiquement
Tu sais quoi faire maintenant :p
Le fichier dans son intégralité :
La newsletter pour ne rien louper
Rejoins les 2500 lecteurs de la newsletter pour obtenir des conseils, des stratégies et des ressources pour développer et monétiser tes compétences Tech.