HAProxy + Keepalived: отказоустойчивый балансировщик нагрузки для корпоративных сервисов
Семёнов Евгений Сергеевич, директор АйТи Фреш. HAProxy — мой любимый инструмент для балансировки. Лёгкий, быстрый, с отличной документацией, справляется с десятками тысяч соединений на скромном железе. Но сам HAProxy — точка отказа. Падает балансировщик — падает всё, что за ним. Поэтому в production я никогда не ставлю один HAProxy, только пару с Keepalived поверх. В этой статье покажу полный путь: от установки до переключения за 3 секунды при отказе активного узла.
Задача и архитектура
Представим задачу: три веб-сервера с Nginx и 1С веб-клиентом, трафик 500 req/sec в пиках, нужен один входной HTTPS-адрес 10.10.10.100. При падении одного веб-сервера трафик переходит на оставшиеся, при падении балансировщика — запасной подхватывает виртуальный IP за несколько секунд. Никаких разрывов пользовательских сессий.
Архитектура:
- VIP 10.10.10.100 — плавающий адрес, который видит DNS и клиенты.
- lb01 10.10.10.101 — MASTER, HAProxy + Keepalived, приоритет 150.
- lb02 10.10.10.102 — BACKUP, HAProxy + Keepalived, приоритет 100.
- web01/02/03 10.10.10.201-203 — бэкенды Nginx + 1С.
- VRRP instance 51, интерфейс eth0, authentication PASS.
Я всегда беру лёгкие виртуалки под балансировщики: 2 vCPU / 2 ГБ RAM / 20 ГБ диска, Debian 12. HAProxy на таком железе тянет 20-30k req/sec без проблем.
Установка HAProxy и Keepalived
На обеих виртуалках ставим пакеты из официального репозитория Debian. Я использую HAProxy 2.8 LTS из backports — у неё лучшая поддержка HTTP/2 и TLS 1.3.
# На lb01 и lb02
sudo apt update
sudo apt install -y haproxy keepalived
# Включаем forwarding и non-local bind (нужно для VIP)
cat << EOF | sudo tee /etc/sysctl.d/99-haproxy.conf
net.ipv4.ip_nonlocal_bind = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system
Параметр ip_nonlocal_bind позволяет HAProxy слушать VIP даже на пассивной ноде — иначе бэкап-нода при поднятии адреса не сможет принять соединения.
Конфиг HAProxy
Один конфиг на обеих нодах, идентичный. Я всегда синхронизирую через rsync из git-репозитория при деплое.
# /etc/haproxy/haproxy.cfg
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
user haproxy
group haproxy
daemon
maxconn 20000
ssl-default-bind-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5s
timeout client 30s
timeout server 30s
retries 3
frontend web_https
bind *:80
bind *:443 ssl crt /etc/haproxy/certs/portal.corp.ru.pem alpn h2,http/1.1
http-request redirect scheme https unless { ssl_fc }
http-request set-header X-Forwarded-Proto https
default_backend web_pool
backend web_pool
balance leastconn
cookie SRVID insert indirect nocache
option httpchk GET /health HTTP/1.1\r\nHost:\ portal.corp.ru
http-check expect status 200
server web01 10.10.10.201:443 check ssl verify none cookie w1
server web02 10.10.10.202:443 check ssl verify none cookie w2
server web03 10.10.10.203:443 check ssl verify none cookie w3
listen stats
bind 127.0.0.1:9000
stats enable
stats uri /
stats refresh 5s
stats admin if TRUE
Важные моменты: balance leastconn — выбирает бэкенд с наименьшим числом соединений, лучше чем round-robin при длинных запросах. Cookie SRVID — sticky sessions, привязка клиента к бэкенду через cookie. Option httpchk с HTTP 1.1 — проверка живости бэкендов каждые 2 секунды.
Конфиг Keepalived на MASTER
Keepalived — это демон VRRP, который присваивает VIP одному из узлов. Конфиг на lb01 и lb02 отличается только ролью и приоритетом.
# /etc/keepalived/keepalived.conf на lb01 (MASTER)
global_defs {
router_id LB01
script_user root
enable_script_security
}
vrrp_script chk_haproxy {
script "/usr/bin/killall -0 haproxy"
interval 2
weight 2
fall 2
rise 2
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 150
advert_int 1
authentication {
auth_type PASS
auth_pass ДлинныйПароль17Знаков!
}
virtual_ipaddress {
10.10.10.100/24 dev eth0
}
track_script {
chk_haproxy
}
notify_master "/etc/keepalived/notify.sh MASTER"
notify_backup "/etc/keepalived/notify.sh BACKUP"
notify_fault "/etc/keepalived/notify.sh FAULT"
}
Конфиг Keepalived на BACKUP
# /etc/keepalived/keepalived.conf на lb02 (BACKUP)
global_defs {
router_id LB02
script_user root
enable_script_security
}
vrrp_script chk_haproxy {
script "/usr/bin/killall -0 haproxy"
interval 2
weight 2
fall 2
rise 2
}
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass ДлинныйПароль17Знаков!
}
virtual_ipaddress {
10.10.10.100/24 dev eth0
}
track_script {
chk_haproxy
}
}
Отличия: state BACKUP вместо MASTER, priority 100 вместо 150. Всё остальное идентично, особенно virtual_router_id и auth_pass — иначе ноды не увидят друг друга.
Скрипт уведомления о переключении
Когда происходит транзиция MASTER-BACKUP, я всегда отправляю алерт в Telegram — это критичное событие, которое нужно знать в реальном времени.
#!/bin/bash
# /etc/keepalived/notify.sh
TYPE=$1
BOT_TOKEN="1234567890:AAA..."
CHAT_ID="-1001234567890"
HOST=$(hostname)
TIME=$(date '+%Y-%m-%d %H:%M:%S')
curl -sSf -X POST "https://api.telegram.org/bot$BOT_TOKEN/sendMessage" \
-d "chat_id=$CHAT_ID" \
-d "text=HAProxy VRRP transition: $HOST → $TYPE at $TIME"
logger -t keepalived-notify "Transition to $TYPE"
sudo chmod +x /etc/keepalived/notify.sh
sudo systemctl enable --now keepalived haproxy
ip addr show eth0 | grep 10.10.10.100
Таблица: рекомендации по параметрам
| Параметр | Значение | Обоснование |
|---|---|---|
| advert_int | 1 сек | Быстрое обнаружение падения, 3 секунды до переключения |
| priority (MASTER) | 150 | Запас 50 пунктов над BACKUP для track_script weight |
| track_script weight | +2 (positive) | Если HAProxy жив — даёт бонус к приоритету |
| fall/rise | 2/2 | Не ведёт к флапам от разовых затыков |
| virtual_router_id | 1-255 | Уникальный в пределах L2-домена, иначе конфликт с другим кластером |
Проверка отказоустойчивости
После запуска делаем целенаправленное тестирование, не на production. Я всегда проверяю три сценария:
- Падение HAProxy на MASTER:
sudo systemctl stop haproxyна lb01 → через 4 секунды VIP переезжает на lb02, алерт в Telegram. - Перезагрузка MASTER:
sudo rebootна lb01 → VIP переезжает, через 2 минуты lb01 возвращается и забирает VIP обратно (если preemption on). - Падение бэкенда: останавливаем nginx на web02 → HAProxy выводит из пула, трафик идёт на web01/web03 без потерь.
Кейс: HA-балансировщик для медицинской сети
В мае 2026 клиент — сеть из 9 клиник в Москве и области, единый портал записи на приём. До нас работал один HAProxy на виртуалке, раз в пару месяцев падал на час из-за разных причин. Запись в клиники закрывалась, пациенты жаловались.
Развернули пару lb01/lb02 на виртуалках в дата-центре МТС, настроили Keepalived с VIP 10.50.0.100, за ним шесть бэкендов с Nginx и PHP-приложением. Сертификат portal.clinic.ru, http/2 включён, gzip, статика кэшируется на балансировщике. Результаты за два месяца работы:
- Ни одного эпизода недоступности портала, хотя lb01 дважды перезагружался на обновлении ядра.
- Время ответа упало с 450 мс до 180 мс за счёт http/2 и keepalive.
- Нагрузка на 40G Mellanox в дата-центре МТС — не выше 8%, запас 12x.
- Клиент отказался от планируемой миграции на облачный балансировщик — нет смысла платить.
Стоимость проекта 110 000 руб за 3 рабочих дня, без железа.
Типичные ошибки
- Один virtual_router_id на несколько кластеров — VIP будет пинг-понгиться между разными парами. У нас на практике это ломало AD в одной компании, пока не нашли.
- Нет track_script — HAProxy упал, но Keepalived этого не знает, VIP остался на мёртвой ноде.
- Strict ARP не настроен — если используете Linux бэкенды с VIP, включайте arp_ignore=1, arp_announce=2.
- Multicast заблокирован на коммутаторе — VRRP работает через 224.0.0.18, если multicast отфильтрован, ноды не видят друг друга, обе становятся MASTER.
- Не синхронизировали конфиги — поправили HAProxy на lb01, забыли на lb02, после переключения всё сломалось. Я всегда кладу конфиги в git и деплою через Ansible.
Развернём отказоустойчивый балансировщик под ключ
Настраиваем HAProxy + Keepalived для корпоративных веб-сервисов, 1С веб-клиента, SQL-прокси, почтовых серверов. Пара виртуалок, VIP, health-checks, SSL termination, алерты — за 2-3 рабочих дня.
Телефон: +7 903 729-62-41
Telegram: @ITfresh_Boss
Семёнов Евгений Сергеевич, директор АйТи Фреш
FAQ — частые вопросы по HAProxy и Keepalived
- Зачем нужен Keepalived поверх HAProxy?
- HAProxy балансирует трафик между бэкендами, но сам становится точкой отказа. Keepalived через VRRP присваивает виртуальный IP активному узлу и автоматически переключает его на резервный при падении.
- Что такое VRRP?
- Virtual Router Redundancy Protocol — протокол выбора мастера в группе роутеров. Один узел держит виртуальный IP, остальные следят за ним через multicast. Если мастер пропал — выбирается новый по приоритету.
- Active-active или active-passive?
- Active-passive проще в настройке и диагностике: один узел работает, второй ждёт. Active-active с двумя VIP и round-robin DNS даёт вдвое больше ёмкости, но сложнее в отладке и требует синхронизации сессий.
- Нужен ли выделенный vlan для VRRP?
- Не обязательно, но желательно. На одном vlan с боевым трафиком VRRP-пакеты могут потеряться при шторме. Я выношу управляющий трафик на отдельный интерфейс или vlan.
- Как проверить, что VIP переключился?
- На активном узле ip addr show покажет виртуальный IP на интерфейсе. На пассивном — его не будет. journalctl -u keepalived пишет все транзиции MASTER/BACKUP.