Балансировка SMTP-нагрузки
SMTP-балансировщик принимает входящую почту на одном адресе и распределяет SMTP-сессии между backend-серверами SPZGate: поровну, по весам, с основными и резервными серверами, а также по статическим маршрутам — отдельно для отправителей и получателей.
--balancer) на собственный сервер и не затрагивает установленные шлюзы. Он построен на HAProxy с Lua-модулем и работает в контейнере Docker.- Как это работает
- Установка
- Параметры установки
- Настройка балансировки backend-серверов
- Статические маршруты: по отправителю, по получателю
- Журналы и статус
- Частые вопросы
Как это работает
- Клиент подключается к балансировщику и ведёт обычный SMTP-диалог:
EHLO, при необходимостиSTARTTLS,MAIL FROM. - На первой команде
RCPT TOбалансировщик знает отправителя и получателя и выбирает маршрут (см. статические маршруты), а из него — упорядоченный список backend-серверов. - Балансировщик подключается к первому серверу списка: проверяет приветствие, выполняет
EHLO, при TLS-сессииSTARTTLS, передаёт реальный IP клиента командойXCLIENT, затемMAIL FROMиRCPT TO. Если сервер недоступен или не ответил за заданное время, берётся следующий. - Дальше письмо пересылается построчно, а ответы backend-сервера передаются клиенту без изменений. Решения backend-сервера (например, отказ
5xxна получателя или письмо) не считаются сбоем и не вызывают переключения на другой сервер.
RCPT TO. Следующие получатели того же домена идут на тот же сервер. Получатель другого домена в той же сессии получает временный отказ 450 4.3.0 cannot route recipient to a different domain in this session, и отправляющий сервер повторяет его отдельной сессией — так ведут себя все стандартные MTA.Установка
Требования к серверу балансировщика:
- Linux, доступ от имени root (
sudo); - доступ в интернет к
spamorez.ru(загрузка пакета); Docker ставится установщиком автоматически (черезapt, на остальных системах — официальным скриптом); - свободный порт приёма (по умолчанию 25);
- backend-серверы SPZGate, доступные с балансировщика по SMTP, с включённым
XCLIENT(см. ниже).
Включите XCLIENT на backend-серверах
Балансировщик передаёт backend-серверу реальный IP отправителя командой XCLIENT — иначе проверки по спам-листам, greylisting и репутация видели бы только IP балансировщика. На каждом backend-сервере SPZGate создайте в каталоге control почтового сервера файл xclient со значением 1 (внутри контейнера qmail — /var/qmail/control/xclient) и перезапустите почтовую часть:
echo 1 > /var/qmail/control/xclientspzctl restart qmailXCLIENT принимается от любого клиента, а не только от балансировщика. Разрешите доступ к порту 25 backend-серверов только с IP балансировщика (файрвол) или оставляйте их во внутренней сети.Подготовьте имя балансировщика
Балансировщик представляется backend-серверам в EHLO и в приветствии именем, которое задаётся параметром --hostname. Если backend-сервер проверяет HELO, у этого имени должна быть A-запись в DNS на IP балансировщика — иначе приём письма отклоняется ответом вида 550 Invalid HELO, address … not resolved.
Запустите установщик
На сервере балансировщика:
curl -fsSL https://spamorez.ru/spzgate/install.sh | sudo bash -s -- --balancer \ --servers 10.0.0.11,10.0.0.12,10.0.0.13 --hostname bl.example.comУстановщик:
- устанавливает Docker и плагин Docker Compose, если их нет;
- скачивает пакет балансировщика и проверяет его контрольную сумму SHA-256;
- создаёт каталог установки (по умолчанию
/opt/spzgate-balancer) и записывает конфигурацию маршрутовconf/smtp_balancer.conf: серверы из--serversстановятся серверами по умолчанию с равномерным распределением; - при указании
--hostnameзадаёт имя сервера и прописывает его в/etc/hosts; - собирает и запускает контейнер
spz-smtp-balancer(режимnetwork_mode: host, автозапускunless-stopped); - настраивает ротацию журналов (
/etc/logrotate.d/spzgate-balancer) и ставит командуspzctl; - пишет ход установки в журнал (по умолчанию
/var/log/spzgate-balancer-install.log).
Проверка после установки:
spzctl statusnc 127.0.0.1 25220 bl.example.com ESMTPСтруктура каталога установки:
| Путь | Назначение |
|---|---|
conf/smtp_balancer.conf | маршруты и параметры балансировки — основной файл настройки |
conf/haproxy.conf | порт приёма, таймауты сессии, путь к TLS-сертификату |
conf/servercert.pem | сертификат и ключ для STARTTLS со стороны клиентов (поставьте свой, затем пересоберите образ) |
logs/ | журналы: connections.log, backend-error.log, backend-status.log |
docker-compose.yml, Dockerfile, bin/, src/ | описание контейнера, собранный HAProxy и служебные скрипты |
conf/servercert.pem и выполните в каталоге установки docker compose up -d --build.conf/smtp_balancer.conf и каталог logs/ при этом сохраняются, параметр --servers повторно указывать не нужно.Удаление:
cd /opt/spzgate-balancer && docker compose downrm -rf /opt/spzgate-balancer /etc/logrotate.d/spzgate-balancerПараметры установки
Параметры указываются после --balancer:
| Параметр | По умолчанию | Описание |
|---|---|---|
--balancer | — | включает установку только балансировщика (без шлюза SPZGate); обязателен |
--servers IP1,IP2,… | — | backend-серверы по умолчанию (адреса или имена через запятую). Обязателен при первой установке; при обновлении можно не указывать, если конфигурация уже есть |
--backend-port | 25 | порт backend-серверов по умолчанию (default_port в конфигурации) |
--listen-port | 25 | порт, на котором балансировщик принимает почту |
--hostname FQDN | не меняется | имя сервера: hostname ОС, имя в приветствии 220 и в EHLO к backend-серверам |
--dir ПУТЬ | /opt/spzgate-balancer | каталог установки |
--log ФАЙЛ | /var/log/spzgate-balancer-install.log | журнал самой установки |
--log-keep ДНИ | 14 | сколько суток хранить журналы балансировщика (ротация ежедневная, архивы сжимаются) |
--pkg URL|ФАЙЛ | https://spamorez.ru/spzgate/balancer/spzgate-balancer.tar.gz | пакет балансировщика: адрес или локальный файл. Контрольная сумма берётся из файла .sha256 рядом с пакетом |
Пример с нестандартными портами и локальным пакетом:
sudo bash install.sh --balancer --servers mx1.example.com,mx2.example.com \ --backend-port 2525 --listen-port 25 --hostname bl.example.com \ --log /home/admin/balancer-install.log --log-keep 30 --pkg ./spzgate-balancer.tar.gzНастройка балансировки backend-серверов
Вся настройка маршрутов — в файле conf/smtp_balancer.conf. Это текстовый файл с разделами в квадратных скобках, строками ключ = значение и комментариями, начинающимися с #. Файл читается при запуске контейнера, поэтому после правок выполните:
docker restart spz-smtp-balancerРезультат разбора виден в docker logs spz-smtp-balancer: итоговая строка routing loaded: … default_backends=N source_routes=N destination_routes=N и, если есть ошибки, сообщения с номером строки (smtp_balancer.conf:64: cannot parse line (line skipped)). Некорректная строка пропускается, остальные действуют.
Серверы по умолчанию и общие параметры задаются в разделе [routing]. Именно эти серверы получают сессии, для которых не нашлось ни одного статического маршрута:
[routing]order = destination, source, defaultmode = round_robindefault_port = 25connect_timeout = 5health_check_interval = 60backend = 10.0.0.11 priority=1 weight=1backend = 10.0.0.12 priority=1 weight=1| Ключ | По умолчанию | Описание |
|---|---|---|
order | destination, source, default | порядок проверки источников маршрута: по получателю, по отправителю, серверы по умолчанию |
mode | round_robin | round_robin — сессии поочерёдно распределяются внутри группы одного приоритета с учётом весов; failover — всегда первый сервер группы, следующий только при его недоступности |
default_port | 25 | порт для серверов, у которых порт не указан |
connect_timeout | 5 | время в секундах на подключение и SMTP-диалог до RCPT TO; если сервер не уложился, он считается недоступным и берётся следующий |
health_check_interval | 60 | период активной проверки всех серверов в секундах; 0 — отключить |
backend | — | сервер по умолчанию; одна строка — один сервер, строк может быть сколько угодно |
Формат описания сервера (одинаков в [routing], [source] и [destination]):
адрес[:порт] [priority=N] [weight=N]- priority — приоритет, по умолчанию 1. Чем меньше число, тем раньше сервер используется. Серверы с одинаковым приоритетом образуют группу, внутри которой нагрузка распределяется; следующая группа (например, резервная) подключается, только если недоступны все серверы предыдущей;
- weight — вес, по умолчанию 1: доля сессий сервера внутри группы (только в режиме
round_robin).
Равномерное распределение
Все серверы с одинаковыми приоритетом и весом получают сессии по кругу:
backend = 10.0.0.11 priority=1 weight=1backend = 10.0.0.12 priority=1 weight=1backend = 10.0.0.13 priority=1 weight=1Распределение по весам
Более мощному серверу можно отдать большую долю. Ниже первый сервер получает две сессии из трёх:
backend = 10.0.0.11 priority=1 weight=2backend = 10.0.0.12 priority=1 weight=1Основные и резервные серверы
Резервный сервер получает почту, только когда недоступны все основные:
backend = 10.0.0.11 priority=1backend = 10.0.0.12 priority=1backend = 10.0.0.99 priority=2Что происходит при недоступности сервера
Если сервер не принял соединение, не ответил приветствием или EHLO за connect_timeout секунд, сессия переключается на следующий сервер списка — клиент этого не замечает. Каждое переключение записывается в журналы (см. журналы) с причиной: отказ в соединении, сеть или хост недоступны, таймаут и т. д.
Кроме реальных сессий, каждые health_check_interval секунд все серверы всех маршрутов проверяются активно: подключение, приветствие и EHLO (если сервер не знает EHLO — HELO), после первого же ответа балансировщик отправляет QUIT и письмо не передаёт. Результат обновляет статус серверов (spzctl status routes) и журнал ошибок; порядок обхода серверов в новых сессиях он не меняет — недоступный сервер пропускается за время не более connect_timeout.
Статические маршруты
Статические маршруты направляют сессии на выбранные серверы по адресу отправителя или получателя. Каждый маршрут — одна строка вида:
route = ШАБЛОН -> сервер1 [priority=N] [weight=N]; сервер2 …Слева от -> шаблон адреса, справа — один или несколько серверов через ; (формат тот же, что для серверов по умолчанию, включая приоритеты, веса и балансировку по кругу).
Шаблоны, регистр букв не учитывается:
| Шаблон | Что совпадает |
|---|---|
user@example.com | точный адрес |
@example.com | любой адрес домена example.com |
*.example.com | любой поддомен (a.example.com, x.y.example.com), но не сам example.com |
* | любой адрес, включая пустой адрес отправителя <> (отчёты о недоставке) |
Как выбирается маршрут:
- Источники проверяются в порядке, заданном
orderв[routing](по умолчанию: получатель, отправитель, серверы по умолчанию). - Внутри раздела
[source]или[destination]побеждает первый подошедший маршрут — конкретные шаблоны ставьте выше общих. - Если ни один маршрут не подошёл, используются серверы по умолчанию из
[routing].
Маршруты по отправителю — раздел [source]
Маршрут выбирается по адресу из команды MAIL FROM (отправитель в конверте). Подходит, когда почту определённых отправителей нужно обрабатывать на выделенных серверах: партнёрский домен, сервис рассылок, внутренние системы, отчёты о недоставке.
[source]# все письма партнёра — на два сервера по кругуroute = @partner.example -> 10.0.1.10; 10.0.1.11# письма одного адреса — на отдельный портroute = billing@example.com -> 10.0.1.20:2525# все поддомены .net — основной и резервный серверroute = *.net -> 10.0.1.30 priority=1; 10.0.1.31 priority=2<>; перехватить их можно только шаблоном *. Маршрут * в разделе [source] захватит всю почту, если до него не сработал маршрут по получателю, — используйте его осознанно.Маршруты по получателю — раздел [destination]
Маршрут выбирается по получателю из первой команды RCPT TO. Основной сценарий — домены, почта которых обрабатывается разными узлами: один адрес приёма для внешнего мира, а внутри — каждому домену свои серверы.
[destination]# домен example.com: два основных сервера (первый вдвое мощнее) и резервныйroute = @example.com -> 10.0.2.10 priority=1 weight=2; 10.0.2.11 priority=1 weight=1; 10.0.2.20 priority=2# все поддомены example.org — один серверroute = *.example.org -> 10.0.2.30# отдельный ящик — на выделенный сервер (расположите выше общего маршрута домена)route = ceo@example.com -> 10.0.2.40450 и приходят отдельной сессией.Чтобы убедиться, что маршрут сработал, посмотрите журнал: для каждой сессии записывается событие route с разделом, шаблоном и порядком серверов:
route section=destination pattern=@example.com backends=10.0.2.10:25,10.0.2.11:25,10.0.2.20:25Журналы и статус
Журналы лежат в каталоге logs/ установки. Ротация ежедневная (или при размере больше 200 МБ), срок хранения задаётся параметром --log-keep.
| Файл | Содержимое |
|---|---|
connections.log | все подключения: команды клиента целиком (кроме тела письма), выбранный маршрут, попытки подключения к серверам, ответы серверов, переключения. При недоступности сервера пишется строка error: Server адрес: причина и событие failover |
backend-error.log | ошибки подключения к backend-серверам: маршрут, сервер, стадия, причина (refused, unreachable, timeout, reset, smtp_protocol…), затраченное время |
backend-status.log | текущий статус каждого маршрута и каждого сервера; перезаписывается при каждом новом подключении и после каждой проверки |
Быстрый просмотр статуса:
spzctl status routesspzctl status backendsПервая команда показывает по строке на маршрут со статусом UP, DEGRADED (часть серверов недоступна), DOWN (недоступны все) или UNKNOWN (серверы ещё не проверялись), вторая — полный файл статуса с временем последней проверки, счётчиками и текстом последней ошибки.
Частые вопросы
| Проблема | Что проверить |
|---|---|
Backend отвечает 550 Invalid HELO … not resolved | имя из --hostname должно резолвиться в IP балансировщика; задайте имя с A-записью и перезапустите установщик с этим параметром |
| Порт 25 занят | остановите другой почтовый сервер на этой машине или установите балансировщик с другим --listen-port |
| На backend-сервере не виден реальный IP клиента | включён ли control/xclient на этом сервере |
| Почта идёт не на тот сервер | событие route в connections.log: какой раздел и шаблон сработали; порядок строк (первый подошедший побеждает) и значение order |
Изменения в smtp_balancer.conf не действуют | выполните docker restart spz-smtp-balancer; ошибки разбора — в docker logs spz-smtp-balancer |
Сервер в статусе DOWN | причина в backend-error.log: refused — на порту никто не слушает, unreachable — нет маршрута до хоста, timeout — пакеты не проходят (файрвол) |