PQ
PQ.Hosting

Валюта

HAProxy: балансировка нагрузки и health check для нескольких VPS

Автор
Admin
07 апреля 2026
9 мин чтения
38 просмотров

Представьте: один из трёх ваших серверов падает в два часа ночи. Без балансировщика треть запросов упирается в мёртвый хост, пользователи видят ошибку, а вы узнаёте об этом из утренних жалоб. С HAProxy картина иная: балансировщик непрерывно опрашивает каждый бэкенд по расписанию, и как только хост перестаёт отвечать, трафик автоматически перераспределяется между оставшимися узлами - ни ручного вмешательства, ни потерянных запросов.

HAProxy работает с HTTP, HTTPS и чистым TCP, умеет маршрутизировать запросы по содержимому заголовков и URL и ведёт подробную статистику по каждому участнику пула. Сравнение с Nginx в роли балансировщика - не в пользу последнего: у Nginx нет встроенного интерфейса мониторинга, расширенная маршрутизация по условиям требует платных модулей, а проверка доступности бэкендов в свободной версии пассивная - узел помечается недоступным лишь тогда, когда настоящий пользовательский запрос уже вернул ошибку. HAProxy опрашивает серверы самостоятельно, в фоновом режиме, независимо от клиентского трафика.

Установка на Ubuntu 22.04

HAProxy входит в стандартный репозиторий Ubuntu. Для большинства задач пакетной версии достаточно:

apt update
apt install haproxy -y
haproxy -v

Если нужна более свежая сборка с поддержкой HTTP/3 или специфических возможностей - подключите официальный PPA:

add-apt-repository ppa:vbernat/haproxy-2.8
apt update
apt install haproxy=2.8.\*

Служба запускается автоматически сразу после установки. Вся настройка сосредоточена в одном файле: /etc/haproxy/haproxy.cfg.

Структура конфигурационного файла

Конфиг делится на четыре логических блока. Понять назначение каждого важнее, чем заучивать синтаксис.

Секция global задаёт параметры процесса - сколько ядер использовать (параметр nbthread), где хранить управляющий сокет (stats socket), от какого пользователя запускаться. Для стандартного VPS в большинстве случаев подходит конфиг, который создаётся автоматически при установке. Менять стоит только maxconn - лимит одновременных соединений. При 2 ГБ оперативной памяти безопасное значение составляет около 10 000-15 000 в зависимости от характера нагрузки.

Секция defaults определяет таймауты и режим работы, общие для всех последующих frontend и backend. Три ключевых таймаута: connect (время на установку TCP-соединения с бэкендом), client (время ожидания данных от клиента) и server (время ожидания ответа от сервера). Значение, прописанное в defaults, применяется везде, если конкретный блок не задаёт своё.

Секция frontend описывает точку входа: на каком адресе и порту HAProxy принимает соединения, по каким условиям их анализирует и в какой пул направляет.

Секция backend перечисляет реальные серверы, задаёт алгоритм распределения нагрузки и параметры проверки доступности каждого узла.

Балансировка HTTP между тремя VPS

Допустим, три сервера с адресами 10.0.0.1, 10.0.0.2 и 10.0.0.3 принимают запросы на порту 8080. HAProxy слушает внешний 80-й порт и поочерёдно передаёт запросы в пул:

frontend http_in
    bind *:80
    default_backend app_servers

backend app_servers
    balance roundrobin
    option httpchk GET /health
    http-check expect status 200

    server app1 10.0.0.1:8080 check inter 10s fall 3 rise 2
    server app2 10.0.0.2:8080 check inter 10s fall 3 rise 2
    server app3 10.0.0.3:8080 check inter 10s fall 3 rise 2

Параметры в строке сервера образуют единую систему защиты от мигания. Три неудачных ответа подряд переводят узел в статус DOWN - именно столько нужно, чтобы отделить разовый сбой от устойчивой проблемы. Для возврата в ротацию требуется всего два успешных ответа подряд: порог восстановления намеренно ниже порога отключения. Интервал 10 секунд означает, что упавший сервер обнаруживается максимум через 30 секунд, и это не влияет на пользовательские запросы - проверки идут в отдельном потоке.

Помимо roundrobin, который делит запросы поровну по кругу, есть два других подхода. leastconn направляет каждый новый запрос туда, где в данный момент меньше всего открытых соединений - это полезно, когда запросы сильно различаются по времени обработки: долгие задачи не будут искусственно перегружать один узел. source вычисляет хэш от IP-адреса клиента и закрепляет его за конкретным сервером - соединения от одного источника всегда попадают на один и тот же узел, пока тот доступен.

Ограничение частоты запросов через таблицу состояний

Таблицы состояний (stick table) в HAProxy хранят данные о клиентах - в том числе счётчики запросов - и позволяют принимать решения прямо на frontend без обращения к внешним компонентам. Типичный сценарий: заблокировать IP, который генерирует слишком много запросов за короткий промежуток времени.

frontend http_in
    bind *:80
    stick-table type ip size 100k expire 1m store http_req_rate(60s)
    http-request track-sc0 src
    http-request deny deny_status 429 if { sc_http_req_rate(0) gt 100 }
    default_backend app_servers

Таблица хранит записи по IP-адресу источника, вмещает до 100 000 записей и автоматически удаляет те, по которым не было активности в течение минуты. Директива track-sc0 src привязывает каждый входящий запрос к записи в таблице. Если счётчик запросов от одного IP за 60 секунд превышает 100, соединение блокируется и клиент получает ответ 429 (Too Many Requests). Порог и размер таблицы подбираются под конкретную нагрузку: для API с авторизацией значение 100 в минуту избыточно, для публичной страницы - вполне разумно.

Сохранение сессий для приложений с состоянием

Приложения, хранящие пользовательское состояние в памяти процесса, ломаются при переключении между серверами - сессия оказывается на одном хосте, а следующий запрос уходит на другой. Для таких случаев используют закрепление через куки.

backend app_servers_sticky
    balance roundrobin
    cookie SERVERID insert indirect nocache
    option httpchk GET /health
    http-check expect status 200

    server app1 10.0.0.1:8080 check inter 10s fall 3 rise 2 cookie app1
    server app2 10.0.0.2:8080 check inter 10s fall 3 rise 2 cookie app2
    server app3 10.0.0.3:8080 check inter 10s fall 3 rise 2 cookie app3

При первом ответе HAProxy добавляет куку SERVERID со значением вроде app1. В каждом последующем запросе балансировщик считывает эту куку и направляет клиента на тот же сервер. Если хост недоступен, клиент автоматически переходит на другой узел и получает обновлённое значение куки.

Вариант без куки - закрепление по IP через таблицу состояний:

backend app_servers_sticky_ip
    balance roundrobin
    stick-table type ip size 100k expire 30m
    stick on src

    server app1 10.0.0.1:8080 check inter 10s fall 3 rise 2
    server app2 10.0.0.2:8080 check inter 10s fall 3 rise 2
    server app3 10.0.0.3:8080 check inter 10s fall 3 rise 2

ACL-маршрутизация: /api на один пул, остальное на другой

ACL позволяют принимать решение о маршруте прямо на frontend, анализируя URL, заголовки, метод запроса и другие атрибуты. Типичный сценарий - выделить API в отдельный пул с другими настройками балансировки.

frontend http_in
    bind *:80
    acl is_api path_beg /api
    use_backend api_servers if is_api
    default_backend app_servers

backend api_servers
    balance leastconn
    option httpchk GET /api/health
    http-check expect status 200

    server api1 10.0.1.1:9000 check inter 10s fall 3 rise 2
    server api2 10.0.1.2:9000 check inter 10s fall 3 rise 2

backend app_servers
    balance roundrobin
    option httpchk GET /health
    http-check expect status 200

    server app1 10.0.0.1:8080 check inter 10s fall 3 rise 2
    server app2 10.0.0.2:8080 check inter 10s fall 3 rise 2
    server app3 10.0.0.3:8080 check inter 10s fall 3 rise 2

Условие path_beg /api срабатывает, если путь запроса начинается с /api. Несколько ACL можно объединять: например, выделять POST-запросы к конкретному пути или маршрутизировать по значению заголовка Host - это удобно, когда за одним балансировщиком стоят несколько доменов.

Страница статистики

Встроенный веб-интерфейс HAProxy даёт оперативную картину всего кластера без дополнительных инструментов. Настраивается через секцию listen:

listen stats
    bind *:8404
    mode http
    stats enable
    stats uri /stats
    stats realm HAProxy\ Statistics
    stats auth admin:yourpassword
    stats refresh 10s
    stats show-node
    stats show-legends

После перезапуска страница открывается по адресу http://ваш-ip:8404/stats. На ней отображается состояние каждого сервера, число активных соединений, накопленный объём трафика, количество ошибок и среднее время ответа. На практике полезнее всего два показателя: резкий рост частоты запросов (нередко признак волны ботов ещё до срабатывания оповещения) и рост числа ошибок на одном конкретном бэкенде при стабильных остальных - это характерная картина, когда узел начинает давать сбои на определённом типе запросов, оставаясь формально доступным. Порт 8404 нужно закрыть на брандмауэре и открывать только через SSH-туннель при необходимости.

Применение конфигурации и проверка состояния

Перед перезапуском проверяйте конфиг на синтаксические ошибки:

haproxy -c -f /etc/haproxy/haproxy.cfg

Применение без разрыва активных соединений:

systemctl reload haproxy

Просмотр текущего состояния через административный сокет:

echo "show info" | socat stdio /run/haproxy/admin.sock
echo "show servers state" | socat stdio /run/haproxy/admin.sock

Типичные проблемы

Ошибка 503 и пустой пул. Такая ситуация возникает, когда HAProxy вывел из ротации все серверы одновременно. Чаще всего причина не в самих серверах, а в неверно настроенной проверке доступности: эндпоинт /health может требовать авторизации или возвращать перенаправление с кодом 301 вместо 200. Проверьте состояние пула:

echo "show servers state" | socat stdio /run/haproxy/admin.sock

Узлы в статусе DOWN - сигнал смотреть журнал HAProxy и параллельно проверять сам эндпоинт вручную через curl с того же хоста, где запущен балансировщик.

Приложение тормозит, хотя проверки зелёные. HAProxy оценивает только то, что прописано в option httpchk. Если /health мгновенно отвечает 200, а настоящие запросы подвисают на несколько секунд, балансировщик считает узел здоровым. Решение: переписать эндпоинт так, чтобы он проверял внутреннее состояние приложения - соединение с базой данных, доступность зависимых служб, - а не просто возвращал заглушку.

Сессия пропадает при каждом втором запросе. Поведение характерно для приложений, хранящих состояние локально. Подключите закрепление через куки или таблицу состояний, как описано выше. Если архитектура позволяет - лучший долгосрочный вариант - вынести сессии во внешнее хранилище, например Redis, и сделать каждый узел независимым от конкретного запроса.

Служба не поднимается после правки конфига. В большинстве таких случаев причина - опечатка или лишний пробел в названии директивы. Команда haproxy -c -f /etc/haproxy/haproxy.cfg выдаёт точный номер строки с ошибкой - читайте вывод внимательно, он конкретен.

Когда HAProxy оправдан, а когда избыточен

HAProxy приносит реальную пользу там, где несколько VPS работают вместе и нужна предсказуемая маршрутизация, оперативная статистика или балансировка TCP-трафика. Инструмент устойчив под высокой нагрузкой, экономит память и ведёт себя предсказуемо при отказах - это ценно в рабочей среде.

При этом есть сценарии, где он избыточен. Один сервер с Nginx и парой бэкендов закрывается директивой upstream без дополнительного компонента в схеме. Кластер под управлением Kubernetes уже имеет встроенные механизмы балансировки через Ingress и сервисный тип LoadBalancer - добавлять поверх HAProxy нет смысла.

Для небольшого кластера VPS без оркестратора HAProxy остаётся разумным выбором: минимум зависимостей, понятный конфигурационный файл и многолетняя надёжность в условиях реальной нагрузки.

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

Похожие статьи