04 — GitLab CI/CD#
GitLab CI/CD est une plateforme CI/CD intégrée directement dans GitLab, disponible depuis la version 8.0 (2015). Contrairement à GitHub Actions qui repose sur un écosystème de marketplace, GitLab CI/CD propose une approche plus intégrée avec son propre container registry, ses environnements de déploiement dynamiques (review apps), ses merge trains et une gestion fine des règles de déclenchement.
Structure du fichier .gitlab-ci.yml#
Le point d’entrée de toute configuration GitLab CI/CD est le fichier .gitlab-ci.yml à la racine du dépôt.
# Structure complète d'un .gitlab-ci.yml (non exécutable)
# Variables globales
variables:
DOCKER_DRIVER: overlay2
PYTHON_VERSION: "3.12"
# Définition des stages (phases)
stages:
- lint
- test
- build
- security
- deploy
# Job de lint
lint:
stage: lint
image: python:3.12-slim
script:
- pip install ruff
- ruff check .
- ruff format --check .
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Job de tests unitaires
unit-tests:
stage: test
image: python:$PYTHON_VERSION-slim
script:
- pip install -r requirements.txt
- pytest tests/unit/ --cov=src --cov-report=xml
coverage: '/TOTAL.*\s+(\d+%)$/'
artifacts:
when: always # Conserver même en cas d'échec
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
expire_in: 1 week
# Job de build de l'image Docker
docker-build:
stage: build
image: docker:24
services:
- docker:24-dind # Docker-in-Docker
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
only:
- main
- tags
Stages#
Les stages définissent les phases du pipeline. Les jobs d’un même stage s’exécutent en parallèle ; les stages s’enchaînent séquentiellement. Si un job d’un stage échoue, les stages suivants sont bloqués (comportement configurable avec allow_failure).
only/rules#
GitLab offre deux mécanismes de déclenchement conditionnel :
only/except(ancienne syntaxe, encore largement utilisée) : liste de branches, tags, événements.rules(nouvelle syntaxe, recommandée) : évaluée dans l’ordre, plus expressive, supporte les expressions régulières et les variables CI.
# Comparaison only/except vs rules (non exécutable)
# Ancienne syntaxe
deploy-prod:
only:
- main
except:
- schedules
# Nouvelle syntaxe équivalente (plus puissante)
deploy-prod:
rules:
- if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE != "schedule"
when: on_success
- when: never
when#
La directive when contrôle le déclenchement d’un job :
on_success(défaut) : seulement si les jobs précédents ont réussi.on_failure: seulement si un job précédent a échoué.always: toujours, quel que soit l’état précédent.manual: déclenchement manuel depuis l’interface.delayed: avec un délai configurable (start_in: 30 minutes).never: désactivé (utile dans les règles conditionnelles).
Includes et templates#
GitLab CI/CD permet de découper la configuration en plusieurs fichiers et de réutiliser des templates.
# Include avec templates locaux et distants (non exécutable)
include:
# Fichier local au dépôt
- local: '.gitlab/ci/test.yml'
# Template officiel GitLab
- template: 'Security/SAST.gitlab-ci.yml'
- template: 'Security/Dependency-Scanning.gitlab-ci.yml'
# Fichier d'un autre projet GitLab
- project: 'org/shared-ci-templates'
ref: main
file: '/templates/python.yml'
# URL distante
- remote: 'https://example.com/ci/template.yml'
extends#
extends permet d’hériter la configuration d’un job template (prefixé par . pour être ignoré comme job réel) :
# Héritage avec extends (non exécutable)
.base-python:
image: python:3.12-slim
before_script:
- pip install -r requirements.txt
cache:
key: pip-$CI_COMMIT_REF_SLUG
paths:
- .cache/pip/
unit-tests:
extends: .base-python
stage: test
script:
- pytest tests/unit/
integration-tests:
extends: .base-python
stage: test
script:
- pytest tests/integration/
!reference#
!reference permet de réutiliser des fragments de configuration YAML de manière plus granulaire qu”extends :
# Réutilisation de fragments avec !reference (non exécutable)
.setup-steps:
before_script:
- apt-get update -qq && apt-get install -y curl
- curl -fsSL https://example.com/install.sh | bash
deploy-staging:
before_script:
- !reference [.setup-steps, before_script]
- echo "Configuration staging supplémentaire"
Variables CI/CD#
GitLab propose un système de variables CI/CD à plusieurs niveaux de portée.
Scopes de variables#
Instance (admin) : disponibles pour tous les groupes et projets de l’instance.
Groupe : héritées par tous les sous-groupes et projets du groupe.
Projet : spécifiques au projet.
Pipeline : passées lors du déclenchement via API ou interface.
Job : définies directement dans le fichier
.gitlab-ci.yml.
Variables prédéfinies#
GitLab expose automatiquement des dizaines de variables :
# Variables prédéfinies utiles (non exécutable)
script:
- echo $CI_COMMIT_SHA # SHA du commit
- echo $CI_COMMIT_REF_NAME # Nom de la branche ou du tag
- echo $CI_PIPELINE_ID # ID du pipeline
- echo $CI_JOB_ID # ID du job
- echo $CI_REGISTRY_IMAGE # URL du container registry
- echo $CI_ENVIRONMENT_NAME # Nom de l'environnement (si défini)
- echo $CI_MERGE_REQUEST_IID # Numéro de la MR (si pipeline MR)
Masquage et protection#
Variables masquées : valeur cachée dans les logs CI (uniquement si la valeur respecte les contraintes de masquage : pas de saut de ligne, longueur > 8 caractères).
Variables protégées : disponibles uniquement sur les branches et tags protégés — protège les credentials de production contre les branches non autorisées.
Variables de groupe vs projet
Les variables de groupe permettent de partager des credentials communs (registry, tokens de déploiement) entre tous les projets d’une organisation sans les dupliquer. Toujours préférer les variables de groupe pour les credentials partagés, et les variables de projet pour les configurations spécifiques.
Environnements et review apps#
Environnements#
Les environnements GitLab modélisent les cibles de déploiement avec historique, URL et statut en temps réel.
# Déploiement avec environnement (non exécutable)
deploy-staging:
stage: deploy
script:
- ./deploy.sh staging
environment:
name: staging
url: https://staging.example.com
rules:
- if: $CI_COMMIT_BRANCH == "develop"
deploy-production:
stage: deploy
script:
- ./deploy.sh production
environment:
name: production
url: https://app.example.com
when: manual # Déclenchement manuel obligatoire
rules:
- if: $CI_COMMIT_BRANCH == "main"
Review Apps#
Les review apps sont des environnements de déploiement dynamiques créés automatiquement pour chaque branche de feature ou merge request. Elles permettent de visualiser les changements dans un environnement réel avant le merge.
# Review App dynamique (non exécutable)
deploy-review:
stage: deploy
script:
- ./deploy.sh review-$CI_COMMIT_REF_SLUG
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://$CI_COMMIT_REF_SLUG.review.example.com
on_stop: stop-review # Job de nettoyage
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
stop-review:
stage: deploy
script:
- ./teardown.sh review-$CI_COMMIT_REF_SLUG
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop
when: manual
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
GitLab Container Registry et intégration Kubernetes#
GitLab intègre un container registry par projet, accessible sans configuration supplémentaire via les variables CI_REGISTRY_*.
# Build et push vers le registry GitLab (non exécutable)
build-image:
stage: build
image: docker:24
services:
- docker:24-dind
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build --cache-from $CI_REGISTRY_IMAGE:latest
--tag $IMAGE_TAG
--tag $CI_REGISTRY_IMAGE:latest .
- docker push $IMAGE_TAG
- docker push $CI_REGISTRY_IMAGE:latest
L’intégration Kubernetes (GitLab Agent for Kubernetes, anciennement GitLab Kubernetes integration) permet de déployer directement depuis le pipeline vers un cluster Kubernetes via l’agent agentk, sans exposer l’API Kubernetes publiquement.
GitLab Runners — types et executors#
Types de runners#
Shared runners : disponibles pour tous les projets de l’instance GitLab.com ou de l’instance self-hosted. Pool partagé géré par GitLab ou l’admin.
Group runners : disponibles pour tous les projets d’un groupe.
Project runners : dédiés à un projet spécifique.
Executors#
L’executor définit comment le runner exécute les jobs :
Docker : chaque job dans un conteneur éphémère — isolation forte, recommandé.
Kubernetes : chaque job dans un pod Kubernetes éphémère — scalabilité maximale.
Shell : exécution directe sur la machine hôte — rapide mais peu isolé.
Docker Machine (déprécié) : provisionnement dynamique de VMs.
VirtualBox / Parallels : isolation maximale pour les tests sur macOS/Windows.
Executor recommandé en production
L’executor Docker ou Kubernetes est recommandé pour les environnements de production. L’executor Shell expose le système hôte aux scripts CI et ne doit être utilisé que sur des runners dédiés à un projet de confiance, dans un environnement réseau isolé.
Merge trains et merge request pipelines#
Merge request pipelines#
Un merge request pipeline s’exécute sur le code de la branche source d’une MR, avant le merge. Il peut être configuré différemment du pipeline de branche classique.
# Job uniquement pour les MR pipelines (non exécutable)
mr-lint:
stage: lint
script:
- echo "Validation spécifique MR"
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
Merged results pipelines#
Exécute le pipeline sur le résultat du merge simulé (branche source + branche cible), pas uniquement sur la branche source. Détecte les régressions d’intégration avant le merge réel.
Merge trains#
Les merge trains fusionnent plusieurs MRs en attente de façon ordonnée et sécurisée. Chaque MR est testée dans une « queue » qui inclut les MRs précédentes déjà en attente. Si une MR échoue, elle est retirée du train sans impacter les autres.
Artefacts, cache et dépendances#
Artefacts GitLab#
# Configuration complète des artefacts (non exécutable)
build:
script:
- make build
artifacts:
name: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
paths:
- dist/
- reports/
exclude:
- dist/**/*.map # Exclure les source maps
expire_in: 30 days
when: on_success
reports:
junit: reports/junit.xml # Rapport de tests dans l'interface MR
coverage_report:
coverage_format: cobertura
path: coverage.xml
Cache GitLab#
# Cache avec clé dynamique (non exécutable)
.python-cache:
cache:
key:
files:
- requirements.txt # Clé basée sur le contenu du fichier
paths:
- .cache/pip/
policy: pull-push # pull au début, push à la fin
unit-tests:
extends: .python-cache
script:
- pytest
dependencies#
Par défaut, un job télécharge les artefacts de tous les jobs des stages précédents. dependencies: [] désactive ce comportement pour accélérer le job.
# Contrôle fin des dépendances d'artefacts (non exécutable)
deploy:
stage: deploy
dependencies:
- docker-build # Uniquement les artefacts de ce job
script:
- ./deploy.sh
Règles avancées#
workflow:rules#
Contrôle si un pipeline est créé ou non, avant même d’évaluer les jobs :
# Règles de workflow globales (non exécutable)
workflow:
rules:
# Ne pas créer de pipeline pour les commits WIP
- if: $CI_COMMIT_TITLE =~ /^WIP:/
when: never
# Pipeline pour les MRs
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# Pipeline pour les commits sur main et develop
- if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "develop"
# Pipeline pour les tags
- if: $CI_COMMIT_TAG
# Tout le reste est ignoré
- when: never
parallel:matrix#
parallel:matrix est l’équivalent GitLab de la stratégie matrix de GitHub Actions :
# Matrice de test GitLab (non exécutable)
test:
parallel:
matrix:
- PYTHON_VERSION: ["3.10", "3.11", "3.12"]
OS: ["ubuntu", "alpine"]
image: python:${PYTHON_VERSION}-${OS}
script:
- pytest
Visualisations#
import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import seaborn as sns
import networkx as nx
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
# DAG de pipeline GitLab avec stages comme couches
G = nx.DiGraph()
# Stages et jobs associés
stage_jobs = {
'lint': ['lint-python', 'lint-yaml'],
'test': ['unit-tests', 'integration', 'contract-tests'],
'build': ['build-wheel', 'build-image'],
'security': ['sast', 'dependency-scan', 'container-scan'],
'deploy': ['deploy-review', 'deploy-staging', 'deploy-prod'],
}
stage_colors = {
'lint': '#B3E5FC',
'test': '#C8E6C9',
'build': '#FFE0B2',
'security': '#F8BBD0',
'deploy': '#E1BEE7',
}
# Positions : stage sur l'axe X, jobs distribués sur l'axe Y
pos = {}
stage_x = {'lint': 0, 'test': 2, 'build': 4, 'security': 6, 'deploy': 8}
node_colors_map = {}
for stage, jobs in stage_jobs.items():
n = len(jobs)
for i, job in enumerate(jobs):
y = (i - (n - 1) / 2) * 1.5
pos[job] = (stage_x[stage], y)
node_colors_map[job] = stage_colors[stage]
# Arêtes : chaque job d'un stage pointe vers tous les jobs du stage suivant
stage_list = list(stage_jobs.keys())
for i in range(len(stage_list) - 1):
for src in stage_jobs[stage_list[i]]:
for dst in stage_jobs[stage_list[i + 1]]:
# Heuristique : connecter les jobs logiquement liés
G.add_edge(src, dst)
# Simplifier : uniquement les connexions logiques
G.clear_edges()
connexions = [
('lint-python', 'unit-tests'),
('lint-python', 'integration'),
('lint-yaml', 'unit-tests'),
('unit-tests', 'build-wheel'),
('unit-tests', 'build-image'),
('integration', 'build-wheel'),
('integration', 'build-image'),
('contract-tests', 'build-image'),
('build-wheel', 'sast'),
('build-wheel', 'dependency-scan'),
('build-image', 'container-scan'),
('sast', 'deploy-staging'),
('dependency-scan','deploy-staging'),
('container-scan', 'deploy-review'),
('container-scan', 'deploy-staging'),
('deploy-review', 'deploy-staging'),
('deploy-staging', 'deploy-prod'),
]
G.add_edges_from(connexions)
colors_list = [node_colors_map[n] for n in G.nodes()]
fig, ax = plt.subplots(figsize=(14, 8))
nx.draw_networkx_nodes(G, pos, node_color=colors_list, node_size=2400, ax=ax, alpha=0.9)
nx.draw_networkx_labels(G, pos, font_size=7.5, font_weight='bold', ax=ax)
nx.draw_networkx_edges(G, pos, ax=ax, arrows=True, arrowstyle='->',
arrowsize=18, edge_color='#546E7A', width=1.5,
connectionstyle='arc3,rad=0.03')
# Annotations de stage
for stage, x in stage_x.items():
ax.text(x, -2.6, stage.upper(), ha='center', va='center', fontsize=10,
fontweight='bold', color='#37474F',
bbox=dict(boxstyle='round,pad=0.3', facecolor=stage_colors[stage], alpha=0.8))
legend_elements = [mpatches.Patch(color=c, label=s.capitalize())
for s, c in stage_colors.items()]
ax.legend(handles=legend_elements, loc='upper left', fontsize=9, frameon=True)
ax.set_title('DAG de pipeline GitLab CI/CD — stages et dépendances', fontsize=13, fontweight='bold', pad=15)
ax.set_ylim(-3.2, 3.2)
ax.axis('off')
plt.show()
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
# Heatmap comparative GitHub Actions vs GitLab CI
features = [
'Syntaxe YAML',
'Marketplace / Templates',
'Runners self-hosted',
'Matrice de build',
'Cache natif',
'Artefacts',
'Workflows réutilisables',
'Environnements',
'Review Apps',
'Container Registry intégré',
'Merge trains',
'OIDC / Secrets gestion',
'Intégration Kubernetes',
'Interface utilisateur',
'Coût (self-hosted)',
]
# Scores sur 5 (5 = meilleur)
scores_github = [4, 5, 4, 4, 4, 4, 4, 4, 2, 3, 2, 5, 3, 4, 5]
scores_gitlab = [4, 3, 5, 5, 4, 5, 4, 5, 5, 5, 5, 4, 5, 3, 4]
data = np.array([scores_github, scores_gitlab]).T
df = pd.DataFrame(data, index=features, columns=['GitHub Actions', 'GitLab CI/CD'])
fig, ax = plt.subplots(figsize=(9, 9))
im = ax.imshow(data, cmap='RdYlGn', aspect='auto', vmin=1, vmax=5)
ax.set_xticks([0, 1])
ax.set_xticklabels(['GitHub Actions', 'GitLab CI/CD'], fontsize=12, fontweight='bold')
ax.set_yticks(range(len(features)))
ax.set_yticklabels(features, fontsize=10)
for i in range(len(features)):
for j in range(2):
score = data[i, j]
ax.text(j, i, str(score), ha='center', va='center', fontsize=11,
fontweight='bold', color='white' if score <= 2 else 'black')
cbar = fig.colorbar(im, ax=ax, fraction=0.03, pad=0.04)
cbar.set_label('Score (1 = faible, 5 = excellent)', fontsize=10)
cbar.set_ticks([1, 2, 3, 4, 5])
ax.set_title('Comparaison GitHub Actions vs GitLab CI/CD', fontsize=13, fontweight='bold', pad=15)
plt.show()
Tableau de décision : GitHub Actions vs GitLab CI#
Critère |
GitHub Actions |
GitLab CI/CD |
|---|---|---|
Hébergement du code |
GitHub |
GitLab (SaaS ou self-hosted) |
Écosystème d’actions |
Très riche (Marketplace) |
Templates officiels limités |
Review Apps |
Non natif |
Natif et intégré |
Container Registry |
GHCR (séparé) |
Intégré au projet |
Merge trains |
Non |
Oui |
Coût runners SaaS |
Minutes incluses par plan |
Minutes incluses par plan |
Self-hosted runner |
Facile |
Facile + plus d’executors |
Secrets gestion |
GitHub Secrets + OIDC |
Variables CI/CD + Vault natif |
Intégration Kubernetes |
Via actions tierces |
GitLab Agent natif |
Pipeline as Code |
YAML dans |
YAML |
Includes/templates |
Actions composites |
|
Règles avancées |
|
|
Recommandation : choisir GitHub Actions si le code est sur GitHub et que l’écosystème Marketplace est un avantage décisif. Choisir GitLab CI/CD pour une plateforme DevOps complète et intégrée (code, CI, registry, déploiement, monitoring), en particulier en self-hosted pour des raisons de conformité ou de coût.
Résumé#
Le fichier
.gitlab-ci.ymlstructure le pipeline en stages (séquentiels) et jobs (parallèles au sein d’un stage), avec une syntaxe expressive pour les conditions de déclenchement viarules.La directive
rulesremplaceonly/exceptet offre une granularité fine sur les conditions de déclenchement, avec support des expressions régulières et des variables CI.Les
include,extendset!referencepermettent de factoriser la configuration CI en templates réutilisables, partagés entre projets via un dépôt de templates centralisé.Les variables CI/CD à trois niveaux de portée (instance, groupe, projet) permettent de gérer les credentials de manière hiérarchique avec masquage et protection.
Les review apps créent automatiquement des environnements de déploiement éphémères par branche, permettant de visualiser les changements avant le merge.
Le container registry GitLab est intégré sans configuration supplémentaire, accessible via les variables
CI_REGISTRY_*prédéfinies dans chaque pipeline.Les runners GitLab supportent plusieurs executors (Docker, Kubernetes, Shell) ; l’executor Docker ou Kubernetes est recommandé pour l’isolation et la reproductibilité.
Les merge trains permettent de fusionner plusieurs MRs en file d’attente de façon ordonnée, en testant chaque MR dans le contexte des MRs précédentes.
workflow:rulescontrôle la création même du pipeline, avant l’évaluation des jobs — utile pour éviter des pipelines inutiles sur les commits WIP.GitLab CI/CD est préférable à GitHub Actions pour les déploiements nécessitant une plateforme DevOps intégrée (registry, Kubernetes, review apps, merge trains) en particulier dans des environnements self-hosted soumis à des contraintes de conformité.