Aller au contenu
English
CI/CD & Automatisation medium

Service containers et job containers

Read this page in English

30 min de lecture

Tester une application qui parle à PostgreSQL avec une base simulée ne prouve pas grand-chose. Les service containers démarrent de vraies dépendances à côté de votre job, le temps de son exécution. Le job container, lui, change la machine sur laquelle votre code tourne. Les deux se confondent souvent, et répondent à des besoins opposés.

  • Démarrer une base de données ou un cache à côté du job
  • Attendre qu'un service soit réellement prêt, sans sleep
  • Joindre le service, selon que le job tourne sur le runner ou en conteneur
  • Distinguer service container et job container, et choisir le bon

Un service container est un conteneur que GitHub démarre avant votre job et détruit après. Votre code s'y connecte comme il se connecterait à un service réel, parce que c'en est un.

name: Tests d'intégration
on:
pull_request:
branches: [main]
permissions: {}
jobs:
integration:
runs-on: ubuntu-24.04
permissions:
contents: read
services:
postgres:
image: postgres:17-alpine@sha256:18cfe3ef5e6815560c98237d6216d1e5119702fb0f3894c8785dd58b8bbe5d73
env:
POSTGRES_PASSWORD: motdepasse-de-test
POSTGRES_DB: app_test
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: "3.13"
- run: pip install --require-hashes -r requirements.txt
- name: Lancer les tests d'intégration
env:
DATABASE_URL: postgresql://postgres:motdepasse-de-test@localhost:5432/app_test
run: pytest tests/integration -q

L'image porte un digest, comme partout ailleurs sur ce site : un service de test épinglé sur un tag mutable rend vos tests non reproductibles, et introduit la même dépendance non maîtrisée que dans une image de production.

C'est le piège numéro un. Un conteneur démarré n'est pas un service prêt : PostgreSQL accepte les connexions plusieurs secondes après le lancement du conteneur. Sans précaution, le premier test échoue une fois sur trois, de façon apparemment aléatoire.

La réponse n'est pas un sleep, qui est soit trop court soit trop long, mais le health check déclaré dans options. GitHub attend que le conteneur soit sain avant de lancer la première étape.

ServiceCommande de santé
PostgreSQLpg_isready -U postgres
MySQLmysqladmin ping
Redisredis-cli ping
MongoDBmongosh --eval "db.adminCommand('ping')"

Les quatre paramètres qui les accompagnent se lisent ensemble : --health-interval donne la fréquence des tentatives, --health-timeout le délai avant qu'une tentative soit déclarée perdue, --health-retries le nombre d'échecs tolérés, et --health-start-period une période de grâce initiale utile aux services lents à démarrer.

L'adresse à utiliser dépend de l'endroit où tourne votre job, et c'est la source de confusion la plus fréquente sur ce sujet.

Le job tourneAdresse du servicePort
Directement sur le runnerlocalhostLe port publié via ports:
Dans un job containerLe nom du serviceLe port interne du conteneur

Quand le job tourne sur le runner, les services sont joignables sur localhost grâce à la publication de ports. Quand le job tourne lui-même dans un conteneur, il partage un réseau Docker avec les services : on les joint par leur nom, et la publication de ports devient inutile.

integration-en-conteneur:
runs-on: ubuntu-24.04
permissions:
contents: read
container:
image: python:3.13-slim@sha256:9534e5a8e315485d4061ed659af0fd78a284c015f9b73661b41d6bab25604534
services:
redis:
image: redis:8-alpine@sha256:becdda6c7f4b3fb42e42fd7f120bbf5c54c4caaaf16f26da24e4563d2c1f0576
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Lancer les tests
env:
REDIS_URL: redis://redis:6379
run: pytest tests/integration -q

Notez l'absence de ports: sur le service, et l'adresse redis://redis:6379 qui reprend le nom de la clé déclarée sous services:.

Le container: d'un job ne démarre pas une dépendance : il change la machine d'exécution de vos étapes. Au lieu de tourner sur l'image du runner GitHub, vos commandes tournent dans l'image que vous choisissez.

Service containerJob container
RôleUne dépendance à côté du jobL'environnement du job lui-même
Cléservices:container:
Cas d'usageBase de données, cache, file de messagesToolchain figée, distribution précise
NombrePlusieursUn seul

Le job container répond à un besoin de reproductibilité : la même image en CI et en local, donc les mêmes versions d'outils, donc la disparition du « ça marche sur ma machine ». Il a une contrepartie à connaître : certaines actions supposent des outils présents sur l'image du runner et échouent dans une image minimale. Une image slim ou alpine sans git, curl ou node peut faire échouer actions/checkout ou d'autres actions JavaScript.

Trois situations où le réflexe coûte plus qu'il ne rapporte.

Les tests unitaires. Si le test n'a pas besoin de la base, démarrer PostgreSQL ajoute vingt secondes à chaque exécution pour rien. Réservez les services aux tests qui traversent réellement la dépendance.

Les dépendances externes non conteneurisables. Un service tiers accessible uniquement par API ne se simule pas avec un conteneur officiel : ce sont des doublures applicatives qu'il faut, pas un service container.

Les matrices larges. Un service démarre par combinaison de la matrice. Neuf combinaisons, c'est neuf PostgreSQL. Quand le temps de démarrage domine, regrouper les tests d'intégration dans un job dédié hors matrice coûte moins cher.

Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

  • Un service container démarre avant le job et meurt après : le code parle à une vraie dépendance, pas à une simulation.
  • L'image d'un service s'épingle par digest, comme toute autre image : un tag mutable rend les tests non reproductibles.
  • Le mot de passe d'un service de test n'est pas un secret : le conteneur est éphémère et joignable du seul job.
  • Un conteneur démarré n'est pas un service prêt : utilisez un --health-cmd, jamais un sleep.
  • L'adresse dépend du contexte : localhost et le port publié depuis le runner, le nom du service et le port interne depuis un job container.
  • Le job container change l'environnement d'exécution, pas les dépendances ; il n'y en a qu'un, et il peut manquer des outils attendus par certaines actions.
  • Un job container n'est pas une frontière de sécurité : il s'exécute sur le runner et en partage le sort.
  • Les services démarrent par combinaison de matrice : isolez les tests d'intégration quand le démarrage domine le temps d'exécution.
  • Actions composites : Factoriser la préparation répétitive d'un environnement de test entre plusieurs workflows.
  • Sécuriser GitHub Actions : Ce que l'ajout d'images tierces dans vos workflows change pour la chaîne d'approvisionnement.
  • Épingler par SHA : La même exigence d'immuabilité, appliquée aux actions comme ici aux images de service.

Ce site vous est utile ?

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

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn