infra / docker-compose
Scaling de services
Explication
Ce que vous allez apprendre
- Distinguer le scaling vertical (plus de ressources) du scaling horizontal (plus d'instances)
- Lancer plusieurs instances identiques d'un service avec
--scaleoudeploy.replicas - Comprendre pourquoi un service scalé ne peut pas publier de port hôte fixe
- Utiliser
exposeet un reverse proxy pour router le trafic vers plusieurs instances - Savoir que le DNS interne Docker fait du round-robin entre les replicas d'un service
Dans quel contexte ?
Le service worker du projet boutique-api, qui traite les images uploadées par les clients, commence à accumuler un retard visible dans sa file d'attente aux heures de pointe. Plutôt que de lui donner plus de CPU (scaling vertical, limité), l'équipe veut faire tourner cinq exemplaires identiques de ce worker en parallèle, qui se répartissent la charge automatiquement. C'est exactement ce que permet docker compose up --scale worker=5.
Le problème : une seule instance ne suffit plus
D'abord, imagine un service qui traite de plus en plus de requêtes. À un moment, une seule instance devient un goulot d'étranglement : elle sature, et les temps de réponse augmentent.
Deux façons de répondre à ce problème
Il existe deux approches. La première, le scaling vertical, consiste à rendre une seule instance plus puissante (plus de CPU, plus de mémoire). La seconde, le scaling horizontal, consiste à faire tourner PLUSIEURS instances identiques en parallèle, pour répartir la charge entre elles.
Ce que Compose permet de simuler
Compose permet de tester ce second comportement facilement en local, avec l'option --scale ou la clé deploy.replicas. On obtient alors plusieurs conteneurs identiques du même service.
Il reste un problème : le port fixe
Une fois qu'on a plusieurs instances (replicas) d'un service, un problème apparaît vite. Chaque replica est un conteneur séparé. Si on essaie de publier le même port hôte fixe, comme 3000:3000, pour chacun, ils entrent tous en conflit pour occuper la même adresse sur la machine.
La solution : exposer sans publier, et passer par un proxy
La solution consiste à ne PAS publier de port fixe sur le service scalé, en utilisant expose à la place. Un reverse proxy unique, lui, expose un seul port public et répartit ensuite le trafic vers les différentes instances.
Comment le trafic est réellement réparti
Maintenant que le proxy sait qu'il doit parler à plusieurs instances, comment fait-il ? Docker résout le nom d'un service scalé vers TOUTES les IP de ses replicas : c'est le DNS round-robin qui répartit naturellement les connexions, sans configuration supplémentaire.
| Approche | Ce qu'elle change | Limite |
|---|---|---|
| Scaling vertical | Plus de CPU/RAM sur une seule instance | Plafonné par la machine hôte |
| Scaling horizontal | Plusieurs instances identiques en parallèle | Nécessite un point d'entrée unique (proxy) |
Le piège à connaître
Publier un port fixe sur un service scalé empêche tout simplement le démarrage au-delà d'une instance : Compose refuse de créer un deuxième conteneur qui voudrait occuper le même port.
Piège fréquent
Déclarer ports: - "3000:3000" sur un service avec deploy.replicas: 3 échoue au démarrage du deuxième conteneur avec une erreur "port is already allocated". Utilise expose à la place et laisse un reverse proxy (nginx, Traefik) publier le seul port réellement accessible depuis l'extérieur.
Ne pas confondre avec un vrai load balancing de production
Ce mécanisme reste un outil de test local. Pour un vrai load balancing en production, avec un contrôle fin de la santé des instances et une répartition pondérée, il faut un reverse proxy dédié comme nginx ou Traefik — ce qu'on abordera plus tard dans ce cours.
La prochaine leçon change de sujet : protéger les données sensibles, comme les mots de passe, avec les secrets Compose.
Commandes & code
Scaling de services
Compose peut lancer plusieurs instances (replicas) d'un même service, utile pour tester la répartition de charge en local.
services:
worker:
build: ./worker
command: ["node", "worker.js"]
deploy:
replicas: 3 # utilisé par "docker compose up" (hors Swarm aussi, v2)
depends_on:
- queue
api:
build: ./api
# Pas de "ports" fixe ici : avec plusieurs replicas, un port fixe entrerait en conflit
expose:
- "3000"
nginx:
image: nginx:1.27-alpine
ports:
- "8080:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- api# nginx.conf - répartit la charge entre les instances "api" via le DNS round-robin de Docker
upstream api_backend {
server api:3000; # Compose résout ce nom vers TOUTES les IP des replicas
}
server {
listen 80;
location / {
proxy_pass http://api_backend;
}
}# Démarrer avec un nombre de replicas donné en ligne de commande (surcharge "deploy.replicas")
docker compose up -d --scale worker=5 --scale api=3
# Vérifier les instances créées (suffixe -1, -2, -3...)
docker compose ps
# Erreur classique : "--scale" incompatible avec un "ports" fixe (host:container)
# -> utiliser "expose" ou un port dynamique côté hôteRésumé
--scale <service>=Noudeploy.replicascrée plusieurs instances d'un service.- Un service scalé ne doit pas publier de port hôte fixe (
ports:) sans plage dynamique. - Le DNS interne Docker fait du round-robin entre les replicas d'un même nom de service.
- Pour un vrai load balancing en prod, préférer un reverse proxy (nginx/traefik) devant.
Exercices pratiques
Mission : scaler l'API sans faire exploser les ports
Objectif : Corriger un conflit de port sur un service scalé et router le trafic via un reverse proxy.
Contexte
Le service worker de boutique-api accumule du retard aux heures de pointe. L'équipe veut lancer docker compose up -d --scale api=3, mais le service api déclare encore ports: - "3000:3000". Elle veut aussi comprendre comment un nginx frontal pourra router le trafic vers les trois instances sans connaître leurs IP.
Corrige la configuration pour rendre le scaling possible.