Como tornar um reinício do backend invisível para os seus visitantes
Dois web servers ainda mostram erros quando um reinicia. As definições de HAProxy, Varnish e keepalived que tornam um reinício invisível, e como o testar.

Ter dois web servers atrás de um load balancer não significa que um reinício seja invisível. Significa que um reinício produz uma breve rajada de erros em vez de uma interrupção. A diferença entre «breve» e «nenhuma» vive numas poucas definições no edge, e em testá-las com tráfego real antes de confiar nelas. É assim que fechamos esse intervalo.
Porque é que a redundância por si só ainda mostra erros
Quando o PHP-FPM ou o nginx reinicia num nó web, acontecem três coisas no segundo ou dois que isso demora. Os pedidos já em curso nesse nó são cortados. Continuam a chegar novos pedidos, porque o load balancer ainda não reparou que o nó desapareceu. E quando o nó volta, pode receber uma quota completa de tráfego antes de as suas caches e a sua opcode cache estarem quentes.
Um load balancer com definições por omissão não lida com nenhuma destas situações com elegância. O seu health check corre a cada poucos segundos e precisa de várias falhas antes de marcar um nó como down, pelo que, durante essa janela, está deliberadamente a enviar visitantes para um servidor que não responde. Cada um desses pedidos torna-se um 502 ou um 503 no ecrã de alguém.
Retry e redispatch no edge
A primeira correção é deixar o HAProxy tentar de novo, noutro sítio, quando uma ligação falha:
defaults
mode http
retries 3
option redispatch 1
retry-on conn-failure empty-response response-timeout
timeout connect 3s
retries permite ao HAProxy repetir um pedido cuja ligação falhou. option redispatch 1 faz com que a repetição vá para um servidor diferente em vez de martelar aquele que acabou de recusar. retry-on alarga as repetições para além das ligações recusadas, a respostas vazias e timeouts, que é exatamente o que um processo a meio de um reinício produz com frequência.
As repetições são seguras para pedidos que não foram processados. Para pedidos POST que possam ter chegado à aplicação, o HAProxy é conservador por omissão, e é isso que quer: um checkout nunca deve ser submetido duas vezes.
Health checks que veem a aplicação, não a porta
Uma verificação TCP passa enquanto o nginx estiver à escuta, mesmo quando todos os PHP workers estão bloqueados. Verifique algo que exercite a 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
O endpoint /healthz deve ser um script PHP minúsculo que toca no que importa (abre uma ligação à base de dados, lê uma chave de cache) e devolve rapidamente. fall 3 significa que uma verificação lenta durante uma rotação de logs ou um reload não expulsa um nó saudável. rise 2 mantém um nó fora até responder duas vezes. slowstart aumenta gradualmente a carga de um servidor que regressa, para que um nó com caches frias não receba de uma vez uma quota completa de tráfego.
Drene antes de reiniciar
O reinício mais limpo é aquele de que o load balancer sabe antecipadamente. A runtime API do HAProxy permite tirar um nó da rotação sem deixar cair os pedidos estabelecidos:
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
Embrulhe isso num script e cada reinício planeado (uma atualização de segurança do PHP, uma alteração de configuração, um reboot do kernel) torna-se drenar, esperar, reiniciar, verificar, devolver, um nó de cada vez.
Deixe a cache cobrir o intervalo
Uma full page cache faz mais do que acelerar o tráfego anónimo. Com o grace configurado, o Varnish continua a servir a última cópia boa de uma página quando não consegue obter uma 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;
}
Enquanto os backends estão saudáveis, os objetos são renovados segundos depois de expirarem. Quando todos os backends estão em falha, o Varnish pode servir objetos até um dia para lá do seu TTL. Os leitores continuam a receber páginas durante uma manutenção da base de dados ou um PHP pool bloqueado, e os percursos de utilizadores autenticados e de checkout, que contornam a cache por desenho, são os únicos que dependem de os backends estarem em cima.
O próprio edge precisa de um parceiro
Dois nós de edge a correr keepalived partilham endereços flutuantes, e um tracking script despromove um edge cujo HAProxy parou:
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> }
}
Construímos os edges em cruz: cada nó é primary para um endereço público e standby para o outro, com o registo DNS a transportar ambos. Os dois edges transportam tráfego o tempo todo, e uma falha move um endereço para o sobrevivente em segundos.
Prove-o com tráfego, não com uma sensação
Um reinício só é invisível se o tiver visto ser invisível. O teste que executamos antes da entrega é simples:
- Coloque carga constante no site a partir de fora, com uma mistura de URLs em cache e fora de cache. Um pequeno gerador de carga ou um ciclo de curl chega.
- Conte as respostas por código de estado no cliente, com marca temporal.
- Reinicie o PHP-FPM num nó web. Depois no outro. Depois derrube um edge.
- Compare a contagem do lado do cliente com os logs da origem.
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
A origem registará habitualmente um punhado de erros durante cada reinício: esse é o intervalo que o edge existe para absorver. O que importa é o número do lado do cliente. Quando retry, redispatch e grace estão a funcionar, a contagem do visitante fica em nada enquanto a origem mostra o seu breve intervalo de reinício. Se alguns erros chegarem ao cliente, as marcas temporais dizem-lhe que camada os deixou passar.
A sua checklist
- O HAProxy repete com redispatch, e
retry-oncobre respostas vazias e timeouts. - Um health check HTTP que toca no PHP e na base de dados, com
falleriseafinados contra o flapping. - Reinícios planeados que drenam primeiro, através da runtime API.
- Varnish grace suficientemente longo para cobrir a manutenção de backend mais longa que faz.
- Dois edges com endereços flutuantes e um tracking script, ambos a transportar tráfego.
- Um teste de reinício executado sob carga, com a contagem de erros do lado do cliente anotada.
Se quiser ajuda para afinar uma stack até os seus reinícios desaparecerem, fazemos isto todas as semanas.
CTA
Related
Relacionados: Camadas web de alta disponibilidade, N+1 em cada nível · Object caching com failover automático · Controlo de tráfego no edge para crawlers e scrapers
Publicado em 27 de agosto de 2026.
Todas as notas de campo