Scaling et Load Balancing
Objectifs du chapitre
- Comprendre le scaling horizontal
- Maîtriser le load balancing natif
- Configurer le scaling automatique
- Optimiser la distribution de charge
1 - Scaling dans Docker Swarm
Types de scaling
┌─────────────────────────────────────────────────────────────┐
│ Types de Scaling │
├─────────────────────────────────────────────────────────────┤
│ │
│ Vertical (Scale Up) Horizontal (Scale Out) │
│ ┌─────────────────┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ Grande │ │ S │ │ S │ │ S │ │ S │ │
│ │ instance │ vs │ m │ │ m │ │ m │ │ m │ │
│ │ │ │ a │ │ a │ │ a │ │ a │ │
│ │ │ │ l │ │ l │ │ l │ │ l │ │
│ │ │ │ l │ │ l │ │ l │ │ l │ │
│ └─────────────────┘ └───┘ └───┘ └───┘ └───┘ │
│ │
│ Docker Swarm utilise le scaling HORIZONTAL │
│ │
└─────────────────────────────────────────────────────────────┘
Scaling manuel
# Augmenter le nombre de réplicas
docker service scale web=10
# Réduire
docker service scale web=3
# Scaler plusieurs services
docker service scale web=5 api=3 worker=10
# Vérifier
docker service ls
Scaling via update
# Alternative avec update
docker service update --replicas 10 web
# Dans une stack, modifier le fichier et re-déployer
# replicas: 10
docker stack deploy -c docker-compose.yml myapp
2 - Load Balancing natif
Routing Mesh
Docker Swarm inclut un load balancer L4 intégré :
Client Request
│
▼
┌─────────────────────────────────────────────────────────────┐
│ ROUTING MESH (Ingress) │
│ ┌─────────────────────────┐ │
│ │ Internal Load Balancer│ │
│ │ (Round Robin) │ │
│ └────────────┬────────────┘ │
└───────────────────────────┼───────────────────────────── ────┘
│
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ web.1 │ │ web.2 │ │ web.3 │
│ Node 1 │ │ Node 2 │ │ Node 3 │
└─────────┘ └─────────┘ └─────────┘
Caractéristiques
| Aspect | Description |
|---|---|
| Algorithme | Round Robin |
| Niveau | Layer 4 (TCP/UDP) |
| Port | N'importe quel nœud du cluster |
| DNS | VIP (Virtual IP) |
Exemple
# Créer un service
docker service create --name web -p 80:80 --replicas 5 nginx
# Accéder depuis n'importe quel nœud
curl http://node1:80 # → web.1 ou web.2 ou web.3...
curl http://node2:80 # → distribution round robin
curl http://node3:80 # → même sans conteneur sur ce nœud
3 - Load Balancing interne
VIP (Virtual IP)
# Par défaut, chaque service a une VIP
docker service create --name api --network mynet --replicas 3 myapi
# La VIP est utilisée pour le load balancing interne
docker service inspect api --format '{{.Endpoint.VirtualIPs}}'
┌─────────────────────────────────────────────────────────────┐
│ Network "mynet" │
├─────────────────────────────────────────────────────────────┤
│ │
│ Service "api" VIP: 10.0.0.5 │
│ │ │
│ ┌─────────────┼─────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ api.1 │ │ api.2 │ │ api.3 │ │
│ │10.0.0.10│ │10.0.0.11│ │10.0.0.12│ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ Client appelle "api" → résout vers VIP 10.0.0.5 │
│ → Load balanced vers api.1, api.2, ou api.3 │
│ │
└─────────────────────────────────────────────────────────────┘
DNS Round Robin (DNSRR)
# Mode DNSRR : pas de VIP, DNS retourne toutes les IPs
docker service create --name api \
--network mynet \
--replicas 3 \
--endpoint-mode dnsrr \
myapi
| Mode | VIP | DNSRR |
|---|---|---|
| Résolution | 1 IP (VIP) | N IPs (toutes les tâches) |
| LB | Transparent | Côté client |
| Usage | Standard | gRPC, LB externe |
4 - Load Balancing externe
Avec Traefik
version: "3.9"
services:
traefik:
image: traefik:v2.10
command:
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.swarmMode=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- traefik-public
deploy:
mode: global
placement:
constraints:
- node.role == manager
web:
image: nginx:alpine
networks:
- traefik-public
deploy:
replicas: 5
labels:
- "traefik.enable=true"
- "traefik.http.routers.web.rule=Host(`example.com`)"
- "traefik.http.routers.web.entrypoints=web"
- "traefik.http.services.web.loadbalancer.server.port=80"
networks:
traefik-public:
driver: overlay
Avec HAProxy
version: "3.9"
services:
haproxy:
image: haproxy:2.8
ports:
- "80:80"
- "443:443"
- "8404:8404" # Stats
volumes:
- ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
networks:
- frontend
deploy:
mode: global
placement:
constraints:
- node.role == manager
web:
image: nginx:alpine
networks:
- frontend
deploy:
replicas: 5
endpoint_mode: dnsrr # HAProxy gère le LB
networks:
frontend:
driver: overlay
5 - Stratégies de placement
Contraintes
# Placer sur des nœuds spécifiques
docker service create --name web \
--constraint 'node.role==worker' \
--constraint 'node.labels.zone==eu-west' \
--replicas 6 \
nginx
# Éviter certains nœuds
docker service create --name web \
--constraint 'node.hostname!=prod-db-1' \
nginx
Préférences de spread
# Distribuer uniformément par zone
docker service create --name web \
--replicas 9 \
--placement-pref 'spread=node.labels.zone' \
nginx
Zone A (3 nœuds) Zone B (3 nœuds) Zone C (3 nœuds)
┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐
│w.1│ │w.2│ │w.3│ │w.4│ │w.5│ │w.6│ │w.7│ │w.8│ │w.9│
└── ─┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘
9 réplicas répartis uniformément : 3 par zone
Réservation de ressources
services:
api:
deploy:
replicas: 10
resources:
reservations:
cpus: '0.5'
memory: 256M
limits:
cpus: '1'
memory: 512M
Le scheduler ne place les tâches que sur les nœuds ayant assez de ressources disponibles.
6 - Scaling automatique
Docker Swarm n'a pas de scaling automatique natif. Solutions :
Avec Orbiter
# Orbiter surveille les métriques et scale automatiquement
docker service create --name orbiter \
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
-e DOCKER_API_VERSION=1.41 \
gianarb/orbiter:latest
Script de monitoring personnalisé
#!/bin/bash
# autoscale.sh
SERVICE="web"
MIN_REPLICAS=2
MAX_REPLICAS=20
CPU_THRESHOLD=70
while true; do
# Obtenir l'utilisation CPU moyenne
CPU_USAGE=$(docker stats --no-stream --format "{{.CPUPerc}}" \
$(docker ps -q -f name=${SERVICE}) | \
sed 's/%//g' | \
awk '{sum+=$1} END {print sum/NR}')
CURRENT=$(docker service ls -f name=${SERVICE} --format "{{.Replicas}}" | cut -d'/' -f1)
if (( $(echo "$CPU_USAGE > $CPU_THRESHOLD" | bc -l) )); then
NEW_REPLICAS=$((CURRENT + 2))
if [ $NEW_REPLICAS -le $MAX_REPLICAS ]; then
echo "Scaling up to $NEW_REPLICAS replicas (CPU: ${CPU_USAGE}%)"
docker service scale ${SERVICE}=$NEW_REPLICAS
fi
elif (( $(echo "$CPU_USAGE < 30" | bc -l) )); then
NEW_REPLICAS=$((CURRENT - 1))
if [ $NEW_REPLICAS -ge $MIN_REPLICAS ]; then
echo "Scaling down to $NEW_REPLICAS replicas (CPU: ${CPU_USAGE}%)"
docker service scale ${SERVICE}=$NEW_REPLICAS
fi
fi
sleep 30
done
Avec Prometheus + alertes
# prometheus-alerts.yml
groups:
- name: swarm-scaling
rules:
- alert: HighCPUUsage
expr: avg(rate(container_cpu_usage_seconds_total{container_label_com_docker_swarm_service_name="web"}[5m])) > 0.7
for: 2m
annotations:
summary: "Scale up web service"
7 - Rolling Updates et Blue-Green
Rolling Update (par défaut)
docker service update \
--update-parallelism 2 \
--update-delay 10s \
--update-failure-action rollback \
--image nginx:1.25 \
web
Temps ──────────────────────────────────────────────────────►
État initial: [v1.24] [v1.24] [v1.24] [v1.24] [v1.24]
Étape 1: [v1.25] [v1.25] [v1.24] [v1.24] [v1.24]
▲ ▲
Mis à jour
Étape 2: [v1.25] [v1.25] [v1.25] [v1.25] [v1.24]
▲ ▲
Mis à jour
Étape 3: [v1.25] [v1.25] [v1.25] [v1.25] [v1.25]
▲
Terminé
Blue-Green avec services
# Service "blue" actif
docker service create --name web-blue -p 80:80 --replicas 5 myapp:v1
# Déployer "green" sur un autre port
docker service create --name web-green -p 8080:80 --replicas 5 myapp:v2
# Tester green
curl http://swarm-node:8080
# Basculer le trafic
docker service update --publish-rm 80:80 web-blue
docker service update --publish-add 80:80 --publish-rm 8080:80 web-green
# Supprimer blue
docker service rm web-blue
8 - Monitoring du scaling
Métriques à surveiller
# Réplicas actuels vs désirés
docker service ls --format "{{.Name}}: {{.Replicas}}"
# État des tâches
docker service ps web --format "{{.Name}}: {{.CurrentState}}"
# Ressources utilisées
docker stats --no-stream
Avec Prometheus
# docker-compose.monitoring.yml
services:
prometheus:
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
deploy:
placement:
constraints:
- node.role == manager
cadvisor:
image: gcr.io/cadvisor/cadvisor
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker:/var/lib/docker:ro
deploy:
mode: global
grafana:
image: grafana/grafana
ports:
- "3000:3000"
Résumé
| Concept | Description |
|---|---|
| Scale | docker service scale web=N |
| Routing Mesh | LB L4 intégré sur tous les nœuds |
| VIP | IP virtuelle pour LB interne |
| DNSRR | DNS retourne toutes les IPs |
| Placement | Contraintes et préférences |
Points clés
- Le scaling horizontal est natif et simple
- Le routing mesh distribue le trafic automatiquement
- Utilisez des contraintes pour contrôler le placement
- Le scaling automatique nécessite des outils tiers
Exercices pratiques
- Créez un service avec 3 réplicas et scalez-le à 10
- Testez le routing mesh en accédant depuis différents nœuds
- Configurez des contraintes de placement par zone
- Mettez en place un rolling update avec monitoring