Прямой ответ
Балансировка нужна, когда один сервер приложения перестал справляться (CPU упирается при запасе памяти/БД — горизонталь «два-три инстанса за прокси» дешевле вертикали): nginx-upstream объявляет группу бэкендов (upstream app { server 127.0.0.1:8001; server 127.0.0.1:8002; }), proxy_pass распределяет запросы; алгоритм по умолчанию — round-robin (по кругу), альтернативы open-source nginx: **least_conn** (запрос — серверу с меньшим числом активных соединений — лучше при разнодлительных запросах), **ip_hash** (клиент — всегда одному серверу — «липкость» по IP; нужна только при состоянии на сервере, которого быть не должно — [stateless-принцип](./12-factor-prilozhenie-praktika.md)), **weight** (разновесные серверы — сильному больше трафика). Второй обязательный элемент — **health checks**: пассивные (open source: max_fails=3 fail_timeout=30s — сервер с 3 ошибками за 30 с выводится на 30 с — минимум, который должен быть всегда) и активные (регулярный опрос /health — полноценны в nginx Plus, в open source эмулируются отдельным мониторингом — [healthcheck-эндпоинт](./graceful-shutdown-i-healthchecki-servisov.md) приложения обязателен в любом случае). Третий — корректный **вывод сервера**: деплой/обслуживание не должны рвать запросы (флаг-файл для health → nginx исключает сервер → соединения стекают → обновление — [rolling-логика](./strategii-deploya-blue-green-canary.md)); и keepalive к апстримам (keepalive 32` в upstream — переиспользование соединений — меньше latency и TIME_WAIT).
Upstream: конфигурация и алгоритмы
Базовая конфигурация: серверы с параметрами (weight=2 — двойная доля; backup — включается только при недоступности основных; down — помечен выключенным без удаления — плановое обслуживание), keepalive (keepalive 64; в блоке upstream + proxy_http_version 1.1 и proxy_set_header Connection ""; в location — без этих трёх строк keepalive к апстриму не работает — частая неочевидность: каждый запрос открывает новое соединение к бэкенду — латентность и порты впустую). Выбор алгоритма: round-robin — дефолт для однородных коротких запросов; least_conn — когда длительности запросов сильно различаются (отчёт 10 с рядом с API-запросом 50 мс — round-robin нагрузит «отчётный» сервер очередью, least_conn — распределит по фактической занятости); ip_hash/hash — вынужденная мера при серверном состоянии (сессии в памяти, локальный кеш без инвалидации) — правильная борьба не за липкость, а за stateless (сессии в Redis — 12-factor); hash по ключу (hash $request_uri consistent) — для кеш-локальности (одинаковые URL — одному бэкенду с его кешем — consistent-хеш минимизирует перераспределение при изменении состава). Балансировка L4 (TCP/не-HTTP — stream-модуль) — для БД/очередей/произвольных протоколов — тот же upstream-подход без HTTP-семантики.
Health checks: пассивные, активные, реалии
Пассивные (единственные встроенные в open source): nginx считает неудачи (connect/timeout/5xx — proxy_next_upstream определяет, что считать провалом и ретраить ли на следующем сервере) — max_fails=3 fail_timeout=30s — после порога сервер исключается на окно; настраивать обязательно (дефолт max_fails=1 — одна сетевая флуктуация выводит сервер — слишком нервно; и «что считать fail» — 502/504 да, 404/429 — нет: proxy_next_upstream error timeout http_502 http_503 явно). Активные (штатно — nginx Plus health_check interval=10s uri=/health; open source — эмуляция: отдельный скрипт/мониторинг опрашивает /health бэкендов и управляет составом upstream через reload/API — либо sidecar-подход; для большинства команд open source + пассивные + приложение, которое само закрывает /health при нездоровье (readiness-логика) — достаточный контур). Важно понимать границу: пассивные проверки реагируют после пользовательских ошибок (первые запросы на упавший сервер — 502 пользователям, потом исключение); активные — до; компенсация без Plus — ретраи на следующий сервер (proxy_next_upstream + proxy_next_upstream_tries 2 — пользовательская ошибка превращается в задержку, не в 502 — для идемпотентных GET безопасно, для POST — осторожно с дублями — идемпотентность).
Деплой и обслуживание за балансировщиком
Цикл корректного вывода сервера: (1) пометить «не готов» — флаг для health-эндпоинта (touch /tmp/unhealthy — приложение отвечает 503 на /health) или nginx-down; (2) подождать стекание активных запросов (graceful — приложение перестаёт принимать новые, доделывает текущие — shutdown-механика); (3) обновить/перезапустить; (4) дождаться готовности (health зелёный, прогрев кешей/пулов — startup-фаза) и вернуть в ротацию. Автоматизация — в деплой-скрипте/CI (ansible-rolling — serial+проверки; K8s — readiness делает это встроенно — K8s-основы); ручной деплой без вывода — источник «мигающих» 502 на каждом выкате. Смежные практики: connection draining (nginx не рвёт существующие keepalive-соединения при reload — worker_shutdown_timeout задаёт предел), canary-слот (сервер с weight=1 против 10 у основных — новая версия получает ~10% трафика — canary-стратегия средствами weight), сессии деплоя без простоя на одном балансировщике — reload nginx сам graceful (новые воркеры принимают, старые дорабатывают — nginx-основы).
Практический сценарий: второй сервер приложения за день
Исходник: FastAPI на одном сервере, пиковые CPU 95%, p95 деградирует до 2 с. Шаг 1 — второй инстанс (тот же образ/конфиг — 12-factor: конфигурация из env, различий нет); Step 2 — nginx: upstream с двумя серверами, least_conn (запросы разнодлительные: API + отчёты), keepalive 32, proxy_next_upstream error timeout http_502 http_503 + tries 2, max_fails=3 fail_timeout=30s; Step 3 — проверки: нагрузка (locust) — p95 вернулся к 300 мс при CPU ~55% на сервер; kill одного бэкенда — первые запросы ретраятся на живой (пользователь видит +100 мс, не 502 — пассивный health выводит мёртвый через 3 ошибки); возврат — автоматически по истечении fail_timeout; Step 4 — деплой-рутина: скрипт вывода (флаг unhealthy → 10 с стекания → restart → health-ожидание → возврат) — выкат без единого 502 (проверено намеренным деплоем под фоновой нагрузкой). Открытые вопросы на вырост: состояние сессий уже в Redis (липень не понадобилась вовсе); балансировщик — один, SPOF — план: DNS-failover или второй nginx+keepalived при росте критичности (отказоустойчивость — по SLO, не заранее).
FAQ
Балансировка на одном хосте (несколько воркеров за nginx) — имеет смысл? Да — это обычная схема (gunicorn сам форкает воркеры — внутренний балансир; или несколько процессов на портах + nginx-upstream — внешний контроль/health/rolling каждого); смысл — те же health checks, canary и вывод по одному; от «одного процесса на 8 потоков» схема отличается изоляцией падений и независимыми рестартами.
Чем балансировка отличается от CDN? CDN кеширует и распределяет статику/контент географически (запросы не доходят до ваших серверов — кеш-слой), upstream-балансировка делит дошедший трафик между вычислителями; в стеке они последовательны: CDN → балансировщик → серверы приложений.
Нужен ли sticky-session для WebSocket? Нет в смысле «липкости по IP»: WebSocket-соединение живет на одном бэкенде по природе (длинное соединение), но при обрыве клиент переподключится на любой (WebSocket-практика — состояние сессии во внешнем хранилище/Redis pub/sub для мульти-серверности); липкость нужна только если состояние соединения не вынесено — лечится вынесением, не балансом.
Когда одного nginx мало и нужны L4/L7-балансировщики другого класса? Пороги: десятки бэкендов с частыми изменениями состава (nginx reload с генерацией конфига — ок до умеренных масштабов; дальше — динамические upstream API/консул-интеграции), требования активных health checks и метрик ротации из коробки (nginx Plus/HAProxy/Envoy), мульти-ЦОД и глобальная балансировка (DNS/GSLB) — для 2–10 серверов open source nginx покрывает всё с запасом.
Источники: документация nginx upstream/health-check/load-balancing — ссылки в шапке.