Les performances de GitLab CI/CD influencent directement la vélocité des équipes d'ingénierie. Des files d'attente trop longues retardent le retour d'information, des runners surchargés provoquent des échecs aléatoires, et des caches mal configurés gaspillent la bande passante et le stockage. Ce guide propose une approche progressive et mesurable pour identifier et corriger les goulets d'étranglement sans mettre en péril votre pipeline de livraison. Vous allez inventorier votre environnement, établir des références, appliquer des optimisations ciblées, valider chaque modification et instaurer une routine d'exploitation reproductible.

## Inventorier votre environnement avant tout

Avant toute modification, capturez l'état actuel. Cela clarifie les leviers d'optimisation disponibles et la documentation applicable. Enregistrez les éléments suivants :

- Édition et version de GitLab (SaaS vs autogéré)

- Versions des runners et exécuteurs (shell, Docker, Kubernetes)

- Backends de cache et d'artéfacts (disque local, S3, GCS, NFS)

- Localité réseau (où se situent les runners par rapport à GitLab, aux registres et aux miroirs de paquets)

- Caractéristiques des dépôts (taille, sous-modules, utilisation de LFS)

- Droits d'accès réels (configuration des runners, paramètres de projet, jeton API)

Commandes rapides pour collecter ces données :

# Depuis un journal de job
echo "$CI_SERVER_VERSION $CI_RUNNER_EXECUTOR $CI_PROJECT_PATH"

# Depuis l'hôte du runner
gitlab-runner --version
gitlab-runner list

# Depuis l'API GitLab (remplacez les valeurs entre crochets)
curl -s --header "PRIVATE-TOKEN: <token>" https://<gitlab_url>/api/v4/version 
 Utilisez ce tableau pour standardiser votre inventaire :

<div class="my-stack-md overflow-x-auto">
<table class="min-w-[42rem] border-collapse text-left">
<thead><tr><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Champ</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Exemple de valeur</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Notes</th></tr></thead>
<tbody><tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Version GitLab</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">16.11 SaaS</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Influence les fonctionnalités et valeurs par défaut disponibles</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Version(s) runner</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">16.11</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Maintenir aligné avec le serveur dans la mesure du possible</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Exécuteurs runner</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">shell, docker</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Détermine la stratégie de cache et l&#39;isolation</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Backend cache</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">S3, bucket gitlab-runner-cache</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Permet le partage de cache entre runners</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Politique artéfacts</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">expire_in: 2 days</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Garder les artéfacts petits et de courte durée</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Traits du dépôt</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">2,1 Go, 5 sous-modules, LFS activé</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Clone lourd si non optimisé</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Localité réseau</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Runners dans la même région que GitLab</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Réduit la latence de transfert</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Accès</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Config runner, maintainer projet, token read_api</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Requis pour appliquer les modifications</td></tr></tbody>
</table>
</div>

## Chemin de configuration sécurisé

Appliquez les changements par petites étapes avec un retour arrière clair. Suivez cette séquence : (1) métriques de référence, (2) gains rapides, (3) dimensionnement du débit, (4) optimisation de la latence et du cache, (5) parallélisation prudente, (6) re-mesure et extension seulement après validation.

### 1. Établir une référence mesurable

Choisissez un ou deux pipelines représentatifs (par exemple, build et tests de la branche principale) et capturez trois à cinq exécutions récentes. Enregistrez la durée du pipeline, les durées des jobs critiques, le temps de file d'attente et les temps de téléversement/téléchargement des artéfacts. Ajoutez un chronométrage simple autour des étapes lourdes dans vos scripts :

set -euo pipefail
stamp() { date +"%Y-%m-%d %H:%M:%S"; }
step() { echo "[STEP] $(stamp) $*"; }

step "Installation dépendances"
# vos commandes d'installation

step "Exécution tests"
# vos commandes de test 
 Utilisez l'API pour collecter les données de référence :

# 10 derniers pipelines réussis et leurs durées
curl -s --header "PRIVATE-TOKEN: <token>" \
 "https://<gitlab_url>/api/v4/projects/<id>/pipelines?status=success&per_page=10" | jq '[.[] | {id, status, duration}]'

# Jobs d'un pipeline spécifique
curl -s --header "PRIVATE-TOKEN: <token>" \
 "https://<gitlab_url>/api/v4/projects/<id>/pipelines/<pipeline_id>/jobs" | jq '[.[] | {name, status, duration, queued_duration}]' 

### 2. Gains rapides dans .gitlab-ci.yml

 Réduisez la surcharge du clone Git avec un fetch superficiel :

variables:
 GIT_STRATEGY: fetch
 GIT_DEPTH: "1"
 GIT_SUBMODULE_STRATEGY: none 
 Dimensionnez correctement les artéfacts. N'archiver pas de gros résultats reproductibles. Gardez des expirations courtes :

artifacts:
 when: on_success
 expire_in: 2 days
 paths:
 - build/reports/ 
 Ajoutez des caches pour les répertoires de dépendances que vous savez réutilisables sans risque. Cléyez les caches par les fichiers de verrouillage pour qu'ils s'invalident seulement quand les dépendances changent.

Exemple Node.js :

cache:
 key:
 files:
 - package-lock.json
 paths:
 - node_modules/
 policy: pull-push 
 Exemple Python :

cache:
 key:
 files:
 - requirements.txt
 paths:
 - .venv/
 - $PIP_CACHE_DIR
 policy: pull-push 
 Exemple Maven :

cache:
 key: maven-$CI_COMMIT_REF_SLUG
 paths:
 - .m2/repository
 policy: pull-push 

### 3. Dimensionnement du débit sur les runners

 Définissez la concurrence globale pour que l'hôte reste occupé sans être surchargé. Pour des jobs liés au CPU, commencez avec concurrent proche du nombre de vCPU, puis ajustez selon la pression I/O et mémoire.

Dans /etc/gitlab-runner/config.toml , examinez ces paramètres :

concurrent = 6
check_interval = 0

runners
 name = "build-runner-01"
 url = "https://gitlab.example.com/"
 token = "REDACTED"
 executor = "docker"
 request_concurrency = 2
 [runners.cache]
 Type = "s3"
 Path = "runner"
 Shared = true
 [runners.cache.s3]
 ServerAddress = "s3.amazonaws.com"
 BucketName = "gitlab-runner-cache"
 BucketLocation = "us-east-1"
 Insecure = false 
 Redémarrez le runner pour appliquer les changements :

systemctl restart gitlab-runner 
 Lignes directrices de dimensionnement (points de départ ; ajustez à votre charge) :

- Builds CPU-bound : concurrent ≈ nombre de vCPU, request_concurrency = 2 à 4

- Builds I/O-intensifs : concurrent < vCPU (par exemple, 0,5 × vCPU)

- Tests gros consommateurs de mémoire : plafonnez concurrent pour que le plus gros job ait au moins 1,5× sa mémoire de pointe disponible

### 4. Optimisation de la latence et du cache

- Colocalisez les runners près de GitLab et de vos registres de conteneurs/paquets pour réduire les temps de transfert.

- Préférez un cache distant (S3/GCS) pour les flottes afin que les caches survivent aux redémarrages et soient partagés entre machines.

- Gardez les clés de cache stables au sein d'une branche mais différentes entre branches pour éviter l'empoisonnement.

- Évitez d'archiver d'énormes caches ; mettez en cache seulement ce qui ne se re-télécharge pas vite (répertoires de dépendances, pas les sorties de build).

### 5. Parallélisation prudente

Divisez les longs jobs de test avec parallel de GitLab. Commencez petit et assurez l'isolation.

Exemple à nombre fixe :

unit_tests:
 stage: test
 script: run_tests.sh "$CI_NODE_INDEX" "$CI_NODE_TOTAL"
 parallel: 4 
 Exemple matrice :

build:
 stage: build
 parallel:
 matrix:
 - PLATFORM: [linux, darwin]
 ARCH: [amd64, arm64]
 script:
 - ./build.sh "$PLATFORM" "$ARCH" 
 Notes d'implémentation :

- Assurez-vous que les tests ne partagent pas d'état global mutable (bases de données, ports, répertoires temporaires). Paramétrez par nœud avec $CI_NODE_INDEX .

- Protégez la parallélisation derrière un drapeau de fonctionnalité pour pouvoir revenir en arrière vite :

workflow:
 rules:
 - if: '$ENABLE_PARALLEL == "1"' 

### 6. Re-mesurer et décider des prochaines étapes

 Comparez durées, temps de file d'attente et temps de transfert d'artéfacts à votre référence. Ne conservez que les changements qui produisent des gains constants sur plusieurs exécutions.

## Vérification et diagnostics

La vérification prouve que vous avez amélioré le bon indicateur. Utilisez ces contrôles.

### Chronométrage pipeline et jobs

- Comparez trois à cinq exécutions avant et trois à cinq après un changement pour lisser la variance aléatoire.

- Inspectez les détails des jobs pour Temps en file vs Temps d'exécution . Le temps en file signale un problème de capacité ; le temps d'exécution signale une inefficacité au niveau du job.

### Santé des runners

- Sur les hôtes runners, surveillez CPU, mémoire, I/O disque et réseau aux heures de pointe. Si le CPU est inactif alors que des jobs attendent, il faut probablement plus de runners enregistrés ou augmenter concurrent .

- Validez la connectivité du runner :

gitlab-runner verify 

### Guide symptôme-métrique

 Symptôme Métrique à vérifier Cause probable Première action 
 Long temps en file queued_duration Capacité runner insuffisante Augmenter concurrent ou ajouter runners 
 Temps de clone longs Journaux, taille repo Historique profond, sous-modules Utiliser GIT_DEPTH , désactiver sous-modules 
 Étapes dépendances lentes Chronos par étape Caches absents ou instables Ajouter cache clé par fichiers de verrou 
 Artéfacts lents Temps upload/download Artéfacts surdimensionnés Réduire paths, définir expire_in 
 Tests flaky après split Taux d'échec par nœud Ressources partagées Isoler avec CI_NODE_INDEX 

## Modes de défaillance et récupération

 Problèmes courants et comment récupérer sans risque.

### 1. Empoisonnement de cache ou ratés de cache

Symptômes : Le build utilise de mauvaises versions de dépendances, ou les caches ne touchent jamais. Cause probable : Clés de cache trop larges ou instables. Récupération :

- Restreignez les clés aux fichiers de verrouillage ou manifestes (pour Node : key.files: package-lock.json ).

- Purgez les caches. Pour caches locaux, supprimez /var/lib/gitlab-runner/cache/* sur le runner. Pour S3, effacez le préfixe concerné.

### 2. Surcharge runner et timeouts

Symptômes : queued_duration en hausse, échecs sporadiques sous charge. Causes : concurrent trop élevé pour le matériel, saturation réseau. Récupération :

- Réduisez concurrent dans config.toml et redémarrez gitlab-runner .

- Ajoutez des runners ou déplacez les jobs lourds vers des machines plus grosses.

### 3. Tests flaky après parallélisation

Symptômes : Échecs intermittents, collisions de ports, répertoires temporaires partagés. Récupération :

- Paramétrez les ressources par nœud avec $CI_NODE_INDEX . Isolez répertoires temporaires et bases de données par nœud.

- Revenir en arrière : mettez ENABLE_PARALLEL=0 dans les variables du projet, ou annulez le changement dans .gitlab-ci.yml .

### 4. Gonflement d'artéfacts ralentissant les pipelines

Symptômes : Longs temps d'upload/download. Causes : Archivage de gros binaires reproductibles ou d'espaces de travail entiers. Récupération :

- Limitez les artéfacts aux rapports nécessaires et petits livrables. Définissez expire_in agressivement.

- Utilisez les caches pour le contenu réutilisé fréquemment et les artéfacts pour les livrables minimaux.

### 5. Clones superficiels cassant la gestion de versions

Symptômes : Les scripts de build ne trouvent pas les tags ou l'historique. Cause : GIT_DEPTH=1 alors que les scripts nécessitent les tags. Récupération :

- Augmentez GIT_DEPTH pour inclure l'historique nécessaire, ou récupérez les tags explicitement :

script:
 - git fetch --tags --depth=50 

### 6. Erreurs de permissions sur cache distant

 Symptômes : 403 ou 404 lors du push/pull de cache. Récupération :

- Vérifiez les identifiants et politiques de bucket pour le profil d'instance runner ou les clés d'accès.

- Désactivez temporairement le cache ( policy: pull ) pour débloquer les builds, puis corrigez les permissions.

## Liste de contrôle opérationnelle

Utilisez cette routine pour maintenir les performances sous contrôle.

### Quotidien

- Surveillez queued_duration sur les jobs les plus sollicités.

- Traitez le job le plus lent des dernières 24 heures.

### Hebdomadaire

- Revoyez les tendances de durée des pipelines et les graphiques de ressources runners.

- Purgez les caches plus anciens que la durée de vie moyenne de vos branches.

### Par changement

- Référencez trois exécutions, appliquez un changement, re-mesurez trois exécutions.

- Documentez les deltas et conservez ou annulez le changement.

Modèle de journal des changements :

<div class="my-stack-md overflow-x-auto">
<table class="min-w-[42rem] border-collapse text-left">
<thead><tr><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Changement</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Métrique référence</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Métrique après</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Delta</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Conserver/Annuler</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Responsable</th></tr></thead>
<tbody><tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">GIT_DEPTH=1 sur repo X</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Clone 180s</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Clone 35s</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">-81%</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Conserver</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">A. Dev</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Cache node_modules</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Install 240s</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Install 55s</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">-77%</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Conserver</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">B. Ops</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Tests parallèles ×4</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Test 28m</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Test 9m</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">-68%</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Conserver</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">C. QA</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Réduire paths artéfacts</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Upload 120s</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Upload 20s</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">-83%</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Conserver</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">D. Dev</td></tr></tbody>
</table>
</div>

## Exemples pratiques

### 1. Réduire le temps de file en ajoutant de la capacité

- Référence : queued_duration ~ 300s aux heures de pointe.

- Changement : Augmenter concurrent de 4 à 8 sur deux runners (capacité logique totale de 8 à 16), en gardant l'utilisation CPU sous 75%.

- Résultat attendu : queued_duration tombe sous 60s.

- Vérification : Comparer la moyenne queued_duration sur 10 pipelines avant/après.

- Retour arrière : Si CPU dépasse 90% et échecs montent, revenez sur concurrent et planifiez un troisième runner.

### 2. Réduire les temps de clone avec fetch superficiel

- Référence : Clone 150s sur repo 2 Go avec sous-modules.

- Changement : GIT_STRATEGY: fetch , GIT_DEPTH: 1 , GIT_SUBMODULE_STRATEGY: none .

- Résultat attendu : Clone sous 40s.

- Vérification : Chronométrage début-job à première commande dans les logs.

- Retour arrière : Si le build nécessite tags ou sous-modules, augmentez GIT_DEPTH ou réactivez les sous-modules sélectivement.

### 3. Accélérer les dépendances avec cache clé par fichier de verrou

- Référence : Install Node 200s à froid, 200s à chaque build.

- Changement : Cache node_modules clé par package-lock.json .

- Résultat attendu : Install en cache ~40-60s, invalide seulement au changement de dépendances.

- Vérification : Observer message de restauration cache et durée d'install.

- Retour arrière : Si conflits de cache entre branches, incluez $CI_COMMIT_REF_SLUG dans la clé.

### 4. Diviser les longs tests en toute sécurité

- Référence : Job de test unique 30min.

- Changement : parallel: 4 et passer $CI_NODE_INDEX pour sharder les tests.

- Résultat attendu : 8-12min temps total selon surcoût.

- Vérification : S'assurer que chaque shard dure dans 10-20% des autres ; ajuster la logique de sharding sinon.

- Retour arrière : Mettre ENABLE_PARALLEL=0 pour désactiver pendant l'investigation des flakiness.

## À quoi ressemble un bon état

- Temps de file : Sous 60s pour les pipelines courants aux heures ouvrées.

- Temps de clone : Proportionnel à GIT_DEPTH et taille du repo ; sous 60s pour la plupart des repos avec fetch superficiel.

- Étapes dépendances : Sous 1 minute avec caches chauds pour les écosystèmes de langages typiques.

- Artéfacts : Upload/download sous 30s quand limités aux rapports et petits livrables.

- Stabilité : Taux d'échec n'augmente pas après optimisation ; sinon, annulez le dernier changement.

## Conclusion

Vous disposez désormais d'une méthode sûre et progressive pour optimiser les performances de GitLab CI/CD : inventorier l'existant, mesurer ce qui compte, appliquer de petits changements réversibles, vérifier les résultats et opérationnaliser la routine. Commencez par le plus petit pilote facile à mesurer et inspecter, conservez ce qui améliore constamment le débit ou la latence, et déployez délibérément. Revisitez votre inventaire et votre dimensionnement chaque trimestre ou après des changements majeurs de charge (nouveaux langages, dépôts plus gros, nouvelles suites de tests). Avec une mesure disciplinée et des retours arrière prudents, la vitesse des pipelines devient une composante gérée et prévisible de votre système d'ingénierie.