Aller au contenu
CI/CD & Automatisation medium

Synthèse : construire un pipeline complet

21 min de lecture

logo gitlab

Vous avez travaillé les 13 modules précédents séparément, stages, variables, rules, cache, environnements, rapports. Maintenant, assemblez-les. Ce module est un mini-capstone : vous construisez un pipeline de bout en bout pour une application réelle, en appliquant toutes les briques apprises.

L'objectif n'est pas d'apprendre du neuf, mais de vérifier que tout s'imbrique. Si vous bloquez, chaque section renvoie au module concerné.

  • Assembler un .gitlab-ci.yml complet combinant toutes les briques du Volet 1
  • Structurer les stages dans un ordre logique (lint → test → build → deploy)
  • Appliquer les bonnes pratiques : variables protégées, rules ciblées, cache optimisé
  • Diagnostiquer rapidement un pipeline cassé avec une méthode systématique
  • Évaluer si votre pipeline est prêt pour l'industrialisation (Volet 2)

Vous avez terminé les 13 premiers modules. Vous savez créer un job, configurer un runner, utiliser le cache et les artifacts, écrire des rules, configurer des environnements, générer des rapports. Mais quand vous ouvrez un .gitlab-ci.yml de production, vous voyez 200 lignes et vous ne savez pas par où commencer.

Ce guide vous met dans cette situation :

  • Vous rejoignez un projet avec un pipeline incomplet
  • Vous devez construire un pipeline complet pour une app Node.js
  • Le pipeline doit compiler, tester, analyser la qualité et déployer en staging puis en production
  • Vous devez gérer les cas : push sur branche, merge request, tag de release

Voici le pipeline complet que vous allez construire, brique par brique. Chaque section est annotée avec le module du Volet 1 qui l'explique en détail.

Ne cherchez pas à tout comprendre d'un bloc. Lisez-le dans l'ordre des numéros de commentaire : les sections 1 à 3 posent le squelette (stages, variables, cache) et ne contiennent aucun job. Les sections 4 à 9 déclarent les jobs eux-mêmes, du plus rapide au plus engageant. Chaque décision qui n'est pas évidente à la lecture est reprise et justifiée juste après.

.gitlab-ci.yml
# ── 1. Stages : l'ordre d'exécution (V1-01) ──────────────────────
stages:
- lint
- test
- build
- deploy
# ── 2. Variables globales (V1-07) ─────────────────────────────────
variables:
NODE_VERSION: "20"
APP_NAME: "mon-app"
DEPLOY_PATH: "/srv/$APP_NAME"
# Variables CI_ automatiques : CI_COMMIT_REF_NAME, CI_PIPELINE_SOURCE…
# ── 3. Cache global (V1-06) ───────────────────────────────────────
default:
cache:
key:
files:
- package-lock.json
paths:
- node_modules/
policy: pull
# ── 4. Lint (V1-01, V1-08) ────────────────────────────────────────
lint:
stage: lint
image: node:${NODE_VERSION}
cache:
key:
files:
- package-lock.json
paths:
- node_modules/
policy: pull-push
script:
- npm ci --prefer-offline
- npm run lint
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# ── 5. Tests unitaires + couverture (V1-06, V1-11) ────────────────
test:unit:
stage: test
image: node:${NODE_VERSION}
script:
- npm ci --prefer-offline
- npm test -- --coverage --reporters=default --reporters=jest-junit
artifacts:
when: always
paths:
- coverage/
reports:
junit: junit.xml
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
expire_in: 7 days
coverage: '/All files\s*\|\s*(\d+\.?\d*)\s*\|/'
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_TAG
# ── 6. Build (V1-06, V1-07) ───────────────────────────────────────
build:
stage: build
image: node:${NODE_VERSION}
script:
- npm ci --prefer-offline
- npm run build
- echo "BUILD_VERSION=$(node -p 'require(\"./package.json\").version')" >> build.env
artifacts:
paths:
- dist/
reports:
dotenv: build.env
expire_in: 30 days
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_TAG
# ── 7. Déploiement staging (V1-10, V1-08) ─────────────────────────
deploy:staging:
stage: deploy
image: alpine:3.24.1
environment:
name: staging
url: https://staging.example.com
on_stop: stop:staging
script:
- echo "Deploying $APP_NAME v$BUILD_VERSION to staging..."
- echo "Target → $DEPLOY_PATH on staging server"
# Remplacer par votre méthode de déploiement (rsync, scp, kubectl…)
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# ── 8. Stop staging (V1-10) ───────────────────────────────────────
stop:staging:
stage: deploy
image: alpine:3.24.1
environment:
name: staging
action: stop
script:
- echo "Stopping staging environment..."
when: manual
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: manual
# ── 9. Déploiement production (V1-10, V1-09, V1-07) ───────────────
deploy:production:
stage: deploy
image: alpine:3.24.1
environment:
name: production
url: https://www.example.com
variables:
DEPLOY_STRATEGY: "rolling"
script:
- echo "Deploying $APP_NAME v$BUILD_VERSION to production..."
- echo "Strategy → $DEPLOY_STRATEGY"
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
when: manual
allow_failure: false
resource_group: production
stages: [lint, test, build, deploy]
OrdreStageRaison
1lintFeedback le plus rapide, erreurs de style en quelques secondes
2testValide la logique métier, plus lent que le lint
3buildConstruit l'artefact, inutile si les tests échouent
4deployDéploie, uniquement si tout est vert

Principe : les jobs les plus rapides et les moins coûteux d'abord. Si le lint échoue en 10 secondes, inutile de lancer 5 minutes de tests.

Module de référence : Concepts de base (V1-01)

cache:
key:
files:
- package-lock.json

Le cache est invalidé uniquement quand les dépendances changent. Tant que package-lock.json ne bouge pas, les node_modules/ sont réutilisés. C'est plus fiable que $CI_COMMIT_REF_SLUG qui crée un cache par branche.

La politique pull-push sur le job lint (premier à tourner) alimente le cache. Les jobs suivants utilisent pull (lecture seule) pour éviter les écritures concurrentes.

Module de référence : Artifacts et cache (V1-06)

rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

rules est le mécanisme recommandé depuis GitLab 12.3. Il remplace only/except qui ne gère pas les conditions complexes. Chaque if est évalué dans l'ordre : la première règle qui matche détermine si le job tourne et avec quel when.

Attention au piège classique : si aucune règle ne matche, le job est exclu du pipeline. Pas de rules par défaut = le job tourne toujours.

Module de référence : Conditions d'exécution, rules (V1-08)

artifacts:
reports:
junit: junit.xml
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml

Ces rapports s'intègrent directement dans l'interface GitLab :

  • JUnit : les résultats de tests apparaissent dans l'onglet "Tests" du pipeline et dans la merge request
  • Cobertura : l'indicateur de couverture apparaît sur le badge du projet et la diff de la MR montre les lignes couvertes ou non

Sans ces rapports, vous devez fouiller les logs pour trouver quels tests ont échoué.

Module de référence : Rapports qualité (V1-11)

environment:
name: staging
url: https://staging.example.com
on_stop: stop:staging

Les environnements GitLab offrent :

  • Un historique des déploiements visible dans l'interface
  • Un lien direct vers l'URL de l'application
  • Un bouton Stop pour les environnements temporaires (review apps)

Le on_stop est essentiel pour le nettoyage. GitLab déclenche le job stop:staging dans trois situations : quand vous cliquez sur Stop dans l'interface, quand la branche associée est supprimée, ou à l'expiration d'un délai auto_stop_in si vous en déclarez un. Sans on_stop, un environnement de test créé par branche reste listé indéfiniment et ses ressources ne sont jamais libérées.

Module de référence : Environnements (V1-10)

Pourquoi when: manual et resource_group en production ?

Section intitulée « Pourquoi when: manual et resource_group en production ? »
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
when: manual
allow_failure: false
resource_group: production

Un déploiement en production doit résister à deux scénarios : le déclenchement accidentel (un commit sur la mauvaise branche) et le déploiement concurrent (deux pipelines qui écrivent en même temps sur le même serveur). Aucun mot-clé ne couvre les deux, d'où l'empilement de quatre garde-fous complémentaires listés ci-dessous.

Le plus souvent oublié est resource_group : sans lui, deux tags poussés à une minute d'intervalle lancent deux jobs de déploiement en parallèle, et l'ordre d'arrivée sur le serveur cible n'est pas garanti.

MécanismeProtection
when: manualUn humain doit cliquer pour déployer, pas de déploiement accidentel
allow_failure: falseLe pipeline reste bloqué tant que le déploiement n'est pas fait (pas de faux vert)
resource_groupUn seul déploiement en production à la fois, évite les race conditions
Regex sur le tagSeuls les tags au format vX.Y.Z déclenchent le job

Modules de référence : Déclencheurs (V1-09) et Variables (V1-07)

Avant de pousser votre pipeline, vérifiez chaque point. Les six familles ci-dessous correspondent aux six endroits où un pipeline se dégrade avec le temps : la structure, les variables, le cache, les conditions, les environnements et la validation.

La structure est ce que vous lisez en premier quand un pipeline échoue six mois plus tard. Le nom du job est la seule information affichée dans la vue pipeline et dans la notification d'échec : test:unit vous dit immédiatement quoi rejouer, job1 vous oblige à rouvrir le YAML. Quant à l'ordre des stages, il conditionne le temps de retour : mal ordonné, un pipeline peut mettre huit minutes à signaler une faute de style détectable en dix secondes.

  • Les stages sont ordonnés du plus rapide au plus lent
  • Chaque job a un stage explicite
  • Pas de job sans script (ou trigger)
  • Les noms de jobs sont descriptifs (test:unit, pas job1)

Le point critique est la portée. Une variable déclarée à la racine s'applique à tous les jobs, une variable de job écrase la valeur globale, et une variable définie dans l'interface du projet l'emporte sur les deux. Cette précédence explique la majorité des « ma variable est vide » : la valeur existe, mais un autre niveau l'a remplacée.

Côté secrets, deux attributs se cumulent et ne font pas la même chose. Masked remplace la valeur par des [MASKED] dans les logs de job. Protected interdit purement l'injection de la variable en dehors des branches et tags protégés. Un secret de production doit porter les deux.

  • Pas de secret dans le .gitlab-ci.yml, tout dans Settings > CI/CD > Variables
  • Les variables sensibles sont masked et protected
  • Les variables globales sont dans variables: au niveau racine
  • Les variables spécifiques sont dans le job concerné

Les deux mécanismes se ressemblent et servent des buts opposés. Le cache est une optimisation : s'il est perdu, le pipeline reste correct, seulement plus lent. Un artifact est un livrable : s'il est perdu, le job suivant échoue. Un dist/ mis en cache au lieu d'être publié en artifact produit exactement ce symptôme, un déploiement qui marche une fois sur deux selon le runner qui a récupéré le job.

Le when: always sur les artifacts de test mérite une attention particulière : par défaut, GitLab ne conserve les artifacts que si le job réussit. Or c'est précisément quand les tests échouent que vous voulez lire le rapport JUnit.

  • Le cache utilise une clé basée sur un fichier de lock (package-lock.json, Gemfile.lock…)
  • Les artifacts ont un expire_in raisonnable (pas de rétention infinie)
  • Les rapports (JUnit, Cobertura) sont déclarés dans artifacts:reports
  • when: always sur les artifacts de test (pour voir les échecs)

Une règle trop permissive ne provoque jamais d'erreur visible : le job tourne, simplement il tourne trop souvent. Le coût se paie en minutes de compute et, pour un job de déploiement, en incidents. Un deploy déclenché depuis une merge request pousse le code d'une branche non relue sur un serveur réel.

Relisez chaque bloc rules en vous posant une seule question : quelles sont toutes les situations où cette condition devient vraie ? Les pipelines de merge request, les pipelines de tag et les pipelines planifiés comptent parmi elles, et ce sont ceux qu'on oublie.

  • Chaque job a des rules explicites (pas de only/except)
  • Les jobs de déploiement ne tournent pas sur les merge requests
  • Le déploiement production nécessite when: manual
  • Pas de règle trop permissive (if: $CI_COMMIT_BRANCH sans filtre)

Déclarer un environment ne change rien à l'exécution du script : c'est une déclaration à destination de GitLab, qui construit à partir d'elle l'historique des déploiements, le lien vers l'application et le bouton d'arrêt. Un job de déploiement sans environment fonctionne, mais vous perdez la trace de qui a déployé quoi et quand, exactement l'information réclamée le jour d'un incident.

  • Chaque job de déploiement déclare un environment
  • Les environnements temporaires ont un on_stop
  • La production utilise resource_group

Une erreur de syntaxe YAML ne casse pas un job, elle empêche le pipeline entier de démarrer. Le retour arrive alors sous forme d'un message générique dans l'interface, plusieurs minutes après le push. Valider avant de pousser ramène ce délai à quelques secondes et évite de polluer l'historique de la branche avec des commits « fix ci » successifs.

La validation locale ne couvre en revanche que la syntaxe. Vérifiez séparément le comportement : un pipeline correct en mode push peut ne rien déclencher en mode merge request si les rules ne prévoient pas ce cas.

  • Le YAML est validé avant le push (CI Lint ou extension VS Code)
  • Le pipeline tourne correctement en mode MR et en mode push
  • Les rapports s'affichent dans l'interface GitLab

Diagnostic rapide : les 5 erreurs les plus fréquentes

Section intitulée « Diagnostic rapide : les 5 erreurs les plus fréquentes »

Quand un pipeline complet ne fonctionne pas, la tentation est de relire le YAML de haut en bas. C'est rarement rentable : sur un pipeline assemblé à partir des briques du Volet 1, cinq causes couvrent l'écrasante majorité des échecs, et chacune se reconnaît à un symptôme distinct avant d'ouvrir le fichier.

Suivez donc l'ordre ci-dessous, du plus global au plus local : d'abord le pipeline existe-t-il, ensuite le job démarre-t-il, enfin le script fait-il ce qu'on attend.

  1. Pipeline skipped ?

    Vérifiez workflow:rules au niveau racine. Si aucune règle ne matche, le pipeline entier est ignoré.

    Debug : pipeline skipped (V1-13)

  2. Job pending depuis 10 minutes ?

    Le runner n'est pas disponible. Vérifiez les tags du job vs ceux du runner, et que le runner est bien actif.

    Debug : job pending (V1-12)

  3. Job échoué avec "file not found" ?

    L'artefact d'un job précédent n'est pas transmis. Vérifiez artifacts:paths du job producteur et que les stages sont dans le bon ordre.

    Artifacts et cache (V1-06)

  4. Variable vide dans le script ?

    Vérifiez la précédence : une variable de job écrase une variable globale, une variable protégée n'est disponible que sur les branches protégées.

    Variables et secrets (V1-07)

  5. Règle qui ne matche pas ?

    Ajoutez un job de debug temporaire qui affiche les variables CI :

    debug:rules:
    stage: lint
    script:
    - echo "SOURCE=$CI_PIPELINE_SOURCE"
    - echo "BRANCH=$CI_COMMIT_BRANCH"
    - echo "TAG=$CI_COMMIT_TAG"
    - echo "MR=$CI_MERGE_REQUEST_IID"
    rules:
    - when: always

    Conditions d'exécution, rules (V1-08)

Auto-évaluation : êtes-vous prêt pour le Volet 2 ?

Section intitulée « Auto-évaluation : êtes-vous prêt pour le Volet 2 ? »

Répondez honnêtement à ces questions. Si vous répondez oui à au moins 8 sur 10, vous êtes prêt pour l'industrialisation.

#QuestionModule
1Je sais écrire un .gitlab-ci.yml sans copier-coller un exempleV1-01, V1-02
2Je sais valider ma syntaxe YAML avant de pousserV1-03
3Je sais lire les logs d'un job pour trouver l'erreurV1-04
4Je comprends la différence entre un runner shared et un runner projectV1-05
5Je sais quand utiliser cache et quand utiliser artifactsV1-06
6Je sais protéger un secret et comprendre la précédence des variablesV1-07
7Je sais écrire des rules avec if, changes et existsV1-08
8Je sais configurer un pipeline pour MR, tags et schedulesV1-09
9Je sais créer un environnement avec une stop actionV1-10
10Je sais intégrer des rapports JUnit et couvertureV1-11
  • Un pipeline de production combine toutes les briques : stages, variables, cache, rules, artifacts, environnements, rapports
  • L'ordre des stages suit le principe du feedback rapide : lint → test → build → deploy
  • Le cache utilise un fichier de lock comme clé, pas le nom de branche
  • Les rules remplacent only/except, chaque job doit avoir des conditions explicites
  • Les secrets vont dans Settings > CI/CD > Variables, jamais dans le YAML
  • La production nécessite when: manual + resource_group + tag regex
  • Les rapports JUnit et Cobertura s'intègrent dans l'interface GitLab, utilisez-les
  • Un pipeline qui ne se valide pas avant le push = du temps perdu

Ce quiz porte uniquement sur les briques assemblées dans ce module. Une réponse hésitante sur une question signale le module du Volet 1 à relire avant de passer à l'industrialisation.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

10 questions
5 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Vous maîtrisez les fondamentaux. C'est le moment de passer à la vitesse supérieure : factoriser, accélérer et orchestrer vos pipelines.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn