Support principal & Travaux Pratiques
- Bien contrôler son environnement de test :
- le construire et maintenir la recette,
- déployer l'image servant aux tests,
- automatiser la construction et le déploiement via gitlab-ci.
- Réfléchir à la notion d'images vs. machine bare-metal qu'il faut
réinstaller à chaque fois :
- tester le déploiement du même environnement sur plusieurs runners,
- discuter du choix de réinstaller toutes les dépendances ou non selon le contexte (si trop lourd/long à redéployer, il vaut mieux maintenir une image préconstruite).
- Quand et comment (différentes stratégies) mettre à jour l'environnement de test ?
Introduction à GitLab CI et aux runners
- Voir le cours sur l'intégration continue avec GitLab (https://ramet.gitlab.io/r4.02-qualite-dev/gitlab-ci.html)
- Voir également le cours sur l'orchestration de conteneurs avec
docker/podman (https://moodle.u-bordeaux.fr/course/view.php?id=5358)
- on a vu comment construire une image podman contenant un runner GitLab configuré en mode shell
- on va voir comment configurer un runner GitLab en mode docker, mais configuré nativement pour utiliser podman
- Mettre en place des runners de type shell et docker
- Tester avec un job dans les 2 configurations
Forker le projet Heat, accès à l'API GitLab avec un jeton personnel
Forker le projet Heat sur le GitLab de l'IUT. Il devrait être visible à cette URL : https://gitlab-ce.iut.u-bordeaux.fr/$USER/heat. Ajouter fpruvost et ramet comme "Maintainer" à votre projet via Manage -> Members -> Invite members. Si vous aviez déjà un fork de Heat, le supprimer via Settings -> General -> Advanced -> Delete project.
Activer les pipelines (CI/CD) dans les paramètres (Settings -> General -> Visibility, project features, permissions) de votre projet Heat.
Désactiver les "Instance runners" : dans Settings -> CI/CD -> Runners -> Instance runners, désactiver l'option Enable instance runners for this project.
Créer un jeton d'accès personnel (Personal Access Token) (Preferences -> Access Tokens -> Add new token) sur le GitLab de l'IUT avec accès api, puis le sauvegarder localement :
echo "le token avec acces complet api" > ~/.gitlabtoken export TOKEN=`cat ~/.gitlabtoken`Note : Ce jeton est personnel et doit être gardé secret, il permet de vous authentifier sur GitLab. Il inclut les droits de lecture/écriture sur le registre GitLab. Il n'est pas nécessaire pour cloner votre projet si vous avez déjà configuré une clé SSH sur votre compte GitLab.
Bonnes pratiques professionnelles : gestion des secrets et des jetons :- Jamais de secret dans Git : Ne commitez
jamais de fichier contenant un jeton d'accès ou un mot
de passe (pensez à ajouter
/.gitlabtokenou les fichiers d'environnement dans votre.gitignore). - Variables CI/CD sécurisées : Dans un pipeline
GitLab CI, on ne stocke pas de jeton dans les fichiers sources. On
utilise les variables de projet (Settings -> CI/CD ->
Variables) en cochant impérativement les options :
- Masked : empêche la valeur du secret d'apparaître en clair dans les logs d'exécution des jobs.
- Protected : restreint l'exposition de la variable
aux seules branches ou tags protégés (ex.
masterou tags de release).
- Jeton temporaire de job : Pour les opérations au
sein du pipeline (téléversement de paquet, pull d'image de registre),
privilégiez la variable prédéfinie
$CI_JOB_TOKENqui est générée à la volée par GitLab, valide uniquement pendant la durée du job, et avec des privilèges restreints. - Détection automatique de fuites : GitLab propose le
template officiel
Security/Secret-Detection.gitlab-ci.ymlqui analyse automatiquement chaque commit pour détecter si des clés privées, mots de passe ou jetons d'API ont été accidentellement ajoutés au code source.
- Jamais de secret dans Git : Ne commitez
jamais de fichier contenant un jeton d'accès ou un mot
de passe (pensez à ajouter
Vous pouvez cloner votre fork de Heat de deux manières différentes.
Avec votre clé
sshsi vous l'avez configurée :git clone git@gitlab-ce.iut.u-bordeaux.fr:$USER/heat.gitAvec votre jeton personnel
git clone https://$USER:$TOKEN@gitlab-ce.iut.u-bordeaux.fr/$USER/heat.gitAttention : si vous perdez votre jeton, vous pourrez le renouveler ou en créer un nouveau, mais il sera alors nécessaire de recloner votre dépôt Heat.
Installation de gitlab-runner
Installer une version récente de gitlab-runner (>=16.0) :
cd $HOME
curl -L --output ./gitlab-runner "https://s3.dualstack.us-east-1.amazonaws.com/gitlab-runner-downloads/latest/binaries/gitlab-runner-linux-amd64"
chmod +x ./gitlab-runner
export PATH=$PATH:$HOME
# puis démarrer le programme gitlab-runner
~/gitlab-runner run
# laissez-le tourner, ouvrez un nouvel onglet dans le terminal pour la suitePour le moment, cela renvoie une erreur car le fichier de
configuration config.toml n'existe pas encore. Il faut
faire un premier enregistrement de runner pour le créer
automatiquement.
Runner de type shell
Enregistrer un runner de type executor shell via : Settings
-> CI/CD -> Runners -> Create project runner. Remplir les
champs Tags "shell" et Runner
description "IUT shell mode", puis valider Create
runner. Ensuite, sélectionner Operating system
: Linux. Puis copier/coller la commande
gitlab-runner register proposée dans votre terminal.
Exemple :
~/gitlab-runner register --url https://gitlab-ce.iut.u-bordeaux.fr --token glrt-iMzkBpbwCRpJ-vysaXA4Suivre les étapes en tapant sur la touche Entrée deux fois, puis à la troisième étape choisir l'executor : shell.
Note : Remarquez que le token du runner est spécifique à chaque projet et doit être gardé secret (il est également différent de celui utilisé pour l'API GitLab).
Puis redémarrer gitlab-runner :
~/gitlab-runner runIl doit bien s'exécuter sans erreurs cette fois.
Note : Si
~/gitlab-runner runrenvoie des erreurs, vérifiez votre fichier~/.gitlab-runner/config.tomlcar il pourrait comporter d'anciennes configurations de runners (années précédentes). Dans ce cas, supprimez les lignes correspondant aux anciens runners et redémarrez~/gitlab-runner run.
À cette étape, vous devriez voir votre runner associé à votre projet GitLab dans la section Settings -> CI/CD -> Runners -> Project runners :
Dans un terminal shell, naviguer dans votre fork de Heat,
cd heat, créer une branche git 'auto', se
positionner dessus, puis ajouter le fichier .gitlab-ci.yml
permettant de lancer un job sur chaque runner, par exemple pour juste
vérifier que le job se lance bien :
job-shell:
tags: ['shell']
script: ls -la
Pour voir les logs des jobs gitlab-ci, il faut se rendre sur la page Build -> Pipelines puis cliquer sur la pastille du pipeline (dans la colonne Status) ou sur la pastille d'un job en particulier (colonne Stages).
L'erreur suivante peut apparaître :
ERROR: Job failed: prepare environment: exit status 1. Check https://docs.gitlab.com/runner/shells/index.html#shell-profile-loading for more information
Pour régler le problème, ouvrir le fichier .bash_logout
à la racine de votre home, puis commenter la section suivante :
#if [ "$SHLVL" = 1 ]; then
# [ -x /usr/bin/clear_console ] && /usr/bin/clear_console -q
#fi
Vous pouvez relancer le job en cliquant sur le bouton "Retry all failed or cancelled jobs" sur la page des pipelines sur la droite ou en cliquant directement sur le job en échec puis sur le bouton "Run again".
Le job doit passer au vert et les logs doivent afficher le résultat des commandes inscrites dans la section script du job :
Note : Remarquez que GitLab clone automatiquement le projet sur la bonne branche 'auto' et vous positionne directement dans le dossier heat. Remarquez aussi que dans la colonne de droite, le champ Runner donne le nom du runner sur lequel le job s'est exécuté.
En mode shell, les jobs sont directement lancés dans votre environnement utilisateur tandis qu'en mode docker, les jobs sont lancés dans un conteneur d'une image docker (ou podman). Comme l'environnement shell n'est pas facilement configurable, car vous n'êtes pas administrateur sur la session Ubuntu, on travaillera exclusivement à partir d'une image docker/podman en mode conteneur par la suite.
Création du service gitlab-runner
À la place de gitlab-runner run, gitlab-runner peut
aussi être démarré comme un service. L'avantage sera d'avoir les runners
GitLab automatiquement disponibles dans vos prochaines sessions
Ubuntu.
Arrêtez votre gitlab-runner run en cours avec
Ctrl+C.
Voici les commandes pour créer un service gitlab-runner utilisateur :
mkdir -p ~/.config/systemd/user/
# copier le template vers ~/.config/systemd/user
curl https://automatisation-ramet-09f19b71cd035def286edcb4a35616cad4167379e2.gitlab.io/gitlab-runner.service -o ~/.config/systemd/user/gitlab-runner.service
sed -i -e "s#\$HOME#$HOME#g" ~/.config/systemd/user/gitlab-runner.service
systemctl --user daemon-reload
systemctl --user enable --now gitlab-runner.service
# vérifier que le service gitlab-runner est bien actif avec
systemctl --user status gitlab-runner.servicePour exécuter toutes ces commandes d'un coup vous pouvez utiliser le script config-gitlab.sh de la manière suivante :
source config-gitlab.sh
Cela va créer le fichier
~/.config/systemd/user/gitlab-runner.service à partir de gitlab-runner.service, remplacer
$HOME par votre répertoire home (cf.
echo $HOME) puis activer le service gitlab-runner.
Runner de type docker
Enregistrer un nouveau runner de type executor docker via :
Settings -> CI/CD -> Runners -> Create project runner. Remplir
les champs Tags "docker" et Runner
description "IUT docker mode", puis valider Create
runner. Ensuite, sélectionner Operating system
: Linux. Puis copier/coller la commande
gitlab-runner register proposée dans votre terminal.
Exemple :
gitlab-runner register --url https://gitlab-ce.iut.u-bordeaux.fr --token glrt-iMzkBpbwCRpJ-vysaXA4Suivre les étapes en tapant sur la touche Entrée et indiquer executor
docker et image par défaut :
registry.u-bordeaux.fr/vhub/alpine:3.18. Un nouveau runner
est créé et visible sur la page Settings -> CI/CD -> Runners ->
Project runners.
Modifier le script .gitlab-ci.yml afin d'ajouter un
nouveau job qui devra s'exécuter sur le runner docker, on utilise le
système de tags pour cela. Le job shell précédent peut être commenté et
ainsi non exécuté juste en ajoutant un point (.) devant le nom du job.
Commiter et pousser puis observer les logs du job (Build ->
Pipelines).
.job-shell:
tags: ['shell']
script: ls -la
job-docker:
tags: ['docker']
image: registry.u-bordeaux.fr/vhub/alpine:3.18
script: ls -la
Note : Pour s'assurer que les jobs seront bien lancés avec les runners enregistrés lors de ce TP, il est nécessaire de désactiver les instance runners déjà installés au département. Pour cela, aller sur : Settings -> CI/CD -> Runners et désactiver l'option "Enable instance runners for this project" !
Le job doit être en erreur car docker n'est pas installé.
En effet, sur les machines de l'IUT, docker n'est pas installé car podman est considéré comme une alternative plus sûre. Une configuration additionnelle de gitlab-runner est requise pour fonctionner avec podman (cf. https://docs.gitlab.com/runner/executors/docker.html#use-podman-to-run-docker-commands).
Voici les commandes pour créer un service podman utilisateur :
/mnt/netta/apps/vnet/bin/podman-init-storage
mkdir -p ~/.config/systemd
mkdir -p ~/.config/systemd/user
# copier les fichiers de /usr/lib/systemd/user/ vers ~/.config/systemd/user
cp /usr/lib/systemd/user/podman.service ~/.config/systemd/user/
cp /usr/lib/systemd/user/podman.socket ~/.config/systemd/user/
systemctl --user enable podman.socket
systemctl --user daemon-reload
systemctl --user start podman.socket
# vérifier que le service podman est bien actif avec
systemctl --user status podman.socketVous pouvez utiliser le script config-podman.sh de la manière suivante :
source config-podman.sh
Puis modifier le fichier ~/.gitlab-runner/config.toml
dans la section [[runners]] correspondante au runner docker
:
- Ajouter
environment = ["FF_NETWORK_PER_BUILD=1"]dans la section[[runners]] - Modifier le champ
privileged = falseentruedans la section[runners.docker] - Ajouter
host = "unix:///run/user/?????/podman/podman.sock"dans la section[runners.docker], les points d'interrogation?????doivent être remplacés par le nombre donné parsystemctl --user status podman.socketdans le champ Listen.
Un exemple de configuration :
[[runners]] name = "runner-docker" url = "https://gitlab-ce.iut.u-bordeaux.fr" id = 482 token = "glrt-blablabla-1234567890" token_obtained_at = 2023-08-10T15:16:12Z token_expires_at = 0001-01-01T00:00:00Z executor = "docker" environment = ["FF_NETWORK_PER_BUILD=1"] [runners.cache] MaxUploadedArchiveSize = 0 [runners.docker] host = "unix:///run/user/76955/podman/podman.sock" tls_verify = false image = "registry.u-bordeaux.fr/vhub/alpine:3.18" privileged = true disable_entrypoint_overwrite = false oom_kill_disable = false disable_cache = false volumes = ["/cache"] shm_size = 0 network_mtu = 0
Puis redémarrer le service :
systemctl --user restart gitlab-runnerRelancer le job-docker en erreur. Il doit maintenant passer au vert.
Notez que le job est lancé dans un conteneur podman issu de l'image "registry.u-bordeaux.fr/vhub/alpine:3.18".
Vous pouvez supprimer votre runner de type shell car on ne va plus s'en servir par la suite, cf. Settings -> CI/CD -> Runners -> Delete runner sur le runner "IUT shell mode".
Mise en place d'un pipeline
Le programme Heat est développé en langage C (avec des éléments C++ et scripts Python) et nécessite une phase de compilation. On va donc mettre en place un pipeline de type phase 1 build, puis phase 2 test.
Commiter et pousser ces modifications dans le fichier
.gitlab-ci.yml.
stages:
- build
- test
default:
image: registry.u-bordeaux.fr/vhub/alpine:3.18
tags: ['docker']
build:
stage: build
script:
- cmake -B build
- cmake --build build
cache:
key: "$CI_COMMIT_REF_SLUG"
untracked: true
policy: push
test:
stage: test
needs: ["build"]
script:
- ctest --test-dir build --verbose
cache:
key: "$CI_COMMIT_REF_SLUG"
untracked: true
policy: pull
Notes sur le fichier .gitlab-ci.yml :
- stages permet de définir les étapes intermédiaires du pipeline et d'y associer un ensemble de jobs. Les jobs d'une étape suivante ne pourront pas démarrer tant que tous les jobs de l'étape précédente n'auront pas terminé.
- default permet de définir certaines propriétés qui seront utilisées dans tous les jobs.
- cache permet de sauvegarder des fichiers entre jobs ou pipelines successifs (untracked: true pour sauvegarder tous les fichiers en dehors de ce qui est indexé par Git i.e. tout ce qui a été généré après le git clone par la portion script). Dans notre cas précis, cette sauvegarde est stockée dans
/tmp/containers.$USER.d/storage/volumesdans un fichier cache.zip. Plus d'explications sont disponibles dans la documentation en relation avec le cache GitLab.
En l'état, le job build va échouer car les
dépendances du projet ne sont pas installées (gcc, cmake, etc.). En
effet, l'image docker/podman utilisée,
registry.u-bordeaux.fr/vhub/alpine:3.18 par défaut, est
très minimaliste et ne contient qu'un Linux de base, sans outils
particuliers pour le développement C/C++. On va justement installer les
dépendances manquantes dans la section "2. Définir son environnement de
test".
Guide de démarrage rapide pour CMake
- Le programme Heat est constitué de fichiers .h (en-têtes) et .c à compiler et installer avec CMake.
- Les propriétés du projet CMake sont définies dans les fichiers
CMakeLists.txt. - L'installation d'un projet se déroule généralement en 3 étapes : 1. Configuration, 2. Compilation, 3. Installation.
La configuration du projet et la détection des dépendances installées sur le système s'effectuent avec
cmake -B buildLors de cette étape, des fichiers
Makefilesont générés dans le dossierbuild/afin de pouvoir compiler par la suite avec des appels àmakeoucmake --build.Des options peuvent être choisies à ce moment-là avec -D, ex. :
-DHEAT_USE_MPI=ONpour activer la dépendance optionnelle MPI,-DCMAKE_INSTALL_PREFIX=$PWD/installpour modifier le répertoire d'installation par défaut,-DBUILD_SHARED_LIBS=ONpour compiler les éventuelles bibliothèques en mode dynamique (.so et non .a).cmake -B build -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=$PWD/install -DHEAT_USE_MPI=ON
La compilation se fait via
cmake --build build # ajouter --verbose pour avoir des détailsDes fichiers .c sont convertis en .o, qui pour certains sont archivés en bibliothèque .a ou .so, et pour d'autres associés pour construire un binaire exécutable. Pour supprimer/nettoyer les binaires :
cmake --build build/ --target cleanChaque cible existante peut être construite séparément :
cmake --build build/ --target heatpour compiler la bibliothèque cf.
ll build/lib/libheat.a, etcmake --build build/ --target heat_seqpour compiler un des exécutables cf.
ll build/heat_seq.L'installation peut se faire via
cmake --install build
Si des tests sont définis pour ctest (outil en ligne de commande de CMake), ceux-ci peuvent être déclenchés avec
ctest --test-dir build # ajouter --verbose pour avoir des détailsDéfinir son environnement de test
Installation des dépendances dans les jobs gitlab-ci
Les images de base existantes sur internet sont rarement suffisantes
pour satisfaire les besoins de notre logiciel. Il faut donc ajouter
l'installation des dépendances spécifiques dans les scripts de test.
Modifier la configuration pour ajouter l'installation des dépendances,
ici apk add --update git make gcc g++ cmake, dans une
section before_script mise dans la partie
default (sera exécutée avant les instructions dans
script:) puis commit+push.
default:
image: registry.u-bordeaux.fr/vhub/alpine:3.18
before_script: apk add --update git make gcc g++ cmake
Construction d'une image podman en local
En l'état, chaque nouveau push dans des branches déclenchera l'installation des dépendances. Cela peut être long et gourmand en ressources lorsque le poids des dépendances est élevé. On va donc construire une image une fois pour toutes contenant le bon environnement de test.
Construire une image docker en local (avec la commande podman) :
podman run -it registry.u-bordeaux.fr/vhub/alpine:3.18
# ajouter les paquets (apk add --update git make gcc g++ cmake), tester que heat compile, une fois terminé sortir avec ctrl+dÀ présent que l'environnement de test pour Heat est fonctionnel dans
l'image, on peut sauvegarder l'ensemble des commandes dans un fichier
dockerfile-testing (la recette) :
FROM registry.u-bordeaux.fr/vhub/alpine:3.18
RUN apk add --update git make gcc g++ cmake
puis taper :
podman build -t heat -f dockerfile-testing .et vérifier que l'image se construit bien.
Stocker votre image sur le registre GitLab de Heat
Pousser l'image sur le registre de conteneurs de votre projet GitLab, cf. Deploy -> Container Registry (l'option Container Registry doit être activée dans Settings -> General -> Visibility, project features, permissions).
D'abord construire l'image avec la bonne convention de nommage pour pouvoir la pousser sur le registre. Le nom du registre est indiqué dans GitLab dans Deploy -> Container Registry, cf. Build image. En plus d'un nom, l'image est dotée d'une étiquette de version, par défaut latest.
podman build -t gitlab-ce.iut.u-bordeaux.fr:5050/$USER/heat/testing -f dockerfile-testing .Vous pouvez associer à cette image un numéro de version, ex. v0.1 en plus, à des fins d'archivage et de reproductibilité. En effet, lorsque vous mettrez à jour l'image par défaut (latest), la précédente version sera écrasée sans possibilité de vérifier plus tard quelles étaient les versions des dépendances.
podman tag gitlab-ce.iut.u-bordeaux.fr:5050/$USER/heat/testing gitlab-ce.iut.u-bordeaux.fr:5050/$USER/heat/testing:v0.1Authentifiez-vous sur le registre GitLab puis poussez l'image :
export TOKEN="votre jeton d'accès personnel, cf. https://gitlab-ce.iut.u-bordeaux.fr/-/profile/personal_access_tokens" export TOKEN=`cat ~/.gitlabtoken` # si vous l'avez stocké dans ce fichier podman login gitlab-ce.iut.u-bordeaux.fr:5050 -u $USER -p $TOKEN podman push gitlab-ce.iut.u-bordeaux.fr:5050/$USER/heat/testing podman push gitlab-ce.iut.u-bordeaux.fr:5050/$USER/heat/testing:v0.1
Vous retrouvez les images sur GitLab dans Deploy -> Container Registry, et elles seront réutilisables par les jobs des pipelines.
Utiliser votre image dans les jobs gitlab-ci
Maintenant, on voudrait pouvoir exploiter notre environnement défini
par l'image heat/testing (cf. fichier dockerfile-testing)
dans notre pipeline de tests.
Utilisation de l'image heat/testing dans le .gitlab-ci.yml
Modifier son pipeline pour utiliser l'image docker qui vient d'être construite et stockée dans le registre GitLab :
- Il suffit de changer le nom de l'image dans le
.gitlab-ci.yml, en remplaçantimage: registry.u-bordeaux.fr/vhub/alpine:3.18parimage: gitlab-ce.iut.u-bordeaux.fr:5050/$USER/heat/testing(remplacer $USER par votre login). - Pour être plus générique, vous pouvez remplacer l'adresse de l'image
inscrite en dur par
image: $CI_REGISTRY_IMAGE/testing,CI_REGISTRY_IMAGEétant une variable d'environnement créée par GitLab lors du job et qui contient le nom par défaut des images docker du registre associé au projet. - Vu que votre image contient déjà les dépendances requises pour la
compilation de Heat, vous pouvez ainsi simplifier le script
.gitlab-ci.ymlen supprimant l'installation des dépendancesbefore_script: apk add --update git make gcc g++ cmakepuis relancer le pipeline (commit+push) et vérifier que les tests passent toujours.
Ajout de la documentation
Ajouter le paquet doxygen dans la liste des paquets
à installer via le dockerfile-testing. Reconstruire et
pousser l'image sur le registre :
podman build -t gitlab-ce.iut.u-bordeaux.fr:5050/$USER/heat/testing -f dockerfile-testing .
podman push gitlab-ce.iut.u-bordeaux.fr:5050/$USER/heat/testingActiver la construction de la documentation avec l'option de
configuration CMake -DHEAT_DOC=ON cf.
cmake -B build -DHEAT_DOC=ON, commit+push. Vérifier que
dans le job build, Doxygen est bien trouvé par CMake
(-- Looking for doxygen - found) et que la
documentation est bien construite (Generating html).
Ajout d'une dépendance optionnelle
Faites de même pour la dépendance MPI : ajoutez les paquets
musl-dev openmpi openmpi-dev openssh dans les dépendances et
activez l'option CMake à la configuration
cmake -B build -DHEAT_DOC=ON -DHEAT_USE_MPI=ON,
commit+push. En cas d'échec du job de test, il faut ajouter des
variables d'environnement au job, exemple :
test:
stage: test
needs: ["build"]
variables:
OMPI_ALLOW_RUN_AS_ROOT: "1"
OMPI_ALLOW_RUN_AS_ROOT_CONFIRM: "1"
script:
- ctest --test-dir build --verbose
cache:
key: "$CI_COMMIT_REF_SLUG"
untracked: true
policy: pull
Vérifier que le job test s'exécute bien et contient le test parallèle MPI heat_par_4 :
Conclusions
Remarquez qu'il est plus flexible de maintenir l'environnement de test via un fichier tel que le
dockerfile-testing, car ainsi l'environnement est sauvegardé, partagé et peut être redéployé rapidement sur d'autres machines avec une simple commande docker. La maintenance au coup par coup d'une machine de test spécifique est pratique au départ, mais il est difficile de suivre ce qui a été finalement installé, et si la machine plante définitivement, il faut réinstaller une nouvelle machine spécifique, ce qui peut être fastidieux.
La reconstruction et le redéploiement de l'image docker à la main à chaque mise à jour ne sont pas très pratiques. On va donc automatiser cette action.
Automatisation de la construction de l'image docker
On va ajouter un job GitLab pour construire l'image docker et la pousser sur le registre comme première étape.
Commencer par ajouter votre fichier dockerfile-testing
dans l'index Git avec git add et git commit.
Puis modifier le fichier .gitlab-ci.yml et y ajouter une
première étape buildenv dans stages (avant
build et test) ainsi qu'un nouveau job (cf. https://docs.gitlab.com/ee/user/packages/container_registry/build_and_push_images.html#use-gitlab-cicd)
comme ci-dessous :
buildenv:
stage: buildenv
image: quay.io/podman/stable
before_script:
- podman login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
- podman build -t $CI_REGISTRY_IMAGE/testing -f dockerfile-testing .
- podman push $CI_REGISTRY_IMAGE/testing
Note : L'authentification nécessaire pour accéder au registre est gérée automatiquement pour un pipeline.
Pour information, en Docker natif, cela donnerait :
buildenv:
stage: buildenv
image: docker
services:
- docker:dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build -t $CI_REGISTRY_IMAGE/testing -f dockerfile-testing .
- docker push $CI_REGISTRY_IMAGE/testing
Si le job échoue avec un message d'erreur du type "dial tcp: lookup
docker on… no such host", il faudra ajouter une ligne à votre fichier de
configuration du runner. Éditer le fichier
~/.gitlab-runner/config.toml et modifier le champ volume
tel que
volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"].
Maintenance de l'image
Réfléchir aux différentes conditions possibles pour le déclenchement de la reconstruction de l'image docker de test. Pourquoi reconstruire :
- Selon une politique interne de rétrocompatibilité.
- Si on a besoin d'une version plus récente des dépendances (changement d'API, nouvelle fonctionnalité).
- Selon la cible client (contrainte sur l'environnement).
- Valider pour les versions LTS des OS ciblés (ex. Windows 10, Ubuntu 22.04), ne pas rester sur un vieil OS car les dépendances vont devenir figées, nous empêchant de traiter des mises à jour nécessaires avec les versions plus récentes.
Toujours tout reconstruire
C'est le cas par défaut dans notre situation actuelle : chaque nouveau commit poussé sur le serveur déclenche un nouveau pipeline incluant le job buildenv. Pour changer cet état de fait, il faut ajouter des règles spécifiques au job GitLab en question afin de le déclencher uniquement sous certaines conditions (cf. sections suivantes).
Cette stratégie permet de démontrer que nos tests sont valides dans un environnement dont on a testé le déploiement juste avant. Cela renforce le sentiment de robustesse. En revanche, ce n'est envisageable que pour des cas où la réinstallation des dépendances à chaque pipeline est assez rapide (quelques minutes maximum). Dans le cas contraire, cela peut devenir contre-productif et on envisagera de réduire le temps dû au déploiement de l'environnement de test (cf. sections suivantes).
Ne reconstruire qu'une partie
Dans certaines situations, on peut choisir d'identifier une partie "stable" et une autre plus "en avance de phase" (avec des dépendances plus ou moins exotiques). Par exemple, sur certaines dépendances on peut avoir besoin d'une version non release (non numérotée, ex. branche master ou autre branche). Ainsi, on peut construire un environnement de base stable (template), qu'on ne construit qu'une fois de temps en temps, et redéployer la partie "en avance de phase" à chaque pipeline.
Dans cette optique, commentez le job de reconstruction de l'image (on
va considérer que c'est la partie stable qui n'a pas besoin d'être
réinstallée) et ajoutez un job gtest installant la
toute dernière version de googletest à chaque nouveau
pipeline, exemple :
stages:
- buildenv
- gtest
- build
- test
default:
image: $CI_REGISTRY_IMAGE/testing
tags: ['docker']
.buildenv:
stage: buildenv
image: quay.io/podman/stable
before_script:
- podman login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
- podman build -t $CI_REGISTRY_IMAGE/testing -f dockerfile-testing .
- podman push $CI_REGISTRY_IMAGE/testing
gtest:
stage: gtest
script:
- git clone https://github.com/google/googletest.git
- cd googletest/
- cmake -B build -S . -DCMAKE_INSTALL_PREFIX=$PWD/../gtest
- cmake --build build
- cmake --install build
cache:
key: "$CI_COMMIT_REF_SLUG"
untracked: true
policy: push
Modifier le fichier CMakeLists.txt afin de vérifier que
googletest a bien été installé :
- ajouter
find_package(GTest REQUIRED)à la ligne 12 deCMakeLists.txt
Modifier le job build afin de pouvoir utiliser
l'installation de googletest :
cmake -B build -DHEAT_DOC=ON -DHEAT_USE_MPI=ON -DCMAKE_PREFIX_PATH=$PWD/gtestpolicy: pull-push
Commiter et pousser afin de valider ce nouveau pipeline. Dans le job build, vous devriez observer que CMake trouve bien GTest :
-- Found GTest: /builds/fpruvost/heat/gtest/lib/cmake/GTest/GTestConfig.cmake (found version "1.16.0")Note : Une explication détaillée de l'option
policyest disponible dans la documentation en relation avec le cache GitLab.
Cette expérience étant terminée, et parce qu'on ne se servira pas de GTest par la suite, inversez le commit afin de revenir à l'état précédent :
git revert HEAD~1
git pushMise à jour de l'image stable
Avec une stratégie où l'on ne reconstruit pas tout lors de chaque pipeline, il faut décider d'une politique de mise à jour pour la construction de l'image de base "stable". Plusieurs possibilités s'offrent à nous pour déclencher la mise à jour.
Si le Dockerfile change
Une première méthode consiste à mettre à jour l'image dès que la
recette, c'est-à-dire le fichier dockerfile-testing,
change.
Ajouter ce code dans buildenv (décommenter le job en
enlevant le point de la ligne .buildenv:), commiter,
pousser :
only:
changes:
- dockerfile-testing
Normalement, buildenv n'est pas déclenché.
Maintenant, ajouter le paquet curl à la liste des
paquets à installer dans le dockerfile-testing, commiter,
pousser. Observez que buildenv se déclenche bien.
Il est clair que si la recette change, il faut mettre à jour l'image. En revanche, si la recette ne change pas pendant longtemps, cela implique que l'environnement sera figé dans le même temps et peut devenir trop ancien. Il faudra donc mettre à jour l'image de temps en temps manuellement via un build+push docker (podman) à partir de sa machine.
Manuellement (bouton manual)
Une seconde méthode consiste à ajouter un déclencheur manuel dans le pipeline afin de laisser la liberté aux développeurs de mettre à jour quand ils le souhaitent.
Supprimer la partie only: changes: et ajouter
when: manual au job buildenv, puis
déclencher manuellement le job en allant sur la page des pipelines et en
cliquant sur le bouton associé au job buildenv.
C'est une méthode flexible, mais le risque est de ne pas faire la mise à jour assez souvent.
Périodique (schedule)
Une troisième méthode consiste à déclencher le job périodiquement. Pour cela, il faut créer un pipeline spécifique de type schedule.
Dans GitLab, aller dans Build -> Pipeline schedules -> New schedule :
- Description : buildenv
- Interval Pattern : Every week
- Cron Timezone : UTC+2 Paris
- Target branch or tag : auto
Cliquer sur Create pipeline schedule.
Remarquez la date de prochain déclenchement du pipeline dans la colonne Next Run.
Ensuite, modifier buildenv pour supprimer la partie
when: manual et la remplacer par :
only:
- schedules
Commiter, pousser. Le pipeline se déclenche sans buildenv. En effet, dans cette configuration, ce dernier ne sera lancé que lors du pipeline de type schedule, à la date décidée dans les paramètres du schedule buildenv. Il est possible de tester le schedule en cliquant sur le bouton Play dans la page des schedules. Vérifiez que cela fonctionne bien.
Optimisation de l'image
Réflexion sur la taille de l'image
Pour certains projets, il y a beaucoup de dépendances et elles sont lourdes en termes de stockage. Il est donc important d'optimiser au mieux la taille de l'image.
Vérifier la taille occupée par les images et conteneurs podman avec
la commande podman system df. Si l'image de base utilisée
est par exemple Ubuntu, cela peut peser plusieurs gigaoctets. Regarder
image par image avec podman images.
Il existe fort heureusement des techniques pour réduire la taille des images docker :
- https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
- https://devopscube.com/reduce-docker-image-size/
Testez avec le fichier Dockerfile suivant :
FROM registry.u-bordeaux.fr/vhub/debian:12-slim
#FROM ubuntu:20.04
# Installing as root: docker images are usually set up as root.
# Since some autotools scripts might complain about this being unsafe, we set
# FORCE_UNSAFE_CONFIGURE=1 to avoid configure errors.
ENV FORCE_UNSAFE_CONFIGURE=1
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update -y
RUN apt-get install -y git curl make gcc g++ cmake doxygen musl-dev openmpi-bin openmpi-common libopenmpi-dev openssh-server openssh-client
RUN apt-get autoremove -y
Remarquez la nouvelle taille de l'image, environ 960 Mo pour Debian (890 Mo pour Ubuntu) au lieu de 340 Mo dans le cas Alpine.
Éviter l'exécution des jobs en mode root
Lorsqu'un programme ou service peut être lancé sans les privilèges root, il est préférable de créer un utilisateur Linux dans l'image et de lancer les commandes de test avec cette identité.
Modifier la recette du dockerfile-testing en ajoutant
:
# Create a group and user
RUN addgroup -S gitlab && adduser -S gitlab -G gitlab
# Create a directory where ci jobs are performed
RUN mkdir /builds && \
chown -R gitlab:gitlab /builds && \
chmod g+s /builds
# default user
USER gitlab
# default working directory
WORKDIR /builds
Il est maintenant possible d'enlever les variables d'environnement lors des ctest :
#variables:
# OMPI_ALLOW_RUN_AS_ROOT: "1"
# OMPI_ALLOW_RUN_AS_ROOT_CONFIRM: "1"
Commit+push.
Construction multi-étapes (multi-stage build)
Une image de compilation a besoin d'outils lourds : compilateurs (GCC, Clang), outils de construction (CMake, Make), fichiers d'en-têtes et paquets de développement (*-dev). En revanche, l'image finale servant à exécuter l'application en production n'a besoin que du binaire compilé et de ses bibliothèques dynamiques d'exécution.
La technique des constructions
multi-étapes (multi-stage builds) permet d'enchaîner plusieurs
directives FROM dans un même Dockerfile :
# Étape 1 : Environnement de compilation lourd (builder)
FROM registry.u-bordeaux.fr/vhub/debian:12-slim AS builder
RUN apt-get update && apt-get install -y cmake gcc make libopenmpi-dev
COPY . /heat
WORKDIR /heat
RUN cmake -B build -DHEAT_USE_MPI=ON && cmake --build build
# Étape 2 : Image finale d'exécution minimale
FROM registry.u-bordeaux.fr/vhub/debian:12-slim
RUN apt-get update && apt-get install -y libopenmpi3 && rm -rf /var/lib/apt/lists/*
# On ne copie que l'exécutable produit depuis l'étape précédente
COPY --from=builder /heat/build/heat_seq /usr/local/bin/heat_seq
COPY --from=builder /heat/build/heat_par /usr/local/bin/heat_par
USER 1000:1000
ENTRYPOINT ["/usr/local/bin/heat_seq"]
Constat :
- L'image de build complète pèse près de 900 Mo.
- L'image finale produite ne pèse qu'environ 35 Mo, tout en garantissant une surface d'attaque de sécurité considérablement réduite.
Dépannage et FAQ Podman Rootless
Sur les machines de l'IUT, l'utilisation conjointe de Podman en mode
sans privilèges (rootless), de quotas disque stricts et
d'un stockage redirigé dans /tmp/containers peut générer
des comportements spécifiques. Voici comment diagnostiquer et résoudre
les problèmes les plus fréquents :
Quota disque saturé ou erreur « No space left on device »
Si vous obtenez une erreur lors du pull ou de la compilation d'une image, vérifiez l'espace disque consommé par Podman :
podman system dfPour libérer l'espace disque occupé par les couches intermédiaires inutilisées, conteneurs arrêtés et volumes temporaires :
# Supprimer tous les conteneurs arrêtés et les images non référencées
podman system prune -a --volumes -f
# Si le dossier /tmp/containers/<login> reste encombré
rm -rf /tmp/containers/$USER/storage/tmp/*Le socket Podman ne répond plus après une reconnexion SSH
L'émulation Docker repose sur le socket UNIX de Podman géré par le systemd utilisateur. Après une déconnexion ou un redémarrage de machine, ce service peut s'arrêter :
# Vérifier le statut du service
systemctl --user status podman.socket
# Redémarrer le socket
systemctl --user restart podman.socket
# Vérifier que la variable DOCKER_HOST pointe bien sur le socket actif
export DOCKER_HOST="unix://$XDG_RUNTIME_DIR/podman/podman.sock"
podman --remote infoLe runner ne parvient pas à joindre un service local (ex. SonarQube sur localhost:9000)
Par défaut, chaque conteneur lancé par le runner possède son propre espace de noms réseau (network namespace) isolé. Pour qu'un job s'exécutant dans un conteneur puisse joindre un service tournant sur la machine hôte (comme SonarQube sur le port 9000), il faut impérativement spécifier le mode réseau hôte dans la configuration du runner :
# dans ~/.gitlab-runner/config.toml
[runners.docker]
network_mode = "host"Puis redémarrer le service runner :
systemctl --user restart gitlab-runner.serviceProblème de droits sur les fichiers créés dans un volume (UID mapping rootless)
En mode rootless, l'utilisateur intérieur du conteneur (UID 0) est
mappé sur votre propre compte Linux à l'extérieur. Si vous créez un
utilisateur non-root dans le conteneur (ex. UID 1000), les fichiers
générés dans les volumes montés peuvent sembler appartenir à un UID
inaccessible. Utilisez la commande podman unshare pour vous
placer dans l'espace de noms du conteneur et inspecter ou réparer les
droits des fichiers :
podman unshare chown -R 1000:1000 /builds