Skip to content

Группа central на нескольких серверах

Эта страница описывает резервирование управляющего контура Agora: два и более инстанса central над одной базой PostgreSQL и отдельные стримеры. Она содержит конфигурацию инстансов, настройку адресов группы и проверки после развёртывания.

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

Что именно разворачивается

Инсталляция с резервом состоит из четырёх частей, и только одна из них — сама Agora:

  • Один кластер PostgreSQL, доступный всем инстансам по одному адресу. Это единственное хранилище состояния и единственная часть, отказоустойчивость которой Agora на себя не берёт.
  • Два и более инстанса central — отдельные машины с одинаковым конфигом, отличающиеся ролями и объявленным адресом.
  • Стримеры — отдельные машины, которым задан не адрес одного инстанса, а список адресов группы.
  • Точка входа оператора — один адрес, за которым живой инстанс: DNS-имя с переключением или балансировщик.

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

Сколько чего нужно

Часть Сколько Замечание
PostgreSQL Один логический кластер Два независимых сервера базы — это две разные инсталляции, а не резерв
Инстансы central Два и больше Третий добавляется тем же конфигом; переписывать существующие не нужно
Стримеры Сколько требует эфир Резервируются не здесь, а размещением
Точка входа оператора Один адрес Стримеры через него не ходят

Конфиг инстанса central

Сохраните конфигурацию каждого инстанса в /etc/agora/agora.yaml.d/60-central-group.conf. Для управляющего процесса добавьте в этот файл секцию agora: {}. Адрес общей базы в DATABASE_URL файла /etc/agora/secrets.env, если он там задан, должен совпадать с central.database_url. На инстансах central не задавайте CENTRAL_URL и JOIN_TOKEN: эти переменные предназначены для стримеров.

Инстансы группы отличаются друг от друга ровно двумя строками — объявленным адресом и набором ролей. Всё остальное у них одинаково, включая строку подключения к базе.

Первый инстанс:

listeners:
  http:
    - port: 80

central:
  database_url: postgres://central:пароль@db.example.com:5432/central
  admin_keys:
    - ключ-управления
  roles: [run, migrate, layouter, stats]
  advertise_url: http://central-a.example.com

agora: {}

api_auth:
  login: admin
  password: пароль-оператора

Второй — то же самое без роли migrate и со своим адресом:

central:
  database_url: postgres://central:пароль@db.example.com:5432/central
  admin_keys:
    - ключ-управления
  roles: [run, layouter, stats]
  advertise_url: http://central-b.example.com

Роли — это притязания, а не назначения

Роль в конфиге означает «эта машина готова делать такую работу», а не «эта машина её делает». Кто из инстансов действительно исполняет работу, решает PostgreSQL, а не конфиг, — поэтому layouter и stats заявлены у обоих, и это не ошибка и не двойной запуск.

  • run — обслуживать запросы: консоль, management API, приём синхронизации со стримеров, выдачу метрик. Нужна каждому инстансу.
  • migrate — накатить миграции схемы при старте.
  • layouter — исполнять прогоны размещения.
  • stats — держать картину «прямо сейчас» по всему кластеру.

Единственная роль, которую нельзя оставлять у всех, — migrate. Она не защищена мандатом: два процесса, стартовавшие одновременно, погонят миграции наперегонки. Поэтому схему накатывает один инстанс, а остальные стартуют после него.

Это же — причина не оставлять roles пустым на втором инстансе: пустой список означает не «никаких ролей», а все четыре, включая migrate.

Объявленный адрес

advertise_url — адрес, по которому до этой машины достучатся соседи по группе. Он нужен потому, что картину «прямо сейчас» держит один инстанс, а остальные ходят к ней по сети: держатель записывает свой адрес, соседи читают его оттуда.

Адрес балансировщика здесь не годится — он привёл бы соседа к самому себе. Адрес обязан быть адресом конкретной машины.

Настройку можно и не задавать: инстанс без объявленного адреса полноценно работает и даже может держать картину — но соседи до неё не дойдут и скажут об этом в лог. В консоли такая строка помечена отдельно, см. проверку после развёртывания.

Лицензия

Лицензия нужна каждому процессу central, а не одному на группу: без неё процесс не стартует вовсе. Ключ и объявленный продукт у инстансов одинаковые.

Кто что исполняет: мандаты

Работу, дубль которой вреден, в группе исполняет один инстанс — тот, который взял на неё мандат. Мандат живёт в PostgreSQL и выдаётся на отдельном соединении, поэтому гибель процесса освобождает его сама, без разбора зависших исполнителей по таймаутам: сессия умерла — мандат свободен.

Мандатов несколько, и они независимы. Единого лидера у central нет: layouter и stats могут оказаться на разных машинах, и это штатное состояние, а не перекос.

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

Смена держателя занимает секунды — столько, сколько нужно PostgreSQL, чтобы заметить смерть сессии, а соседу — чтобы прийти за освободившимся мандатом.

Инстанс, не держащий ничего, — не запасной и не спящий: он полноценно обслуживает консоль, API и синхронизацию стримеров. Мандат касается только фоновой работы.

Конфиг стримера

Стример обращается к группе, а не к процессу, поэтому в его конфиге список адресов:

managed_by:
  name: streamer-01
  url:
    - http://central-a.example.com
    - http://central-b.example.com
  join_token: токен-присоединения

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

Тот же список принимает переменная окружения CENTRAL_URL — адреса через запятую.

Стример узнаёт группу сам

Конфиг стримера — не единственный источник адресов. Каждый ответ на синхронизацию несёт состав группы: адреса живых инстансов, которые обслуживают стримеры. Стример дописывает их к адресам из конфига — те остаются впереди, в заданном порядке, — и хранит выученное на диске рядом с остальным состоянием машины, так что оно переживает перезапуск и работает уже при регистрации.

Отсюда следствия для эксплуатации:

  • Инстанс добавляется без обхода парка. Поднятый над той же базой инстанс с advertise_url попадает в состав группы за секунды, и каждый стример узнаёт о нём при следующей синхронизации.
  • Стример, поставленный с одним адресом, получает резерв при первом же ответе. Так к группе присоединяется существующий парк: конфиги на машинах не переписываются.
  • Инстанс, переставший отмечаться в группе, выпадает из анонса через двадцать секунд, и стримеры вычёркивают его из выученных. Адреса из конфига анонс не трогает никогда — ни добавить к ним, ни убрать из них он не может.

Второй адрес в конфиге всё равно нужен машинам, которыми парк поднимается с нуля. До первого ответа стример знает о группе ровно то, что написано в его конфиге, а разрыв петли синхронизации — единственный признак живости стримера, который есть у central. Стример, потерявший свой единственный адрес до первой синхронизации, через полторы минуты числится offline, после чего лейаутер уводит его стримы на другую машину — с живой и вещающей.

Список удобно поделить по половинам парка: одной половине первым адресом задать первый инстанс, другой — второй. Тогда в спокойном состоянии нагрузка приёма синхронизации распределена, а при гибели любого инстанса на второй адрес уходит только его половина — и возвращается, когда инстанс оживёт.

Точка входа оператора

Оператору нужен один адрес, который сам находит живой инстанс: DNS-имя с переключением или балансировщик перед группой. Состояния у обработчика запросов своего нет, поэтому на запрос оператора отвечает любой инстанс.

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

Порядок развёртывания

Чтобы central работал отдельным управляющим процессом, без локального стримера, задайте команду запуска через override systemd. Создайте /etc/systemd/system/agora.service.d/20-central-only.conf:

[Service]
ExecStart=
ExecStart=/usr/sbin/agora central run
systemctl daemon-reload
systemctl restart agora
  1. Поднять PostgreSQL и создать базу.
  2. Запустить первый инстанс — тот, у которого есть роль migrate. Дождаться, пока он ответит на запросы: схема накатывается до готовности.
  3. Запустить остальные инстансы.
  4. Открыть окно присоединения и завести стримеры, задав каждому список адресов группы.
  5. Направить точку входа оператора на группу.

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

Порядок важен только на первом шаге: остальные инстансы не поднимутся раньше схемы, а стримеры не присоединятся раньше живого central.

Что проверить после развёртывания

Два инстанса central с объявленными адресами, притязаниями и мандатами

Всё, что нужно посмотреть, лежит в консоли в разделе Кластер → Central.

  • В таблице столько строк, сколько инстансов. Строка появляется через секунды после старта. Не появилась — инстанс не дошёл до базы.
  • У каждой строки заполнен «Объявленный адрес». Пометка «не объявлен» означает, что соседи до этого инстанса не дойдут: ему не задан advertise_url.
  • Столбец «Притязания» показывает заявленные роли. Убедитесь, что migrate заявлен ровно у одного.
  • Столбец «Держит мандаты» непустой хотя бы у одного инстанса. Пусто у всех — значит, ни один не заявил ролей layouter и stats, и ни размещения, ни картины «прямо сейчас» в группе нет.
  • Столбец «Сборка» одинаков у всех строк. Разные версии в группе — штатное состояние только на время обновления.
  • Раздел «Стримеры» показывает весь парк с любого инстанса. Половина парка, видимая с одного адреса, и другая половина — с другого, означают, что инстансы не видят картину друг друга: проверьте advertise_url.
  • На стримере появился файл group.json в каталоге состояния (/var/lib/agora на пакетной машине) с адресами инстансов, которых нет в его конфиге. Файла нет — стример ещё не получил ни одного ответа, или у соседей не задан advertise_url.

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

Чего эта схема не делает

  • Не резервирует базу. Отказоустойчивость PostgreSQL — задача самой базы: реплика с переключением или управляемый инстанс провайдера.
  • Не заменяет бэкап. Резерв защищает от потери машины, но не от ошибочного удаления — см. Бэкап и восстановление.
  • Не касается эфира. Стримы вещают и пишут архив без central; резервируется только управление, см. Автономия стримера.