Retour au cours

infra / nginx

Load balancing : upstream et algorithmes

Leçon 51 exercice

Explication

Ce que vous allez apprendre

  • Déclarer un groupe upstream regroupant plusieurs serveurs backend
  • Choisir un algorithme de répartition adapté à ta situation (round robin, least_conn, ip_hash, weight)
  • Gérer la santé des backends avec max_fails, fail_timeout, backup et down
  • Comprendre l'intérêt de keepalive entre Nginx et ses backends
  • Distinguer les cas où la répartition doit être "sticky" (même client, même serveur) de ceux où elle ne le doit pas

Dans quel contexte ?

Une application FastAPI, initialement lancée sur une seule instance, reçoit maintenant trop de trafic pour tenir sur un seul serveur. L'équipe déploie trois instances identiques sur trois machines différentes (10.0.0.11, 10.0.0.12, 10.0.0.13) et doit répartir les requêtes entre elles, sans que le client ne remarque la moindre différence.

D'abord, la brique de base : le groupe upstream

Un bloc upstream backend_app { server 10.0.0.11:8000; server 10.0.0.12:8000; } déclare un pool nommé de serveurs backend. Le proxy_pass référence ensuite ce nom (http://backend_app;) plutôt qu'une IP précise — c'est ce changement qui active la répartition de charge, de façon totalement transparente pour le reste de la configuration.

Une fois ce pool défini, il faut choisir comment répartir les requêtes

Par défaut, sans rien préciser, Nginx utilise le round robin : chaque nouvelle requête va au serveur suivant dans la liste, en boucle. C'est simple et suffisant quand tous les serveurs ont une capacité équivalente et que chaque requête prend un temps de traitement comparable.

Il reste des cas où le round robin simple ne suffit pas

Si le temps de traitement varie beaucoup d'une requête à l'autre, un serveur round robin peut accumuler des requêtes lentes pendant qu'un autre reste sous-utilisé. least_conn corrige ça en envoyant chaque nouvelle requête au serveur ayant actuellement le moins de connexions actives, ce qui équilibre mieux la charge réelle plutôt que le simple nombre de requêtes.

AlgorithmePrincipeCas d'usage typique
Round robin (défaut)Distribution séquentielle, en boucleBackends homogènes, requêtes de durée comparable
least_connEnvoie au serveur le moins chargé actuellementTemps de traitement très variable entre requêtes
ip_hashUn même client IP revient toujours sur le même serveurPas de session store partagé entre backends
weight=NPondère la part de trafic reçue par serveurServeurs de puissance différente dans le même pool

Ensuite, une question de résilience : que se passe-t-il si un backend tombe ?

max_fails=3 fail_timeout=30s exclut automatiquement un serveur du pool après trois échecs consécutifs, pendant 30 secondes, avant de retenter. backup désigne un serveur utilisé UNIQUEMENT si tous les autres sont indisponibles, typique d'un serveur de secours peu dimensionné gardé en réserve. down retire manuellement un serveur pour une maintenance planifiée, sans avoir à le supprimer de la configuration.

Prérequis

Cette leçon suppose que tu es à l'aise avec proxy_pass (leçon précédente) : un upstream est simplement une cible nommée qu'on utilise à sa place.

Piège fréquent

Choisir ip_hash sans réfléchir alors que l'application gère déjà ses sessions dans une base partagée (Redis, par exemple) est inutilement contraignant : ça empêche une bascule fluide en cas de panne d'un serveur précis, puisque le client "collé" à ce serveur n'a plus d'alternative immédiate. ip_hash n'est réellement nécessaire que si aucune session partagée n'existe entre les instances.

Bonne pratique

Active toujours keepalive sur un upstream à fort trafic, avec proxy_http_version 1.1; et proxy_set_header Connection "";. Sans ça, Nginx ouvre une nouvelle connexion TCP vers le backend à chaque requête, ce qui ajoute une latence de handshake évitable sur chaque appel.

Maintenant que la charge est répartie entre plusieurs backends, la prochaine leçon revient à un sujet plus simple mais tout aussi important au quotidien : servir efficacement des fichiers statiques et gérer leur mise en cache.

Commandes & code

Load balancing : upstream et algorithmes

nginx
# upstream : groupe de serveurs backend, référencé ensuite dans proxy_pass
upstream backend_app {
    server 10.0.0.11:8000;
    server 10.0.0.12:8000;
    server 10.0.0.13:8000;
}

server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://backend_app;   # PAS de "http://" vers une IP : on référence l'upstream par son nom
        proxy_set_header Host $host;
    }
}
nginx
# Round robin (défaut, implicite) : requêtes distribuées séquentiellement
upstream backend_round_robin {
    server 10.0.0.11:8000;
    server 10.0.0.12:8000;
}

# Least connections : envoie au serveur ayant le MOINS de connexions actives (utile si temps de traitement variable)
upstream backend_least_conn {
    least_conn;
    server 10.0.0.11:8000;
    server 10.0.0.12:8000;
}

# IP hash : un même client (IP) revient toujours sur le même serveur (utile sans session partagée)
upstream backend_ip_hash {
    ip_hash;
    server 10.0.0.11:8000;
    server 10.0.0.12:8000;
}

# Weighted round robin : pondération manuelle (ex : un serveur plus puissant reçoit plus de trafic)
upstream backend_weighted {
    server 10.0.0.11:8000 weight=3;   # reçoit ~3x plus de requêtes que les autres
    server 10.0.0.12:8000 weight=1;
    server 10.0.0.13:8000 weight=1;
}
nginx
# Santé des backends : marquer un serveur down, backup, ou avec des seuils d'échec
upstream backend_resilient {
    server 10.0.0.11:8000 max_fails=3 fail_timeout=30s;  # exclu 30s après 3 échecs consécutifs
    server 10.0.0.12:8000 max_fails=3 fail_timeout=30s;
    server 10.0.0.13:8000 backup;                        # utilisé UNIQUEMENT si tous les autres sont down
    server 10.0.0.14:8000 down;                           # exclu manuellement (maintenance)
}
nginx
# keepalive vers les backends : réutilise les connexions TCP (évite un 3-way handshake par requête)
upstream backend_keepalive {
    server 10.0.0.11:8000;
    server 10.0.0.12:8000;
    keepalive 32;                # jusqu'à 32 connexions keepalive idle par worker
}

server {
    location / {
        proxy_pass http://backend_keepalive;
        proxy_http_version 1.1;              # requis pour le keepalive vers le backend
        proxy_set_header Connection "";      # vide "Connection" pour ne pas la fermer
    }
}

Résumé

  • upstream { } définit un pool de serveurs ; proxy_pass http://nom_upstream; (sans IP) l'utilise.
  • Algorithmes : round robin (défaut), least_conn (charge variable), ip_hash (sticky sans session store), weight= (pondération).
  • max_fails / fail_timeout gèrent la santé passive ; backup et down gèrent le failover manuel.
  • keepalive + proxy_http_version 1.1 évitent de recréer une connexion TCP par requête vers le backend : gain de perf notable.

Exercices pratiques

1 disponible
1

Mission : absorber un backend instable dans un pool de 3 instances

Objectif : Choisir le bon algorithme de répartition, configurer un failover automatique et activer le keepalive vers les backends.

Contexte

Trois instances FastAPI (10.0.0.11:8000, 10.0.0.12:8000, 10.0.0.13:8000) sont derrière un upstream en round robin par défaut. Le temps de traitement varie énormément d'une requête à l'autre (certains endpoints font des calculs lourds), et l'instance 10.0.0.13 tombe occasionnellement en erreur pendant quelques secondes.

Résoudre l’exercice →