Инфраструктура

Load Balancer: как настроить балансировку без лишней магии

Инструкция для проекта, где один backend уже становится слабым местом: как добавить второй инстанс, прокинуть трафик через Nginx и проверить, что отказ одного процесса не кладёт весь сайт.

1. Подними два backend-инстанса

Для начала не усложняй архитектуру. Достаточно двух процессов приложения на разных портах или в разных Docker-контейнерах.

app_1: 127.0.0.1:8001
app_2: 127.0.0.1:8002

2. Добавь upstream в Nginx

Upstream описывает, куда Nginx может отправлять запросы. Если один backend упал, Nginx временно перестанет слать туда трафик.

upstream app_backend {
  least_conn;
  server 127.0.0.1:8001 max_fails=3 fail_timeout=20s;
  server 127.0.0.1:8002 max_fails=3 fail_timeout=20s;
}

server {
  server_name 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_set_header X-Forwarded-Proto $scheme;
  }
}

3. Сделай health endpoint

В приложении нужен быстрый endpoint, который не ходит в тяжёлые внешние API. Обычно достаточно проверить, что процесс жив и может подключиться к базе.

  1. Добавь endpoint /health.
  2. Верни HTTP 200, если приложение готово принимать трафик.
  3. Верни 500, если нет соединения с базой или критичным сервисом.

4. Проверь отказоустойчивость

curl -I https://example.com/health
docker stop app_1
curl -I https://example.com/
docker start app_1

Если сайт продолжает открываться после остановки одного инстанса, базовая балансировка работает.

5. Выбери стратегию распределения

Для большинства web-проектов достаточно least_conn: новый запрос уходит туда, где меньше активных соединений. Если проект хранит состояние в памяти процесса, сначала вынеси session в Redis или базу. Sticky sessions оставляй только как временный компромисс, иначе один перегруженный инстанс будет получать одного и того же активного клиента.

  1. round_robin — простой вариант по умолчанию.
  2. least_conn — лучше для запросов разной длительности.
  3. ip_hash — привязка клиента к одному backend, но хуже для равномерной нагрузки.

6. Zero-downtime deploy

Не перезапускай все backend-инстансы одновременно. Обновляй по одному: сначала убери инстанс из upstream или останови контейнер, дождись запуска новой версии, проверь /health, потом переходи к следующему.

docker compose up -d --build app_1
curl -f https://example.com/health
docker compose up -d --build app_2
curl -f https://example.com/health

7. Что логировать

В access log добавь upstream, время ответа и request id. Тогда при ошибке будет понятно, какой backend отвечал и почему запрос тормозил.

log_format upstreamlog '$remote_addr $request '
  'status=$status upstream=$upstream_addr '
  'request_time=$request_time upstream_time=$upstream_response_time';

Checklist перед production

  • SSL работает на внешнем Nginx.
  • Логи каждого backend-инстанса разделены.
  • Static/media файлы вынесены в общий volume или object storage.
  • Миграции запускаются отдельно от web-процессов.
  • Deploy не перезапускает все инстансы одновременно.
Все постыСледующая инструкция