Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 621 minIntermédiaire

Répartition de charge : HAProxy, DNS round-robin et contrôles de santé

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • comparer le DNS round-robin et un répartiteur de charge ;
  • choisir un algorithme de répartition et des contrôles de santé ;
  • configurer HAProxy en HTTPS devant deux serveurs web ;
  • gérer les sessions et retirer un serveur sans coupure.

5 exercices corrigés1 schéma

La répartition de charge (load balancing) distribue les requêtes des clients entre plusieurs serveurs qui rendent le même service. Un répartiteur de charge (load balancer) choisit, pour chaque requête, un serveur en bonne santé : il augmente la capacité du service et retire automatiquement les serveurs en panne. Ce chapitre compare le DNS round-robin et les répartiteurs, présente les algorithmes, les contrôles de santé et la gestion des sessions, puis configure HAProxy pas à pas, jusqu'à la maintenance sans coupure.

Répartir la charge : deux objectifs

Un service répartit sa charge pour deux raisons, qu'il faut savoir distinguer à l'oral :

  • la capacité : quand un serveur ne suffit plus, on peut en acheter un plus gros (montée en charge verticale, limitée et coûteuse) ou en ajouter un second (montée en charge horizontale, presque sans limite). La répartition de charge rend la seconde possible ;
  • la disponibilité : le répartiteur vérifie en permanence l'état de chaque serveur et cesse d'envoyer des requêtes à celui qui ne répond plus. Le service survit à la panne d'un serveur, et on peut en arrêter un pour le mettre à jour sans couper personne.

Il existe trois grandes familles de solutions : la répartition par le DNS, le répartiteur réseau (niveau 4, qui voit des connexions TCP ou UDP) et le répartiteur applicatif (niveau 7, qui comprend le protocole, le plus souvent HTTP).

Le DNS round-robin

La solution la plus simple consiste à publier plusieurs enregistrements A pour le même nom. Dans une zone BIND :

text
intranet   300   IN   A   10.10.20.31
intranet   300   IN   A   10.10.20.32

Le serveur DNS renvoie les deux adresses, en changeant leur ordre à chaque réponse (c'est le round-robin), et la plupart des clients utilisent la première. La charge se répartit, sans aucun équipement supplémentaire. Le serveur DNS de Windows Server fait de même ; attention, il applique aussi par défaut le tri par masque de réseau (netmask ordering), qui place en tête l'adresse la plus proche du réseau du client et peut défaire la rotation.

Mais le DNS ne sait rien de l'état des serveurs :

DNS round-robinRépartiteur de charge
Serveur en panneSon adresse continue d'être distribuée : une partie des clients échoueRetiré du groupe en quelques secondes
Retrait pour maintenanceModifier la zone, puis attendre l'expiration des caches (la durée de vie, ici 300 s)Une commande, effet immédiat
RépartitionApproximative, dépend des caches et des clientsMaîtrisée par l'algorithme choisi
SessionsAucune gestionPersistance possible
CoûtNulUne ou deux machines de plus

Le DNS round-robin n'est donc pas une solution de haute disponibilité en soi. Il sert en complément : par exemple pour répartir les clients entre deux adresses virtuelles de répartiteurs, chacune elle-même protégée par VRRP.

Répartiteur de niveau 4 ou de niveau 7

Niveau 4 (transport)Niveau 7 (application)
Ce qu'il voitAdresses IP et portsLe contenu de la requête : URL, nom d'hôte, en-têtes, cookies
Ce qu'il faitTransmet chaque connexion TCP à un serveurReçoit la requête HTTP, choisit un serveur, lui retransmet la requête
ProtocolesTous ceux qui passent sur TCP ou UDP : HTTPS non déchiffré, LDAPS, SMTP, MariaDBHTTP et HTTPS (après déchiffrement)
PossibilitésRapide, simpleRoutage selon l'URL (/api vers un groupe, / vers un autre), persistance par cookie, ajout d'en-têtes, déchiffrement TLS centralisé
ExemplesHAProxy en mode tcp, LVS (IPVS) piloté par keepalivedHAProxy en mode http, Nginx, Traefik

Un répartiteur de niveau 7 est un proxy inverse (reverse proxy) : le client parle au répartiteur, et le répartiteur parle au serveur. Deux conséquences pratiques :

  • le certificat TLS est installé sur le répartiteur, qui déchiffre (on parle de terminaison TLS) ; il transmet ensuite en HTTP vers les serveurs, dans un VLAN serveurs protégé, ou rechiffre si la politique de sécurité l'exige. En niveau 4, le flux chiffré passe tel quel (TLS passthrough) et chaque serveur porte son certificat ;
  • l'adresse du client : les serveurs voient arriver toutes les requêtes depuis l'adresse du répartiteur. Pour garder l'adresse réelle dans les journaux, le répartiteur l'ajoute dans l'en-tête X-Forwarded-For, et le serveur web est configuré pour la lire.

Les algorithmes de répartition

Algorithme (mot-clé HAProxy)PrincipeQuand l'utiliser
Tourniquet (roundrobin)Chaque serveur à son tourRequêtes courtes et de durée comparable : le cas de la plupart des sites web
Tourniquet pondéré (roundrobin et weight)Un serveur de poids 3 reçoit trois fois plus de requêtes qu'un serveur de poids 1Serveurs de puissances différentes
Moins de connexions (leastconn)Le serveur qui a le moins de connexions en coursConnexions longues : bases de données, bureaux à distance, WebSocket
Hachage de l'adresse source (source)Un même client va toujours au même serveur, tant que le groupe ne change pasPersistance sans cookie, en niveau 4
Hachage de l'URL (uri)Une même URL va toujours au même serveurServeurs de cache
Premier disponible (first)Remplit le premier serveur jusqu'à sa limite, puis le suivantÉconomiser des serveurs (on éteint ceux qui ne servent pas)

Les contrôles de santé

Un contrôle de santé (health check) est une vérification que le répartiteur effectue régulièrement sur chaque serveur pour décider s'il peut recevoir des requêtes. C'est lui qui transforme une simple répartition en solution de haute disponibilité.

Type de contrôleCe qu'il vérifieCe qu'il rate
Connexion TCPLe port 80 du serveur accepte une connexionUne application qui répond par des erreurs 500
Requête HTTP simpleUne page répond avec le code 200Une application qui ne joint plus sa base de données, si la page testée n'en a pas besoin
Requête HTTP « profonde »Une page dédiée vérifie les dépendances (la base, le stockage) et répond 200 ou 503Rien d'important, mais attention : si la base tombe, tous les serveurs échouent ensemble et le service affiche une erreur du répartiteur

Trois paramètres règlent la réactivité, avec leurs valeurs dans HAProxy :

  • inter : l'intervalle entre deux contrôles (2 secondes par défaut) ;
  • fall : le nombre d'échecs consécutifs avant de retirer le serveur (3 par défaut) ;
  • rise : le nombre de succès consécutifs avant de le réintégrer (2 par défaut).

Avec inter 2s fall 3 rise 2, un serveur qui tombe est retiré au bout d'environ 6 secondes (3 contrôles ratés à 2 secondes d'intervalle), et réintégré 4 secondes après son retour. Pendant ces 6 secondes, une partie des requêtes échoue encore : HAProxy peut les renvoyer vers un autre serveur avec option redispatch et retries.

La page de santé. Sur un serveur Nginx, une page de santé simple tient en une ligne dans le bloc server :

text
location = /sante {
    access_log off;
    return 200 "OK\n";
}

Une page de santé profonde, en PHP, vérifie que l'application joint sa base de données :

php
<?php
// /var/www/intranet/sante.php : 200 si la base répond, 503 sinon
require '/etc/intranet/sante-config.php'; // définit $dsn, $utilisateur, $motDePasse (hors du site)
try {
    $pdo = new PDO($dsn, $utilisateur, $motDePasse, [PDO::ATTR_TIMEOUT => 2]);
    $pdo->query('SELECT 1');
    http_response_code(200);
    echo "OK\n";
} catch (Throwable $e) {
    http_response_code(503);
    echo "KO\n";
}

Le compte utilisé n'a besoin d'aucun droit sur les tables. Et la page ne doit rien afficher d'autre que OK ou KO : un message d'erreur détaillé donnerait des informations à un attaquant.

Les sessions : persistance ou serveurs sans état

Une application PHP range par défaut les sessions des utilisateurs dans des fichiers, sur le serveur qui les a créées. Si la première requête arrive sur WEB1 (connexion de l'utilisateur) et la suivante sur WEB2, WEB2 ne connaît pas la session : l'utilisateur est renvoyé à la page de connexion. Deux familles de solutions :

  1. La persistance (sticky sessions) : le répartiteur renvoie toujours un même utilisateur vers le même serveur. En niveau 7, il insère un cookie qui désigne le serveur ; en niveau 4, il utilise le hachage de l'adresse source. C'est simple, mais la répartition devient inégale (un gros client derrière une seule adresse NAT pèse sur un seul serveur), et les sessions du serveur qui tombe sont perdues : ses utilisateurs doivent se reconnecter.
  2. Des serveurs sans état (stateless) : les sessions sont rangées dans un stockage partagé (une base Redis, la base de données de l'application), et les fichiers déposés par les utilisateurs dans un stockage commun. N'importe quel serveur peut alors traiter n'importe quelle requête, et la panne d'un serveur ne déconnecte personne. C'est la bonne architecture, mais elle demande de configurer l'application.

En pratique, on combine souvent les deux : un cookie de persistance pour l'efficacité des caches, et des sessions partagées pour survivre aux pannes.

HAProxy pas à pas

HAProxy est le répartiteur libre de référence, en niveau 4 comme en niveau 7. Debian 13 fournit sa branche 3.0, maintenue à long terme. Sur LB1 et LB2 :

bash
apt install haproxy socat

Le fichier /etc/haproxy/haproxy.cfg de Ravelle Conseil, avec la VIP 10.10.20.30 partagée par keepalived (chapitre 5) :

text
global
    log /dev/log local0
    user haproxy
    group haproxy
    daemon
    stats socket /run/haproxy/admin.sock mode 660 level admin

defaults
    mode http
    log global
    option httplog
    option forwardfor
    option redispatch
    retries 2
    timeout connect 5s
    timeout client 30s
    timeout server 30s

frontend intranet
    bind 10.10.20.30:80
    bind 10.10.20.30:443 ssl crt /etc/haproxy/certs/intranet.pem
    http-request redirect scheme https unless { ssl_fc }
    default_backend web_intranet

backend web_intranet
    balance roundrobin
    option httpchk GET /sante
    http-check expect status 200
    cookie SRV insert indirect nocache
    default-server inter 2s fall 3 rise 2
    server web1 10.10.20.31:80 check cookie w1
    server web2 10.10.20.32:80 check cookie w2

listen stats
    bind :8404
    stats enable
    stats uri /stats
    stats refresh 10s
    stats auth admin:Remplace-Moi-Par-Une-Longue-Phrase
SectionRôle
globalLe processus : journalisation vers syslog, compte non privilégié, socket d'administration pour les commandes en direct
defaultsLes réglages communs : mode HTTP, ajout de X-Forwarded-For, nouvel essai sur un autre serveur en cas d'échec, délais d'expiration
frontend intranetCe qui est exposé aux clients : la VIP en HTTP et en HTTPS, la redirection de HTTP vers HTTPS, le groupe de serveurs par défaut
backend web_intranetLe groupe de serveurs : algorithme, contrôle de santé sur /sante, cookie de persistance, paramètres des contrôles, et les serveurs avec le mot-clé check
listen statsLa page de statistiques : état de chaque serveur, nombre de sessions, erreurs

Le fichier intranet.pem contient le certificat suivi de sa clé privée (lisible par root seul). Le mot-clé check est indispensable sur chaque ligne server : sans lui, HAProxy n'effectue aucun contrôle et continue d'envoyer des requêtes à un serveur mort. La page de statistiques ne doit être accessible que depuis le VLAN d'administration : filtre le port 8404 sur le pare-feu.

Vérifier, recharger, contrôler.

bash
haproxy -c -f /etc/haproxy/haproxy.cfg       # « Configuration file is valid »
systemctl reload haproxy                      # rechargement sans couper les connexions
echo "show servers state" | socat stdio /run/haproxy/admin.sock

Côté serveurs web, Nginx doit lire l'adresse réelle du client dans l'en-tête ajouté par les répartiteurs, et seulement quand la requête vient d'eux :

text
set_real_ip_from 10.10.20.28;
set_real_ip_from 10.10.20.29;
real_ip_header X-Forwarded-For;

Prouver que ça marche. Dépose sur chaque serveur une page qui.html qui affiche son nom, puis, depuis un poste :

bash
for i in 1 2 3 4; do curl -s https://intranet.ravelle.lan/qui.html; done
# web1 / web2 / web1 / web2 : curl n'envoie pas le cookie, le tourniquet s'applique

Arrête Nginx sur WEB2 : la page de statistiques le passe en rouge (DOWN) au bout de quelques secondes, et la boucle ne renvoie plus que web1, sans aucune erreur. Redémarre-le : il repasse en vert (UP) après deux contrôles réussis.

La maintenance sans coupure

Le répartiteur permet de mettre à jour les serveurs un par un, sans que les utilisateurs le voient. La socket d'administration accepte trois états :

  • drain : le serveur ne reçoit plus de nouveaux utilisateurs, mais termine les sessions en cours (y compris celles liées par le cookie) ;
  • maint : le serveur est retiré complètement ;
  • ready : il reprend du service.
bash
echo "set server web_intranet/web1 state drain" | socat stdio /run/haproxy/admin.sock
# attendre que les sessions en cours se terminent (page de statistiques)
echo "set server web_intranet/web1 state maint" | socat stdio /run/haproxy/admin.sock
# mettre à jour WEB1, redémarrer, tester directement http://10.10.20.31/sante
echo "set server web_intranet/web1 state ready" | socat stdio /run/haproxy/admin.sock

On recommence ensuite pour WEB2. Cette mise à jour progressive (rolling update) suppose que le serveur restant tienne seul la charge pendant l'opération : c'est la règle N+1 du chapitre 1.

Le répartiteur ne doit pas devenir le SPOF

Un répartiteur unique est un SPOF placé devant des serveurs redondants. On le double :

  • en actif-passif : deux répartiteurs identiques, une VIP partagée par keepalived. C'est l'architecture de Ravelle Conseil ;
  • en actif-actif : deux VIP, chacune active sur un répartiteur et en secours sur l'autre, et un DNS round-robin qui distribue les deux adresses. Les deux répartiteurs travaillent, et chacun peut tout reprendre.
Postes du VLAN 10intranet.ravelle.lan → 10.10.20.30VIPLB1 10.10.20.28maître VRRP, priorité 150LB2 10.10.20.29secours VRRP, priorité 130VRRPWEB1 10.10.20.31NginxWEB2 10.10.20.32NginxSRV-DB 10.10.20.35MariaDBchemins de LB2, utilisés dès qu’il devient maîtreChaque répartiteur interroge /sante sur WEB1 et WEB2 toutes les 2 secondes.
Deux répartiteurs HAProxy avec keepalived devant deux serveurs web : plus aucun SPOF dans le chemin web

Les deux répartiteurs doivent avoir la même configuration. Modifie-la sur LB1, vérifie-la avec haproxy -c, puis recopie-la sur LB2 (avec scp, un script ou un outil de gestion de configuration comme Ansible), vérifie et recharge. Un secours dont la configuration a trois mois de retard trahit le jour de la bascule.

Exercices

Exercice 1 sur 5 Choisir l'algorithme Réussi À revoir

Pour chaque service, choisis un algorithme de répartition et justifie.

  1. Un site web de pages courtes, sur trois serveurs identiques.
  2. Une ferme de serveurs de bureau à distance, où chaque connexion dure plusieurs heures.
  3. Deux serveurs web, l'un deux fois plus puissant que l'autre.
  4. Un service TCP non HTTP qui a besoin qu'un client retrouve toujours le même serveur.
Voir la réponseMasquer la réponse de l'exercice 1
  1. Tourniquet (roundrobin) : les requêtes sont courtes et comparables, chacun son tour suffit.
  2. Moins de connexions (leastconn) : le tourniquet ignorerait que certains serveurs portent déjà beaucoup de sessions longues.
  3. Tourniquet pondéré : weight 2 sur le serveur puissant, weight 1 sur l'autre. Sur 300 requêtes, il en reçoit 200 et l'autre 100.
  4. Hachage de l'adresse source (source) : en niveau 4, pas de cookie possible ; la même adresse source mène au même serveur, tant que le groupe ne change pas.

Tu avais trouvé ?

Exercice 2 sur 5 Combien de temps pour retirer un serveur ? Réussi À revoir

Un groupe HAProxy a les paramètres inter 3s fall 4 rise 3.

  1. Combien de temps faut-il environ pour qu'un serveur en panne soit retiré ?
  2. Combien de temps après son retour est-il réintégré ?
  3. Le responsable trouve ce délai trop long. Que proposes-tu, et quel est le risque ?
Voir la réponseMasquer la réponse de l'exercice 2
  1. 4 contrôles ratés à 3 secondes d'intervalle : environ 12 secondes.
  2. 3 succès à 3 secondes d'intervalle : environ 9 secondes.
  3. Réduire inter (2 secondes, voire 1) ou fall (2). Le risque : retirer un serveur simplement lent pendant un pic de charge, ce qui reporte sa charge sur les autres et peut les faire tomber à leur tour. On complète par option redispatch et retries, qui renvoient vers un autre serveur une requête qui a échoué, pendant que le contrôle de santé n'a pas encore conclu.

Tu avais trouvé ?

Exercice 3 sur 5 Déconnectés au hasard Réussi À revoir

Depuis la mise en place de deux serveurs web derrière HAProxy, les utilisateurs de l'intranet sont renvoyés à la page de connexion au hasard, plusieurs fois par heure. Le backend contient balance roundrobin et deux lignes server … check, rien d'autre.

  1. Explique la cause.
  2. Propose une correction rapide dans HAProxy, avec les lignes à ajouter.
  3. Quelle est la limite de cette correction, et quelle est la solution durable ?
Voir la réponseMasquer la réponse de l'exercice 3
  1. Les sessions PHP sont stockées en fichiers sur chaque serveur. Le tourniquet envoie les requêtes d'un même utilisateur tantôt à WEB1, tantôt à WEB2 : le serveur qui ne connaît pas la session renvoie à la page de connexion.
  2. Une persistance par cookie :
    text
    cookie SRV insert indirect nocache
    server web1 10.10.20.31:80 check cookie w1
    server web2 10.10.20.32:80 check cookie w2
    
  3. Si un serveur tombe, ses utilisateurs basculent sur l'autre, qui ne connaît pas leur session : ils doivent se reconnecter. La solution durable est de rendre les serveurs sans état : sessions dans un stockage partagé (Redis, ou la base de données de l'application), fichiers dans un stockage commun.

Tu avais trouvé ?

Exercice 4 sur 5 Trouver les erreurs Réussi À revoir

Un camarade te montre sa configuration et se plaint que « la haute disponibilité ne marche pas » : quand il arrête WEB2, un rechargement de page sur deux affiche une erreur 503.

text
frontend site
    bind 10.10.20.28:80
    default_backend web

backend web
    balance roundrobin
    option httpchk GET /Sante
    server web1 10.10.20.31:80 check
    server web2 10.10.20.32:80

Trouve trois problèmes, dont celui qui explique les erreurs.

Voir la réponseMasquer la réponse de l'exercice 4
  1. web2 n'a pas le mot-clé check : HAProxy ne le contrôle jamais et continue de lui envoyer une requête sur deux, d'où les erreurs quand il est arrêté. C'est la cause du symptôme.
  2. Le chemin /Sante avec une majuscule. Ici, web1 reste UP, puisqu'il reçoit une requête sur deux : une page répond donc aussi sous ce nom. Mais si la page de santé s'appelle /sante, comme partout dans ce cours, le contrôle de web1 échouera (404, Nginx distingue les majuscules) et web1 sera retiré alors qu'il fonctionne. Toutes les requêtes iraient alors à web2, et l'arrêt de WEB2 couperait tout le service. Le chemin du contrôle doit être exactement celui de la page.
  3. Le bind sur 10.10.20.28, l'adresse réelle de LB1, et pas sur la VIP 10.10.20.30 : si LB1 tombe, keepalived déplace la VIP vers LB2, mais les clients qui visaient .28 n'ont plus rien. Le service doit être publié sur la VIP.

Tu avais trouvé ?

Exercice 5 sur 5 Le DNS round-robin et la panne Réussi À revoir

Le nom intranet d'une petite structure pointe, en round-robin, vers deux serveurs web. Le serveur .32 tombe en panne et personne ne s'en aperçoit.

  1. Quelle proportion approximative des premières tentatives de connexion échoue ?
  2. Pourquoi supprimer l'enregistrement de .32 ne règle-t-il pas tout immédiatement ?
  3. Qu'apporterait un répartiteur à la place ?
Voir la réponseMasquer la réponse de l'exercice 5
  1. Environ une sur deux : le DNS continue de donner .32 en première position une fois sur deux. Certains navigateurs essaient ensuite la seconde adresse, après un délai, d'autres non.
  2. Les clients et les serveurs DNS intermédiaires ont gardé la réponse en cache pendant sa durée de vie (le TTL de l'enregistrement) : ils continuent d'utiliser .32 jusqu'à expiration.
  3. Un contrôle de santé qui retire .32 en quelques secondes, sans toucher au DNS ; un retrait propre pour maintenance (drain, maint) ; une répartition maîtrisée. À condition de doubler aussi le répartiteur.

Tu avais trouvé ?

Chapitre terminé !

Fais les 5 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Cluster, basculement et quorum : actif/passif, actif/actifRevoir le sommaire du cours