Несколько экземпляров панели

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

Для большинства установок это не нужно. Одна панель справляется с сотнями игровых серверов — тяжёлую работу выполняют демоны на выделенных серверах, а не панель.

Что должно быть общим

Компонент Настройка Что будет иначе
База данных PostgreSQL или MySQL SQLite не рассчитан на несколько подключённых экземпляров
Кэш CACHE_DRIVER=redis При memory сессии и ключи установки видит только один экземпляр
Обмен событиями PUBSUB_DRIVER=redis или postgres При memory экземпляры не узнают о событиях друг друга
Файлы FILES_DRIVER=s3 При local у каждого экземпляра свои файлы и свои сертификаты

Пример конфигурации:

DATABASE_DRIVER=postgres
DATABASE_URL=postgres://gameap:пароль@db.example.com:5432/gameap?sslmode=require

CACHE_DRIVER=redis
CACHE_REDIS_ADDR=redis.example.com:6379

PUBSUB_DRIVER=redis
PUBSUB_REDIS_ADDR=redis.example.com:6379
PUBSUB_INSTANCE_ID=panel-1

FILES_DRIVER=s3
FILES_S3_ENDPOINT=https://s3.example.com
FILES_S3_BUCKET=gameap
FILES_S3_ACCESS_KEY_ID=...
FILES_S3_SECRET_ACCESS_KEY=...

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

Идентификатор экземпляра

PUBSUB_INSTANCE_ID=panel-1

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

Если переменную не задать, используется значение default — одинаковое у всех, и обмен между экземплярами работать не будет. Задавайте её явно.

Как это работает

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

Поэтому команда, пришедшая на экземпляр, который не владеет соединением с нужным демоном, передаётся через общий обмен событиями тому экземпляру, который им владеет; ответ возвращается тем же путём. Так же работают файловые операции, консоль и запросы метрик.

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

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

Балансировщик и порты

Разделите два потока:

  • HTTP (8025) — можно балансировать обычным образом, привязка сессии к экземпляру не нужна: токены проверяются по общему ключу, а сессионное состояние лежит в общем кэше.
  • gRPC (31718) — соединения долгоживущие. Балансировщик должен уметь их держать и не рвать по таймауту простоя: собственного механизма keepalive в канале нет, живость держится на heartbeat раз в 30 секунд.

Задайте GRPC_EXTERNAL_HOST с адресом, по которому демоны видят панель через балансировщик, и GRPC_EXTERNAL_PORT, если порт наружу опубликован под другим номером.

Сертификаты

Сертификаты gRPC должны быть общими. Они хранятся в файловом хранилище панели, поэтому при FILES_DRIVER=s3 автоматически оказываются общими для всех экземпляров.

При FILES_DRIVER=local каждый экземпляр сгенерирует собственный центр сертификации, и демон, зарегистрированный через один экземпляр, не сможет подключиться к другому.

HTTPS

Если сертификат панели выпускается через ACME, есть две особенности.

Хранилище ACME тоже лежит в файловом хранилище — при S3 оно общее.

Блокировка, не дающая нескольким экземплярам одновременно запрашивать сертификат, работает через Redis. Она включается только при CACHE_DRIVER=redis; при кэше в памяти блокировка локальная, и экземпляры будут мешать друг другу.

Подтверждение по http-01 требует, чтобы запрос центра сертификации попал на тот же экземпляр, который начал выпуск. Проще использовать dns-01 либо завершать TLS на балансировщике. См. HTTPS и сертификаты.

Ограничения

Счётчики попыток входа живут в кэше. При CACHE_DRIVER=redis они общие, при memory — свои у каждого экземпляра, то есть фактический предел умножается на число экземпляров.

Журнал аудита пишется в вывод каждого экземпляра отдельно. Сводить их нужно внешним сборщиком журналов, см. Безопасность.

Задачи плагинов дедуплицируются между экземплярами распределёнными блокировками, поэтому периодическая задача плагина выполнится один раз, а не на каждом экземпляре.

Проверка

После запуска убедитесь, что:

  • демон, зарегистрированный через один экземпляр, управляется и через другой;
  • вход, выполненный на одном экземпляре, действует на остальных;
  • файловый менеджер открывается независимо от того, на какой экземпляр попал запрос.

Если что-то из этого не работает, почти всегда причина в том, что один из четырёх общих компонентов остался локальным.