Aller au contenu principal

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

AspectDescription
AlgorithmeRound Robin
NiveauLayer 4 (TCP/UDP)
PortN'importe quel nœud du cluster
DNSVIP (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
ModeVIPDNSRR
Résolution1 IP (VIP)N IPs (toutes les tâches)
LBTransparentCôté client
UsageStandardgRPC, 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é

ConceptDescription
Scaledocker service scale web=N
Routing MeshLB L4 intégré sur tous les nœuds
VIPIP virtuelle pour LB interne
DNSRRDNS retourne toutes les IPs
PlacementContraintes 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

  1. Créez un service avec 3 réplicas et scalez-le à 10
  2. Testez le routing mesh en accédant depuis différents nœuds
  3. Configurez des contraintes de placement par zone
  4. Mettez en place un rolling update avec monitoring

← Secrets et Configs | Haute disponibilité →