Aller au contenu
CI/CD & Automatisation medium

Capstone : pipeline industriel complet

14 min de lecture

logo gitlab

Vous avez travaillé les 11 modules du Volet 2 séparément — templates, DAG, components, services, matrices, pipelines enfants, multi-projet, workflows, fiabilité. Maintenant, assemblez-les. Ce capstone vous plonge dans un scénario réaliste : industrialiser le pipeline d’une équipe qui gère trois microservices, un frontend SPA et une bibliothèque partagée.

L’objectif n’est pas d’apprendre des nouvelles directives, mais de vérifier que toutes les techniques avancées s’imbriquent. Chaque décision renvoie au module concerné.

  • Combiner templates, DAG, components et matrices dans un pipeline industriel
  • Orchestrer des builds multi-services avec pipelines enfants et multi-projet
  • Appliquer un workflow adapté (MR + tags) avec fiabilité intégrée
  • Diagnostiquer les problèmes d’un pipeline complexe
  • Évaluer la maturité industrielle avec une grille de critères

Vous êtes DevOps dans une équipe qui gère un produit composé de :

  • api-gateway : Go — point d’entrée, routage
  • api-users : Python — gestion des utilisateurs
  • api-orders : Node.js — gestion des commandes
  • frontend : React SPA consommant les 3 APIs
  • shared-lib : bibliothèque interne de validation, utilisée par les 3 APIs

Le pipeline actuel ? Un .gitlab-ci.yml de 400 lignes sans templates, tout dupliqué, 25 minutes d’exécution, aucun retry. Votre mission : industrialiser.

Le pipeline cible utilise plusieurs niveaux d’orchestration :

shared-lib (multi-projet)
↓ déclenche les pipelines des consommateurs
api-gateway ──┐
api-users ────┼── pipeline parent → enfants par service
api-orders ───┘
frontend ──────── pipeline dédié (déclenché par tag)
DécisionTechniqueModule
Factoriser les jobs communsTemplates + ComponentsV2-01, V2-03
Paralléliser lint/test/buildDAG (needs:)V2-02
Tester sur 3 OS / 3 versionsMatricesV2-04
Builder chaque service séparémentPipelines enfantsV2-07
Déclencher quand shared-lib changeMulti-projetV2-09
Exécuter uniquement sur MR + tagsWorkflowsV2-10
Absorber les erreurs transitoiresFiabilitéV2-11

Commencez par créer un CI/CD Component pour le pattern commun à tous les services : lint → test → build Docker → push.

templates/service-pipeline/template.yml
spec:
inputs:
service_name:
type: string
language:
type: string
test_command:
type: string
default: "make test"
docker_context:
type: string
default: "."
---
.lint-$[[ inputs.service_name ]]:
stage: lint
image: $[[ inputs.language ]]:latest
script:
- make lint
needs: []
.test-$[[ inputs.service_name ]]:
stage: test
image: $[[ inputs.language ]]:latest
services:
- postgres:16-alpine
variables:
POSTGRES_DB: test
POSTGRES_USER: test
POSTGRES_PASSWORD: test
script:
- $[[ inputs.test_command ]]
needs: []
retry:
max: 1
when:
- runner_system_failure
- stuck_or_timeout_failure
.build-$[[ inputs.service_name ]]:
stage: build
image: docker:27
services:
- docker:27-dind
script:
- docker build -t $CI_REGISTRY_IMAGE/$[[ inputs.service_name ]]:$CI_COMMIT_SHA
-f $[[ inputs.docker_context ]]/Dockerfile $[[ inputs.docker_context ]]
- docker push $CI_REGISTRY_IMAGE/$[[ inputs.service_name ]]:$CI_COMMIT_SHA
needs:
- .test-$[[ inputs.service_name ]]
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failure
- unknown_failure

Lien : V2-03 — Components & Catalog pour la syntaxe spec:inputs et la publication.

Le pipeline parent orchestre : il lance un pipeline enfant par service, en parallèle.

.gitlab-ci.yml (racine du monorepo)
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_TAG =~ /^v\d+/
default:
interruptible: true
retry:
max: 1
when:
- runner_system_failure
- stuck_or_timeout_failure
stages:
- trigger
- deploy
# ── Pipelines enfants (V2-07) ──────────────────────────────────
trigger:api-gateway:
stage: trigger
trigger:
include: services/api-gateway/.gitlab-ci.yml
strategy: depend
rules:
- changes:
- services/api-gateway/**/*
- shared-lib/**/*
trigger:api-users:
stage: trigger
trigger:
include: services/api-users/.gitlab-ci.yml
strategy: depend
rules:
- changes:
- services/api-users/**/*
- shared-lib/**/*
trigger:api-orders:
stage: trigger
trigger:
include: services/api-orders/.gitlab-ci.yml
strategy: depend
rules:
- changes:
- services/api-orders/**/*
- shared-lib/**/*
trigger:frontend:
stage: trigger
trigger:
include: frontend/.gitlab-ci.yml
strategy: depend
rules:
- changes:
- frontend/**/*
# ── Déploiement global (après succès des enfants) ──────────────
deploy:staging:
stage: deploy
interruptible: false
script: ./deploy.sh staging
resource_group: staging
environment:
name: staging
url: https://staging.example.com
needs:
- trigger:api-gateway
- trigger:api-users
- trigger:api-orders
- trigger:frontend
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
deploy:production:
stage: deploy
interruptible: false
script: ./deploy.sh production --atomic
resource_group: production
environment:
name: production
url: https://app.example.com
needs:
- trigger:api-gateway
- trigger:api-users
- trigger:api-orders
- trigger:frontend
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+/
when: manual

Liens :

Chaque service a son propre .gitlab-ci.yml qui utilise le component :

services/api-users/.gitlab-ci.yml
include:
- component: $CI_SERVER_FQDN/myorg/ci-components/service-pipeline@1.0.0
inputs:
service_name: api-users
language: python
test_command: "pytest --reruns 2"
docker_context: "."
stages:
- lint
- test
- build
# Matrice de tests multi-version (V2-04)
test:matrix:
extends: .test-api-users
parallel:
matrix:
- PYTHON_VERSION: ["3.11", "3.12", "3.13"]
image: python:$PYTHON_VERSION
timeout: 15 minutes
# Services CI pour les tests d'intégration (V2-06)
test:integration:
stage: test
image: python:3.12
services:
- name: postgres:16-alpine
alias: db
- name: redis:7-alpine
alias: cache
variables:
DATABASE_URL: "postgresql://test:test@db:5432/test"
REDIS_URL: "redis://cache:6379"
script:
- pytest tests/integration/ --reruns 1
needs: []
retry:
max: 1
when:
- stuck_or_timeout_failure

Liens :

Quand shared-lib est modifiée, elle doit déclencher le pipeline des services qui l’utilisent :

shared-lib/.gitlab-ci.yml
stages:
- test
- publish
- notify
test:
stage: test
script: make test
publish:
stage: publish
script:
- make build
- twine upload dist/*
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+/
# ── Déclenchement multi-projet (V2-09) ─────────────────────────
notify:api-gateway:
stage: notify
trigger:
project: myorg/api-gateway
branch: main
strategy: depend
variables:
SHARED_LIB_VERSION: $CI_COMMIT_TAG
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+/
notify:api-users:
stage: notify
trigger:
project: myorg/api-users
branch: main
strategy: depend
variables:
SHARED_LIB_VERSION: $CI_COMMIT_TAG
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+/
notify:api-orders:
stage: notify
trigger:
project: myorg/api-orders
branch: main
strategy: depend
variables:
SHARED_LIB_VERSION: $CI_COMMIT_TAG
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+/

Lien : V2-09 — Multi-projet pour trigger:project et la propagation de variables.

Revoyez chaque job avec la grille de fiabilité :

VérificationAppliqué ?
retry:when ciblé (pas always) → V2-11
timeout adapté à chaque type de job
interruptible: true par défaut, false sur deploy
resource_group sur les déploiements
Retry framework (pytest —reruns) pour les tests
Jobs idempotents (noms basés sur $CI_COMMIT_SHA)
allow_failure sur les scans non bloquantsÀ ajouter

Votre pipeline du capstone échoue. Voici les symptômes — trouvez la cause et la solution pour chacun.

Cas 1 — Le pipeline enfant n’est pas déclenché

Section intitulée « Cas 1 — Le pipeline enfant n’est pas déclenché »
trigger:api-users → skipped
Indice

Vérifiez la section rules: changes:. Le chemin du fichier modifié correspond-il au pattern ?

Solution

Le fichier modifié est dans services/api-users/src/ mais la règle changes: pointe vers services/api-users/**/*. Vérifiez que le glob couvre bien les sous-dossiers. Si votre commit ne touche que shared-lib/, le trigger api-users ne se lance que si shared-lib/**/* est dans ses changes:.

Cas 2 — La matrice de tests échoue sur une seule version

Section intitulée « Cas 2 — La matrice de tests échoue sur une seule version »
test:matrix 3/3 (Python 3.13) → failed (script_failure)
test:matrix 1/3 (Python 3.11) → passed
test:matrix 2/3 (Python 3.12) → passed
Indice

Ce n’est pas un problème de CI. C’est un problème de compatibilité.

Solution

Vérifiez les dépendances dans requirements.txt : une bibliothèque peut ne pas encore supporter Python 3.13. Utilisez allow_failure temporairement sur cette version pendant la migration :

test:matrix:
parallel:
matrix:
- PYTHON_VERSION: ["3.11", "3.12"]
- PYTHON_VERSION: "3.13"
ALLOW_FAIL: "true"
allow_failure:
exit_codes: [1]
rules:
- if: $ALLOW_FAIL == "true"
allow_failure: true
- when: on_success

Cas 3 — Le deploy staging se lance avant la fin des enfants

Section intitulée « Cas 3 — Le deploy staging se lance avant la fin des enfants »
deploy:staging → running (alors que trigger:api-orders est encore en cours)
Indice

Vérifiez la directive needs: du job de déploiement.

Solution

Sans strategy: depend sur les triggers, le job parent se termine dès que le pipeline enfant est déclenché, pas quand il est terminé. Ajoutez strategy: depend à chaque trigger, et assurez-vous que le deploy a bien needs: sur tous les triggers.

Évaluez votre pipeline avec cette grille. Chaque critère correspond à un module du Volet 2 :

CritèreScoreModule
Templates factorisent au moins 60 % du YAML/2V2-01
DAG activé, durée réduite vs stages linéaires/2V2-02
Au moins 1 CI/CD Component publié/1V2-03
Matrice de tests sur ≥ 2 versions/1V2-04
Services CI avec healthcheck/1V2-06
Pipeline enfant par service (monorepo)/2V2-07
Trigger multi-projet pour les libs partagées/1V2-09
Workflow:rules avec MR + tags/2V2-10
Retry ciblé + timeouts + interruptible/2V2-11
Jobs de deploy idempotents + resource_group/1V2-11

Interprétation :

  • 12–15 : Pipeline industriel solide, prêt pour la production
  • 8–11 : Bonne base, quelques optimisations manquantes
  • < 8 : Revoyez les modules où vous avez 0

Répondez sans regarder les modules. Si vous bloquez, le lien vous renvoie au bon guide.

  1. Quelle directive remplace include: pour les composants réutilisables ? (V2-03)
  2. Comment exécuter un job dès que ses dépendances sont finies, sans attendre le stage ? (V2-02)
  3. Quelle directive empêche deux déploiements de tourner en parallèle ? (V2-11)
  4. Comment déclencher le pipeline d’un autre projet quand shared-lib change ? (V2-09)
  5. Quelle section du .gitlab-ci.yml empêche les pipelines en double (branche + MR) ? (V2-10)
  6. Quel keyword permet de ne builder que les services dont le code a changé ? (V2-07)
  7. Comment faire tourner PostgreSQL et Redis comme services dans un job de test ? (V2-06)
  8. Quel type d’erreur ne devrait jamais être dans retry:when pour un job de tests ? (V2-11)
  • Un pipeline industriel n’est pas un gros fichier — c’est un ensemble orchestré de pipelines factorisés
  • Components > templates > copier-coller : factorisez dans cet ordre de préférence
  • Les pipelines enfants isolent chaque service : échec local, pas global
  • Le multi-projet connecte les dépendances : quand la lib change, les consommateurs re-testent
  • La fiabilité se configure partout : retry ciblé, timeout adapté, interruptible sélectif
  • Les workflows évitent les doublons : workflow:rules est votre garde global
  • Mesurez la maturité : la grille de critères donne un score objectif
  • Le but final : un pipeline qui échoue vite, récupère seul, et déploie en confiance

Ce site vous est utile ?

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

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn