Rendere invisibile ai vostri visitatori il riavvio di un backend
Due web server mostrano ancora errori quando uno si riavvia. Le impostazioni di HAProxy, Varnish e keepalived che rendono un riavvio invisibile, e come testarle.

Avere due web server dietro un load balancer non significa che un riavvio sia invisibile. Significa che un riavvio produce una breve raffica di errori invece di un'interruzione. La differenza tra «breve» e «nessuna» vive in poche impostazioni al bordo, e nel testarle con traffico reale prima di farci affidamento. Ecco come chiudiamo quel divario.
Perché la sola ridondanza mostra ancora errori
Quando PHP-FPM o nginx si riavvia su un nodo web, nel secondo o due che impiega succedono tre cose. Le richieste già in volo su quel nodo vengono troncate. Nuove richieste continuano ad arrivare, perché il load balancer non si è ancora accorto che il nodo è sparito. E quando il nodo torna, può ricevere una quota piena di traffico prima che le sue cache e la sua opcode cache siano calde.
Un load balancer con le impostazioni predefinite non gestisce con grazia nessuna di queste cose. Il suo health check gira ogni pochi secondi e ha bisogno di più fallimenti prima di marcare un nodo come down, quindi per quella finestra manda deliberatamente i visitatori a un server che non risponde. Ognuna di quelle richieste diventa un 502 o un 503 sullo schermo di qualcuno.
Retry e redispatch al bordo
La prima correzione è permettere a HAProxy di riprovare, altrove, quando una connessione fallisce:
defaults
mode http
retries 3
option redispatch 1
retry-on conn-failure empty-response response-timeout
timeout connect 3s
retries permette a HAProxy di riprovare una richiesta la cui connessione è fallita. option redispatch 1 fa andare il nuovo tentativo a un server diverso invece di martellare quello che ha appena rifiutato. retry-on estende i tentativi oltre le connessioni rifiutate alle risposte vuote e ai timeout, che è ciò che spesso produce un processo a metà riavvio.
I retry sono sicuri per le richieste che non sono state elaborate. Per le richieste POST che potrebbero aver raggiunto l'applicazione, HAProxy è conservativo per default, ed è ciò che volete: un checkout non deve mai essere inviato due volte.
Health check che vedono l'applicazione, non la porta
Un controllo TCP passa finché nginx è in ascolto, anche quando ogni worker PHP è bloccato. Controllate qualcosa che esercita lo 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
L'endpoint /healthz dovrebbe essere un minuscolo script PHP che tocca ciò che conta (apre una connessione al database, legge una chiave di cache) e risponde in fretta. fall 3 significa che un controllo lento durante una rotazione dei log o un reload non espelle un nodo sano; rise 2 tiene un nodo fuori finché non risponde due volte. slowstart riporta gradualmente in carico un server che rientra, così un nodo con le cache fredde non riceve tutta la sua quota di traffico in una volta.
Drenate prima di riavviare
Il riavvio più pulito è quello di cui il load balancer sa in anticipo. L'API di runtime di HAProxy vi permette di togliere un nodo dalla rotazione senza interrompere le richieste stabilite:
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
Avvolgetelo in uno script e ogni riavvio pianificato (un aggiornamento di sicurezza di PHP, un cambio di configurazione, un riavvio del kernel) diventa drena, aspetta, riavvia, verifica, rimetti, un nodo alla volta.
Lasciate che la cache copra il vuoto
Una cache di pagina completa fa più che accelerare il traffico anonimo. Con la grace configurata, Varnish continua a servire l'ultima copia buona di una pagina quando non riesce a recuperarne una fresca:
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;
}
Finché i backend sono sani, gli oggetti vengono aggiornati entro pochi secondi dalla scadenza. Quando ogni backend è malsano, Varnish può servire oggetti fino a un giorno oltre il loro TTL. I lettori continuano a ricevere pagine durante una manutenzione del database o con un pool PHP bloccato, e i percorsi autenticati e di checkout, che aggirano la cache per progetto, sono gli unici a dipendere dal fatto che i backend siano su.
Anche il bordo ha bisogno di un partner
Due nodi edge con keepalived condividono indirizzi flottanti, e uno script di tracking retrocede un edge il cui HAProxy si è fermato:
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> }
}
Costruiamo gli edge incrociati: ogni nodo è primario per un indirizzo pubblico e standby per l'altro, con il record DNS che li porta entrambi. Entrambi gli edge portano traffico tutto il tempo, e un guasto sposta un indirizzo sul superstite in pochi secondi.
Dimostratelo con il traffico, non con una sensazione
Un riavvio è invisibile solo se lo avete visto essere invisibile. Il test che eseguiamo prima della consegna è semplice:
- Mettete carico costante sul sito dall'esterno, con un mix di URL in cache e non. Un piccolo generatore di carico o un ciclo di curl bastano.
- Contate le risposte per codice di stato lato client, con timestamp.
- Riavviate PHP-FPM su un nodo web. Poi sull'altro. Poi fate cadere un edge.
- Confrontate il conteggio lato client con i log di 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 registrerà di solito una manciata di errori durante ogni riavvio: è il vuoto che il bordo deve assorbire. Ciò che conta è il numero lato client. Quando retry, redispatch e grace funzionano, il conteggio dei visitatori resta a zero mentre l'origine mostra il suo breve vuoto da riavvio. Se qualche errore raggiunge il client, i timestamp vi dicono quale livello li ha lasciati passare.
La vostra checklist
- HAProxy riprova con redispatch, e
retry-oncopre risposte vuote e timeout. - Un health check HTTP che tocca PHP e il database, con
fallerisetarati contro il flapping. - Riavvii pianificati che drenano prima, tramite l'API di runtime.
- Una grace di Varnish abbastanza lunga da coprire la vostra manutenzione di backend più lunga.
- Due edge con indirizzi flottanti e uno script di tracking, entrambi con traffico.
- Un test di riavvio eseguito sotto carico, con il conteggio degli errori lato client annotato.
Se volete aiuto per tarare uno stack finché i suoi riavvii non spariscono, lo facciamo ogni settimana.
Related
- /what-we-do/data-web/high-availability-web-tiers/
- /what-we-do/data-web/object-caching/
- /what-we-do/operations/edge-traffic-control/
Pubblicato il 27 agosto 2026.
Tutte le note dal campo