Optimisations de GitLab-CI : 6 minutes pour en gagner beaucoup plus

Nicolas Poste
6 min readNov 22, 2019

--

Vous êtes un·e développeur·se, Lead Dev/Tech, et vous n’avez pas de feedback rapide de vos pipelines GitLab-CI ? Je consacre cet article à la mise en place d’un pipeline efficace et optimisé !

Dans un article précédent, j’abordais déjà la nécessité d’avoir une plateforme d’intégration continue. Ce pipeline est fortement lié à la boucle de feedback (boucle de rétroaction en français !) qui doit être la plus courte possible. Il est hors de question de devoir attendre une dizaine de minutes voire des heures, une nuit, avant d’avoir un feedback sur le travail qu’on vient de faire !

Mais alors comment, via quelques astuces rapides à mettre en place, avoir un retour rapide et fiable ?

Rappels du découpage en étapes

Il est actuellement considéré comme étant une bonne pratique de découper son pipeline en trois étapes :

  • Build, pendant laquelle la compilation/transpilation, packaging, … se fait
  • Test, pendant laquelle tous les tests automatisés (ou au minimum unitaires) sont faits.
  • Déploiement, pendant laquelle le packaging est envoyé sur un repository, tel que Nexus, Artifactory, … ou même un environnement tel que Staging, Integration, ou même la Production !
Source : GitLab.com

💡 Ce principe respecte d’ailleurs le principe de responsabilité unique (SRP). Chaque étape, chaque job, a son rôle.

L’utilisation du cache pour une première optimisation

Le cache de GitLab fonctionne à travers différents pipelines. Il peut être construit/réutilisé par n’importe quel pipeline.

💡 NB : la mise en cache est encore plus pertinente si le cache est partagé entre tous vos runners !

Il peut y en avoir plusieurs mais seul un seul cache ne peut être utilisé par un job, il est alors identifié par une clé.

Si le cache n’est pas nécessaire, n’hésitez pas à l’indiquer clairement !

L’utilisation des artefacts pour ne builder qu’une fois

Contrairement au cache, les artefacts ne traversent pas plusieurs pipelines. Ils sont construits par un job d’une étape précédente.

Si un job défini des artefacts, alors par défaut tous les jobs des étapes suivantes vont les télécharger avant de commencer l’exécution des scripts. Pour ne télécharger que ceux qui sont utiles, il faudra alors spécifier des dépendances telles que :

Pour ne pas télécharger d’éventuels artefacts, on mettra alors :

Ne récupérer le repository Git que s’il est nécessaire

Il arrive souvent, notamment pour les jobs de déploiement, que le checkout du repository ne soit pas nécessaire. Il s’agira alors d’indiquer à GitLab de skipper celui-ci à l’aide de la variable GIT_STRATEGY.

Du côté de Java

Habituellement, et comme conseillé par le template proposé par GitLab à une époque, l’étape de build utilise le goal “test-compile” (pour Maven). Depuis, il semblerait qu’il n’y ait plus qu’une étape qui dépend de la branche :

  • deploy (mvn deploy) pour la branche master, qui effectue build, test, et déploiement au cours du même job (d’ailleurs, n’oubliez pas d’utiliser les paramètres Maven -DinstallAtEnd=true -DdeployAtEnd=true tels que recommandé dans le template de GitLab si vous préférez utiliser cet unique job)
  • verify (mvn verify), qui build et teste (joue les tests unitaires et d’intégration).

En respectant le principe de responsabilité unique, on aurait :

  • build : mvn test-compile
  • test : mvn verify
  • deploy : mvn deploy

💡 Rappel du cycle de vie de Maven

S’il est plus courant de mettre en cache le local repository de Maven ou du store de Gradle pour éviter le téléchargement à répétition des dépendances, il est plus rare d’exposer le résultat de l’étape de build en tant qu’artefacts, mais pourtant...

L’exposition de ces artefacts permet également de gagner de précieuses minutes ! En plus de cela, l’étape de test ne se contente plus que de jouer les tests. Exit la double voire triple compilation (une pendant le build et une seconde pendant les tests, et éventuellement une dernière pendant le déploiement).

Il en est de même avec le déploiement : on réutilise les artefacts générés lors du build au lieu de les reconstruire une nouvelle fois.

L’intérêt de distinguer ces trois étapes est d’avoir un fail-fast, surtout dans le cas des multi-modules. Il est inutile de lancer d’éventuels tests si la compilation échoue.

Du côté de Node/NPM

Contrairement à Maven, la mise en cache des nodes_modules à travers différents pipelines ne permet pas de gagner du temps sur l’exécution d’un seul pipeline.

L’utilisation du package-lock.json au lieu du package.json permet de gagner de précieuses secondes . On utilisera alors la commande “npm ci” pour installer toutes les dépendances et les dépendances transitives dans leur version exacte, ce qui fait gagner beaucoup de temps.

Pour rappel, “npm ci” supprime totalement le dossier node_modules au préalable.

Sur notre projet, nous avons mis en place une étape “pre-build”, qui permet de mettre dans un cache dédié au pipeline en cours ces dépendances. L’utilisation du cache policy push/pull permet d’optimiser :

  • push permet d’éviter un éventuel téléchargement d’un cache, inutile car les node_modules sont de toute façon écrasé par “npm ci”
  • pull permet d’éviter de re-uploader le cache, inchangé

💡 On remarque que les tests pourraient être effectués en même temps que le build. A vous de voir si vous souhaitez les paralléliser ou non 😃

Peaufiner son pipeline

L’utilisation des GitLab Pages pour exposer plus facilement le résultats des tests

Dans nos pipelines, la phase de tests construit des rapports d’exécution de tests. Ce sont ces rapports d’exécution qui peuvent avoir un intérêt à être présentés dans les GitLab Pages.

Côté Java, on va notamment trouver un rapport construit par cucumber.

Côté JS, plusieurs outils peuvent être utilisés tels que Karma, Jest et ESLint (avec eslint-detailed-reporter). Chacun peut mettre à disposition des rapports au format .html qui sont pertinents à être affichés.

💡 Côté JS, on peut également construire des storybooks qui peuvent être présentés sur ces pages

Supprimer une image docker à la suppression d’une branche

Pour des raisons de simplicité, nous avons choisi de matérialiser la création d’une image docker à un environnement. Cela permet d’utiliser les jobs “on_stop” de GitLab et ainsi de supprimer l’image dédiée à la branche lors de sa suppression.

Les variables $DOCKER_USERNAME, $DOCKER_PASSWORD, … sont définies dans les variables des paramètres CI/CD.

L’utilisation d’ancres et d’extensions pour alléger votre fichier .gitlab-ci.yml

Comme vu dans l’exemple ci-dessus, l’utilisation des ancres et extensions peut alléger le fichier .gitlab-ci.yml.

📔️ Depuis la version 12 de GitLab, il est possible de mettre plusieurs extensions.

⚠️ Attention avec les ancres ! Il est très facile de se tromper et d’écraser tout ou une partie de la description du job.

Utilisation du pipeline SonarQube pour GitLab

Ce plugin, développé par mon CTO à l’époque, et désormais plugin officiel de SonarQube, permet de lier les deux outils.

Le résultat de l’analyse SonarQube pourra alors faire échouer votre pipeline si la QualityGate définie n’est pas respectée. Il fonctionne très bien avec le workflow de gestion des branches de type Feature Branches.

Malheureusement, avec la décision récente de SonarQube qui est de passer le mode d’analyse “preview” en deprecated afin de faire utiliser le branch review, disponible dans la version payante de SonarQube, ce plugin est probablement amené à ne plus être supporté ou à fonctionner aussi bien avec les Feature Branches.

L’utilisation du mot clé “interruptible”

Ce mot clé permet quant à lui d’optimiser l’utilisation des runners : si deux pipelines concernent la même branche, les jobs notés comme étant interruptibles seront annulés (sauf si un job d’une étape précédente ne l’est pas, cf la doc !).

Et vous ?

Avez-vous des trucs et astuces autour des pipelines de GitLab ? Lesquels préférez-vous ?

Je serais heureux d’avoir vos avis sur la matière, d’échanger, … alors n’hésitez pas à me contacter ou à laisser des commentaires !

--

--

Nicolas Poste

CTO @ Ceetiz, passionné par le Software Craftsmanship, les aspects techniques, d'automatisation pour gagner en efficacité, Docker, ...