Зачем вообще нужны health-check и graceful failover, если балансировщик уже есть
Балансировщик сам по себе не гарантирует устойчивость. Он распределяет трафик, но без корректных health-check может продолжать слать запросы на «живой по TCP, но мёртвый по приложению» бэкенд. А без graceful-подхода при переключениях вы ловите обрывы долгих запросов, всплески 5xx, лавинообразные ретраи и шторм подключений после рестарта (иногда это выглядит как само-DDoS соседних узлов).
В проде обычно нужно одновременно закрыть три задачи:
- Быстро обнаруживать деградацию (health-check) и выводить больной узел из ротации.
- Переключаться предсказуемо (failover), не создавая каскадных отказов.
- Уметь выводить узел на обслуживание (drain) так, чтобы новые соединения на него не шли, а старые успели завершиться (graceful shutdown).
Ниже — практический разбор для двух самых частых схем: Nginx upstream и HAProxy, включая active-passive, weights, slow start, и ключевые параметры вроде max_fails/fail_timeout и rise/fall.
Базовые термины: чтобы не перепутать check, retry и failover
Health-check: L4 vs L7
L4 checks — проверки на транспортном уровне (обычно TCP connect). Они быстрые и дешёвые, но не видят проблем приложения: процесс может слушать порт, но отдавать 500, зависнуть в GC или ждать внешнюю зависимость.
L7 checks — HTTP(S) запросы до конкретного URI и анализ ответа (код, заголовки, иногда тело). Они точнее, но дороже: создают нагрузку и требуют нормального «здорового» endpoint’а, который не завязан на тяжёлые зависимости.
Практика: для веба почти всегда нужен L7 health-check хотя бы уровня «HTTP 200 от /healthz». L4 полезен для TCP-сервисов и как самый базовый сигнал доступности, но для HTTP его часто недостаточно.
Failover: active-active vs active-passive
Active-active — несколько бэкендов одновременно получают трафик. Это даёт суммарную производительность и обычно улучшает отказоустойчивость, но требует дисциплины: идемпотентность, корректные таймауты, общие сессии/кэш, отсутствие «липких» локальных артефактов.
Active-passive — один сервер обслуживает почти весь трафик, второй включается только при отказе. Это проще для stateful-сервисов, но возврат «основного» узла и его прогрев нужно продумывать, иначе переключения будут заметны пользователю.
Graceful shutdown и drain
Graceful shutdown — процесс перестаёт принимать новые подключения, но не рвёт существующие, давая им завершиться в пределах таймаутов.
Drain — режим на стороне балансировщика: новые соединения на выбранный бэкенд не направляются, а старые «доживают». В HAProxy это делается штатно (disable server / set server state), в Nginx чаще через перезагрузку конфигурации и/или внешнюю оркестрацию.

Nginx upstream: что реально умеет и где ограничения
Nginx в классической open source-версии не имеет полноценного активного HTTP health-check «из коробки» (есть коммерческие возможности, сторонние модули или внешний мониторинг). Зато у него есть важные базовые механизмы: пассивные проверки через ошибки соединения, управление распределением трафика и failover на уровне запроса.
Пассивный health-check в Nginx: max_fails и fail_timeout
Самый распространённый вариант — использовать max_fails и fail_timeout в upstream. Логика: если Nginx при проксировании получил N ошибок подряд на конкретном сервере, он помечает его «неподходящим» на период fail_timeout.
Нюанс: это пассивный механизм — он срабатывает только на реальных запросах пользователей. Если трафика мало, реакция на сбой будет медленнее.
upstream app_backend {
zone app_backend 64k;
server 10.0.0.11:8080 weight=10 max_fails=3 fail_timeout=10s;
server 10.0.0.12:8080 weight=10 max_fails=3 fail_timeout=10s;
keepalive 64;
}
server {
listen 80;
location / {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_connect_timeout 2s;
proxy_read_timeout 30s;
proxy_send_timeout 30s;
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 2;
proxy_pass http://app_backend;
}
}
Что здесь важно:
max_fails— сколько «фейлов» допускаем (ошибки коннекта/таймаута и часть ошибок ответа, в зависимости от условий).fail_timeout— окно, в котором считаются фейлы, и период «бана» после превышения.proxy_next_upstream— когда Nginx имеет право попробовать другой upstream (по сути, failover на уровне конкретного запроса).proxy_next_upstream_tries— ограничение числа попыток, чтобы не устроить ретрай-шторм.
Почему ретраи могут сделать хуже
Ретраи полезны при кратковременных сбоях, но опасны при деградации. Если бэкенд начал отвечать медленно, часть запросов упадёт по таймауту, Nginx ретраит на другой узел, нагрузка растёт, второй узел тоже деградирует — и вы получаете каскад.
Практический подход:
- Делайте ретраи только для идемпотентных запросов (обычно GET/HEAD) или там, где вы уверены в корректности повторов.
- Держите малое
proxy_next_upstream_tries(обычно 1–2). - Согласуйте таймауты: слишком маленькие дают ложный failover, слишком большие — долгое «зависание» на больном узле.
Active-passive в Nginx: backup и веса
Для схемы active-passive в Nginx есть директива backup: «запасной» сервер используется только если основные недоступны.
upstream api_upstream {
zone api_upstream 64k;
server 10.0.0.21:8080 max_fails=2 fail_timeout=5s;
server 10.0.0.22:8080 max_fails=2 fail_timeout=5s;
server 10.0.0.23:8080 backup;
}
Если «поиграться весами» (например 100 и 1), это уже active-active, пусть и с перекосом: небольшой трафик всё равно будет уходить на вторую ноду. Для строгого active-passive используйте именно backup.
Slow start в Nginx: что делать, если он нужен
Slow start — это «не заливать трафиком только что вернувшийся узел». В Nginx OSS полноценного slow start для upstream’ов штатно нет, поэтому обычно применяют одну из практик ниже:
- Постепенное включение через веса: сначала
weight=1, затемweight=5, затемweight=10с перезагрузкой конфигурации (reload). Это требует процедуры или автоматизации. - Readiness на стороне приложения: сервис при старте не должен считаться готовым, пока не прогреты кэши/пулы/миграции. Тогда вероятность «встретить» холодный узел минимальна.
- Внешний балансировщик (часто HAProxy) перед Nginx, если нужен активный L7 health-check и slow start в одном месте.
Если вы держите Nginx как фронтовой прокси для нескольких сайтов, под такие задачи отлично подходит виртуальный хостинг. А если нужна своя схема балансировки/VRRP/несколько сетевых интерфейсов и гибкая обвязка под инфраструктуру — чаще удобнее VDS.
Graceful reload Nginx vs graceful shutdown бэкенда
Частая ошибка — ожидать, что graceful reload Nginx автоматически решит «мягкое обслуживание» бэкенда. Reload Nginx действительно аккуратно перезапускает воркеры, не рвя текущие соединения с клиентами. Но соединения к upstream’ам и вывод бэкенда из ротации зависят от keepalive, таймаутов и поведения самого приложения.
Если вы обновляете бэкенд (например, systemctl stop), убедитесь, что он:
- перестаёт принимать новые соединения (или начинает отвечать 503 на readiness),
- даёт текущим запросам завершиться (graceful shutdown timeout),
- корректно закрывает keep-alive.
HAProxy: когда нужен «правильный» check, slowstart и управляемый drain
HAProxy часто выбирают как центральный балансировщик именно из-за сильных health-check’ов и управляемых режимов серверов. Здесь проще построить предсказуемый failover и обслуживание без сюрпризов, особенно при частых деплоях.
L4 и L7 checks в конфигурации HAProxy
Пример L4 (TCP) проверки:
backend be_tcp
mode tcp
balance roundrobin
option tcp-check
default-server inter 2s fall 3 rise 2
server s1 10.0.0.31:5432 check
server s2 10.0.0.32:5432 check backup
Ключевые параметры:
inter— интервал проверок.fall— сколько провалов подряд нужно, чтобы считать сервер DOWN.rise— сколько успехов подряд нужно для возврата в UP.
Теперь L7 (HTTP) проверка на конкретный endpoint:
backend be_http
mode http
balance roundrobin
option httpchk GET /healthz
http-check expect status 200
default-server inter 2s fall 3 rise 2
server app1 10.0.0.41:8080 check
server app2 10.0.0.42:8080 check
Это уже «настоящий» health-check: HAProxy сам опрашивает /healthz и решает, держать ли ноду в пуле.
Active-passive в HAProxy: backup
Для active-passive обычно используют backup. Основные сервера получают трафик, а backup — только если основные DOWN.
backend be_api
mode http
balance roundrobin
option httpchk GET /healthz
http-check expect status 200
default-server inter 2s fall 2 rise 3
server primary 10.0.0.51:8080 check
server standby 10.0.0.52:8080 check backup
Weights: неравномерное распределение нагрузки
Weights полезны, когда узлы разной мощности или когда вы вводите новый сервер постепенно. В HAProxy вес задаётся через weight.
backend be_weighted
mode http
balance roundrobin
option httpchk GET /healthz
http-check expect status 200
server a 10.0.0.61:8080 check weight 80
server b 10.0.0.62:8080 check weight 20
Схема остаётся active-active, но вы контролируемо смещаете нагрузку.
Slow start в HAProxy: slowstart
slowstart помогает в типовой ситуации: сервер вернулся в UP, но кэши холодные и пулы не прогреты. Если сразу дать полный трафик, он снова начнёт ошибаться. Slow start наращивает долю трафика постепенно.
backend be_slowstart
mode http
balance roundrobin
option httpchk GET /healthz
http-check expect status 200
default-server inter 2s fall 3 rise 2 slowstart 30s
server app1 10.0.0.71:8080 check
server app2 10.0.0.72:8080 check
После перехода в UP сервер в течение 30 секунд будет получать трафик по нарастающей.

Drain и graceful shutdown: как выводить сервер без обрывов
Для обслуживания (деплой, обновление пакетов, миграции) безопасный путь такой: вывести сервер в drain, дождаться завершения активных сессий, и только потом останавливать сервис.
На уровне HAProxy это делают через админ-сокет. Конкретный путь к сокету и права зависят от вашей установки.
echo "show servers state" | socat stdio /run/haproxy/admin.sock
echo "disable server be_http/app1" | socat stdio /run/haproxy/admin.sock
После disable server новые соединения на app1 не пойдут, а старые будут «доживать» (насколько позволяет режим и таймауты).
Дальше полезно мониторить текущие сессии, чтобы понять, когда можно безопасно останавливать приложение:
echo "show stat" | socat stdio /run/haproxy/admin.sock
Нюанс: «graceful» зависит не только от HAProxy. Если у приложения маленький таймаут graceful shutdown или оно жёстко закрывает keep-alive, часть клиентов всё равно увидит ошибки. Балансировщик лишь создаёт условия для мягкого вывода из ротации.
Тюнинг порогов: max_fails/fail_timeout vs rise/fall
Одна из самых частых причин «флаппинга» (сервер то DOWN, то UP) — слишком агрессивные пороги. В итоге балансировщик переключается из-за микропотерь сети или кратковременных лагов (GC/IO), и вы теряете стабильность.
Практическая логика выбора:
- Если сервис редко даёт краткие лаги, можно делать быстрее fail (меньше
fallили меньшеmax_fails) и медленнее recovery (большеrise). - Если у вас бывают короткие деградации (пики CPU, ротации логов, фоновые джобы), лучше увеличить окна и пороги, иначе получите флаппинг.
- Для критичных API добавляйте slowstart при возврате и ограничивайте ретраи на прокси.
Если бэкенд зависит от базы, полезно отдельно продумать и отказоустойчивость самой БД. Для PostgreSQL может пригодиться материал про PITR и восстановление из WAL, чтобы плановые переключения не превращались в долгий простой.
Типовые рецепты под реальные сценарии
1) Один активный и один пассивный для монолита или stateful API
Цель: минимизировать вариативность и упростить состояние (сессии, локальные файлы), но иметь быстрый failover.
- Nginx: основной upstream +
backup, пассивныеmax_fails/fail_timeout. - HAProxy:
check+backup, лучше L7 checks на/healthz, чтобы не переключаться на «полуживой» сервис.
2) Active-active с разной мощностью серверов
Цель: дать более мощному серверу больше трафика (weights), но сохранить отказоустойчивость.
- HAProxy:
weight+slowstartпри возврате. - Nginx:
weightвозможен, но учитывайте, что health-check пассивный и ретраи могут усиливать деградацию.
3) Плановое обслуживание без 5xx
Цель: вывести узел из ротации, дождаться завершения активных запросов, затем рестартовать.
- HAProxy: drain через
disable server, ожидание нулевых сессий, обновление, возврат черезenable server, дальше своё сделаетslowstart. - Nginx: чаще это делается оркестратором (вывод инстанса из внешнего LB) или изменением upstream + reload; критично иметь readiness на приложении, чтобы не принимать трафик во время прогрева.
Чек-лист: что проверить, прежде чем считать failover готовым
- Health endpoint: отдельный URI, быстрый, без тяжёлых внешних зависимостей; чёткие коды ответа.
- Таймауты: connect/read/send на прокси и на приложении согласованы; нет ситуации, когда прокси ждёт 30s, а приложение убивает запрос через 10s (или наоборот).
- Ретраи: ограничены по числу; включены только там, где безопасно повторять.
- Slow start: есть (в HAProxy) или компенсирован процедурой прогрева/весами (в Nginx).
- Drain: есть понятная процедура обслуживания, и она реально протестирована на стенде.
- Наблюдаемость: метрики 4xx/5xx, latency, активные соединения, события UP/DOWN и флаппинг.
Частые ошибки и как их избежать
Ошибка: проверять «главную страницу» вместо /healthz
Главная страница может зависеть от БД, внешних API или тяжёлых шаблонов. В итоге вы выключаете ноду из пула из-за частичной деградации, хотя сервис мог бы продолжать отдавать «упрощённые» ответы. Лучше иметь отдельный endpoint, который отражает именно готовность принимать трафик.
Ошибка: слишком короткий fail_timeout или слишком маленькие fall/max_fails
Это вызывает флаппинг, особенно при сетевых микропотерях и редких пиках latency. Делайте пороги и окна не «по умолчанию», а под реальную статистику задержек и ошибок.
Ошибка: не учитывать keep-alive и долгие запросы
Если у вас бывают долгие ответы (стриминг, отчёты, большие выгрузки), «мягкое» обслуживание должно учитывать максимальную длительность. Иначе вы вывели ноду в drain, но через 30 секунд убили сервис — и всё равно порвали активные запросы.
Итог: когда выбрать Nginx, а когда HAProxy
Nginx upstream подходит, если достаточно пассивных проверок и простого failover на уровне запроса, а роль Nginx — обратный прокси, кэш и/или терминация TLS. Но для активного L7 health-check, удобного drain и штатного slowstart возможностей может не хватить без дополнительных компонентов.
HAProxy сильнее в контроле состояния бэкендов: L4/L7 checks, rise/fall, понятный active-passive, веса и плавный возврат через slowstart. Если важны предсказуемые переключения и частые обслуживающие операции, это часто самый практичный выбор.
Если захотите углубиться, можно собрать референс-конфиг под ваш протокол (HTTP API, WebSocket, gRPC, TCP) и описать пошаговую процедуру обслуживания: drain, ожидание, деплой, прогрев, возврат в пул — с безопасными таймаутами и ограничением ретраев.


