Load Balancing dans Docker
Le load balancing distribue les requêtes entrantes entre plusieurs containers pour éviter qu'un seul soit surchargé. Docker propose plusieurs mécanismes, du plus simple (DNS round-robin) au plus robuste (IPVS en mode kernel).
Les 3 niveaux de load balancing Docker
| Mécanisme | Niveau | Cas d'usage | Stabilité |
|---|---|---|---|
| DNS round-robin | Application | Dev, tests | Faible (cache DNS) |
| VIP + IPVS (Swarm) | Kernel L4 | Production Swarm | Élevée |
| Reverse proxy L7 (Traefik/Nginx) | Application L7 | HTTP/HTTPS avec routing | Élevée + features |
DNS Round-Robin : la base
Quand plusieurs containers partagent le même network alias, Docker retourne toutes leurs IPs en rotation à chaque requête DNS.
docker network create lb-demo
# Lancer 3 instances avec le même alias
for i in 1 2 3; do
docker run -d --name app$i --network lb-demo --network-alias app nginx
done
# Chaque nslookup retourne les IPs dans un ordre différent
docker run --rm --network lb-demo alpine nslookup app
# Address 1: 172.28.0.2 app1
# Address 2: 172.28.0.3 app2
# Address 3: 172.28.0.4 app3
Limitation majeure : les clients HTTP et JVM mettent en cache les réponses DNS. Résultat : toutes les requêtes d'un client peuvent aller sur le même container pendant un moment, annulant l'effet de distribution.
Utiliser en développement ou pour des clients qui ne cachent pas le DNS. En production → VIP ou reverse proxy.
VIP + IPVS (Swarm) : production-ready
Swarm attribue un Virtual IP (VIP) stable à chaque service. IPVS (IP Virtual Server) est un module du noyau Linux qui distribue les connexions TCP/UDP de manière ultra-performante.
Container client → DNS "api" → VIP 10.0.9.5
│
IPVS (kernel)
├── Replica 1 (10.0.9.6) — host-a
├── Replica 2 (10.0.9.7) — host-b
└── Replica 3 (10.0.9.8) — host-a
Algorithme : Round-robin par défaut. IPVS choisit au niveau de la connexion (pas de la requête), donc une connexion TCP longue restera sur le même replica.
# Créer un service Swarm avec 3 replicas
docker service create \
--name api \
--replicas 3 \
--network my-overlay \
--endpoint-mode vip \
myapi:latest
# Le VIP ne change jamais, même si les replicas redémarrent
docker service inspect api --format '{{json .Endpoint.VirtualIPs}}'
# [{"NetworkID":"...", "Addr":"10.0.9.5/24"}]
Load Balancing Externe : le Routing Mesh
Pour le trafic externe, le routing mesh de Swarm permet à n'importe quel node du cluster de recevoir et router le trafic, même sans replica local.
docker service create \
--name web \
--publish published=80,target=80 \
--replicas 3 \
nginx
# node-a, node-b, node-c écoutent tous sur :80
# Même si un node n'a pas de replica, il route vers un node qui en a un
Client → http://node-c:80
│
node-c : pas de replica web ici
│
iptables REDIRECT → ingress network
│
IPVS → replica sur node-a ou node-b
Load Balancing L7 avec Traefik
Pour du routing HTTP (par domaine, par chemin, TLS), utiliser un reverse proxy comme Traefik. Traefik s'intègre avec Docker via les labels et se reconfigure automatiquement quand des containers démarrent ou s'arrêtent.
services:
traefik:
image: traefik:v3
command:
- "--providers.docker=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
api:
image: myapi
deploy:
replicas: 3
labels:
- "traefik.http.routers.api.rule=Host(`api.example.com`)"
- "traefik.http.services.api.loadbalancer.server.port=8080"
# Sticky sessions (même client → même replica)
- "traefik.http.services.api.loadbalancer.sticky.cookie=true"
web:
image: nginx
labels:
- "traefik.http.routers.web.rule=Host(`app.example.com`)"
- "traefik.http.services.web.loadbalancer.server.port=80"
Traefik détecte les containers, lit leurs labels, et configure le routing dynamiquement.
Load Balancing avec NGINX
Pour plus de contrôle sur les algorithmes :
upstream backend {
server app1:8080 weight=3; # app1 reçoit 3x plus de trafic
server app2:8080 weight=1;
server app3:8080 backup; # backup : utilisé seulement si app1/app2 sont down
least_conn; # algorithme : connexion la moins chargée
keepalive 32; # connexions persistantes
}
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_next_upstream error timeout; # retry sur le prochain server si erreur
}
}
Algorithmes disponibles dans Nginx :
- (par défaut) round-robin
least_conn: vers le server avec le moins de connexions activesip_hash: même client → même server (sessions)random: serveur aléatoire
Health Checks et LB
Docker arrête automatiquement d'envoyer du trafic vers un container marqué comme unhealthy.
# Dans docker-compose.yml ou Dockerfile
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 10s # fréquence du check
timeout: 5s # délai max pour la réponse
retries: 3 # nombre d'échecs avant "unhealthy"
start_period: 30s # délai avant de commencer les checks (démarrage app)
# Voir l'état de santé
docker ps # STATUS: "Up 5 minutes (healthy)" ou "(unhealthy)"
docker inspect my-container | grep -A 5 Health
Dans Swarm, un replica unhealthy est automatiquement retiré du VIP IPVS et un nouveau est planifié.
Résumé
- DNS round-robin : simple, mais le caching DNS réduit l'efficacité — pour dev seulement
- VIP + IPVS (Swarm) : distribue au niveau kernel, VIP stable, production-ready
- Routing mesh : tout node reçoit le trafic externe, même sans replica local
- Traefik : reverse proxy L7 avec auto-découverte Docker et routing HTTP/TLS
- Health checks : les containers unhealthy sont automatiquement exclus du load balancing