Выберите продукт

Health-check и graceful failover в Nginx и HAProxy: active-passive, веса, slow start

Разбираем, как настроить быстрый и при этом «мягкий» failover между бэкендами: отличие L4 и L7 health-check, active-passive и веса, slow start, а также правильный drain и graceful shutdown в Nginx и HAProxy.
Health-check и graceful failover в Nginx и HAProxy: active-passive, веса, slow start

Зачем вообще нужны 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 (max_fails, fail_timeout, proxy_next_upstream)

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 и обслуживание без сюрпризов, особенно при частых деплоях.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

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 в HAProxy и контроль активных сессий перед остановкой бэкенда

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 готовым

  1. Health endpoint: отдельный URI, быстрый, без тяжёлых внешних зависимостей; чёткие коды ответа.
  2. Таймауты: connect/read/send на прокси и на приложении согласованы; нет ситуации, когда прокси ждёт 30s, а приложение убивает запрос через 10s (или наоборот).
  3. Ретраи: ограничены по числу; включены только там, где безопасно повторять.
  4. Slow start: есть (в HAProxy) или компенсирован процедурой прогрева/весами (в Nginx).
  5. Drain: есть понятная процедура обслуживания, и она реально протестирована на стенде.
  6. Наблюдаемость: метрики 4xx/5xx, latency, активные соединения, события UP/DOWN и флаппинг.
FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

Частые ошибки и как их избежать

Ошибка: проверять «главную страницу» вместо /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, ожидание, деплой, прогрев, возврат в пул — с безопасными таймаутами и ограничением ретраев.

Поделиться статьей

Вам будет интересно

SNI и default server в Nginx/Apache: почему отдаётся «чужой» сертификат и как это быстро исправить OpenAI Статья написана AI (GPT 5)

SNI и default server в Nginx/Apache: почему отдаётся «чужой» сертификат и как это быстро исправить

Если при HTTPS браузер показывает сертификат другого домена, чаще всего виноваты SNI и выбор default server (или первого vhost). Р ...
nftables + WireGuard: NAT, forward chain и policy routing без сюрпризов OpenAI Статья написана AI (GPT 5)

nftables + WireGuard: NAT, forward chain и policy routing без сюрпризов

Разбираем типовые схемы WireGuard на Linux с nftables: интернет через wg0, доступ LAN в VPN, split-tunnel и policy routing. Дам ра ...
Linux 6.x: UDP GRO/GSO и USO (tx-udp-segmentation) для ускорения VPN OpenAI Статья написана AI (GPT 5)

Linux 6.x: UDP GRO/GSO и USO (tx-udp-segmentation) для ускорения VPN

UDP GRO/GSO и USO в Linux 6.x могут заметно поднять throughput в WireGuard и других UDP‑VPN, снижая нагрузку на CPU и softirq. В с ...