6 ноября 2026 г.

Балансировщик нагрузки HAProxy на Ubuntu 24.04: round robin, проверки, отказоустойчивость

HAProxy на VPS: round robin, проверки /health, веса, страница статистики. Убили бэкенд — трафик ушёл на остальные. Ubuntu 24.04.

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

Коротко. sudo apt install haproxy ставит HAProxy 2.8 на Ubuntu 24.04. В /etc/haproxy/haproxy.cfg добавьте frontend (куда приходят запросы) и backend (серверы за ним). Включите option httpchk и check у каждого сервера — тогда мёртвые выводятся из работы сами. Перед systemctl reload haproxy всегда запускайте haproxy -c -f /etc/haproxy/haproxy.cfg. Один балансировщик сам по себе единая точка отказа: решите, насколько это важно именно вам.

Всё проверено на тестовом VPS: Ubuntu 24.04.5, HAProxy 2.8.30 из штатного репозитория apt. Чтобы уместить стенд на одной машине, три «сервера приложения» — маленькие скрипты на Python на портах 9001, 9002 и 9003 адреса 127.0.0.1. В жизни это отдельные серверы или контейнеры. Каждая заглушка отвечает на любой путь своим именем и возвращает 200 на /health (обычный python3 -m http.server не подойдёт: там ответ 404 и сервер будет помечен как DOWN). Выводы ниже взяты из этого прогона. TLS и методы балансировки кроме round robin мы здесь не тестировали и в нужных местах так и пишем.

Зачем нужен балансировщик

Причин две, и это разные задачи.

  • Мощность. Один процесс держит ограниченное число запросов. Несколько копий за балансировщиком делят нагрузку.
  • Живучесть. Серверы падают, упираются в память, перезагружаются после обновлений. С балансировщиком впереди упавшая копия пропускается, а обновлять копии можно по одной.

Если вам нужно лишь «чтобы сайт не лежал минуту при выкладке», балансировщик, возможно, не нужен: хватит обратного прокси и скрипта перезапуска. Начинайте с балансировщика, когда копий приложения уже больше одной или скоро будет.

Установка HAProxy

Ubuntu поставляет HAProxy в своём репозитории, лишние источники не нужны. Вторая команда ставит пакет, третья показывает версию — чтобы вы знали, по какой документации сверяться:

sudo apt update
sudo apt install -y haproxy
haproxy -v

На нашем сервере строка вывода начиналась с HAProxy version 2.8.30-0ubuntu0.24.04.2. Пакет сразу запускает службу, но конфиг по умолчанию ничего не слушает, поэтому вреда нет. systemctl is-active haproxy выдал active.

Конфиг: frontend, backend, проверка

HAProxy читает один файл — /etc/haproxy/haproxy.cfg. Сверху пакет кладёт секции global и defaults; не трогайте их, свои секции дописывайте ниже. Мы добавили вот что:

frontend web
    bind 127.0.0.1:8080
    mode http
    option forwardfor
    default_backend app
backend app
    mode http
    balance roundrobin
    option httpchk GET /health
    http-check expect status 200
    default-server inter 2s fall 2 rise 2
    server app1 127.0.0.1:9001 check
    server app2 127.0.0.1:9002 check
    server app3 127.0.0.1:9003 check

Построчно:

  • frontend web — «дверь». bind 127.0.0.1:8080 — адрес и порт, которые он слушает. На настоящем сервере вы напишете bind *:80. Мы привязались к localhost, чтобы лабораторию не было видно снаружи.
  • mode http — HAProxy понимает HTTP-запросы, а не просто поток байтов TCP. Нужно для forwardfor, заголовков и маршрутизации по HTTP.
  • option forwardfor — добавляет заголовок X-Forwarded-For с адресом клиента: иначе каждый бэкенд видит только адрес балансировщика.
  • default_backend app — всё отправлять в группу app.
  • balance roundrobin — серверы берутся по очереди: 1, 2, 3, 1, 2, 3.
  • option httpchk GET /health вместе с http-check expect status 200 — раз в несколько секунд HAProxy запрашивает у каждого сервера /health. Любой ответ, кроме 200, считается сбоем. Если у приложения виртуальные хосты, проверке может понадобиться заголовок Host: см. http-check send в документации. Сделайте в приложении дешёвую страницу /health, которая проверяет действительно важное, например живое ли соединение с базой.
  • default-server inter 2s fall 2 rise 2 — проверка каждые 2 секунды; два сбоя подряд выводят сервер из работы, две удачи подряд возвращают. inter 2s и rise 2 — значения по умолчанию; по умолчанию fall равен 3, мы поставили 2, чтобы демонстрация шла быстрее. Для нагруженного приложения интервал можно увеличить.
  • check в конце каждой строки server — без этого слова сервер не проверяется, и HAProxy продолжает слать запросы мёртвому. Это частое упущение.

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

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

Нужна строка Configuration file is valid. Затем применяем:

sudo systemctl restart haproxy
ss -ltnp | grep haproxy

В выводе ss был 127.0.0.1:8080 от процесса haproxy. Балансировщик работает.

Round robin в деле

Каждый бэкенд отвечает своим именем. Мы отправили шесть запросов:

for i in 1 2 3 4 5 6; do curl -s http://127.0.0.1:8080/; done

Ответы пришли строго в порядке app1, app2, app3, app1, app2, app3. Round robin устроен просто: не помнит, кто клиент, просто берёт следующий сервер из списка. Если приложение хранит сессию входа в памяти одного процесса, это проблема: следующий запрос может попасть на другую копию. Либо храните сессии в общем месте (база, Redis или Valkey), либо смотрите в документации HAProxy варианты balance source и привязку по cookie. Мы их не тестировали.

Убиваем один бэкенд и смотрим

Это главное в упражнении. Мы остановили второй процесс приложения и подождали шесть секунд — втрое больше интервала проверки. Затем ещё шесть запросов (в каждой строке ответа для краткости убрали хвост , X-Forwarded-For=…):

hello from app1
hello from app3
hello from app1
hello from app3
hello from app1
hello from app3

Ни одного неудавшегося запроса, ни одного ответа от app2. HAProxy вывел его из работы сам. Почему, видно в статистике. Мы взяли её в формате CSV командой curl -s 'http://127.0.0.1:8404/stats;csv' (секция listen stats из нашего конфига, ниже) и оставили пять столбцов (строку самого backend опустили):

app app1 UP L7OK 0
app app2 DOWN L4CON 2
app app3 UP L7OK 0
  • UP / DOWN — текущее состояние.
  • L7OK — последняя проверка прошла на уровне приложения (HTTP 200). L4CON — проверка даже не смогла открыть соединение; здесь это «в соединении отказано».
  • последнее число — неудачные проверки, посчитанные, пока сервер был UP. Получилось 2 из-за fall 2: после второй неудачи сервер ушёл в DOWN.

То же самое есть в системном журнале. В journalctl -u haproxy появляется строка вроде Server app/app2 is DOWN, reason: Layer4 connection problem, info: "Connection refused". Повесьте оповещение на эту строку или на страницу статистики.

Затем мы снова запустили app2. После двух успешных проверок он вернулся в работу, и в шести ответах снова были все три имени.

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

Допишите эту секцию в тот же файл, затем выполните проверку и перезагрузку, описанные ниже:

listen stats
    bind 127.0.0.1:8404
    mode http
    stats enable
    stats uri /stats
    stats refresh 5s

Она отдаёт живую таблицу по каждому серверу: состояние, запросы, ошибки, время ответа. Мы читали только CSV через curl. HTML-версия открывается в браузере по адресу /stats; раз мы привязали её к localhost, заходите с компьютера через SSH-туннель (ssh -L 8404:127.0.0.1:8404 user@ваш-сервер). Не привязывайте её к публичному адресу без пароля: страница рассказывает посторонним, как устроен ваш бэкенд.

Веса: сильный сервер берёт больше

Серверы редко одинаковы. Мы задали app1 weight 3 (по умолчанию 1) и перезагрузили конфиг. На десять запросов вышло:

      6 hello from app1
      2 hello from app2
      2 hello from app3

Это 3 : 1 : 1, как и просили. Важна команда перезагрузки:

haproxy -c -f /etc/haproxy/haproxy.cfg && sudo systemctl reload haproxy

reload запускает новый процесс с новым конфигом и дает старому завершить открытые соединения, так что пользователей не обрывает. Знак && означает: перезагрузка выполнится, только если проверка прошла. Ещё один метод, balance leastconn (отправлять на сервер с наименьшим числом активных соединений), подходит для долгих соединений; он описан в документации HAProxy, мы его не тестировали.

Что будет, если конфиг с ошибкой

Мы заменили в копии файла roundrobin на roundrobinn и проверили её:

[ALERT] (36471) : config : parsing [/tmp/hap_bad.cfg:44] : balance only supports 'roundrobin', 'static-rr', 'leastconn', 'source', 'uri', 'url_param', 'hdr(name)' and 'rdp-cookie(name)' options.
[ALERT] (36471) : config : Error(s) found in configuration file : /tmp/hap_bad.cfg
[ALERT] (36471) : config : Fatal errors found in configuration.

Код возврата был 1, в сообщении есть имя файла и номер строки, а живая служба осталась нетронутой — мы проверяли копию. Поэтому мы всегда запускаем -c первым.

Когда лежат все бэкенды

Мы остановили все три. Балансировщику спрашивать некого, и клиент получает:

HTTP/1.0 503 Service Unavailable

а в журнале появляется backend 'app' has no server available!. Сам HAProxy жив, но ответ — ошибка. Показать страницу поприветливее можно через errorfile 503 (см. документацию), а слово backup у сервера оставит его запасным: он получит трафик, только когда остальных не станет.

Адрес клиента и предостережение

С option forwardfor бэкенд получает адрес клиента в X-Forwarded-For. В лаборатории все клиенты были локальные, поэтому в заголовке стоял 127.0.0.1. Затем мы отправили из curl собственный заголовок X-Forwarded-For: 203.0.113.7, и приложение первым вывело подделанное значение. Значит, клиент может выдумать этот заголовок. Настройте приложение или веб-сервер перед ним так, чтобы доверять адресу, добавленному вашим балансировщиком, а не первому значению в списке. На внешнем балансировщике можно сначала убрать собственный заголовок клиента: http-request del-header X-Forwarded-For.

HTTPS

Документация описывает завершение TLS строкой вроде bind *:443 ssl crt /etc/haproxy/certs/site.pem, где в файле лежат вместе сертификат и ключ. Сертификат в этой лаборатории мы не настраивали, поэтому готовый проверенный рецепт не заявляем. Если не хочется возиться с сертификатами вручную, проще может оказаться Caddy или Nginx впереди, а HAProxy за ним или вместо него.

Балансировщик — единая точка отказа

Если упадёт VPS с HAProxy, до живых бэкендов не дойдёт ничего. Три способа с этим жить: принять (небольшой проект), держать конфиг балансировщика настолько простым, чтобы собрать его за десять минут на свежем сервере, либо запустить два балансировщика и переключать адрес в DNS или общим IP. Последнее мы здесь не проверяли. Для большинства проектов в одиночку достаточно маленького, редко меняемого сервера под балансировщик: обычно он надёжнее приложения за ним: в нём мало движущихся частей.

Какого размера сервер под балансировщик

HAProxy экономен. Для обычной пересылки HTTP обычно хватает самого маленького тарифа (нагрузку мы не мерили); нагрузка растёт с числом соединений и с работой TLS, так что при добавлении HTTPS следите за процессором. Держите балансировщик и бэкенды в одной локации, чтобы запрос не пересекал континент дважды; лишний переход мы не измеряли.

Частые вопросы

HAProxy или Nginx как балансировщик?

Справятся оба. Nginx — прежде всего веб-сервер, а потом балансировщик; открытая версия проверяет серверы только пассивно, замечая неудавшиеся запросы. HAProxy создан для балансировки, проверяет серверы активно (как мы показали) и умеет отдавать страницу статистики. Если Nginx у вас уже стоит и нужна простая балансировка, он подойдёт.

Как обновлять приложение без простоя?

Выкладывайте на один бэкенд, пока остальные работают, убедитесь, что его /health отвечает 200, и переходите к следующему. При включённых проверках HAProxy сам вернёт сервер после нужного числа успешных проверок (rise). Смежный рецепт — в статье про несколько сервисов вместе в Docker Compose.

Нужно ли балансировщику много памяти?

Нет. Память зависит от числа одновременных соединений; прежде чем выбирать размер, проверьте на своём трафике.

Может ли HAProxy балансировать не только сайты?

Да, в режиме mode tcp он пересылает любой TCP-трафик, например базу данных или любую другую TCP-службу. Мы разобрали только mode http и ничего другого не тестировали.

Почему не обойтись DNS с несколькими адресами?

DNS round robin раздаёт адреса, не зная, какие серверы живы, а клиенты кэшируют ответ. Мёртвый сервер продолжает получать долю запросов, пока вы не правите DNS и не истекут старые записи. Балансировщик с проверками при наших настройках (в тесте) реагирует за секунды.

Итоги

  • Установка: apt install haproxy; на Ubuntu 24.04 это версия 2.8.
  • Минимальный конфиг: frontend, backend, balance roundrobin и check у каждого сервера.
  • Добавьте option httpchk GET /health, чтобы выводился не только выключенный сервер, но и плохо отвечающий. В нашем тесте остановленный бэкенд ушёл из ротации за секунды и вернулся сам.
  • Перед каждой перезагрузкой — haproxy -c -f ...; на живой службе используйте reload, а не restart.
  • Не доверяйте X-Forwarded-For слепо и не открывайте страницу статистики наружу.
  • Один балансировщик — одна точка отказа. Решайте это сознательно.

Что дальше

Если бэкенды — контейнеры, Docker Compose покажет, как запускать несколько сервисов вместе, а это руководство поможет, когда образы и журналы начнут съедать диск бэкенда.

PUBLISHED
6 ноября 2026 г.
AUTHOR
HIP-HOSTING
LANGUAGES
EN · RU