Un pipeline parfaitement écrit ne sert à rien si les runners sont lents ou mal configurés. Le runner est le muscle de ton CI/CD — c’est lui qui clone le repo, exécute les scripts, upload les artifacts. Sur GitLab.com tu as des shared runners gratuits (avec quotas), mais en entreprise tu gères tes propres runners. Et là, les choix d’architecture font toute la différence entre des pipelines de 2 minutes et des pipelines de 20 minutes.
Comment fonctionne un runner
Le gitlab-runner est un binaire Go qui s’enregistre auprès de GitLab, interroge l’API pour des jobs, puis les exécute via un executor — le moteur d’isolation. Le choix de l’executor détermine tout : isolation, performance, sécurité, scaling.
Installation et enregistrement
# Linux (Debian/Ubuntu)
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
sudo apt install gitlab-runner
# Enregistrement (GitLab 16+ : runner authentication tokens)
# Token glrt-XXXX obtenu dans Settings > CI/CD > Runners > New project runner
sudo gitlab-runner register \
--url https://gitlab.com \
--token glrt-XXXXXXXXXXXXXXXXXXXX \
--executor docker \
--docker-image python:3.12-slim \
--tag-list "docker,python,linux"
La config se trouve dans /etc/gitlab-runner/config.toml. C’est là que tu ajustes tout : executor, limites de ressources, cache, sécurité.
Les executors : choisir le bon moteur
Shell — simple mais sans isolation. Exécute les commandes directement sur la machine du runner. Pas de Docker, pas d’overhead. Mais zéro isolation : un job peut impacter les suivants, les dépendances sont globales. À réserver aux runners dédiés à un seul projet ou aux cas où il faut un accès hardware (GPU).
Docker — le standard
Chaque job tourne dans un conteneur frais. Isolation propre, reproductibilité, pas de pollution entre jobs. C’est le choix par défaut en entreprise.
[[runners]]
name = "docker-runner"
executor = "docker"
[runners.docker]
image = "python:3.12-slim"
privileged = false
pull_policy = ["if-not-present"]
volumes = ["/cache", "/certs/client"]
allowed_images = ["python:*", "node:*", "docker:*", "alpine:*"]
cpus = "2"
memory = "4g"
💡 pull_policy: if-not-present accélère drastiquement les builds. Par défaut GitLab pull l’image à chaque job — inutile si tu utilises des images stables comme python:3.12-slim. Réserve always aux images latest qui changent souvent.
Docker-in-Docker : builder des images. Pour builder des images Docker dans un job CI, trois approches :
# Méthode 1 : Socket binding (rapide, moins sécurisé)
docker-build:
image: docker:24
variables:
DOCKER_HOST: "unix:///var/run/docker.sock"
script:
- docker build -t myapp .
# Méthode 2 : DinD service (sécurisé, plus lent)
docker-build:
image: docker:24
services:
- docker:24-dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
script:
- docker build -t myapp .
# Méthode 3 : Kaniko (recommandé en prod — pas de privilèges)
docker-build:
image:
name: gcr.io/kaniko-project/executor:v1.22.0-debug
entrypoint: [""]
script:
- /kaniko/executor
--context $CI_PROJECT_DIR
--destination $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
🔥 Kaniko est la méthode recommandée en production. Pas de privilèges root, pas de Docker daemon, fonctionne nativement avec Kubernetes. Le socket binding monte /var/run/docker.sock et donne un accès root à la machine hôte — un job malicieux peut tout faire.
Kubernetes — scaling automatique
Si tu as un cluster K8s, c’est l’executor ultime. Chaque job crée un pod éphémère, détruit après exécution. Scaling automatique via Kubernetes.
[[runners]]
executor = "kubernetes"
[runners.kubernetes]
namespace = "gitlab-ci"
image = "alpine:latest"
cpu_request = "500m"
cpu_limit = "2"
memory_request = "512Mi"
memory_limit = "4Gi"
service_account = "gitlab-ci-runner"
pull_policy = ["if-not-present"]
Déploiement via Helm :
helm install gitlab-runner gitlab/gitlab-runner \
--namespace gitlab-ci --create-namespace \
--set gitlabUrl=https://gitlab.com \
--set runnerToken="glrt-XXXX"
Les variables KUBERNETES_CPU_REQUEST, KUBERNETES_MEMORY_LIMIT etc. permettent d’override les ressources par job dans le .gitlab-ci.yml. Un job de ML qui a besoin de 8 Go de RAM ? Tu le spécifies dans le pipeline, pas dans la config du runner.
Tags : router les jobs
Les tags sont le mécanisme de routing des jobs vers les bons runners. Un runner déclare ses capabilities via des tags, un job demande les tags dont il a besoin.
train-model:
tags: [gpu, cuda]
script: python train.py
build-frontend:
tags: [docker, linux]
script: npm run build
deploy-ios:
tags: [macos, xcode]
script: xcodebuild ...
Convention de nommage : par capability (docker, gpu, privileged), par environnement (production, staging), par taille (large, small). Un job deploy-production avec tags: [docker, production, large] atterrit sur le bon runner automatiquement.
⚠️ Si un job a des tags mais qu’aucun runner ne les matche, le job reste en “Pending” indéfiniment. C’est le problème numéro 1 de debugging : vérifie les tags avant tout.
Shared, Group et Project runners
Shared : disponibles pour tous les projets de l’instance. Configurés par les admins. Sur GitLab.com, ce sont des VMs éphémères avec des quotas de minutes.
Group : disponibles pour tous les projets d’un groupe. Configurés par les mainteneurs du groupe. Idéal pour une équipe avec des besoins spécifiques.
Project : dédiés à un seul projet. Pour les cas très spécifiques (accès hardware, réseau particulier).
Priorité d’exécution : Project > Group > Shared. Si tu veux forcer un runner spécifique, utilise les tags.
🎯 Architecture typique en entreprise : des shared runners Docker pour le build et les tests (80% des jobs), un group runner avec accès au réseau de prod pour les déploiements, et un project runner GPU pour le ML. Trois niveaux, chacun avec ses tags et ses permissions.
Optimisation des performances
Trois leviers principaux pour accélérer tes pipelines :
# 1. Clone superficiel (évite de télécharger tout l'historique Git)
variables:
GIT_DEPTH: 1
GIT_STRATEGY: fetch # Réutilise le clone existant
# 2. Cache partagé via S3 (tous les runners partagent le même cache)
# Dans config.toml :
# [runners.cache]
# Type = "s3"
# Shared = true
# [runners.cache.s3]
# BucketName = "gitlab-ci-cache"
# BucketLocation = "eu-west-1"
# 3. Concurrence ajustée
# concurrent = 10 dans config.toml (max jobs simultanés)
Le GIT_STRATEGY: fetch seul peut diviser le temps de clone par 10 sur les gros repos. Combiné avec GIT_DEPTH: 1 et un cache S3, tu élimines les deux plus gros goulots d’étranglement : le clone et le téléchargement des dépendances.
Pièges fréquents et ce qu’il faut retenir
Job en “Pending” infini. Trois causes : pas de runner avec les bons tags, runner offline (gitlab-runner verify pour diagnostiquer), ou quotas de minutes CI atteints sur GitLab.com.
privileged = true en production. C’est l’équivalent de donner root à tous les jobs. Un job malicieux peut accéder à la machine hôte, lire les secrets d’autres projets, compromettre l’infrastructure. Utilise Kaniko pour builder des images Docker sans privilèges.
Images toujours re-pull. Le pull_policy: always par défaut re-télécharge l’image Docker à chaque job. Pour des images stables (tags versionnés), if-not-present économise des dizaines de secondes par job.
Runner sous-dimensionné. Un runner avec 2 Go de RAM qui lance des tests Node.js avec Cypress va OOM régulièrement. Monitore les métriques Prometheus du runner (port 9252) et ajuste les ressources.
💡 Sécurité minimale pour les runners : privileged = false, allowed_images pour limiter les images utilisables, cap_drop = ["ALL"], et security_opt = ["no-new-privileges:true"]. Sépare toujours les runners de dev et de prod — un runner de dev ne doit jamais avoir accès au réseau de production.
L’executor Docker couvre 90% des besoins. Kubernetes prend le relais quand tu as besoin de scaling automatique. Shell, c’est pour les cas particuliers. Commence par un runner Docker bien configuré, ajoute du cache S3, et tu auras des pipelines rapides et fiables. Le scaling viendra quand tu en auras besoin.
Dans le prochain chapitre, on passe aux techniques avancées : templates, includes, parent-child pipelines et DAG.
🖥️ Pratique sur ton propre serveur
Pour suivre GitLab CI/CD en conditions réelles, tu as besoin d'un VPS. DigitalOcean offre 200$ de crédit gratuit pour démarrer.
Contenu réservé aux abonnés
Ce chapitre fait partie de la formation complète. Abonne-toi pour débloquer tous les contenus.
Débloquer pour 29 CHF/moisLe chapitre 1 de chaque formation est gratuit.
Série pas encore débloquée
Termine la série prérequise d'abord pour accéder à ce contenu.
Aller à la série prérequiseSérie : GitLab CI/CD
3 / 6- GitLab CI/CD : comprendre la plateforme avant de pipeline
- Ton premier pipeline GitLab CI/CD
- 3 Runners GitLab : Docker, Kubernetes et autoscaling
- 4 GitLab CI avancé : templates, includes, DAG et rules
- 5 Sécurité dans GitLab CI : SAST, DAST et scanning
- 6 GitLab CI en production : environments, review apps et feature flags
Sur cette page
Articles liés
Ton premier pipeline GitLab CI/CD
Écris ton premier .gitlab-ci.yml, comprends stages, jobs, artifacts et cache. Un vrai pipeline, pas un hello world.
GitLab CI/CD : comprendre la plateforme avant de pipeline
GitLab vs GitHub Actions, l'architecture interne, les runners, le modèle DevOps intégré. Tout ce qu'il faut comprendre avant d'écrire ta première ligne de YAML.
GitLab CI avancé : templates, includes, DAG et rules
Maîtrise les techniques avancées de GitLab CI : templates réutilisables, includes, pipelines parent-child, DAG pour la parallélisation, et rules complexes.