Кластер и отказоустойчивость
Несколько узлов SPZGate реплицируют друг другу историю трекера в реальном времени — без единой точки отказа и без промежуточного сервера.
Как устроена репликация
Каждый узел ведёт собственный WAL (write-ahead log) всех событий трекера. Узлы обмениваются данными напрямую друг с другом по собственному бинарному протоколу — без HTTP и без зависимости от веб-панели: сама панель лишь просит свой локальный движок выполнить операцию, а дозванивается до другого узла уже он сам.
- Каждая запись подтверждается индивидуально — при недоступности одного узла остальные продолжают обмен между собой без задержек
- Очередь на недоступный узел копится в WAL и досылается автоматически, когда узел возвращается
- Приём почты и приём подтверждений от кластера идут независимо — недоступность соседнего узла не замедляет обработку входящей почты на этом
- Для связи между узлами нужен только один сетевой порт (по умолчанию
7656) — тот же, что уже используется для передачи данных трекера
Добавление узла в кластер
На первом узле: добавьте пару без кода
В панели первого узла откройте раздел Шлюзы → Добавить шлюз, укажите IP второго узла и направление (обычно «оба»), поле «Код» оставьте пустым. Узел сгенерирует пару и код подтверждения — пара останется в статусе «Ожидает подтверждения», пока её не подтвердят со второй стороны.
На втором узле: добавьте пару с этим кодом
В панели второго узла — тот же раздел Шлюзы → Добавить шлюз, IP первого узла и код, полученный на шаге 1. Второй узел дозванивается до первого напрямую по бинарному протоколу и подтверждает пару — оба узла переходят в статус «Активна» одновременно, без дополнительных действий на первом узле.
Автообнаружение остальных узлов кластера
Как только пара становится активной, оба узла «знакомят» новый узел со всеми своими остальными активными парами. Поэтому для кластера из трёх и более узлов достаточно одной ручной пары между новым узлом и любым узлом уже существующего кластера — остальные связи выстроятся сами, без похода по каждому узлу отдельно.
При необходимости — подтяните полную историю
Новая пара реплицирует новые события сразу, но не досылает историю, накопленную ДО момента пары. Если присоединяется узел с уже накопленными самостоятельно данными (например, поднятый из бэкапа) и историю нужно свести к одному состоянию — используйте кнопку «Синхронизировать» у нужной пары (см. ниже).
Управление репликацией и синхронизацией
Раздел Шлюзы показывает все пары этого узла, их статус и отставание от актуального состояния.
Рядом со статусом активной пары показано отставание — сколько записей ещё не подтверждено соседним узлом и сколько времени прошло с последнего подтверждения. Устойчивое ненулевое отставание, которое не сокращается, — повод проверить сетевую связность между узлами на порту репликации.
Синхронизировать / Синхронизировать весь кластер
Кнопка «Синхронизировать» у конкретной пары запускает полный перенос данных с узла-источника (см. предупреждение в шаге 4). «Синхронизировать весь кластер» — то же самое по очереди для каждой активной пары этого узла. Операция блокирующая и может занять продолжительное время на большой истории — не закрывайте вкладку до завершения.
Отозвать и Удалить
Обе кнопки останавливают пару, но не одинаково надёжно:
- Удалить — удаляет саму пару. Останавливает репликацию гарантированно и сразу.
- Отозвать — помечает пару неактивной, не удаляя её. Если у отозванного узла остаётся хотя бы одна ДРУГАЯ активная пара с третьим узлом кластера, автообнаружение (см. выше) может повторно представить его вашему узлу и восстановить репликацию.
SMTP-балансировщик между узлами
Отдельный компонент — балансировщик на базе HAProxy с Lua-модулем, слушающий один адрес и распределяющий входящие SMTP-сессии по нескольким узлам кластера в зависимости от домена получателя. Полезен, когда почта разных доменов физически обрабатывается разными узлами, а внешний мир должен видеть один адрес.
Соберите и запустите контейнер
Конфигурация балансировщика — в исходниках SPZGate, каталог docker/balancer/. Контейнер работает в режиме network_mode: host (слушает порт непосредственно на хосте, не через docker-сеть):
cd docker/balancer && docker compose up -d --buildПо умолчанию слушает :2525 (порт настраивается в haproxy.conf) и разбирает SMTP-диалог до команды RCPT TO, чтобы узнать домен получателя ещё до выбора backend-узла.
Включите XCLIENT на принимающих узлах
Балансировщик ретранслирует сессию на выбранный узел командой XCLIENT, чтобы узел видел реальный IP отправителя, а не IP балансировщика — это нужно для корректной работы RBL-проверок, greylisting и репутационных механизмов. Backend-узлы должны принимать XCLIENT от IP балансировщика.
Опишите карту «домен → узлы»
Перед каждым решением балансировщик запрашивает HTTP-эндпойнт lookup (адрес задан в haproxy.conf, аргумент lua-load) и кэширует ответ на заданный TTL. Формат ответа — JSON-массив узлов с приоритетом; при недоступности узла с наивысшим приоритетом балансировщик пробует следующий:
GET /lookup?domain=example.com[{"host":"10.0.0.11","port":12645,"priority":1},{"host":"10.0.0.12","port":12645,"priority":2}]docker/balancer/nginx-lookup.conf — статическая карта доменов для локального тестирования (несколько записей примера), а не готовый к продакшену сервис. Для реальной установки замените его на свой lookup-эндпойнт с тем же JSON-контрактом, читающий актуальную карту доменов из вашей БД или API — комментарий в самом файле прямо на это указывает.Таймауты подключения, TTL кэша и путь к TLS-сертификату для STARTTLS передаются аргументами lua-load в haproxy.conf — там же документированы значения по умолчанию. Отдельной страницы статуса/мониторинга у балансировщика сейчас нет — состояние смотрите в логах контейнера (docker logs spz-smtp-balancer).