L’objectif de ce billet est de présenter comment utiliser le système de cache de GitLab CI/CD et les artifacts pour diminuer le temps de construction d’une application Maven.
Dan un premier temps, nous allons utiliser le cache pour éviter de re-télécharger l’ensemble des dépendances du projet Maven. Ensuite, les artifacts, soit les résultats d’une opération, seront mis en oeuvre afin de ne pas recompiler les classes entre les différents “stages” d’un même “pipeline”.
GitLab CI/CD: pipeline, stage et job
L’outil GitLab CI/CD ( Continuous Integration / Continuous Deployment) se configure à partir d’un fichier manifeste .gitlab-ci.yml
:
- il permet de définir le
pipeline
, soit l’ensemble des opérations à lancer automatiquement lorsque une modification est effectuée dans les sources du projet. - Un pipeline est découpé en
stages
qui sont lancés séquentiellement. Par défaut, 3 stages sont utilisés: build -> test -> deploy: le stage test sera lancé à la fin du stage précédent, soit build, si ce dernier est en “réussite” ( il est toutefois possible de modifier ce comportement). - Finalement, un stage est composé de
jobs
qui sont les plus petites unités de travail. Unjob
appartient à un seul stage et lesjobs
d’un même stage sont lancés en parallèle.
En image:
Pour plus de détails, vous retrouverez toute la documentation sur le site officiel de GitLab.
Le Manifeste de départ
Voici un exemple de fichier .gitlab-ci.yml
:
|
|
L’image Docker à utiliser est définie au début du fichier. Ensuite, la variable d’environnement MAVEN_CLI_OPTS
est définie. Finalement, 2 jobs sont déclarés
build_job
: la compilation du projet associé au stagebuild
test_job
: lancement des tests associé au stagetest
Pour simplifier l’exemple, nous n’avons pas défini de job attaché au stage deploy
. En général, les noms des jobs ne contiennent pas le préfixe _job
qui a été ajouté pour bien séparer les notions de job et stage.
Cette première version n’utilise pas les fonctionnalités de cache: à chaque lancement du pipeline, les dépendances sont toutes téléchargées et les classes sont recompilées par le job test_job
.
Utilisation du cache pour les dépendances
Pour mettre en place ce cache, il faut activer le cache de GitLab CI et configurer Maven pour stocker le repository à une emplacement connu:
|
|
A la ligne 5, on indique le chemin du repository à Maven via une variable d’environnement. Ensuite, on demande à GitLab CI de cacher ce répertoire (lignes 7 à 9). Cette première optimisation est simple.
Par contre, en parcourant les logs, nous constatons que le projet est recompilé par le job de test_job
: par défaut, les stages ne partagent rien.
La prochaine étape est d’éviter de recompiler les fichiers java en partageant les résultats (artifacts) entre les stages.
Utiliser le cache pour partager les artifacts: une mauvaise idée
Pour partager ces artifacts, on pourrait envisager d’utiliser la fonctionnalité de cache.
Hors, la documentation de Gitlab concernant le cache décrit bien la différence entre la fonctionnalité de cache
et les artifacts
:
- le cache doit être utilisé pour cacher les dépendances du projet
- les artifacts sont utilisés pour partager des résultats d’opération ( .class, ….) entre les stages.
Pour illustrer l’importance de cette distinction, supposons que nous utilisons le cache pour partager des artifacts:
|
|
Une conséquence directe: le dossier target
étant en cache, il sera restauré à chaque lancement du pipeline.
Si une ressource est supprimée du référentiel (Git), elle sera quand même rétablie dans le dossier target
par le cache et donc livrée avec les artifacts suivants… Si c’est un fichier de configuration, cela peut occasionner des séances de debug fastidieuses…
Activation des artifacts
Pour partager l’artifact, le contenu du dossier target
, voici les modifications à apporter:
|
|
On demande de considérer le dossier target comme un artifact. On définit également une durée de rétention de 10 minutes ce qui sera suffisant pour partager les artifacts entre les stages. Après 10 min, ces résultats intermédiaires ne seront plus disponibles.
Cette configuration est tout à fait adaptée dans le cas d’un projet Maven avec un seul module. Par contre, dans le cas de multi-modules la configuration pourrait être plus verbeuse:
|
|
Pour éviter cela, il est possible d’utiliser des wildcards pour ces sous-dossiers (attention à la notation):
|
|
Les classes sont toujours recompilées !
Malgré cette nouvelle configuration, le résultat n’est pas concluant car les logs indiquent que les classes sont toujours recompilées:
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ springboot-java-insecure ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 9 source files to /builds/capedev-labs/springboot-java-unsecure/target/classes
Une explication:
entre les 2 stages, une opération git
force la mise à jour des dates de modification des fichiers java qui deviennent plus récents que les fichiers .class
correspondants ( information trouvée ici). Une solution est d’actualiser la date de modification des fichiers .class
avec un touch:
|
|
La ligne 44 a été ajoutée: au début du job test_job
, un script remet à jour les dates de modifications des fichiers .class
.
Suite à cette modification, les classes java ne sont plus recompilées:
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ springboot-java-insecure ---
[INFO] Nothing to compile - all classes are up to date
Cette ligne sera à ajouter pour chaque job réutilisant les fichiers .class
( ce qui n’est pas une solution optimale…).
Conclusion
Le cache et la gestion des artifacts de GitLab CI/CD permettent de diminuer le temps de construction d’un projet Maven. Ces 2 fonctionnalités ont des cas d’usage différents à respecter.
Des liens vers la documentation: