Rendre un redémarrage de backend invisible pour vos visiteurs
Deux serveurs web affichent encore des erreurs quand l'un redémarre. Les réglages HAProxy, Varnish et keepalived qui rendent un redémarrage invisible, et comment le tester.

Avoir deux serveurs web derrière un load balancer ne veut pas dire qu'un redémarrage est invisible. Cela veut dire qu'un redémarrage produit une courte rafale d'erreurs au lieu d'une interruption. La différence entre « courte » et « aucune » tient à quelques réglages en edge, et au fait de les tester avec du trafic réel avant de s'y fier. Voici comment nous refermons cet écart.
Pourquoi la redondance seule montre encore des erreurs
Quand PHP-FPM ou nginx redémarre sur un nœud web, trois choses se passent pendant la seconde ou les deux secondes que cela prend. Les requêtes déjà en cours sur ce nœud sont coupées. De nouvelles requêtes continuent d'arriver, parce que le load balancer n'a pas encore remarqué que le nœud a disparu. Et quand le nœud revient, il peut recevoir une part entière du trafic avant que ses caches et son cache d'opcodes soient chauds.
Un load balancer avec ses réglages par défaut ne gère aucun de ces cas avec élégance. Son contrôle de santé tourne toutes les quelques secondes et a besoin de plusieurs échecs avant de marquer un nœud comme down, donc pendant cette fenêtre il envoie délibérément des visiteurs vers un serveur qui ne répond pas. Chacune de ces requêtes devient un 502 ou un 503 sur l'écran de quelqu'un.
Retry et redispatch en edge
La première correction consiste à laisser HAProxy réessayer, ailleurs, quand une connexion échoue :
defaults
mode http
retries 3
option redispatch 1
retry-on conn-failure empty-response response-timeout
timeout connect 3s
retries permet à HAProxy de réessayer une requête dont la connexion a échoué. option redispatch 1 fait partir la nouvelle tentative vers un autre serveur au lieu de marteler celui qui vient de refuser. retry-on étend les nouvelles tentatives au-delà des connexions refusées, aux réponses vides et aux timeouts, ce qu'un processus en plein redémarrage produit souvent.
Les nouvelles tentatives sont sûres pour les requêtes qui n'ont pas été traitées. Pour les requêtes POST qui ont pu atteindre l'application, HAProxy est prudent par défaut, et c'est ce que vous voulez : un checkout ne doit jamais être soumis deux fois.
Des contrôles de santé qui voient l'application, pas le port
Un contrôle TCP passe tant que nginx écoute, même quand chaque worker PHP est bloqué. Contrôlez quelque chose qui sollicite la stack :
backend be_web
option httpchk GET /healthz
http-check expect status 200
default-server inter 2s fall 3 rise 2 slowstart 20s
server web1 <web1>:80 check
server web2 <web2>:80 check
Le endpoint /healthz doit être un minuscule script PHP qui touche ce qui compte (ouvre une connexion à la base de données, lit une clé de cache) et répond vite. fall 3 signifie qu'un contrôle lent pendant une rotation de logs ou un reload n'évince pas un nœud sain. rise 2 garde un nœud à l'écart jusqu'à ce qu'il réponde deux fois. slowstart remonte progressivement un serveur qui revient, pour qu'un nœud aux caches froids ne reçoive pas d'un coup une part entière du trafic.
Vider avant de redémarrer
Le redémarrage le plus propre est celui que le load balancer connaît à l'avance. L'API runtime de HAProxy vous permet de sortir un nœud de la rotation sans couper les requêtes établies :
echo "set server be_web/web1 state drain" | socat stdio /run/haproxy/admin.sock
# wait for current sessions to finish
echo "show servers state be_web" | socat stdio /run/haproxy/admin.sock
systemctl restart php-fpm # on web1
echo "set server be_web/web1 state ready" | socat stdio /run/haproxy/admin.sock
Enveloppez cela dans un script et chaque redémarrage planifié (une mise à jour de sécurité PHP, un changement de configuration, un redémarrage de noyau) devient : vider, attendre, redémarrer, vérifier, remettre en service, un nœud à la fois.
Laisser le cache couvrir le trou
Un cache de page complète fait plus qu'accélérer le trafic anonyme. Avec le grace configuré, Varnish continue de servir la dernière bonne copie d'une page quand il ne peut pas en récupérer une fraîche :
import std;
sub vcl_recv {
if (std.healthy(req.backend_hint)) {
set req.grace = 10s;
}
}
sub vcl_backend_response {
set beresp.ttl = 5m;
set beresp.grace = 24h;
}
Tant que les backends sont sains, les objets sont rafraîchis quelques secondes après leur expiration. Quand tous les backends sont en mauvaise santé, Varnish peut servir des objets jusqu'à un jour après leur TTL. Les lecteurs continuent de recevoir des pages pendant une maintenance de base de données ou un pool PHP bloqué, et les parcours connectés et de checkout, qui contournent le cache par conception, sont les seuls à dépendre du fait que les backends soient en ligne.
L'edge lui-même a besoin d'un partenaire
Deux nœuds edge qui font tourner keepalived partagent des adresses flottantes, et un script de suivi rétrograde un edge dont le HAProxy s'est arrêté :
vrrp_script chk_haproxy {
script "/usr/sbin/pidof haproxy"
interval 2
fall 2
rise 2
}
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 100
track_script { chk_haproxy }
virtual_ipaddress { <floating address>/<prefix> }
}
Nous construisons les edges en croisé : chaque nœud est primaire pour une adresse publique et secours pour l'autre, l'enregistrement DNS portant les deux. Les deux edges transportent du trafic en permanence, et une panne déplace une adresse vers le survivant en quelques secondes.
Le prouver avec du trafic, pas avec une impression
Un redémarrage n'est invisible que si vous l'avez vu être invisible. Le test que nous lançons avant la remise est simple :
- Mettez une charge régulière sur le site depuis l'extérieur, avec un mélange d'URL en cache et hors cache. Un petit générateur de charge ou une boucle curl suffit.
- Comptez les réponses par code de statut côté client, horodatées.
- Redémarrez PHP-FPM sur un nœud web. Puis sur l'autre. Puis faites tomber un edge.
- Comparez le décompte côté client avec les logs de l'origine.
while true; do
printf '%s ' "$(date +%T.%N)"
curl -s -o /dev/null -w '%{http_code} %{time_total}\n' https://www.example.com/
sleep 0.2
done | tee restart-test.log
grep -vc ' 200 ' restart-test.log
L'origine journalisera en général une poignée d'erreurs à chaque redémarrage : c'est le trou que l'edge est censé absorber. Ce qui compte, c'est le nombre côté client. Quand retry, redispatch et grace fonctionnent, le compteur du visiteur reste à rien pendant que l'origine montre sa brève coupure de redémarrage. Si des erreurs atteignent tout de même le client, les horodatages vous disent quelle couche les a laissées passer.
Votre checklist
- HAProxy réessaie avec redispatch, et
retry-oncouvre les réponses vides et les timeouts. - Un contrôle de santé HTTP qui touche PHP et la base de données, avec
falletriseréglés contre le flapping. - Des redémarrages planifiés qui vident d'abord, via l'API runtime.
- Un grace Varnish assez long pour couvrir votre plus longue maintenance de backend.
- Deux edges avec des adresses flottantes et un script de suivi, tous deux en trafic.
- Un test de redémarrage sous charge, avec le nombre d'erreurs côté client noté.
Si vous souhaitez de l'aide pour régler une stack jusqu'à ce que ses redémarrages disparaissent, nous le faisons chaque semaine.
CTA
Related
Articles liés : Tiers web haute disponibilité, N+1 à chaque couche · Cache d'objets avec failover automatique · Contrôle du trafic en edge pour crawlers et scrapers
Publié le 27 août 2026.
Toutes les notes de terrain