Aller au contenu
CI/CD & Automatisation medium

Services CI et cache avancé GitLab

12 min de lecture

logo gitlab

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é.

  • 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

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

Quand vous déclarez services: dans un job, GitLab Runner :

  1. Démarre les conteneurs de services avant le job
  2. Attend que les services soient prêts (healthcheck)
  3. Crée un réseau partagé entre le job et les services
  4. Rend chaque service accessible via un hostname dérivé du nom d’image

L’exemple le plus courant : des tests d’intégration avec PostgreSQL.

.gitlab-ci.yml
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:integration

Clé 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 -).

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/

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:integration

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:integration
CacheArtifacts
ButAccélérer les builds suivantsTransmettre des résultats entre jobs
Durée de vieBest effort (peut disparaître)Garanti pendant expire_in
StockageLocal au runner (ou distribué)Stocké par GitLab (object storage)
Cas d’usagenode_modules/, .pip/, vendor/dist/, rapports JUnit, binaires

Détails dans Artifacts et cache (V1-06)

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.

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 :

  1. Cache correspondant à la clé exacte (hash de package-lock.json)
  2. npm-cache-main (si vous êtes sur une branche)
  3. npm-cache-default (dernier recours)
PolicyComportementUsage
pull-pushLit le cache au début, écrit à la fin (défaut)Le job qui alimente le cache (souvent le premier)
pullLit le cache, n’écrit pasLes jobs suivants (évite les écritures concurrentes)
pushN’utilise pas le cache existant, écrit à la finRegénérer un cache corrompu
Exemple : un job alimente, les autres consomment
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 build

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 test

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 :

config.toml
[[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"

Le cache peut devenir volumineux. Plusieurs approches :

Fenêtre de terminal
# Via l'API GitLab — supprimer le cache d'un projet
curl --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"
SymptômeCause probableSolution
Service connection refusedLe service n’est pas prêtAjouter un wait script ou augmenter wait_for_services_timeout
Hostname du service introuvableMauvais alias ou syntaxe d’imageVérifier alias: et le hostname utilisé dans la connexion
Cache vide sur une nouvelle branchePas de fallback configuréAjouter fallback_keys vers la branche par défaut
Cache incohérent entre jobsÉcriture concurrenteUtiliser 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 servicesTrop de conteneursRéduire les services ou augmenter les ressources du runner
Service MySQL Access deniedVariables d’env non passées au serviceDéclarer les variables sous services[].variables:
  • 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-push sur le premier job et pull sur 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)

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn