Балансировка нагрузки - один из ключевых инструментов для построения отказоустойчивых и масштабируемых систем. HAProxy остаётся золотым стандартом в этой области благодаря высокой производительности, гибкости и нулевой стоимости. В этой статье разберём, как настроить балансировщик с нуля, углубимся в алгоритмы распределения нагрузки и рассмотрим продвинутые сценарии конфигурации.
Зачем нужен балансировщик нагрузки
Представьте ситуацию: Ваше приложение работает на одном сервере, и он выходит из строя. Результат - полная недоступность сервиса. Или другой сценарий: нагрузка растёт, один сервер уже не справляется, но добавить второй без изменения архитектуры невозможно.
Балансировщик решает обе ситуации. Он распределяет входящие запросы между несколькими серверами (бэкендами) и автоматически исключает из ротации те, что перестали отвечать. Помимо этого, балансировщик позволяет проводить обновления без даунтайма - Вы выводите сервер из ротации, обновляете его, возвращаете обратно и переходите к следующему.
Существует два принципиально разных подхода к балансировке: на уровне L4 (TCP/UDP) и на уровне L7 (HTTP). L4-балансировка работает быстрее, поскольку не анализирует содержимое пакетов, а просто перенаправляет соединения. L7-балансировка даёт больше возможностей - маршрутизация по URL, заголовкам, cookies - но требует больше ресурсов. HAProxy эффективно работает на обоих уровнях.
Установка
На Ubuntu или Debian:
sudo apt update
sudo apt install haproxy
На CentOS или Rocky Linux:
sudo dnf install haproxy
Проверим версию:
haproxy -v
Для продакшена рекомендуется версия 2.6 или выше - в ней улучшена работа с HTTP/2 и добавлены оптимизации производительности. Если в репозитории старая версия, подключите официальный PPA:
sudo add-apt-repository ppa:vbernat/haproxy-2.8
sudo apt update
sudo apt install haproxy
Архитектура конфигурации
Конфигурация HAProxy состоит из четырёх основных секций:
global - параметры процесса: логирование, лимиты, пользователь для запуска, настройки многопоточности.
defaults - настройки по умолчанию, которые наследуются остальными секциями. Позволяет избежать дублирования.
frontend - точка входа для клиентских соединений. Здесь указывается, на каком адресе и порту слушать, и определяются правила маршрутизации.
backend - группа серверов, между которыми распределяется нагрузка. Содержит алгоритм балансировки и параметры health checks.
Дополнительно существует секция listen, которая объединяет frontend и backend в одном блоке - удобно для простых конфигураций.
Минимальная рабочая конфигурация
Откройте файл /etc/haproxy/haproxy.cfg и замените содержимое:
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
user haproxy
group haproxy
daemon
maxconn 50000
nbthread 4
defaults
log global
mode http
option httplog
option dontlognull
option forwardfor
option http-server-close
timeout connect 5s
timeout client 30s
timeout server 30s
timeout http-request 10s
timeout http-keep-alive 15s
retries 3
frontend http_front
bind *:80
capture request header Host len 50
capture request header User-Agent len 100
default_backend http_back
backend http_back
balance roundrobin
option httpchk GET /health
http-check expect status 200
server web1 192.168.1.10:8080 check
server web2 192.168.1.11:8080 check
server web3 192.168.1.12:8080 checkНеобходимо изменить IP адреса и порты на необходимые Вам
Разберём ключевые параметры подробнее.
В секции global параметр maxconn 50000 задаёт максимальное число одновременных соединений. Это значение нужно согласовать с системными лимитами - проверьте ulimit -n. Параметр nbthread 4 включает многопоточность, что критично для современных многоядерных серверов.
В секции defaults директива option forwardfor добавляет заголовок X-Forwarded-For с реальным IP клиента - без этого бэкенды будут видеть IP балансировщика. Параметр option http-server-close закрывает соединение с бэкендом после каждого ответа, но сохраняет keep-alive с клиентом - оптимальный режим для большинства случаев.
Таймауты заслуживают отдельного внимания. timeout connect - время на установку TCP-соединения с бэкендом, 5 секунд достаточно для локальной сети. timeout client и timeout server - максимальное время неактивности соединения. timeout http-request ограничивает время на получение полного HTTP-запроса - защита от slowloris-атак. timeout http-keep-alive определяет, сколько ждать следующего запроса в keep-alive соединении.
Алгоритмы балансировки
Выбор алгоритма существенно влияет на распределение нагрузки. HAProxy поддерживает несколько стратегий, каждая оптимальна для своих сценариев.
roundrobin
Запросы распределяются по очереди между серверами. Это динамический алгоритм - веса серверов можно менять на лету. Подходит для большинства случаев с однородными бэкендами.
backend http_back
balance roundrobin
server web1 192.168.1.10:8080 weight 100 check
server web2 192.168.1.11:8080 weight 100 checkНеобходимо изменить IP адреса и порты на необходимые Вам
Пример ответов сервера при использовании этого алгоритма (с тремя бекенд серверами):

leastconn
Запрос уходит на сервер с наименьшим числом активных соединений. Оптимален для долгих запросов: загрузка файлов, WebSocket, стриминг. Также хорошо работает, когда время обработки запросов сильно варьируется.
backend websocket_back
balance leastconn
server ws1 192.168.1.20:8080 check
server ws2 192.168.1.21:8080 checkНеобходимо изменить IP адреса и порты на необходимые Вам
source
Клиент привязывается к серверу на основе хеша IP-адреса. Один и тот же клиент всегда попадает на один сервер (пока тот доступен). Используйте, если приложение хранит сессии локально и нет возможности вынести их в Redis или базу данных.
backend legacy_back
balance source
hash-type consistent
server app1 192.168.1.30:8080 check
server app2 192.168.1.31:8080 checkНеобходимо изменить IP адреса и порты на необходимые Вам
Параметр hash-type consistent включает consistent hashing - при добавлении или удалении сервера перераспределяется минимум клиентов.
uri
Балансировка на основе хеша URI. Запросы к одному и тому же URL всегда попадают на один сервер. Идеально для кеширующих бэкендов - максимизирует cache hit rate.
backend cache_back
balance uri
hash-type consistent
server cache1 192.168.1.40:8080 check
server cache2 192.168.1.41:8080 check
Необходимо изменить IP адреса и порты на необходимые Вам
hdr
Балансировка по значению HTTP-заголовка. Полезно для маршрутизации по API-ключу, tenant ID в мультитенантных приложениях или любому другому заголовку.
backend api_back
balance hdr(X-API-Key)
hash-type consistent
server api1 192.168.1.50:8080 check
server api2 192.168.1.51:8080 checkНеобходимо изменить IP адреса и порты на необходимые Вам
Работа с весами
Для серверов разной мощности используйте веса:
backend http_back
balance roundrobin
server powerful 192.168.1.10:8080 weight 150 check
server standard 192.168.1.11:8080 weight 100 check
server small 192.168.1.12:8080 weight 50 checkНеобходимо изменить IP адреса и порты на необходимые Вам
Сервер с weight 150 получит в три раза больше запросов, чем сервер с weight 50. Веса можно менять без перезапуска через runtime API.
Проверки здоровья
Health checks - критически важный механизм. Без них балансировщик будет отправлять запросы на неработающие серверы.
Базовая HTTP-проверка
backend http_back
option httpchk GET /health
http-check expect status 200
server web1 192.168.1.10:8080 check inter 3s fall 3 rise 2Необходимо изменить IP адреса и порты на необходимые Вам
Параметры после check:
inter 3s- интервал между проверкамиfall 3- после трёх неудачных проверок сервер помечается как DOWNrise 2- для возврата в ротацию нужны две успешные проверки
Проверка с заголовками
Некоторые приложения требуют определённый Host header:
backend http_back
option httpchk
http-check send meth GET uri /health hdr Host app.example.com
http-check expect status 200Необходимо изменить домен на необходимый Вам
Проверка содержимого ответа
Можно проверять не только код ответа, но и тело:
backend http_back
option httpchk GET /health
http-check expect string "status":"ok"
Или с помощью регулярного выражения:
backend http_back
option httpchk GET /health
http-check expect rstring "status":\s*"(ok|healthy)"
TCP-проверка
Для L4-балансировки или когда HTTP-проверка избыточна:
backend tcp_back
mode tcp
option tcp-check
tcp-check connect
tcp-check send PING\r\n
tcp-check expect string PONG
server redis1 192.168.1.60:6379 checkНеобходимо изменить IP адреса и порты на необходимые Вам
Внешние проверки
Для сложной логики можно использовать внешний скрипт:
backend http_back
option external-check
external-check path "/usr/bin:/bin"
external-check command /usr/local/bin/check-backend.sh
server web1 192.168.1.10:8080 check
Необходимо изменить IP адреса и порты на необходимые Вам, также и скрипт.
Скрипт получает параметры сервера и должен вернуть код 0 для healthy, любой другой - для unhealthy.
Маршрутизация запросов
HAProxy позволяет направлять запросы в разные бэкенды на основе множества условий.
ACL - Access Control Lists
ACL определяют условия, которые затем используются в правилах маршрутизации:
frontend http_front
bind *:80
acl is_api path_beg /api/
acl is_static path_end .css .js .png .jpg .gif .ico
acl is_websocket hdr(Upgrade) -i websocket
acl is_admin path_beg /admin/
acl is_internal src 192.168.0.0/16 10.0.0.0/8
use_backend api_back if is_api
use_backend static_back if is_static
use_backend websocket_back if is_websocket
use_backend admin_back if is_admin is_internal
default_backend web_backНеобходимо изменить IP адреса и порты на необходимые Вам
В последнем правиле is_admin is_internal - это логическое AND: запрос должен начинаться с /admin/ И приходить из внутренней сети.
Типы сравнений в ACL
path_beg - путь начинается с указанной строки
path_end - путь заканчивается на указанную строку
path_reg - путь соответствует регулярному выражению
hdr(name) - значение заголовка
hdr_beg(name) - заголовок начинается с...
src - IP-адрес источника
method - HTTP-метод
url_param(name) - значение URL-параметра
Маршрутизация по методу
frontend http_front
bind *:80
acl is_read method GET HEAD OPTIONS
acl is_write method POST PUT PATCH DELETE
use_backend read_replicas if is_read
use_backend primary_back if is_write
default_backend read_replicas
Модификация запросов
HAProxy может изменять запросы на лету:
frontend http_front
bind *:80
http-request set-header X-Real-IP %[src]
http-request set-header X-Request-ID %[uuid()]
http-request del-header X-Debug
http-request set-path /api/v2%[path] if { path_beg /api/v1 }
default_backend http_back
Модификация ответов
backend http_back
http-response set-header X-Content-Type-Options nosniff
http-response set-header X-Frame-Options DENY
http-response set-header X-XSS-Protection "1; mode=block"
http-response del-header Server
server web1 192.168.1.10:8080 checkНеобходимо изменить IP адреса и порты на необходимые Вам
Страница статистики
Встроенная статистика
frontend stats
bind *:8404
stats enable
stats uri /stats
stats refresh 10s
stats auth admin:your_secure_password
stats admin if TRUEНеобходимо изменить пароль на Ваш
Параметр stats admin if TRUE включает административный интерфейс - возможность включать и выключать серверы через веб-интерфейс.
Веб интерфейс:

Готовая конфигурация для старта
Собраная конфигурация со всеми рекомендациями с этой статти:
global
log /dev/log local0
chroot /var/lib/haproxy
stats socket /var/run/haproxy.sock mode 660 level admin
user haproxy
group haproxy
daemon
maxconn 50000
nbthread 4
defaults
log global
mode http
option httplog
option dontlognull
option forwardfor
option http-server-close
timeout connect 5s
timeout client 30s
timeout server 30s
timeout http-request 10s
retries 3
frontend http_front
bind *:80
http-request set-header X-Real-IP %[src]
default_backend http_back
backend http_back
balance roundrobin
option httpchk GET /health
http-check expect status 200
server web1 192.168.1.10:8080 check inter 3s fall 3 rise 2
server web2 192.168.1.11:8080 check inter 3s fall 3 rise 2
frontend stats
bind *:8404
stats enable
stats uri /stats
stats refresh 10s
stats auth admin:change_meНеобходимо изменить IP адреса и порты на необходимые Вам, также и пароль для статистики
Измените адресы серверов на свои, также измените пароль для статистии та проверьте конфигурацию командой haproxy -c -f /etc/haproxy/haproxy.cfg.