Créer sa première pipeline CICD avec GitLab

devops Nov 15, 2022

Hello

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.