Як зробити перезапуск backend непомітним для ваших відвідувачів
Два вебсервери все одно показують помилки, коли один перезапускається. Налаштування HAProxy, Varnish і keepalived, що роблять перезапуск непомітним, і як це протестувати.

Два вебсервери за балансувальником не означають, що перезапуск непомітний. Вони означають, що перезапуск дає коротку чергу помилок замість збою. Різниця між «коротка» і «жодної» живе у кількох налаштуваннях на edge і в тому, щоб протестувати їх реальним трафіком, перш ніж на них покладатися. Ось як ми закриваємо цей розрив.
Чому саме по собі резервування все одно показує помилки
Коли PHP-FPM або nginx перезапускається на одному вебвузлі, за ту секунду чи дві, що це триває, стаються три речі. Запити, що вже виконуються на цьому вузлі, обриваються. Нові запити продовжують надходити, бо балансувальник ще не помітив, що вузол зник. А коли вузол повертається, йому можуть віддати повну частку трафіку до того, як його кеші та opcode cache прогріються.
Балансувальник з типовими налаштуваннями не впорається елегантно з жодною з них. Його перевірка стану виконується кожні кілька секунд і потребує кількох невдач, перш ніж позначить вузол як недоступний, тож у цьому вікні він свідомо відправляє відвідувачів на сервер, який не відповідає. Кожен з цих запитів стає 502 або 503 на чиємусь екрані.
Повтор і redispatch на edge
Перше виправлення: дозволити HAProxy спробувати знову, деінде, коли з’єднання не вдається:
defaults
mode http
retries 3
option redispatch 1
retry-on conn-failure empty-response response-timeout
timeout connect 3s
retries дозволяє HAProxy повторити запит, з’єднання якого не вдалося. option redispatch 1 змушує повтор іти на інший сервер замість того, щоб довбати той, що щойно відмовив. retry-on розширює повтори за межі відхилених з’єднань на порожні відповіді та тайм-аути, тобто саме на те, що часто видає процес посеред перезапуску.
Повтори безпечні для запитів, які не були оброблені. Для запитів POST, які могли дійти до застосунку, HAProxy типово консервативний, і саме цього ви хочете: оформлення замовлення ніколи не має бути надіслане двічі.
Перевірки стану, що бачать застосунок, а не порт
Перевірка TCP проходить, поки nginx слухає, навіть коли кожен worker PHP заклинило. Перевіряйте щось, що задіює стек:
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
Endpoint /healthz має бути крихітним скриптом PHP, який торкається важливого (відкриває з’єднання з базою даних, читає один ключ кешу) і швидко повертається. fall 3 означає, що одна повільна перевірка під час ротації журналів або reload не виганяє здоровий вузол; rise 2 тримає вузол поза роботою, доки він не відповість двічі. slowstart поступово розганяє сервер, що повертається, тож вузлу з холодними кешами не віддають повну частку трафіку одразу.
Зливайте трафік перед перезапуском
Найчистіший перезапуск той, про який балансувальник знає заздалегідь. Runtime API HAProxy дозволяє вивести вузол з ротації, не обриваючи встановлені запити:
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
Загорніть це у скрипт, і кожен запланований перезапуск (оновлення безпеки PHP, зміна конфігурації, перезавантаження ядра) стає послідовністю: злити, зачекати, перезапустити, перевірити, повернути, по одному вузлу за раз.
Нехай кеш прикриє прогалину
Повносторінковий кеш робить більше, ніж прискорює анонімний трафік. З налаштованим grace Varnish продовжує віддавати останню добру копію сторінки, коли не може отримати свіжу:
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;
}
Поки backend здорові, об’єкти оновлюються за секунди після закінчення терміну. Коли кожен backend нездоровий, Varnish може віддавати об’єкти до доби після їхнього TTL. Читачі продовжують отримувати сторінки під час обслуговування бази даних або заклиненого пулу PHP, і лише шляхи авторизованих користувачів та оформлення замовлення, які за задумом оминають кеш, залежать від того, чи працюють backend.
Сам edge потребує партнера
Два вузли edge з keepalived ділять плаваючі адреси, а скрипт відстеження понижує edge, чий HAProxy зупинився:
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> }
}
Ми будуємо edge навхрест: кожен вузол є primary для однієї публічної адреси і standby для іншої, а запис DNS несе обидві. Обидва edge несуть трафік увесь час, і відмова переносить одну адресу на вцілілий за секунди.
Доведіть це трафіком, а не відчуттям
Перезапуск непомітний лише якщо ви спостерігали, як він є непомітним. Тест, який ми проводимо перед передачею, простий:
- Дайте на сайт стабільне навантаження ззовні, із сумішшю кешованих і некешованих URL. Невеликого генератора навантаження або циклу curl достатньо.
- Рахуйте відповіді за кодом стану на клієнті, з позначками часу.
- Перезапустіть PHP-FPM на одному вебвузлі. Потім на іншому. Потім відключіть один edge.
- Порівняйте підрахунок на стороні клієнта з журналами origin.
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
Origin зазвичай запише жменьку помилок під час кожного перезапуску: це і є прогалина, яку має поглинути edge. Важливе число на стороні клієнта. Коли retry, redispatch і grace працюють, лічильник відвідувачів залишається на нулі, тоді як origin показує свою коротку прогалину перезапуску. Якщо якісь помилки все ж доходять до клієнта, позначки часу кажуть, який рівень їх пропустив.
Ваш контрольний список
- HAProxy повторює з redispatch, а
retry-onпокриває порожні відповіді й тайм-аути. - Перевірка стану HTTP, яка торкається PHP і бази даних, з
fallіrise, налаштованими проти хитання. - Заплановані перезапуски, які спершу зливають трафік через runtime API.
- Grace у Varnish достатньо довгий, щоб покрити ваше найдовше обслуговування backend.
- Два edge з плаваючими адресами і скриптом відстеження, обидва несуть трафік.
- Тест перезапуску, проведений під навантаженням, із записаним числом помилок на стороні клієнта.
Якщо вам потрібна допомога з налаштуванням стека, доки його перезапуски не зникнуть, ми робимо це щотижня.
Related
- /what-we-do/data-web/high-availability-web-tiers/
- /what-we-do/data-web/object-caching/
- /what-we-do/operations/edge-traffic-control/
Опубліковано 27 серпня 2026 р..
Усі нотатки з практики