
Vos tests d’intégration ont besoin d’une vraie base de données, pas d’un mock ? GitLab CI/CD lance des services (PostgreSQL, Redis, Elasticsearch…) comme conteneurs adjacents à votre job. Votre code s’y connecte comme s’il était en local. Combiné à un cache avancé bien configuré, vos pipelines sont à la fois complets et rapides.
Le module Artifacts et cache (V1-06) couvrait les bases. Ce guide approfondit les cas avancés : services Docker, stratégies de clé de cache, policies et cache distribué.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Lancer des services (PostgreSQL, Redis, MongoDB) dans un job CI
- Configurer la connexion entre le job et le service (hostname, variables)
- Maîtriser les policies de cache :
pull-push,pull,push - Concevoir des clés de cache robustes (fichier de lock, combinaisons)
- Gérer le cache distribué (S3) pour les runners autoscalés
- Diagnostiquer les problèmes de services et de cache
Dans quel contexte ?
Section intitulée « Dans quel contexte ? »Vous avez des tests unitaires qui passent, mais vos tests d’intégration échouent parce qu’ils ont besoin d’une base de données. Vous pourriez mocker, mais les tests ne seraient pas réalistes. Vous avez besoin d’un PostgreSQL (ou Redis, ou MongoDB) réel dans votre pipeline.
En parallèle, votre cache est imprévisible : parfois il est là, parfois il est vide. Les builds prennent 3 minutes avec cache, 12 sans. Vous voulez comprendre pourquoi et le rendre fiable.
- Vos tests d’intégration ont besoin d’une base SQL ou NoSQL
- Vous testez un microservice qui dépend de Redis pour les sessions
- Votre app a un healthcheck qui vérifie la connexion à la base
- Votre cache disparaît quand le runner change
- Vous avez des runners autoscalés et le cache local ne survit pas
Partie 1 : Services CI
Section intitulée « Partie 1 : Services CI »Comment fonctionnent les services
Section intitulée « Comment fonctionnent les services »Quand vous déclarez services: dans un job, GitLab Runner :
- Démarre les conteneurs de services avant le job
- Attend que les services soient prêts (healthcheck)
- Crée un réseau partagé entre le job et les services
- Rend chaque service accessible via un hostname dérivé du nom d’image
PostgreSQL dans un job
Section intitulée « PostgreSQL dans un job »L’exemple le plus courant : des tests d’intégration avec PostgreSQL.
test:integration: stage: test image: node:20 services: - name: postgres:16-alpine alias: db variables: POSTGRES_DB: testdb POSTGRES_USER: testuser POSTGRES_PASSWORD: testpass variables: DATABASE_URL: "postgresql://testuser:testpass@db:5432/testdb" script: - npm ci - npm run test:integrationClé de compréhension : le service est accessible via le hostname db (défini par alias). Sans alias, le hostname serait postgres (dérivé du nom d’image, en remplaçant / et : par -).
Redis pour les sessions
Section intitulée « Redis pour les sessions »test:integration: stage: test image: python:3.12 services: - name: redis:7-alpine alias: cache variables: REDIS_URL: "redis://cache:6379" script: - pip install -r requirements.txt - pytest tests/integration/Plusieurs services simultanés
Section intitulée « Plusieurs services simultanés »Votre application dépend de PostgreSQL et Redis ? Déclarez les deux :
test:integration: stage: test image: node:20 services: - name: postgres:16-alpine alias: db variables: POSTGRES_DB: app_test POSTGRES_USER: app POSTGRES_PASSWORD: secret - name: redis:7-alpine alias: cache - name: elasticsearch:8.12.0 alias: search variables: discovery.type: single-node xpack.security.enabled: "false" variables: DATABASE_URL: "postgresql://app:secret@db:5432/app_test" REDIS_URL: "redis://cache:6379" ELASTICSEARCH_URL: "http://search:9200" script: - npm ci - npm run test:integrationAttendre qu’un service soit prêt
Section intitulée « Attendre qu’un service soit prêt »Les services ne démarrent pas instantanément. PostgreSQL a besoin de quelques secondes pour être prêt. Deux approches :
test:integration: services: - name: postgres:16-alpine alias: db before_script: - apt-get update && apt-get install -y postgresql-client - | for i in $(seq 1 30); do pg_isready -h db -p 5432 && break echo "Waiting for PostgreSQL... ($i/30)" sleep 1 done - npm ci script: - npm run test:integrationGitLab Runner vérifie automatiquement si le service répond. Pour la plupart des images officielles (PostgreSQL, Redis, MySQL), le healthcheck natif de l’image Docker suffit. Si le service ne démarre pas dans les 30 secondes, le job échoue.
Pour augmenter le timeout, configurez dans le runner :
[[runners]] [runners.docker] wait_for_services_timeout = 60Partie 2 : Cache avancé
Section intitulée « Partie 2 : Cache avancé »Rappel : cache vs artifacts
Section intitulée « Rappel : cache vs artifacts »| Cache | Artifacts | |
|---|---|---|
| But | Accélérer les builds suivants | Transmettre des résultats entre jobs |
| Durée de vie | Best effort (peut disparaître) | Garanti pendant expire_in |
| Stockage | Local au runner (ou distribué) | Stocké par GitLab (object storage) |
| Cas d’usage | node_modules/, .pip/, vendor/ | dist/, rapports JUnit, binaires |
→ Détails dans Artifacts et cache (V1-06)
Stratégies de clé de cache
Section intitulée « Stratégies de clé de cache »La clé de cache détermine quand le cache est réutilisé ou invalidé.
cache: key: files: - package-lock.json paths: - node_modules/Le meilleur choix dans la majorité des cas. Le cache est invalidé uniquement quand les dépendances changent. Deux branches avec le même package-lock.json partagent le cache.
cache: key: $CI_COMMIT_REF_SLUG paths: - node_modules/Chaque branche a son propre cache. Simple mais gaspille de l’espace si les dépendances sont identiques entre branches.
cache: key: prefix: $CI_COMMIT_REF_SLUG files: - package-lock.json paths: - node_modules/Cache par branche et par fichier de lock. Le cache est invalidé si la branche ou les dépendances changent. Utile si différentes branches testent différentes versions de dépendances.
Fallback keys
Section intitulée « Fallback keys »Quand le cache exact n’existe pas, GitLab peut rechercher un cache similaire :
cache: key: files: - package-lock.json fallback_keys: - npm-cache-$CI_DEFAULT_BRANCH - npm-cache-default paths: - node_modules/L’ordre de recherche :
- Cache correspondant à la clé exacte (hash de
package-lock.json) npm-cache-main(si vous êtes sur une branche)npm-cache-default(dernier recours)
Policies : contrôler qui écrit et qui lit
Section intitulée « Policies : contrôler qui écrit et qui lit »| Policy | Comportement | Usage |
|---|---|---|
pull-push | Lit le cache au début, écrit à la fin (défaut) | Le job qui alimente le cache (souvent le premier) |
pull | Lit le cache, n’écrit pas | Les jobs suivants (évite les écritures concurrentes) |
push | N’utilise pas le cache existant, écrit à la fin | Regénérer un cache corrompu |
lint: stage: lint cache: key: files: - package-lock.json paths: - node_modules/ policy: pull-push # Premier job → alimente le cache script: - npm ci - npm run lint
test: stage: test cache: key: files: - package-lock.json paths: - node_modules/ policy: pull # Job suivant → lecture seule script: - npm test
build: stage: build cache: key: files: - package-lock.json paths: - node_modules/ policy: pull # Lecture seule aussi script: - npm run buildCaches multiples
Section intitulée « Caches multiples »Un job peut déclarer plusieurs caches indépendants :
test:python: cache: - key: files: - requirements.txt paths: - .pip/ - key: files: - package-lock.json paths: - node_modules/ script: - pip install -r requirements.txt --cache-dir .pip - npm ci - npm testCache distribué (S3)
Section intitulée « Cache distribué (S3) »Par défaut, le cache est stocké localement sur le runner. Si vos runners sont autoscalés (instances éphémères), le cache disparaît avec l’instance. La solution : un cache distribué sur S3 (ou compatible S3).
Configuration dans le runner :
[[runners]] [runners.cache] Type = "s3" Shared = true [runners.cache.s3] ServerAddress = "s3.amazonaws.com" BucketName = "gitlab-ci-cache" BucketLocation = "eu-west-1" AccessKey = "AKIAIOSFODNN7EXAMPLE" SecretKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"Nettoyer le cache
Section intitulée « Nettoyer le cache »Le cache peut devenir volumineux. Plusieurs approches :
# Via l'API GitLab — supprimer le cache d'un projetcurl --request DELETE \ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "https://gitlab.example.com/api/v4/projects/123/jobs/artifacts"Dans l’interface : CI/CD > Pipelines > Clear runner caches (bouton en haut à droite).
Pour un nettoyage régulier, ajoutez un job scheduled :
clean:cache: stage: maintenance cache: key: "force-refresh" policy: push paths: - node_modules/ script: - rm -rf node_modules/ - npm ci rules: - if: $CI_PIPELINE_SOURCE == "schedule"Dépannage
Section intitulée « Dépannage »| Symptôme | Cause probable | Solution |
|---|---|---|
Service connection refused | Le service n’est pas prêt | Ajouter un wait script ou augmenter wait_for_services_timeout |
| Hostname du service introuvable | Mauvais alias ou syntaxe d’image | Vérifier alias: et le hostname utilisé dans la connexion |
| Cache vide sur une nouvelle branche | Pas de fallback configuré | Ajouter fallback_keys vers la branche par défaut |
| Cache incohérent entre jobs | Écriture concurrente | Utiliser policy: pull sur tous les jobs sauf le premier |
| Cache jamais invalidé | Clé statique (key: "npm-cache") | Utiliser key.files avec un fichier de lock |
| Out of memory avec services | Trop de conteneurs | Réduire les services ou augmenter les ressources du runner |
Service MySQL Access denied | Variables d’env non passées au service | Déclarer les variables sous services[].variables: |
À retenir
Section intitulée « À retenir »- Les services CI lancent des conteneurs adjacents (PostgreSQL, Redis…) accessibles par hostname
- Le hostname est l’alias du service ou le nom d’image simplifié
- Chaque service a ses propres variables (
POSTGRES_DB,REDIS_URL…) - La clé de cache basée sur un fichier de lock est la stratégie la plus fiable
- Utilisez
policy: pull-pushsur le premier job etpullsur les suivants - Les fallback keys évitent les cache miss sur les nouvelles branches
- Le cache distribué (S3/MinIO) est indispensable pour les runners autoscalés
- Cache = best effort (accélérateur), artifacts = garanti (résultats)