Aller au contenu
CI/CD & Automatisation medium

Lab 19, Capstone industriel

60 min de lecture

logo gitlab

Vous arrivez au capstone du bloc industrialisation. Le starter est volontairement minimal : pas de pipeline prêt à l'emploi, uniquement un cahier des charges. Votre mission est de reconstruire une CI complète, cohérente et robuste, comme dans un vrai contexte de reprise ou de migration.

  • Traduire un cahier des charges CI en implémentation concrète
  • Structurer une solution modulaire sans dépendre d'un tutoriel pas à pas
  • Vérifier la cohérence globale d'un pipeline complexe
  • Faire un auto-audit par comparaison avec une branche de référence

Ce type d'exercice reflète les situations réelles où vous devez reprendre un pipeline partiellement absent ou obsolète, puis le remettre au niveau des standards d'équipe.

Vous êtes dans un contexte de :

  • migration vers une nouvelle base CI ;
  • onboarding d'un projet sans historisation claire ;
  • évaluation de compétence DevOps orientée production.

Ces trois commandes ne construisent rien : elles vous placent dans les conditions de départ et vous font lire le cahier des charges. Prenez le temps de la deuxième étape, c'est le seul document qui définit ce qui sera considéré comme livré. Contrairement aux labs précédents, aucune correction progressive ne vous attend entre les étapes, l'évaluation se fait à la fin sur le pipeline complet.

  1. Basculez sur le starter capstone

    Fenêtre de terminal
    cd pipeline-craft
    git checkout starter/lab-19
  2. Lisez le cahier des charges

    Fenêtre de terminal
    cat CAPSTONE-LAB-19.md
  3. Constatez l'état initial

    Le starter ne contient pas de .gitlab-ci.yml prêt à exécuter. C'est normal.

Vous devez livrer une CI complète à partir d'un état vierge, tout en respectant les contraintes d'industrialisation définies dans les labs précédents.

Chaque ligne de cette liste correspond à un lab du bloc industrialisation et constitue un critère observable : soit l'élément est présent dans votre pipeline et vérifiable, soit il ne l'est pas. Traitez-les comme des exigences indépendantes plutôt que comme un ordre de rédaction. La dernière, le contrat de pipeline, est celle qu'on oublie le plus souvent : c'est un job qui vérifie la structure de votre propre CI et échoue si un fichier manque.

  • Pipeline multi-stages : lint, test, orchestrate, build, deploy
  • Templates locaux en ci/*.yml
  • Matrice de tests Python (3.11, 3.12)
  • Orchestration parent-enfant avec YAML généré en artifact
  • Workflow MR/branche/tag cohérent et non redondant
  • Robustesse : retry, timeout, interruptible, resource_group
  • Contrat de pipeline vérifiable via un job dédié

Commencez par le squelette de fichiers, sans écrire un seul job. Un .gitlab-ci.yml réduit à ses stages et à ses include vaut mieux qu'un fichier monolithique que vous découperez plus tard : la modularité se décide au début. Créez les six fichiers ci/*.yml avec au moins un job chacun avant de lancer un lint : un include qui pointe vers un fichier vide est rejeté par GitLab avec le message Included file ... is empty or does not exist!.

  1. Créez .gitlab-ci.yml orchestrateur

  2. Créez les fichiers ci/lint.yml, ci/test.yml, ci/orchestration.yml, ci/build.yml, ci/deploy.yml, ci/quality.yml

  3. Ajoutez le script de génération enfant scripts/generate-child-pipeline.sh

    Indice : le starter ne contient aucun fichier CI, vous partez d'une base vierge.

👉 Vérifier votre solution (Étape 1)
.gitlab-ci.yml
ci/
lint.yml
test.yml
orchestration.yml
build.yml
deploy.yml
quality.yml
scripts/
generate-child-pipeline.sh

Le script doit être exécutable pour que le job d'orchestration fonctionne.

L'ordre proposé n'est pas arbitraire : la matrice et le child pipeline définissent les jobs dont dépendra docker-build, et un needs qui pointe vers un job inexistant fait échouer le lint. Traitez donc les producteurs avant les consommateurs. Le job de contrat vient en dernier, puisqu'il vérifie une structure qui doit déjà être en place.

  1. Branchez la matrice de tests et ses dépendances
  2. Déclenchez correctement le child pipeline
  3. Appliquez les règles workflow et la fiabilité sur build/deploy
  4. Ajoutez un job de contrat pour valider la présence des composants CI
👉 Vérifier votre solution (Étape 2)
  • workflow MR-first avec anti-duplication branche/MR ;
  • matrice pytest-matrix (3.11-slim, 3.12-slim x core, api) ;
  • orchestration parent-enfant (generate-child-pipeline -> run-child-pipeline) ;
  • docker-build attend pytest-matrix et run-child-pipeline ;
  • déploiements fiabilisés avec resource_group, retry, timeout, interruptible: false ;
  • pipeline-contract vérifie les fichiers de la CI.

Validez avant de pousser : glab ci lint interroge l'API de votre instance et détecte les erreurs de structure sans consommer de minutes de runner. Ajoutez --dry-run pour aller plus loin et simuler la création du pipeline, et --include-jobs pour obtenir la liste des jobs qui existeraient réellement, ce qui est la meilleure façon de vérifier vos rules avant le premier push.

  1. Validez le pipeline

    Fenêtre de terminal
    glab ci lint .gitlab-ci.yml
  2. Commit et push

    Fenêtre de terminal
    git add -A
    git commit -m "ci: complete lab-19 industrial capstone"
    git push origin starter/lab-19
  3. Comparez avec la référence

    Fenêtre de terminal
    git diff origin/starter/lab-19..origin/solution/lab-19
👉 Vérifier votre solution (Étape 3)
  • le lint GitLab passe sans erreur ;
  • les jobs des 5 stages s'affichent (lint, test, orchestrate, build, deploy) ;
  • la comparaison starter/lab-19..solution/lab-19 montre un comportement équivalent.

Ne dépliez ces blocs qu'après avoir tenté votre propre implémentation : le but du capstone est de produire un comportement équivalent, pas de recopier une référence. Ils restent utiles pour un point précis qui bloque, par exemple la syntaxe exacte du trigger vers un artefact ou la position du resource_group. Chaque fichier correspond à un include du .gitlab-ci.yml orchestrateur.

📄 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
- local: ci/quality.yml
📄 Voir le fichier ci/lint.yml complet
.python-base:
image: python:3.12-slim@sha256:57cd7c3a7a273101a6485ba99423ee568157882804b1124b4dd04266317710de
cache:
key:
files:
- requirements-dev.txt
paths:
- .pip-cache/
ruff-lint:
extends: .python-base
stage: lint
cache:
policy: pull
before_script:
- pip install ruff
script:
- ruff check app/ tests/
📄 Voir le fichier ci/test.yml complet
pytest-matrix:
extends: .python-base
stage: test
parallel:
matrix:
- PYTHON_VERSION: ["3.11-slim", "3.12-slim"]
TEST_SUITE: ["core", "api"]
image: python:$PYTHON_VERSION
needs: []
before_script:
- pip install -r requirements-dev.txt
script:
- |
if [ "$TEST_SUITE" = "core" ]; then
pytest -v tests/test_health.py tests/test_version.py --junitxml=report.xml --cov=app --cov-report=term-missing
else
pytest -v tests/test_items.py --junitxml=report.xml --cov=app --cov-report=term-missing
fi
coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/'
artifacts:
when: always
paths:
- report.xml
reports:
junit: report.xml
expire_in: 7 days
📄 Voir le fichier ci/orchestration.yml complet
generate-child-pipeline:
stage: orchestrate
image: alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc
before_script:
- apk add --no-cache bash git
script:
- chmod +x scripts/generate-child-pipeline.sh
- ./scripts/generate-child-pipeline.sh
artifacts:
paths:
- child-pipeline.yml
expire_in: 1 day
run-child-pipeline:
stage: orchestrate
needs:
- generate-child-pipeline
trigger:
include:
- artifact: child-pipeline.yml
job: generate-child-pipeline
strategy: depend
rules:
- if: $CI_PIPELINE_SOURCE == "push"
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
📄 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: manual
📄 Voir le fichier ci/quality.yml complet
pipeline-contract:
stage: lint
image: alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc
script:
- test -f .gitlab-ci.yml
- test -f ci/lint.yml
- test -f ci/test.yml
- test -f ci/orchestration.yml
- test -f ci/build.yml
- test -f ci/deploy.yml
- test -x scripts/generate-child-pipeline.sh
- echo "Pipeline contract checks passed"
📄 Voir le fichier scripts/generate-child-pipeline.sh complet
#!/usr/bin/env bash
set -euo pipefail
# Dynamic child pipeline used in Lab 16.
# It adapts jobs according to branch context.
cat > child-pipeline.yml <<'YAML'
stages:
- verify
child-smoke:
stage: verify
image: alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc
script:
- echo "Smoke check in child pipeline"
YAML
if [[ "${CI_COMMIT_BRANCH:-}" == "${CI_DEFAULT_BRANCH:-}" ]]; then
cat >> child-pipeline.yml <<'YAML'
child-default-branch-check:
stage: verify
image: alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc
script:
- echo "Extra checks on default branch"
YAML
fi
cat child-pipeline.yml

Cochez ces points sur un pipeline réellement exécuté, pas sur la lecture de votre fichier : un .gitlab-ci.yml qui passe le lint peut très bien produire un pipeline vide si vos rules ne correspondent à rien. Le troisième point se vérifie dans l'interface, sur la vue du pipeline, où les jobs issus des templates locaux apparaissent au même titre que les autres.

  • Le pipeline lint passe sur la branche capstone
  • Les 5 stages attendus sont bien présents
  • Les templates locaux sont utilisés
  • Le child pipeline est fonctionnel
  • Les garde-fous de fiabilité sont appliqués

Le piège principal est d'oublier un sous-ensemble de contraintes (par exemple workflow correct mais fiabilité absente). Utilisez une checklist stricte et validez point par point.

Autre piège : vouloir reproduire exactement la solution ligne à ligne. L'objectif est d'atteindre un comportement équivalent, pas une copie textuelle parfaite.

  • Un capstone valide la maîtrise d'ensemble, pas un fragment de compétence
  • Un cahier des charges CI doit se traduire en critères observables
  • La comparaison avec solution/lab-19 est un outil d'audit, pas un raccourci
  • La robustesse industrielle repose sur cohérence + validation systématique

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