
Un pipeline peut être propre, modulaire et malgré tout fragile. Il suffit d'une défaillance runner temporaire, d'un timeout non maîtrisé ou de deux déploiements concurrents pour casser la chaîne. Ce lab vous fait traiter cette couche de résilience.
Cette page fait partie d'une série de labs progressifs sur Pipeline Craft. Pour comprendre le projet fil rouge, le workflow fork -> starter -> solution et la progression complète, commencez par Labs Pipeline Craft.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Diagnostiquer une erreur de stage volontairement injectée
- Ajouter
retryavec un ciblage des causes transitoires - Poser des
timeoutexplicites sur les jobs critiques - Utiliser
resource_grouppour sérialiser les déploiements
Dans quel contexte ?
Section intitulée « Dans quel contexte ? »Cette étape correspond au passage d'un pipeline "fonctionnel" à un pipeline "opérationnel". Dans la vraie vie, les échecs transitoires sont inévitables : runners indisponibles, congestion réseau, jobs longs qui s'accumulent, déploiements concurrents.
Ce lab est utile quand :
- les pipelines échouent de manière intermittente ;
- plusieurs déploiements se chevauchent ;
- l'équipe veut réduire les faux négatifs CI.
Prérequis
Section intitulée « Prérequis »- Lab 17, Workflows branches et MR terminé
- Avoir lu Fiabilité des pipelines
Point de départ
Section intitulée « Point de départ »La branche starter/lab-18 contient un pipeline volontairement invalide. L'objectif de cette
première manipulation n'est pas de le corriger tout de suite, mais de constater comment GitLab
signale le problème. Notez la différence entre les deux modes de glab ci lint : sans option, la
commande fait une validation statique du fichier ; avec --dry-run --ref, elle demande au
serveur de simuler la création du pipeline sur la branche indiquée, ce qui détecte en plus les
incohérences liées aux règles et aux stages.
-
Basculez sur la branche de départ
Fenêtre de terminal cd pipeline-craftgit checkout starter/lab-18 -
Lancez un lint branch-aware
Fenêtre de terminal glab ci lint --dry-run --ref starter/lab-18 -
Observez l'erreur attendue
Le starter est volontairement invalide avec un stage
orchestrateabsent.
Le problème
Section intitulée « Le problème »Vous devez d'abord rétablir un pipeline valide, puis renforcer sa robustesse sur les jobs sensibles.
L'exercice
Section intitulée « L'exercice »Étape 1, Corriger la base cassée
Section intitulée « Étape 1, Corriger la base cassée »GitLab refuse un pipeline dont un job référence un stage absent de la liste stages:. L'erreur
est facile à lire une fois qu'on sait où regarder : le message du lint nomme le job fautif et le
stage inconnu. Attention à la position du nouveau stage dans la liste : l'ordre de stages:
détermine l'ordre d'exécution, orchestrate doit donc se placer entre test et build.
-
Ouvrez
.gitlab-ci.yml -
Ajoutez le stage manquant
orchestratedansstages: -
Revalidez
Fenêtre de terminal glab ci lint .gitlab-ci.yml
👉 Vérifier votre solution (Étape 1)
1️⃣ Stage manquant dans .gitlab-ci.yml
Section intitulée « 1️⃣ Stage manquant dans .gitlab-ci.yml »Le starter du lab 18 référence déjà des jobs d'orchestration, mais oublie le stage correspondant.
stages: - lint - test - orchestrate - build - deployÉtape 2, Durcir le job de build
Section intitulée « Étape 2, Durcir le job de build »Le mot-clé retry accepte une liste when qui restreint les causes d'échec donnant droit à une
relance. Deux valeurs comptent ici : runner_system_failure couvre un runner qui disparaît ou
plante, stuck_or_timeout_failure couvre un job qui reste bloqué sans progresser. Un retry
sans when relance sur toutes les causes, y compris un test qui échoue légitimement, ce qui
transforme le pipeline en générateur de faux verts. Le timeout du job, lui, prime sur le timeout
global du projet tant qu'il est inférieur au timeout du runner.
-
Ajoutez un retry ciblé
Indice : ciblez les causes transitoires infra, pas les erreurs applicatives.
-
Ajoutez un timeout explicite
Indice : le timeout doit rester réaliste pour un build Docker normal.
-
Gardez
interruptible: truesur les jobs non critiques de long run
👉 Vérifier votre solution (Étape 2)
1️⃣ Renforcement de docker-build dans ci/build.yml
Section intitulée « 1️⃣ Renforcement de docker-build dans ci/build.yml »Trois ajouts seulement, mais tous placés au niveau du job et non du stage : retry restreint aux
deux causes infrastructure, timeout calibré sur la durée réelle d'un build Docker, et
interruptible: true conservé puisqu'un build annulé ne laisse aucun état à nettoyer.
docker-build: stage: build needs: - pytest-matrix - run-child-pipeline image: docker:27@sha256:aa3df78ecf320f5fafdce71c659f1629e96e9de0968305fe1de670e0ca9176ce services: - docker:27-dind variables: <<: *docker_vars retry: max: 2 when: - runner_system_failure - stuck_or_timeout_failure timeout: 20m interruptible: trueÉtape 3, Fiabiliser les déploiements
Section intitulée « Étape 3, Fiabiliser les déploiements »Un déploiement ne se traite pas comme un build. resource_group crée un verrou nommé : deux
jobs qui portent le même nom de groupe ne s'exécutent jamais en parallèle, le second attend la fin
du premier. Le nom du groupe doit donc correspondre à la ressource réellement partagée, ici
l'environnement cible, sinon vous sérialisez des déploiements qui n'ont rien à voir entre eux.
Symétriquement, interruptible: false empêche qu'un nouveau commit annule un déploiement en
cours et laisse l'environnement dans un état intermédiaire.
-
Ajoutez
resource_grouppour éviter les déploiements concurrentsIndice : utilisez un verrou distinct par environnement.
-
Ajoutez
timeoutetretryraisonnables sur staging et production -
Conservez
interruptible: falsepour éviter une interruption de déploiement en cours
👉 Vérifier votre solution (Étape 3)
1️⃣ Durcissement de ci/deploy.yml
Section intitulée « 1️⃣ Durcissement de ci/deploy.yml »Comparez les deux jobs : les valeurs diffèrent là où l'environnement diffère. Le resource_group
porte le nom de l'environnement, le timeout est plus généreux en production, et le retry reste
à max: 1 des deux côtés car relancer un déploiement partiel est plus risqué qu'échouer proprement.
deploy-staging: stage: deploy image: alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc script: - echo "Deploying $CI_COMMIT_SHORT_SHA to staging..." - ./scripts/deploy-demo.sh staging resource_group: staging retry: max: 1 when: - runner_system_failure timeout: 10m interruptible: false rules: - if: $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
deploy-production: stage: deploy image: alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc script: - echo "Deploying $CI_COMMIT_SHORT_SHA to production..." - ./scripts/deploy-demo.sh production resource_group: production retry: max: 1 when: - runner_system_failure timeout: 15m interruptible: false rules: - if: $CI_COMMIT_TAG - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH when: manualÉtape 4, Valider puis pousser
Section intitulée « Étape 4, Valider puis pousser »Le lint ne valide que la syntaxe et la cohérence des stages : il ne peut pas vous dire si vos
retry sont bien ciblés ni si le verrou de déploiement fonctionne. Ces comportements ne se
constatent qu'à l'exécution, d'où le run complet demandé à la dernière étape. Poussez sur la
branche starter/lab-18 plutôt que sur la branche par défaut, pour que le job
deploy-production reste en déclenchement manuel.
-
Validation CI
Fenêtre de terminal glab ci lint .gitlab-ci.yml -
Commit et push
Fenêtre de terminal git add .gitlab-ci.yml ci/build.yml ci/deploy.ymlgit commit -m "ci: improve reliability with retry timeout and resource locks"git push origin starter/lab-18 -
Vérifiez un run complet
👉 Vérifier votre solution (Étape 4)
1️⃣ Contrôles attendus
Section intitulée « 1️⃣ Contrôles attendus »Les deux premiers points se vérifient sur le run lui-même, les deux derniers demandent de relire la configuration : ni le verrou de déploiement ni les timeouts ne se manifestent tant qu'aucun incident ne survient.
- le lint ne remonte plus l'erreur de stage manquant ;
docker-buildsupporte mieux les pannes transitoires runner/réseau ;- les déploiements sont sérialisés (
resource_group) et non interruptibles ; - les timeouts explicites empêchent les jobs bloqués trop longtemps.
Le fichier complet
Section intitulée « Le fichier complet »Les trois fichiers ci-dessous montrent l'état final attendu. Comparez-les avec les vôtres plutôt
que de les recopier : l'intérêt du lab est le raisonnement sur le placement des garde-fous. Un
détail à ne pas manquer dans ci/build.yml : l'ancre YAML .docker-vars est définie en tête de
fichier, l'extrait de l'étape 2 ne montre que sa référence <<: *docker_vars. Une ancre doit
toujours être déclarée avant son utilisation dans le même document, sinon GitLab rejette le
fichier.
📄 Voir le fichier .gitlab-ci.yml complet
stages: - lint - test - orchestrate - build - deploy
workflow: rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS when: never - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: $CI_COMMIT_BRANCH - if: $CI_COMMIT_TAG
default: interruptible: true
variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.pip-cache"
include: - local: ci/lint.yml - local: ci/test.yml - local: ci/orchestration.yml - local: ci/build.yml - local: ci/deploy.yml📄 Voir le fichier ci/build.yml complet
.docker-vars: &docker_vars DOCKER_TLS_CERTDIR: "/certs"
docker-build: stage: build needs: - pytest-matrix - run-child-pipeline image: docker:27@sha256:aa3df78ecf320f5fafdce71c659f1629e96e9de0968305fe1de670e0ca9176ce services: - docker:27-dind variables: <<: *docker_vars retry: max: 2 when: - runner_system_failure - stuck_or_timeout_failure timeout: 20m interruptible: true script: - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA . - docker build -t $CI_REGISTRY_IMAGE:latest . - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA - docker push $CI_REGISTRY_IMAGE:latest📄 Voir le fichier ci/deploy.yml complet
deploy-staging: stage: deploy image: alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc script: - echo "Deploying $CI_COMMIT_SHORT_SHA to staging..." - ./scripts/deploy-demo.sh staging resource_group: staging retry: max: 1 when: - runner_system_failure timeout: 10m interruptible: false rules: - if: $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
deploy-production: stage: deploy image: alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc script: - echo "Deploying $CI_COMMIT_SHORT_SHA to production..." - ./scripts/deploy-demo.sh production resource_group: production retry: max: 1 when: - runner_system_failure timeout: 15m interruptible: false rules: - if: $CI_COMMIT_TAG - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH when: manualVérification
Section intitulée « Vérification »Le lab est réussi quand les quatre points ci-dessous sont vrais simultanément. Le troisième est
celui qu'on oublie le plus souvent : sans timeout explicite, un job hérite du timeout du projet,
souvent fixé à une heure, pendant laquelle il monopolise un runner sans que personne ne s'en
aperçoive.
- Le lint ne renvoie plus l'erreur de stage
-
retryest ciblé sur des causes transitoires pertinentes - Les jobs critiques ont un
timeoutexplicite - Les déploiements sont sérialisés via
resource_group
Pièges fréquents
Section intitulée « Pièges fréquents »Un retry trop large masque les vraies erreurs applicatives. Ciblez seulement les cas transitoires liés à l'infrastructure CI.
Ne mettez pas interruptible: true sur un job de production critique, sinon un nouveau pipeline peut interrompre un déploiement en cours.
À retenir
Section intitulée « À retenir »- Fiabiliser un pipeline, ce n'est pas seulement le faire passer une fois
retry,timeoutetresource_grouptraitent des risques différents- Le starter cassé est un exercice de diagnostic, pas un accident
- La résilience CI doit rester explicite et mesurable
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Lab 19, capstone industriel : Reconstruire un pipeline complet sans pas-à-pas,
retryettimeoutcompris. - Introduction à la sécurité des pipelines : Passer de la robustesse face aux pannes à la robustesse face à un attaquant.
- Durcir vos pipelines : Appliquer les dix mesures qui empêchent qu'un
retryrejoue du code compromis.