Сэкономьте 15% на всех хостинговых услугах

Проверьте свои навыки и получите скидку на любой тарифный план

Используйте код: Skills Начать
Рубрики
Администрация Безопасность

Как установить HAProxy с Docker Compose на Ubuntu VPS

One web service on a VPS is easy to expose directly — until you want one clean public front door, the freedom to swap the backend later, or a safer way to stop sending traffic to something broken. That is the point where a proxy stops feeling like “something for big infrastructure teams” and starts feeling practical.

intro

HAProxy fits that role well. Think of it as the traffic manager sitting in front of your application: requests hit HAProxy first, and HAProxy decides where they should go next. You do not need a large cluster to benefit from that. Even on one Ubuntu 24.04 VPS, it gives you a cleaner edge between the internet and the service you are actually running.

This guide keeps the first deployment intentionally structured: one Ubuntu 24.04 VPS, Docker Compose, one HAProxy container, one demo backend, and proof that routing really works.

Почему HAProxy важен еще до того, как он вам понадобится

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

whymatters

Все эти требования указывают на один отсутствующий слой: контролируемую точку входа между интернетом и вашим приложением. HAProxy предоставляет этот слой. Клиенты сначала подключаются к HAProxy, а затем HAProxy решает, куда дальше направить каждый запрос.

Это разделение полезно даже до того, как у вас появится несколько серверов. Оно дает вам более чистый публичный край сейчас и более безопасный путь к последующим изменениям, таким как замена бэкенда, маршрутизация с учетом здоровья и HTTPS. Остальная часть руководства показывает этот паттерн в его простейшей рабочей форме и проверяет его с помощью реального пути запроса.

Краткие термины HAProxy, которые облегчат понимание остального руководства

quick

Вам нужен только небольшой набор терминов, чтобы уверенно следовать первому развертыванию HAProxy. Таблица ниже охватывает термины, которые имеют значение в этом руководстве.

ТерминЗначение на простом языке
🌐 reverse proxyФронтальный сервис, который получает запросы первым и передает их другому внутреннему сервису.
⚖️ load balancerФронтальный слой, который может распределять запросы между несколькими целевыми бэкендами.
🚪 frontendМесто, где клиенты подключаются к HAProxy.
🧩 backendСервис или сервер, на который HAProxy отправляет запрос дальше.
❤️ health checkСпособ для HAProxy заметить, должен ли бэкенд продолжать получать трафик.
🐳 imageУпакованный шаблон приложения, используемый для создания контейнеров.
📦 containerЗапущенный экземпляр образа.

Для этого руководства reverse proxy — это первая ментальная модель, которую нужно иметь в виду. HAProxy находится перед чем-то еще и контролирует передачу. Load balancing — это расширенная возможность, которая становится полезной, когда вы позже добавляете несколько бэкенд-серверов.

Два термина, которые имеют наибольшее значение, когда вы открываете конфиг, — это frontend и backend. Frontend — это место, где прибывает клиент. Backend — это место, куда HAProxy отправляет запрос дальше. Health check важен, потому что он позволяет HAProxy заметить, когда цель должна перестать получать трафик.

Что HAProxy делает хорошо — и что это руководство намеренно пропускает

whatgood

Если представить ваш стек как офисное здание, HAProxy — это стойка регистрации: трафик сначала поступает туда, затем направляется в нужный кабинет и перестает отправляться в кабинет, который явно недоступен.

В этом руководстве это переводится в три задачи, релевантные для начинающих:

  1. принимать входящие HTTP запросы
  2. перенаправлять их на демо-бэкенд
  3. контролировать, достаточно ли здоров бэкенд, чтобы продолжать получать трафик

Это уже полезно с одним бэкендом, потому что дает вам один контролируемый публичный край перед приложением.

Позже этот же паттерн масштабируется чисто. Вы можете заменить бэкенд, добавить больше бэкендов, внедрить HTTPS или позволить HAProxy распределять трафик между несколькими целями вместо одной. Чтобы сохранить первый проход обучаемым, это руководство остается в режиме HTTP и намеренно пропускает TLS termination, ACLs, rate limiting, stick tables и HA пары. Это все реальные темы HAProxy. Они просто не являются правильной отправной точкой для первого рабочего развертывания.

Что вы создаёте и что нужно подготовить в первую очередь

buildingsetup

Перед созданием файлов полезно увидеть финальную структуру стека. Развёртывание в этом руководстве выглядит так:

Client browser or curl
        |
        v
HAProxy frontend (:80)
        |
        v
demo backend service (demo:5678)

Optional local-only validation:
HAProxy stats frontend (127.0.0.1:8404/stats)

Docker Compose — основной инструмент здесь, потому что он делает первоначальную установку воспроизводимой, прозрачной и простой в редактировании. Вместо создания пользовательского образа в первый день вы сохраняете конфиг HAProxy на хосте, монтируете его в контейнер и запускаете весь стек из одного файла. На самоуправляемом Ubuntu VPS — например, на AlexHost VPS — это идеально подходит, потому что структура остаётся простой для проверки.

💡 Совет: Это руководство использует Docker Compose плюс bind-mounted haproxy.cfg намеренно. Это самый прозрачный путь первоначальной установки, потому что вы можете редактировать конфиг прокси напрямую без добавления этапа сборки образа.

Перед началом убедитесь, что у вас есть следующее:

  • Ubuntu 24.04 VPS
  • Установлен Docker Engine
  • Docker Compose v2 доступен через docker compose
  • Доступ к терминалу и разрешение на запуск Docker
  • Порт 80 доступен на хосте
  • Входящий HTTP разрешён, если вы используете UFW или правила брандмауэра на стороне провайдера

Сначала проверьте версию Ubuntu

lsb_release -a

ubuntu-version

Затем подтвердите, что Docker и современный Compose доступны:

docker --version
docker compose version

docker-version

Если обе команды возвращают информацию о версии, сторона контейнерного времени выполнения готова, и вы можете сосредоточиться на HAProxy вместо отвлечения на установку Docker.

Затем убедитесь, что порт 80 ещё не используется, и проверьте, активен ли UFW и разрешён ли уже HTTP:

sudo ss -tlnp | grep -E ':(80)s' || true
sudo ufw status
sudo ufw allow 80/tcp

ufw-status

✏️ ПРИМЕЧАНИЕ: Отсутствие вывода из проверки ss обычно означает, что порт 80 свободен. Если вы видите nginx, apache2, caddy или другой сервис, уже прослушивающий этот порт, сначала это исправьте. Это десятисекундный предварительный шаг, который экономит много путаницы позже.

В примере выше sudo ufw status показывает Status: active, и 80/tcp уже присутствует в списке разрешений. Вот почему sudo ufw allow 80/tcp возвращает Skipping adding existing rule вместо добавления нового правила. Этот вывод нормален и просто означает, что правило брандмауэра уже было на месте.

Создайте папку проекта и файл Compose

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

mkdir -p ~/haproxy-docker
cd ~/haproxy-docker

mkdir

После этого макет должен быть максимально компактным:

~/haproxy-docker/
├── compose.yaml
└── haproxy.cfg

Теперь создайте compose.yaml и используйте это точное содержимое:

services:
  demo:
    image: hashicorp/http-echo:1.0
    command: ["-listen=:5678", "-text=Hello from the HAProxy demo backend"]
    restart: unless-stopped

  haproxy:
    image: haproxy:3.4.1
    depends_on:
      - demo
    ports:
      - "80:80"
      - "127.0.0.1:8404:8404"
    volumes:
      - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
    sysctls:
      net.ipv4.ip_unprivileged_port_start: "0"
    restart: unless-stopped

Этот файл связывает контейнеры вместе, но еще не определяет логику запросов HAProxy. Он сообщает Docker, какие образы запускать, какие порты публиковать и откуда на хосте будет смонтирована конфигурация HAProxy.

Следующие параметры наиболее важны для чистого первого развертывания:

Параметр ComposeЗачем это здесь
hashicorp/http-echo:1.0Предоставляет вам крошечный, предсказуемый демо-бэкенд без необходимости одновременно изучать второй веб-сервер.
haproxy:3.4.1Использует закрепленный стабильный тег вместо latest, что делает руководство менее хрупким со временем.
depends_onЗапускает сервис demo перед HAProxy, что полезно для порядка первого запуска.
80:80Публикует основной HTTP-слушатель на стандартном веб-порту, который ожидают читатели.
127.0.0.1:8404:8404Сохраняет страницу статистики доступной для локальной проверки без публичного раскрытия по умолчанию.
./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:roМонтирует видимый файл конфигурации на хосте в официальный образ HAProxy как только для чтения.
sysctls с net.ipv4.ip_unprivileged_port_start: “0”Позволяет контейнеру HAProxy без привилегий привязываться к низким портам, таким как 80.
restart: unless-stoppedДает вам практический VPS-стандарт: перезапуск после сбоя или перезагрузки, но уважение к намеренной ручной остановке.

Еще один важный момент: в этом файле нет пользовательской сети Docker, потому что Docker Compose автоматически создает сеть по умолчанию. Это дает вам DNS с именами сервисов внутри проекта, поэтому HAProxy сможет достичь бэкенда как demo:5678 без дополнительной настройки.

⚠️ Предупреждение: Порт 80 — это привилегированный порт, поэтому строка sysctls не просто украшение. Изменение сопоставления хоста на 8080:80 не устраняет требование привилегированного порта внутри контейнера, если HAProxy все еще привязывается к :80 внутри.

Написание и валидация минимального файла haproxy.cfg

С настройкой контейнеров на месте HAProxy все еще нуждается в инструкциях о том, где поступает трафик, куда он должен идти и как проверяется здоровье бэкенда. Создайте haproxy.cfg далее:

global
    log stdout format raw local0

defaults
    mode http
    timeout connect 5s
    timeout client 30s
    timeout server 30s

frontend http
    bind :80
    default_backend demo_backend

backend demo_backend
    balance roundrobin
    server demo1 demo:5678 check

frontend stats
    bind :8404
    stats enable
    stats refresh 10s
    stats uri /stats

Это минимальная конфигурация, но не одноразовая. log stdout format raw local0 — это удобный для контейнеров выбор логирования, потому что Docker может легко выводить stdout, а mode http в defaults сохраняет весь пример в режиме HTTP, поэтому поведение слушателя и бэкенда остаются согласованными и читаемыми.

✏️ ПРИМЕЧАНИЕ: Одна деталь стоит упомянуть перед разбором раздела: balance roundrobin установлен явно, потому что более новые версии HAProxy изменили алгоритм бэкенда по умолчанию на random, а roundrobin легче преподавать предсказуемо при первом прохождении.

Вот простое описание каждого раздела:

РазделКлючевые строкиЧто это делает
globallog stdout format raw local0Отправляет логи в stdout, чтобы логирование Docker оставалось простым.
defaultsmode http, timeoutsУстанавливает базовое поведение HTTP и разумные значения тайм-аутов.
frontend httpbind :80, default_backend demo_backendСоздает публичный слушатель и подключает его к определению бэкенда.
backend demo_backendbalance roundrobin, server demo1 demo:5678 checkУказывает HAProxy, какой сервис использовать и контролировать его здоровье.
frontend statsbind :8404, stats enable, stats uri /statsДобавляет опциональную локальную страницу валидации, чтобы позже увидеть статус выполнения.

Вы можете заметить одно отсутствующее: option forwardfor. Это пропуск намеренный в базовом пути. Сохранение исходного IP клиента полезно позже, но это первое развертывание — о доказательстве маршрутизации и здоровья бэкенда, а не о преподавании поведения заголовков с демо-контейнером, который не делает этот сигнал особенно ценным.

💡 Совет: Всегда валидируйте конфигурацию HAProxy перед запуском полного стека. Поскольку эта конфигурация ссылается на бэкенд по имени сервиса Compose (demo), сначала запустите этот бэкенд, чтобы HAProxy мог его разрешить во время валидации.

Запустите валидацию из того же каталога проекта:

docker compose up -d demo
docker compose run --rm --no-deps haproxy haproxy -V -c -f /usr/local/etc/haproxy/haproxy.cfg

success

Если вторая команда заканчивается на Configuration file is valid, вы уже доказали, что HAProxy может правильно разобрать файл и разрешить целевой бэкенд перед запуском любого активного слушателя.

Запустите стек и докажите, что прокси работает

После проверки конфигурации запустите стек в режиме отсоединения:

Поскольку шаг проверки уже запустил demo, эта команда в основном запускает HAProxy и согласовывает полный двухсервисный стек:

docker compose up -d

start-cmpose

Затем проверьте, живы ли оба контейнера:

docker compose ps

compose-status

Это представление процесса — только первая контрольная точка. Оно подтверждает, что Docker запустил контейнеры, но еще не то, что HAProxy успешно маршрутизирует трафик на бэкенд. Следующий запрос проверяет фактический путь данных.

Теперь запустите фактический тест маршрутизации с самого VPS:

curl -i http://127.0.0.1

valid

Сигнал успеха — это HTTP/1.1 200 OK плюс тело ответа, содержащее Hello from the HAProxy demo backend. Некоторые сборки http-echo оборачивают этот текст в крошечный HTML-ответ, поэтому сосредоточьтесь на фразе в теле больше, чем на точном форматировании.

Если вам нужно доказательство на уровне браузера, откройте http://YOUR_SERVER_IP с другой машины.

browser-valid

Для второй поверхности проверки посмотрите страницу статистики только для локального использования с VPS:

curl http://127.0.0.1:8404/stats

На странице статистики наиболее полезные сигналы — это фронтенд с именем http, бэкенд с именем demo_backend, строка сервера с именем demo1, статус показан как UP, и обычно значение последней проверки, такое как L4OK in 0ms. Также имейте в виду одну небольшую особенность Docker: краткий синтаксис depends_on управляет порядком запуска, но не ждет, пока сервис станет здоровым. Если самый первый curl один раз не пройдет сразу после запуска, подождите несколько секунд и попробуйте снова, прежде чем предполагать, что конфигурация неправильна.

Разницу между состоянием процесса и реальным успехом легче понять в табличной форме:

СостояниеЧто это вам говорит
Контейнеры работаютDocker запустил процессы.
curl -i http://127.0.0.1 возвращает 200 OK и демо-фразуHAProxy фактически маршрутизирует трафик на бэкенд.
Страница статистики показывает demo1 как UPHAProxy видит бэкенд как здоровый.

8. Распространённые ошибки при первом запуске и быстрые исправления

mistakes

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

Используйте эту матрицу как быстрый уровень диагностики:

  • HAProxy выходит сразу же

    Вероятная причина: haproxy.cfg отсутствует.

    Быстрое исправление: Убедитесь, что haproxy.cfg находится рядом с compose.yaml.

    Почему это происходит: Официальный образ не поставляется с готовой конфигурацией.


  • Ошибка говорит, что не может открыть /usr/local/etc/haproxy/haproxy.cfg

    Вероятная причина: Неправильный путь bind-mount.

    Быстрое исправление: Проверьте ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro точно.

    Почему это происходит: HAProxy не может запуститься без действительного файла конфигурации.


  • Ошибка говорит Permission denied на порту 80

    Вероятная причина: Проблема с привязкой привилегированного порта.

    Быстрое исправление: Оставьте net.ipv4.ip_unprivileged_port_start: “0” в Compose или переместите HAProxy и опубликованный порт на 8080.

    Почему это происходит: Контейнер работает от непривилегированного пользователя haproxy.


  • Вы изменили маппинг на 8080:80 и всё ещё получаете ошибку привязки

    Вероятная причина: HAProxy всё ещё привязан к :80 внутри контейнера.

    Быстрое исправление: Измените как маппинг хоста, так и внутреннюю строку bind, если вы переходите с порта 80.

    Почему это происходит: Правило привилегированного порта применяется и внутри контейнера.


  • Порт 80 уже используется

    Вероятная причина: Другой сервис занимает порт хоста.

    Быстрое исправление: Повторно запустите проверку ss и остановите или переместите конфликтующий сервис.

    Почему это происходит: Только один процесс может слушать один и тот же порт хоста.


  • Проверка синтаксиса сообщает об unknown keyword или ошибках в конкретной строке

    Вероятная причина: Опечатка в конфигурации HAProxy.

    Быстрое исправление: Повторно запустите проверку синтаксиса и исправьте строку, которую она указывает.

    Почему это происходит: Парсер HAProxy строгий, что полезно, когда вы используете его намеренно.


  • Контейнеры работают, но curl не возвращает демо-ответ

    Вероятная причина: Неправильный путь маршрутизации.

    Быстрое исправление: Перепроверьте default_backend demo_backend, server demo1 demo:5678 check и имя сервиса demo.

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


  • Локальный curl работает, но сайт недоступен снаружи

    Вероятная причина: Брандмауэр или правило безопасности провайдера.

    Быстрое исправление: Откройте порт 80 в UFW и в любом брандмауэре на стороне провайдера.

    Почему это происходит: Локальная публикация может работать даже когда публичный доступ всё ещё заблокирован.

⚠️ Предупреждение: Меняйте по одному элементу за раз. Если вы вслепую редактируете и compose.yaml, и haproxy.cfg, вы значительно затрудняете определение того, является ли сбой проблемой пути файла, проблемой порта или проблемой маршрутизации.

Когда вам нужны быстрые доказательства, держите эти команды под рукой:

docker compose logs haproxy
docker compose ps
docker compose up -d demo
docker compose run --rm --no-deps haproxy haproxy -V -c -f /usr/local/etc/haproxy/haproxy.cfg
sudo ss -tlnp | grep -E ':(80|8404)s' || true

Это три паттерна предупреждений, которые стоит узнавать с первого взгляда:

[ALERT] ... Cannot open configuration file /usr/local/etc/haproxy/haproxy.cfg : No such file or directory
[ALERT] ... Starting frontend http: cannot bind socket (Permission denied) [0.0.0.0:80]
[ALERT] ... parsing [/usr/local/etc/haproxy/haproxy.cfg:12] : unknown keyword 'chekc'; did you mean 'check' maybe?

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

Куда идти после установки

Как только демонстрация с одним бэкендом работает, архитектура уже полезна. Следующий реальный шаг — заменить демо-контейнер вашим фактическим приложением, сохраняя ту же структуру HAProxy. После этого добавьте HTTPS/TLS как отдельный этап, и рассматривайте маршрутизацию на основе доменов и ACL как отдельные темы, а не спешите включать их в эту первую установку.

end

Когда вы будете готовы к более чем одному бэкенду, один и тот же паттерн становится явно значимым:

backend app_backend
    balance roundrobin
    server app1 app1:8080 check
    server app2 app2:8080 check

Для безопасного редактирования конфига с bind-mount сначала проверьте, а затем перезагрузите HAProxy корректно:

docker compose up -d demo
docker compose run --rm --no-deps haproxy haproxy -V -c -f /usr/local/etc/haproxy/haproxy.cfg
docker compose kill -s HUP haproxy

📝 Примечание: Страница статистики намеренно доступна только локально в этом руководстве. Если вы когда-либо откроете её публично, сначала добавьте аутентификацию и элементы управления доступом.

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