Автодискавери через DNS в HAProxy закрывает типичные задачи DevOps: автоматически подцеплять появляющиеся/исчезающие инстансы приложения, выполнять healthcheck и балансировать трафик без перезапусков прокси. Ключевые кирпичики — секция resolvers, директива server-template и поддержка DNS SRV. В этой статье соберём боевую конфигурацию, разберёмся с TTL, весами, отладкой и практическими нюансами. Запускать HAProxy удобно на изолированном инстансе — подойдёт управляемый VDS.
Когда пригодится DNS-based service discovery
Классическая статическая конфигурация HAProxy неудобна в динамических средах: контейнеры перезапускаются, инстансы масштабируются, адреса меняются. С DNS-discovery HAProxy подписывается на DNS-имя или SRV-запись и периодически актуализирует пул серверов, создавая/удаляя их на лету. Перезагрузка не требуется — достаточно грамотной секции resolvers и server-template в backend.
Типичные источники записей:
- Consul и CoreDNS отдают
SRVс портами и весами. - Kubernetes headless Service отдает множество
A/AAAAзаписей для pod-ов (SRV — для named ports). - Собственная зона DNS с A/AAAA/SRV для микросервисов.
Главное преимущество — без перезагрузок. HAProxy сам добавляет/удаляет серверы, обновляет порты и веса (при SRV) и не рвёт активные соединения.
Быстрый старт: resolvers и A/AAAA discovery
Начнём с простейшего: динамическое обнаружение множества адресов по одному имени. Секция resolvers описывает, к каким DNS-серверам обращаться и как кэшировать ответы. Затем в backend используем server-template для автосоздания серверов.
global
log 127.0.0.1 local0
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
daemon
resolvers dns_internal
nameserver dns1 10.0.0.53:53
accepted_payload_size 8192
resolve_retries 3
timeout resolve 1s
timeout retry 1s
hold valid 10s
hold nx 30s
hold refused 30s
hold other 30s
hold timeout 5s
hold obsolete 30s
frontend fe_http
bind :80
default_backend be_app
backend be_app
balance roundrobin
option httpchk GET /health
http-check expect status 200
# Автоматически создать до 20 серверов из A/AAAA записей
server-template app 20 app.internal.local:8080 resolvers dns_internal resolve-prefer ipv4 check inter 2s rise 2 fall 3 init-addr last,libc
Что происходит:
server-template app 20 app.internal.local:8080— создаёт до 20 серверов, основываясь на количестве адресов в ответе A/AAAA по имениapp.internal.local. Порт фиксирован (8080).init-addr last,libc— при старте попытается взять последний известный адрес, затем — одноразово спросит системный резолвер, чтобы старт был быстрым даже при временных проблемах с DNS.hold obsolete 30s— если резолвер недоступен, HAProxy ещё 30 секунд удержит последнюю валидную выборку адресов, что снижает флаппинг.accepted_payload_size 8192— полезно, когда список записей велик (EDNS0).
DNS SRV: порты и веса из DNS
SRV-записи удобны, когда у разных инстансов разные порты или нужно передавать веса из DNS. Формат имени SRV: _service._proto.name, например: _http._tcp.app.internal.local. Ответ SRV содержит целевой хост, порт, вес и приоритет.
backend be_app_srv
balance roundrobin
option httpchk GET /health
http-check expect status 200
# Автодискавери через SRV: порт и веса возьмутся из DNS
server-template app 30 _http._tcp.app.internal.local resolvers dns_internal resolve-prefer ipv4 check inter 2s rise 2 fall 3 init-addr none resolve-opts allow-dup-ip
Ключевые моменты:
- SRV требует A/AAAA для целевых имён. Резолвер должен уметь идти по цепочке SRV → A/AAAA.
allow-dup-ipдопускает одинаковые IP на разных серверах (полезно, если несколько инстансов слушают разные порты одного IP).- Вес из SRV обычно маппится в
server weight. Для управляемого сплита трафика задавайте веса в DNS. Если веса из DNS вам не нужны, используйтеresolve-opts ignore-weight.
Про приоритеты SRV
Приоритет в SRV нужен для фейловера между группами инстансов. На практике многие предпочитают управлять приоритетом через разные backends или через внешний оркестратор, а SRV использовать прежде всего для портов и весов. Если вы опираетесь на приоритет из SRV, обязательно протестируйте поведение с отказами низкоприоритетной и высокоприоритетной групп, чтобы убедиться, что оно соответствует вашим ожиданиям.

Healthcheck: TCP, HTTP и TLS к апстриму
В большинстве случаев достаточно option httpchk с проверкой кода ответа. Для SRV порт подтягивается из DNS, и чек автоматом попадает на правильный порт.
# TCP-чек (минималистичный)
backend be_tcp
balance source
server-template t 20 _tcp._tcp.service.local resolvers dns_internal check inter 2s fall 3 rise 2
# HTTP-чек по пути /health
backend be_http
option httpchk GET /health
http-check expect status 200
server-template h 20 _http._tcp.app.local resolvers dns_internal check inter 2s
# TLS к апстриму с SNI и без проверки сертификата (внутренняя сеть)
backend be_https
option httpchk GET /ready
http-check expect status 200
server-template s 20 _https._tcp.app.local resolvers dns_internal check inter 2s ssl verify none sni str(app.local)
Рекомендации:
- Чеки делайте дешевыми для приложения: отдавайте 200 быстро и без тяжёлых зависимостей (БД, внешние API), если вам нужно проверять именно приём трафика, а не глубокое здоровье.
- Интервалы
inter, порогиrise/fallподбирайте так, чтобы снижение/поднятие не было слишком резким. Часто подходятinter 2s,rise 2,fall 3.
Тюнинг resolvers: стабильность и производительность
Секция resolvers критична для стабильности автодискавери. Вот набор опций, которые на практике закрывают большинство кейсов:
accepted_payload_size 4096..8192— для длинных ответов (много SRV/A/AAAA).timeout resolveиtimeout retry— короткие таймауты уменьшают залипание на резолве.resolve_retries— 2–3 обычно достаточно.hold *— задают, как долго удерживать адреса при разных ошибках резолва, чтобы снизить флаппинг.
Пример «стрессоустойчивого» профиля:
resolvers dns_cluster
nameserver ns1 10.10.0.10:53
nameserver ns2 10.10.0.11:53
accepted_payload_size 8192
resolve_retries 3
timeout resolve 1s
timeout retry 1s
hold valid 10s
hold nx 30s
hold refused 30s
hold other 30s
hold timeout 5s
hold obsolete 45s
Практика показывает, что hold obsolete порядка 30–60 секунд часто спасает от лавинообразных отключений при перезагрузке/отказе резолверов, сохраняя последнюю валидную выборку на короткое время.
Лучшие практики для A/AAAA vs SRV
- Если все инстансы на одном порту — используйте A/AAAA. Конфигурация проще.
- Если порты и веса различаются — SRV удобнее и выразительнее.
- Для Kubernetes headless Service чаще всего достаточно A/AAAA. SRV пригодится для named ports и когда важно хранить веса в DNS.
- Избегайте слишком коротких TTL в источнике DNS: частые обновления увеличивают нагрузку и шум.
Миграция со статических серверов на server-template
Пошаговый план, который минимизирует риски:
- Заведите отдельный
backendсserver-template, подключите такой жеhealthcheck. - Убедитесь, что все целевые имена резолвятся как ожидается (проверяйте через
dig, лог HAProxy и Runtime API). - Временно направьте долю трафика через новый backend (через ACL-ы, карту, сплит) и посмотрите метрики/логи.
- Переключите основной трафик после проверки и оставьте старый backend в резерве на короткое время.
Набор «повседневной» отладки
Команды помогут быстро понять, что происходит с резолвом и пулом:
# Посмотреть SRV и A/AAAA ручками
# На узле с HAProxy (или рядом)
dig +short _http._tcp.app.internal.local SRV
dig +short app-01.node.internal A
# Runtime API: состояние резолверов и серверов
# Путь сокета определён в global: stats socket /run/haproxy/admin.sock
socat - UNIX-CONNECT:/run/haproxy/admin.sock <<< "show resolvers"
socat - UNIX-CONNECT:/run/haproxy/admin.sock <<< "show servers state"
# Временно вывести сервер из ротации (напр., для отладки)
socat - UNIX-CONNECT:/run/haproxy/admin.sock <<< "set server be_app/app1 state maint"
# Вернуть в работу
socat - UNIX-CONNECT:/run/haproxy/admin.sock <<< "set server be_app/app1 state ready"
Включайте информативный лог, чтобы видеть события резолва и изменения пула. В global и defaults задайте приёмник логов и формат, а затем анализируйте добавление/удаление серверов и результаты healthcheck.

Частые проблемы и решения
- SRV есть, но бэкенд пуст: проверьте, что у целевых имён из SRV действительно существуют A/AAAA.
- Флаппинг при проблемах с DNS: увеличьте
hold obsoleteи проверьте доступность резервного резолвера. Убедитесь, что сеть не режет фрагментированные UDP-пакеты. - Большой список инстансов: поднимите
accepted_payload_sizeдо 8192 и при необходимости разрешите TCP-запросы к резолверу. - Дубликаты IP при SRV: добавьте
resolve-opts allow-dup-ip, если разные инстансы слушают разные порты одного адреса. - HTTP 503 при переключениях: смягчите
rise/fall, уменьшите интервал чека и убедитесь, что/healthдоступен сразу после старта процесса. - Нужен управляемый трафик-сплит: задавайте веса в SRV или используйте отдельные записи/имена и разные
server-templateв одном backend.
Пример «собранной» конфигурации
global
log 127.0.0.1 local0
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
daemon
defaults
mode http
log global
option httplog
timeout connect 3s
timeout client 30s
timeout server 30s
resolvers dns_app
nameserver ns1 10.0.0.10:53
nameserver ns2 10.0.0.11:53
accepted_payload_size 8192
resolve_retries 3
timeout resolve 1s
timeout retry 1s
hold valid 10s
hold nx 30s
hold refused 30s
hold other 30s
hold timeout 5s
hold obsolete 45s
frontend fe_main
bind :80
bind :443 ssl crt /etc/haproxy/certs
http-request set-header X-Request-Id %[unique-id]
default_backend be_srv
backend be_srv
balance roundrobin
option httpchk GET /health
http-check expect status 200
http-reuse always
# SRV discovery: порт и веса подтягиваются из DNS
server-template app 40 _http._tcp.app.svc.local resolvers dns_app resolve-prefer ipv4 check inter 2s rise 2 fall 3 init-addr none resolve-opts allow-dup-ip
backend be_admin
balance roundrobin
# A/AAAA discovery (один порт у всех инстансов)
server-template adm 10 admin.internal.local:8080 resolvers dns_app resolve-prefer ipv4 check inter 2s rise 2 fall 3 init-addr last,libc
Для HTTPS на фронтенде нужен валидный сертификат — удобнее сразу оформить и выпускать через SSL-сертификаты. Если доменов много и вы используете wildcard, посмотрите про автоматизацию DNS‑01 и выпуск wildcard‑сертов: автоматизация выпуска wildcard по DNS‑01.
Производительность и безопасная эксплуатация
- DNS-запросы асинхронны и экономны, но не злоупотребляйте малыми интервалами чека и крошечными TTL.
- Не указывайте публичные DNS для внутренней резолюции. Держите резолверы рядом с HAProxy.
- Следите за логами резолвера и HAProxy: timeouts, truncated, refused, nx.
- Поднимайте лимит дескрипторов и мониторьте количество серверов в пуле, ошибки чека и отказ резолва.
Когда SRV лучше не использовать
- Нужна жёсткая фиксация портов и простота — A/AAAA читаются проще.
- Резолвер не гарантирует своевременную цепочку SRV → A/AAAA или зоны разнородные.
- Сложная логика приоритетов — прозрачнее держать отдельные backends и переключать их политиками.
Чек-лист внедрения
- Версия HAProxy 2.x (для комфортной работы с
server-template, SRV, Runtime API). - Здоровый резолвер-кластер, успешные тесты
digна SRV и A/AAAA. - Корректные
holdиaccepted_payload_size. - Понятный и быстрый
/healthс валидной семантикой. - Мониторинг: ошибки чека, изменения пула, показатели резолва, latency.
Итоги
Пара resolvers + server-template превращает HAProxy в полноценный участник сервисной сетки: он сам обновляет список апстримов, подхватывает порты и веса из SRV, грамотно переживает кратковременные сбои DNS и не требует перезагрузок. Критично аккуратно настроить резолверы, подобрать «держатели» на ошибки и выстроить лёгкие healthchecks — тогда автодискавери будет работать спокойно и предсказуемо.


