Перейти к содержанию

Отказоустойчивость (HA)

Alatyr не заявляет полную отказоустойчивость на текущем этапе

Часть путей в сервере действительно безопасна при запуске нескольких реплик — они перечислены ниже. Но остальное (в первую очередь — SCEP-поллинг и rate-limiting) на N репликах сегодня работает некорректно или не так, как ожидает оператор. Ни один из поставляемых демо-манифестов (Docker Compose, Helm-чарт) не разворачивает PostgreSQL в отказоустойчивой конфигурации — база всегда один под. Прочитайте эту страницу целиком, прежде чем принимать решение о продакшен-развёртывании с несколькими репликами сервера.

Что безопасно на N репликах уже сегодня

Конкурентные одобрения заявок. Два администратора, одобряющие одну и ту же заявку одновременно через разные реплики сервера, не могут создать дублирующий или рассинхронизированный результат: сервер использует блокировку на уровне базы данных (Postgres session-level advisory lock), ключом которой служит составной идентификатор заявки — это гарантия, которая работает между репликами, а не только внутри одного процесса. Внутрипроцессный fast path лишь ускоряет типичный случай без похода в базу; сама кросс-репличная гарантия обеспечивается именно блокировкой на уровне БД.

Доставка вебхуков. Фоновый воркер, обрабатывающий очередь исходящих вебхуков, использует стандартный для PostgreSQL паттерн блокировки записей при выборке задач из очереди. Если поднято несколько реплик и у каждой свой экземпляр воркера, они забирают из очереди непересекающиеся наборы записей — база пропускает уже занятые другой транзакцией строки вместо того, чтобы ждать их освобождения или отдавать повторно. Одна и та же доставка не будет обработана дважды параллельно двумя воркерами.

Access-токены. Локальные JWT-токены подписываются и проверяются как stateless-токены общим секретом. Проверка подписи не обращается к какому-либо состоянию конкретной реплики, поэтому токен, выданный одной репликой, действителен на любой другой реплике с тем же секретом — без sticky-сессий и без разделяемого кэша токенов. Refresh-токены при этом хранятся в базе данных, так что их отзыв и ротация видны всем репликам одинаково.

Что НЕ безопасно на N репликах сегодня

SCEP-поллинг. Это самое серьёзное ограничение на сегодня. Поллинг ca_pending-заявок рассчитан на работу с одной репликой сервера. Между моментом, когда воркер снимает блокировку с обрабатываемой заявки после коммита, и моментом, когда он отдельным шагом обновляет время следующей попытки, есть короткое окно — в этот момент заявка снова доступна для захвата. Если в этот момент опрашивает вторая реплика, она может забрать ту же самую заявку и повторно отправить запрос во внешний SCEP CA. База данных при этом не рассинхронизируется — успешным считается результат только одного из двух конкурентных обработчиков, — но CA может успеть выпустить два сертификата на одну заявку: один из них осиротеет на стороне CA, так как Alatyr больше не будет о нём знать. Выпуск через Vault этой проблемы не имеет — она специфична для асинхронного SCEP-поллинга.

Практическое следствие: если у вас включён SCEP-issuer и вы масштабируете сервер горизонтально, поллинг ca_pending должен идти только с одной реплики (см. раздел ниже) — иначе рискуете дублированными issuance-запросами к внешнему CA.

Rate-limiting. Ограничение частоты запросов (rate limiting) для /enroll*-эндпоинтов хранится в памяти каждого процесса сервера, а не в общем хранилище (Redis или БД). Это не проблема консистентности данных — лимит просто не общий на все реплики. При N репликах за балансировщиком нагрузки эффективный лимит на IP умножается примерно на N: запросы от одного IP рассеиваются по разным процессам, и каждый считает свой собственный счётчик с нуля. Если строгий лимит на IP — часть вашей модели угроз (защита от перебора/DoS на /enroll), это стоит учитывать при выборе числа реплик.

Отсутствие Postgres HA. Ни один из поставляемых демо-манифестов не разворачивает PostgreSQL в отказоустойчивой конфигурации: база работает как один под с числом реплик 1 и стратегией обновления, при которой под сначала удаляется, а затем создаётся заново. При пересоздании (деплой новой версии, вытеснение с ноды, рестарт) база на время недоступна, реплики для чтения нет, автоматического failover нет. В демо-манифестах нет ни Patroni/Stolon, ни managed-HA-конфигурации, ни настройки потоковой репликации PostgreSQL. Это не то, что Alatyr-приложение решает само — это забота инфраструктурного оператора, который его разворачивает (см. ниже).

Путь к production-ready

Ниже не обещание, а намеченное направление: что нужно закрыть, прежде чем называть развёртывание отказоустойчивым.

  • SCEP-поллинг. Либо жёстко ограничить поллинг-луп одной репликой (например, выделенным компонентом с одной репликой только для SCEP-поллера, при этом остальной API-трафик обслуживают N реплик), либо добавить в реализацию распределённую аренду (lease) поверх текущего механизма блокировки — например, короткий lease с TTL, который блокирует повторный захват заявки до истечения окна, а не только до коммита. Планируется как доработка, не реализовано на сегодня.
  • Rate-limiting. Если строгий per-IP лимит на /enroll* — операционное требование (а не просто защита от случайного шторма запросов), перенести счётчики из памяти процесса в общее хранилище (Redis, либо Postgres-таблица с TTL) так, чтобы лимит был общим для всех реплик, а не умножался на их число.
  • HA PostgreSQL. Приложение не предоставляет отказоустойчивость базы само — это задача инфраструктуры, на которой разворачивается Alatyr. Для продакшена — управляемый Postgres с HA у облачного провайдера (например, реплика + автоматический failover) либо самостоятельно поддерживаемый кластер (Patroni + PostgreSQL streaming replication, или аналог). Демо/пилотный чарт, поставляемый с проектом, сознательно не включает ничего из этого — см. также Расчёт ресурсов про то, где проходит граница между пилотным и production-профилем.

До того как все три пункта закрыты, разворачивать несколько реплик сервера можно (конкурентные одобрения, доставка вебхуков и access-токены это выдержат), но с оговорками из раздела выше — особенно если у вас включён SCEP-issuer.

См. также

  • Известные ограничения — сводка ограничений Alatyr по всем темам, включая краткое резюме по SCEP-поллингу и rate-limiting.
  • Эксплуатация — мониторинг очереди ca_pending и webhook-outbox в проде.
  • Расчёт ресурсов — где проходит граница между пилотным и production-профилем развёртывания.