Основы отказоустойчивой балансировки в Nginx
Nginx решает две задачи: распределяет входящий трафик между несколькими backend-серверами и маскирует их за единой точкой входа. Это снижает риск простоя: при выходе одного сервера из строя остальные продолжают обрабатывать запросы. Базовая схема состоит из upstream-группы, директивы proxy_pass и набора проверок, которые определяют, какие серверы готовы принимать трафик.
Ключевые компоненты отказоустойчивой конфигурации: пул серверов в блоке upstream, алгоритм выбора сервера, таймауты для обрыва зависших соединений и механизмы проверки состояния. Без проверок Nginx будет отправлять запросы на мёртвый backend и получит ошибку 502 или 504. С проверками проблемный сервер временно исключается из ротации, а трафик уходит на рабочие узлы.
Что такое upstream-группа и зачем она нужна
Upstream-группа - это именованный пул backend-серверов, между которыми Nginx распределяет запросы. Она описывается в контексте http директивой upstream. Внутри перечисляются адреса серверов, их веса и параметры отказоустойчивости.
Минимальная конфигурация выглядит так:
upstream backend {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
server {
listen 80;
location / {
proxy_pass http://backend;
}
}Здесь Nginx распределяет запросы по трём серверам методом round-robin: каждый новый запрос уходит на следующий сервер по кругу. Если один сервер перестанет отвечать, Nginx без дополнительных настроек продолжит отправлять на него трафик, пока не истечёт таймаут. Поэтому минимальная конфигурация не обеспечивает отказоустойчивость - нужны проверки состояния и таймауты.
Активные и пассивные проверки: в чем разница
Пассивные проверки анализируют ответы на реальные пользовательские запросы. Nginx считает сервер неработающим, если тот не ответил или вернул ошибку определённое количество раз за заданный интервал. Для этого используются директивы max_fails и fail_timeout в блоке server внутри upstream. Пассивные проверки работают в open-source Nginx без дополнительных модулей.
Активные проверки инициируются самим Nginx: он отправляет тестовые запросы на backend независимо от пользовательского трафика. Это позволяет обнаружить проблему до того, как её заметит пользователь. В open-source Nginx активные проверки требуют стороннего модуля, например ngx_http_upstream_check_module. В Nginx Plus есть встроенная директива health_check.
Разница в скорости обнаружения сбоя. Пассивная проверка сработает только после неудачного пользовательского запроса. Активная проверка обнаружит отказ за несколько секунд, даже если трафика нет. Недостаток активных проверок - дополнительная нагрузка на backend-серверы.
Настройка upstream-группы с проверками состояния
Практическая настройка отказоустойчивого пула начинается с определения критериев отказа. Нужно решить: сколько ошибок допустимо, как долго сервер остаётся исключённым и как быстро он возвращается в ротацию после восстановления.
Пассивные проверки: настройка max_fails и fail_timeout
Директивы max_fails и fail_timeout задают порог отказа. max_fails - количество неудачных попыток за период fail_timeout, после которого сервер помечается как неработающий. fail_timeout - интервал, в течение которого сервер не получает новые запросы, а также окно подсчёта ошибок.
Пример:
upstream backend {
server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.12:8080 max_fails=3 fail_timeout=30s;
}Логика работы: если сервер 192.168.1.10 трижды не ответил или вернул ошибку в течение 30 секунд, Nginx исключает его из ротации на 30 секунд. По истечении этого интервала сервер снова получает трафик. Если первый запрос после возвращения проходит успешно, сервер считается работоспособным. Если снова ошибка - отсчёт начинается заново.
Значения по умолчанию: max_fails=1, fail_timeout=10s. Для высоконагруженных систем часто увеличивают max_fails до 3-5, чтобы избежать ложных срабатываний при кратковременных сетевых всплесках.
Активные проверки: использование health_check
В Nginx Plus активная проверка включается директивой health_check внутри location, который проксирует трафик на upstream:
location / {
proxy_pass http://backend;
health_check interval=5s fails=3 passes=2;
}Параметры: interval - периодичность проверок, fails - количество неудачных проверок для пометки сервера неработающим, passes - количество успешных проверок для возврата в ротацию. При interval=5s, fails=3 и passes=2 сервер будет исключён через 15 секунд после отказа и вернётся через 10 секунд после восстановления.
В open-source Nginx используется модуль ngx_http_upstream_check_module. Конфигурация аналогична, но проверка задаётся в отдельном блоке:
upstream backend {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
check interval=3000 rise=2 fall=3 timeout=1000 type=http;
check_http_send "HEAD /health HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
}Здесь interval в миллисекундах, rise - количество успешных проверок для восстановления, fall - количество неудачных для исключения. type=http указывает на HTTP-проверку, check_http_send задаёт запрос, check_http_expect_alive - ожидаемые коды ответа.
Более детально тема активных проверок и keepalive-соединений разобрана в руководстве по отказоустойчивому кластеру Nginx.
Таймауты и лимиты: защита от сбоев
Таймауты определяют, сколько Nginx ждёт ответа от backend-сервера. Без них один зависший сервер может заблокировать все рабочие процессы: запросы будут висеть в очереди, а пользователи получат таймаут. Лимиты ограничивают количество одновременных соединений, защищая backend от перегрузки.
Настройка proxy_connect_timeout и proxy_read_timeout
proxy_connect_timeout задаёт время на установку TCP-соединения с backend-сервером. Если сервер недоступен, Nginx прерывает попытку через указанный интервал. Типичное значение - 3-5 секунд. Дольше ждать нет смысла: пользователь всё равно не будет ждать 30 секунд установки соединения.
proxy_read_timeout определяет, сколько Nginx ждёт ответа от backend после отправки запроса. Это значение зависит от приложения. Для быстрых API достаточно 10-30 секунд. Для отчётов или экспорта, которые генерируются минуту, нужно 120-300 секунд. Слишком маленькое значение приведёт к обрыву легитимных долгих запросов, слишком большое - к накоплению зависших соединений.
Пример настройки в location:
location / {
proxy_pass http://backend;
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}proxy_send_timeout - время на отправку тела запроса на backend. Для загрузки больших файлов его увеличивают до 300 секунд и более.
Использование proxy_next_upstream для повторных попыток
Директива proxy_next_upstream указывает, при каких ошибках Nginx должен повторить запрос на следующем сервере из upstream-группы. Это ключевой механизм отказоустойчивости: если первый сервер вернул ошибку, запрос автоматически уходит на второй.
location / {
proxy_pass http://backend;
proxy_next_upstream error timeout http_500 http_502 http_503;
proxy_next_upstream_tries 2;
proxy_next_upstream_timeout 10s;
}Условия: error - ошибка соединения, timeout - таймаут, http_500, http_502, http_503 - соответствующие коды ответа. proxy_next_upstream_tries ограничивает количество попыток, proxy_next_upstream_timeout - общее время на все попытки. Без этих ограничений Nginx может перебирать серверы бесконечно при массовом сбое.
Важно: proxy_next_upstream не срабатывает, если backend уже отправил часть ответа клиенту. Для идемпотентных методов (GET, HEAD, PUT, DELETE) повтор безопасен. Для POST с неидемпотентной операцией повтор может привести к дублированию данных. В таких случаях ограничивают условия только error и timeout.
Алгоритмы балансировки: least_conn против ip_hash
Выбор алгоритма определяет, как Nginx выбирает сервер для каждого запроса. По умолчанию работает round-robin: запросы распределяются равномерно по очереди. Для большинства stateless-приложений этого достаточно. Но при неравномерной нагрузке или требовании сохранения сессии нужны другие алгоритмы.
least_conn: балансировка по наименьшему числу соединений
least_conn направляет запрос на сервер с наименьшим количеством активных соединений. Это решает проблему round-robin: при запросах с разной длительностью обработки равномерное распределение по очереди приводит к перекосу. Один сервер получает пять долгих запросов, другой - пять коротких, и первый оказывается перегружен, хотя оба получили одинаковое количество запросов.
upstream backend {
least_conn;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}least_conn оптимален для WebSocket-соединений, длинных HTTP-запросов, стриминга и API с неравномерным временем ответа. Алгоритм учитывает веса серверов: при разной производительности можно задать вес и Nginx будет учитывать его при выборе.
ip_hash: привязка клиента к серверу
ip_hash вычисляет хэш от IP-адреса клиента и направляет все запросы с этого адреса на один и тот же сервер. Это решает проблему сохранения сессии без внешнего хранилища: если приложение хранит сессию в локальной памяти сервера, пользователь всегда попадает на сервер со своей сессией.
upstream backend {
ip_hash;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}Недостаток ip_hash - возможный перекос нагрузки. Если все клиенты приходят с небольшого пула IP-адресов (например, корпоративная сеть за NAT), хэш распределит их неравномерно. Один сервер может получить 80% трафика, остальные - по 10%. Для публичных сайтов с большим количеством уникальных IP распределение обычно приемлемое.
Другой недостаток: при добавлении или удалении сервера из группы хэш пересчитывается, и часть клиентов меняет сервер. Это приводит к потере сессий. Для приложений, чувствительных к потере сессии, лучше использовать внешнее хранилище сессий (Redis, Memcached) и least_conn или round-robin.
Сравнение алгоритмов и стратегий распределения трафика подробно разобрано в статье об алгоритмах балансировки Nginx.
Практические примеры конфигураций
Ниже - готовые конфигурации для типовых сценариев. Они проверены на Nginx 1.24+ и адаптируются под конкретную инфраструктуру заменой IP-адресов и портов.
Пример 1: балансировка с least_conn и пассивными проверками
Сценарий: REST API без состояния, запросы имеют разное время обработки. Нужно равномерно распределять нагрузку с учётом текущей загруженности серверов.
upstream api_backend {
least_conn;
server 10.0.1.10:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.11:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.12:8080 max_fails=3 fail_timeout=30s;
keepalive 32;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://api_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_connect_timeout 5s;
proxy_read_timeout 30s;
proxy_send_timeout 30s;
proxy_next_upstream error timeout http_500 http_502 http_503;
proxy_next_upstream_tries 2;
}
}Ключевые строки: least_conn - алгоритм выбора сервера; max_fails=3 fail_timeout=30s - пассивная проверка; keepalive 32 - поддержание 32 постоянных соединений с каждым backend для снижения накладных расходов на TCP-рукопожатия; proxy_next_upstream - повтор на другом сервере при ошибке или таймауте.
Пример 2: балансировка с ip_hash для сохранения сессий
Сценарий: веб-приложение с серверными сессиями в локальной памяти. Пользователь должен попадать на один и тот же сервер при каждом запросе.
upstream app_backend {
ip_hash;
server 10.0.2.10:8080 max_fails=2 fail_timeout=15s;
server 10.0.2.11:8080 max_fails=2 fail_timeout=15s;
server 10.0.2.12:8080 max_fails=2 fail_timeout=15s;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
proxy_next_upstream error timeout;
proxy_next_upstream_tries 1;
}
}proxy_next_upstream_tries 1 означает: без повторов на другом сервере. Это осознанное решение для ip_hash. Если сервер, к которому привязан пользователь, упал, повтор на другом сервере всё равно не поможет - сессия потеряна. Лучше быстро вернуть ошибку, чем создавать иллюзию работы.
Для приложений с сессиями более надёжный подход - вынести сессии в Redis и использовать least_conn. Тогда отказ любого сервера не приводит к потере пользовательских данных. Подробнее о продвинутой настройке высокой доступности читайте в материале о продвинутой настройке Nginx.
Распространенные ошибки и их решение
Типичные проблемы при настройке балансировки возникают из-за неверных таймаутов, отсутствия проверок и неправильного выбора алгоритма. Ниже - симптомы и способы устранения.
Ошибка 1: отсутствие max_fails и fail_timeout. Симптом: при отказе одного backend-сервера пользователи периодически получают 502 или 504 ошибки, хотя остальные серверы работают. Решение: добавить max_fails=3 fail_timeout=30s к каждому server в upstream.
Ошибка 2: слишком маленький proxy_read_timeout. Симптом: легитимные долгие запросы обрываются с ошибкой 504. Решение: проанализировать время ответа приложения и установить proxy_read_timeout с запасом 20-30%. Для отчётов и экспорта - 120-300 секунд.
Ошибка 3: использование ip_hash при малом количестве клиентов. Симптом: один сервер перегружен, остальные простаивают. Решение: перейти на least_conn или round-robin, а сессии вынести во внешнее хранилище.
Ошибка 4: отсутствие proxy_next_upstream_tries. Симптом: при массовом сбое Nginx перебирает все серверы, увеличивая задержку ответа. Решение: ограничить количество попыток до 2-3.
Ошибка 5: игнорирование keepalive. Симптом: высокая задержка при установке соединений с backend. Решение: добавить директиву keepalive в upstream и proxy_http_version 1.1 с пустым заголовком Connection.
Ошибка 6: активные проверки без учёта нагрузки. Симптом: backend-серверы получают дополнительный трафик от health check, что увеличивает их загрузку. Решение: увеличить interval до 10-30 секунд, если сервис не критичен к секундным задержкам обнаружения сбоя.
Для контроля состояния кластера после настройки используйте метрики. Какие именно метрики отслеживать и как настроить алерты, описано в руководстве по мониторингу Nginx upstream. Базовая шпаргалка по ключевым метрикам - в статье о пяти метриках Nginx.