Tout le monde connait GitLab, et beaucoup de monde connait son outil d'intégration et de déploiement continus : GitLabCI 🦊.
Avec l'arrivée d'une application ne possédant aucun outil de CI/CD dans notre équipe, nous avons pu manipuler GitLabCI. C'est d'ailleurs le sujet d'une présentation sur laquelle nous avons travaillé avec un collègue (Jean-Baptiste Martin pour ne pas le citer 🙂).
C'est cette approche que je vais vous présenter dans cet article.
Sommaire
1 - Création d'un pipeline
2 - Notre premier job
3 - Le registry
4 - Les environnements
5 - GitLab Pages
6 - Kubernetes
The (happy) end
🛠 Création d'un pipeline
Pour créer un pipeline GitLabCI, c'est tout simple, il suffit de créer un fichier .gitlab-ci.yml à la racine de votre projet.
Les pipelines sont disponibles via le menu "CI/CD" > "Pipelines".
Notre premier pipeline
Un pipeline est un élément assez simple, ayant un status : in progress, pending, passed et (je ne vous le souhaite pas) failed.
En consultant un pipeline, nous pouvons voir sa structuration, il est constitué de 1 ou plusieurs jobs, organisés dans différentes colonnes. Tout cela se configure dans le fameux fichier .gitlab-ci.yml.
Je vous entend déjà raler en voyant l'extension yml, mais rassurez vous, GitLab a mis un outil CI Lint qui vous permet de valider vos pipelines. Utile pour éviter de voir vos pipelines planter car le yaml est invalide ! Il est disponible dans la page des pipelines :
Structuration du fichier .gitlab-ci.yml
Le fichier .gitlab-ci.yml commence généralement par les définitions de nos "colonnes". Il s'agit de stage. Un pipeline peut contenir autant de stage qu'il nous semble utile. Les différents traitements se feront de manière séquentielle.
Dans cet exemple il y a 5 étapes dans la construction de notre projet :
stages:
- build_and_push
- deploy_tis
- deploy_re7
- notif_prepa_liv_prod
- deploy_prod
- La première étape permet de builder une image docker de notre projet et le pusher sur un repository.
- La seconde deploie notre composant sur un environnement de fabrication.
- La troisième sur un environnement de recette.
- la quatrième envoie une notification dans slack qu'une livraison en production est en prévision.
- La dernière déploie notre application en production.
Voila graphiquement à quoi cela ressemble :
L'enchainement des différents stages se fait automatiquement si tous les jobs se font sans erreur. Dans l'exemple, il a été (heureusement) mis en place un système d'exécution manuelle. Notre application n'ayant très peu de tests automatisé, il est préférable de faire quelques tests avant d'envoyer une nouvelle version en production 🤭. Il est donc possible de spécifier qu'un stage est à exécuter uniquement après une action manuelle : when: manual
Ce qui visuellement dans notre pipeline est repérable grâce à la flèche noire en haut du stage :
Comment s'exécute le pipeline ?
Sur chaque étape, il est possible de mentionner la branche qui va enclencher notre pipeline.
only:
- dev
- master
Dans cet exemple, dès qu'un push est effectué sur la branche dev et master, le pipeline définit va s'exécuter.
Afin d'optimiser le temps de traitement de notre pipeline, il est possible (voire conseillé) de paralléliser les traitements. Pour cela plusieurs jobs peuvent être associés à un stage. Dans cet exemple, tous les jobs de build de composants sont réalisés en parallèle.
Au niveau du fichier .gitlab-ci.yml cela se traduit de cette façon :
- 1 stage
stages:
- build
- chaque job reçoit la configuration suivante :
📦 front :
stage: build
📦 api_go :
stage: build
📦 node :
stage: build
NB : Un pipeline peut également être programmé via la partie "Schedule". Accessible via le menu, un pipeline peut être programmé.
🛠 Les jobs
Notre premier job
Un job est ce qui va permettre de réaliser une ou plusieurs tâches. Le job le plus simple doit être composé d'une action dans une partie script :
job:
script :
- echo "Voici mon premier job"
Il existe d'autres mots clés qui vont nous permettre de faire des actions plus complexes qu'un simple echo 😁
L'attribut stage
Tout d'abord un stage peut lui être attribué, permettant ainsi de classifier nos jobs comme montré dans le paragraphe ci-dessus. Si on définit 2 stages tels que :
stages:
- first_stage
- second_stage
Nous pourrions avoir plusieurs jobs définis tels que :
job1:
stage: first_stage
script :
- echo "Voici mon premier job"
job2:
stage: second_stage
script :
- echo "Voici mon second job"
et nous pourrions avoir plusieurs jobs associés au même stage :
job1:
stage: first_stage
script :
- echo "Voici mon premier job"
job2:
stage: second_stage
script :
- echo "Voici mon second job"
job3:
stage: first_stage
script :
- echo "Voici mon troisième job exécuté lors du premier stage"
L'attribut image
Il y a ensuite le mot clé image. Il permet d'effectuer une action à l'aide d'image docker. Il peut être positionné pour l'ensemble des jobs dans la première ligne du fichier .gitlab-ci.yml, ou pour chaque job.
Commun à tous les jobs :
image: alpine
job1:
stage: first_stage
script :
- echo "Voici mon premier job"
job2:
stage: second_stage
script :
- echo "Voici mon second job"
ou pour chaque job :
job1:
image: alpine
stage: first_stage
script :
- echo "Voici mon premier job"
job2:
image: alpine
stage: second_stage
script :
- echo "Voici mon second job"
Si on a notre pipeline qui contient la plupart de job qui utilisent une image, il vaut mieux spécifier l'image comme globale à notre pipeline, puis à surcharger les jobs avec des images spécifiques :
image: alpine
job1: # utilisera l'image de base alpine
script :
- echo "Voici mon premier job"
job2:
image: seconde_image_docker # ce job spécifie l'image qu'il a besoin pour effectuer une action spécifique.
stage: second_stage
script :
- echo "Voici mon second job"
En choisissant l'image, on peut imaginer par exemple lancer des commandes maven pour pouvoir exécuter les tests et poursuivre le pipeline si tous les tests sont passés.
test:
stage: test
script:
- mvn clean verify
L'attribut only
L'attribut only est simple à comprendre, il permet d'éxécuter votre job seulement sur une branche ou une typologie de branche. Par exemple un déclenchement de job sur la branche master s'écrit comme ceci :
only:
- master
On peut également définir une liste de branches :
only:
- dev
- master
ou un pattern de branches :
only:
- features*
L'attribut when
L'attribut when permet de conditionné l'exécution d'un job. Cinq valeurs sont disponibles et permettent de conditionner l'exécution d'un job. Elles sont assez parlantes : on_success, on_failure, always, manual et la dernière arrivée manual.
Pour plus de détail sur les différents états, se référer à la doc GitLab https://docs.GitLab.com/ee/ci/yaml/#when
La première utilisation que j'ai utilisée, c'est celle spécifiée précédemment, c'est le manual. En effet, avec cette description, ce job ne s'exécutera qu'après intervention manuelle d'un utilisateur :
job1:
image: alpine
stage: second_stage
script :
- echo "Voici mon second job"
when: manual
L'attribut artifact
Un artifact est un moyen de récupérer des fichiers générés par un job. Cela peut servir à récupérer un rapport de test par exemple. Afin de ne pas trop surcharger les infrastructures et ne garder indéfiniement des fichiers, la notion d'expiration est possible.
expire_in: 1 week
Par exemple, pour ce site réalisé avec l'outil gohugo, j'exécute dans un job la commande hugo qui permet de générer le site statique. Tous les fichiers vont dans le répertoire public :
artifacts:
paths:
- public
Sous l'interface du pipeline il est possible de récupérer les sources :
L'attribut variables
Dans un pipeline il est possible de créer des variables propres à un job :
variables:
DATA: "{'text':'une donnée json que vous voulez utilisez plus tard'}"
Cette variable pourra être simplement utilisée dans la partie script :
script:
- curl -X POST -H "${CONTENT}" --data "${DATA}" "${WEBHOOK_LIVRAISON}"
Pour intégrer et réutiliser une variable dans plusieurs jobs, c'est exactement la même chose mais à l'extérieur d'un job 😉.
Il existe encore un niveau au dessus en terme de variable, notamment utilisé pour stocker les mots de passe qui ne doivent pas se trouver dans votre code source ! Dans le menu "Settings" > "CI / CD" plusieurs options sont disponibles, dont les variables.
Dans cet exemple on voit deux variables créées et masquées qui sont utilisables dans le pipeline comme une variable créée dans le fichier .gitlab-ci.yml.
GitLab met également des variables prédéfinies, comme l'identifiant du pipeline CI_PIPELINE_ID, le user CI_REGISTRY_USER, le message du commit utilisé CI_COMMIT_MESSAGE, des informations sur l'utilisateur exécutant le pipeline, etc.
Plus de détail sur cette page : https://docs.gitlab.com/ee/ci/variables/predefined_variables.html
L'attribut tags
L'attribut tag permet de sélectionner un Runner, un quoi ? 🤔
Rapidement un runner est ce qui permet d'interpréter les fichiers .gitlab-ci.yml. Si vous utilisez l'instance publique GitLab.com, des runners sont configurés et sont ceux utilisés par défaut, ce sont les Shared runner, les runners partagés avec tout le monde. Si on est administrateur de son instance GitLab privée, on peut s'amuser à créer des Specified Runner, c'est-à-dire des runners spécifiés à un projet ou un groupe de projet. Cela permet de partager les ressources machines en fonction des projets ou groupement de projet.
Le registry
Le container registry de GitLab permet de stocker des images dans votre projet, que ce soit via GitLabCI ou même manuellement en ligne de commande ou par d'autres outils. Simple à comprendre et à utiliser !
C'est disponible dans le menu "Packages" > "Container Registry".
Il permet même de stocker plusieurs niveaux. Par exemple dans ce projet je stocke 4 images pour lesquelles je peux déployer plusieurs tags.
Vos images seront accessibles facilement avec un
docker pull registry.gitlab.com/jeanphi-baconnais/arbregen/front:master_133030344
On peut ainsi imaginer un job qui build l'image docker de votre composant via :
🐳 build_and_deploy:
stage: build
image: docker:stable
services:
- docker:dind
script:
- docker login -u GitLabci -p "$CI_SECRET" registry.GitLab.com
- docker build -t <your image name> .
- docker push <your image name>:latest
Cette tache est encore plus facile avec l'utilisation de l'outil Kaniko (https://github.com/GoogleContainerTools/kaniko) de Google :
🐳 build_push_image_docker:
stage: build
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
script:
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE
Kaniko s'occupe de builder l'image à partir d'un dockerfile et de le pusher dans le repository renseigné.
Les environnements
La partie environnement n'est pas un élément que j'ai approfondi à fond. Cela permet de déclarer des environnements, et de les voir dans la partie environnement disponible dans le menu de GitLab. Un environnement peut être nommé, une url peut y être affectée et cela alimenté automatiquement le menu environnement.
En configurant un cluster kubernetes, il est possible de consulter directement les métriques, avoir accès à un terminal ou alors rejouer votre stage.
GitLab Pages
GitLab met à disposition un outil bien pratique, GitLab Pages, qui permet d'héberger gratuitement un site basé sur votre repository. C'est exactement avec cet outil que j'ai pu avoir mon site perso http://jeanphi-baconnais.gitlab.io.
Lors de la création de votre projet, il est possible de partir de templates, et ceux préfixés par "Pages" permettent de générer un projet possédant directement la configuration nécessaire pour vous permettre d'avoir un site disponible.
Le nom du projet doit contenir votre user.gitlab.io. Contrainte imposée par GitLab.
Pour mon site, je me base sur le framework GoHugo (cf https://gohugo.io/).
Le fichier .gitlab-ci.yml généré est le suivant :
image: registry.GitLab.com/pages/hugo:latest
pages:
script:
- hugo
artifacts:
paths:
- public
only:
refs:
- master
La commande hugo permettant de builder le site est exécutée et produit le résultat dans l'artifact "public", le site buildé est mis à disposition et votre site est opérationnel 😎.
Si votre site n'est pas disponible, vous pouvez vérifier la configuration dans le menu "Settings" > "Pages".
L'url de votre page doit être affichée telle que :
Kubernetes
Il est possible d'intégrer (facilement) des cluster kubernetes à GitLab. Après avoir créé son compte sur Google Kubernetes Engine ou Amazon, GitLab va permettre via le menu "Operations" > "Kubernetes" de créer ou déclarer un cluster Kubernetes sur l'une des deux plateformes. Mon exemple concerne l'infrastructure Google.
Après avoir un cluster, plusieurs outils sont intégrables via l'onglet "Applications", comme Helm, Ingress, Prometheus, etc.
On peut même y voir l'état de santé de notre cluster.
Pour déployer une application via GitLabCI, c'est très simple, cela se passe toujours dans le .gitlab-ci.yml et via l'image google/cloud-sdk, l'outil kubectl est disponible et permet donc de déclarer des déploiements, services en ligne de commande ou bien via un fichier yaml. C'est également possible avec Helm si vous avez bien activé l'application auparavant.
🐋 deploy_gcp :
stage: deploy
image: google/cloud-sdk
script:
- kubectl apply -f k8s/hello-world.yaml
The (happy) end
L'approche GitLabCI est très simple à prendre en main, par contre, il faut l'avouer qu'au départ, c'était assez sportif de construire nos pipelines.
Dès qu'un nouveau job était en construction, il plante souvent ... J'ajoute une ligne dans mon job, je git add, git commit, git push, regarde mon pipeline tourner, puis planter. Je modifiais le pipeline, git add, commit, push, regardait mon pipeline tourner, puis planter, etc.
Même avec l'utilisation du Web IDE, ça facilitait les opérations git, mais les statistiques de nos pipelines n'étaient pas dingues. Sur le premier projet de nos tests avec GitLabCI, 40% de tests passés avec succès !
Les GitLab Runner permettent à GitLab de constuire vos pipelines, et bien l'outil GitLab-Runner peut être installé sur vos machines : https://docs.gitlab.com/runner/install/
Cela permet de gagner un temps fou, et d'améliorer vos statistiques de pipeline si votre chef préféré vous espionne 😅
Si vous avez un pipeline constitué de plusieurs jobs, il est même possible d'exécuter un seul job. Par exemple si vous avez un job 'job1' déclaré dans votre .gitlab-ci.yml, vous pourrez l'exécuter avec les process de votre machine via un
gitlab-runner exec docker job1
.
Je ne l'ai pas encore dit, mais vos runner peuvent utiliser plusieurs types d'executor : docker (le seul que j'ai utilisé), shell, sh, virtual box et même kubernetes (que je découvre en écrivant cet article). Je vous laisse lire la documentation si vous voulez approfondir ce point : https://docs.gitlab.com/runner/executors/.
Avec toutes ces notions, nous avons pu automatiser nos pipelines de CI/CD pour automatiser les builds d'applications, les installer sur des machines distantes, informer via Slack des livraisons effectuées avec le commit, bref nous faire gagner en temps et en confort 😎 Même l'intégration sur un cluster Kubernetes est simple et rapide à mettre en place.
La documentation de GitLab est très bien faite et est en constante évolution : https://docs.gitlab.com/ee/ci/yaml/.
Merci de votre lecture et bons pipelines !
Top comments (0)