Aller au contenu principal
GitLabCI/CDRunnersDockerKubernetesFormation

Runners GitLab : Docker, Kubernetes et autoscaling

30 min de lecture GitLab CI/CD — Chapitre 3

Comprends les executors Docker et Kubernetes, configure des runners shared et specific, maîtrise le tagging et l'autoscaling pour des pipelines rapides.

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.

Obtenir 200$

Articles liés