Scenario
Scenario (сценарий) — оркестрационный слой: «как провести одну операцию над целым кластером». create, add_user, restart, add_replica — каждая такая операция над Incarnation описывается отдельным сценарием.
Сценарий получает весь набор блоков задач Destiny (module:, params:, vars:, register:, when:, onchanges:, loop:, …) — он наследует это ядро целиком — плюс оркестрационную дельту: на каких хостах выполнять, в каком порядке, с какой степенью параллелизма. Поэтому ниже — только то, чего у Destiny нет; всё остальное в задаче сценария работает ровно как в Destiny.
Раскладка
Заголовок раздела «Раскладка»Сценарий живёт в репозитории сервиса:
scenario/<name>/├── main.yml # точка входа: input, state_changes, tasks├── <sub>.yml # подключаемые соседи (include)├── templates/ # шаблоны, используемые шагами этого сценария├── vars.yml # локальные значения сценария└── tests/ # тесты сценарияОркестрационная дельта
Заголовок раздела «Оркестрационная дельта»Поверх обычной задачи у сценария добавляются ключи:
| Ключ | Назначение |
|---|---|
on: | Стабильный таргет шага: на каких хостах выполнять (по группам). |
where: | Волатильный предикат: отфильтровать хосты по живой проверке. |
apply: | Вызвать Destiny (вместо инлайн-module:). |
serial: | Волновое (rolling) исполнение: по N хостов за раз. |
run_once: | Выполнить ровно на одном хосте таргета. |
on: — стабильный таргет
Заголовок раздела «on: — стабильный таргет»on: определяет, на каких хостах выполнить шаг, опираясь на стабильные метки (резолвится по базе). Три формы:
| Форма | Семантика |
|---|---|
| опущен | весь incarnation — все его хосты-члены, резолвятся по отношению членства инкарнации (не по coven). |
on: keeper | задача выполняется на самом Keeper-е (например, провижининг облачного инстанса). |
on: [coven-a, coven-b] | пересечение перечисленных групп — результат всегда внутри хостов текущего incarnation. |
on: нужен только чтобы сузить таргет: on: keeper или пересечение стабильных covens (on: [baremetal] — scope инкарнации уже неявен, т.к. roster ограничен её членами). При этом incarnation.name — не Coven: on: ["${ incarnation.name }"] — это ошибка валидации; чтобы взять весь incarnation, просто опустите on:.
# Все члены инкарнации (on: опущен)- name: Apply base config everywhere apply: { destiny: redis-base, input: { ... } }
# Только на keeper-стороне (cloud-провижининг; провайдер/профиль# регистрируются через Operator API, см. docs/modules/cloud.md)- name: Provision VMs on: keeper module: core.cloud.created params: { ... }
# Сузить до стабильного coven (scope инкарнации уже неявен)- name: Tune kernel on bare-metal hosts of this cluster on: [baremetal] apply: { destiny: kernel-tuning, input: { ... } }Таргетинг между разными incarnation запрещён грамматикой — резолвер on: не может вернуть хост за пределами текущего incarnation при любом наборе групп.
where: — волатильный фильтр
Заголовок раздела «where: — волатильный фильтр»where: отбирает хосты per-host уже внутри набора, выбранного on:. Он опирается на результаты предыдущей живой проверки (probe) и/или на стабильные факты хоста (soulprint.self.*):
# probe: узнать живую роль каждого хоста (on: опущен — весь incarnation)- name: Detect actual redis role per host module: core.exec.run register: redis_role changed_when: false failed_when: size(register.redis_role) < incarnation.host_count params: command: "redis-cli role | head -1"
# таргетим по результату probe- name: Restart only the current replicas where: register.redis_role.stdout == 'slave' module: core.service.restarted params: name: redis-serverПорядок резолва строгий: сначала on: сужает множество по базе (стабильно), затем where: фильтрует получившееся множество по живым данным.
Стабильные метки против волатильной роли
Заголовок раздела «Стабильные метки против волатильной роли»Это ключевое архитектурное разделение:
- Coven (группа в
on:) — только стабильные теги: кластер, проект, окружение, тип железа. Роль хоста (master/replica) никогда не Coven. - Волатильная роль (кто сейчас фактически master) определяется только живой проверкой во время прогона — probe-шагом и фильтром
where:по его результату.
Стабильные факты хоста доступны как soulprint.self.<путь> (soulprint.self.os.family, soulprint.self.sid, soulprint.self.network.primary_ip, soulprint.self.covens). Их можно использовать в where: без probe — они известны из реестра.
apply: — вызов Destiny
Заголовок раздела «apply: — вызов Destiny»Сценарий делегирует работу в изолированный Destiny через apply:, передавая ему вход:
- name: Install redis on all cluster hosts apply: destiny: redis input: version: "${ essence.redis_version }" password: "${ input.redis_password }"apply: и module: взаимоисключающи в одной задаче. Чтобы прочитать результат вызванного Destiny (его объявленный output:), на задаче-applier ставится register:.
serial: и run_once:
Заголовок раздела «serial: и run_once:»serial:— волновое исполнение: хосты таргета бьются на последовательные волны размера ≤N (serial: 2) или процента (serial: "25%"). Внутри волны — параллельно, волны — строго одна за другой. Падение в волне останавливает раскатку (последующие волны не стартуют). Типичная идиома — rolling-restart с health-проверкой между волнами.run_once:— выполнить шаг ровно на одном хосте таргета (первом по SID). Для операций, которые достаточно сделать один раз на кластер.
serial: и run_once: взаимоисключающи.
Передача данных между хостами
Заголовок раздела «Передача данных между хостами»Сценарий (в отличие от Destiny) видит топологию прогона целиком:
soulprint.hosts— список всех хостов прогона со стабильными фактами каждого;soulprint.hosts.where("<предикат>")фильтрует список по любому стабильному атрибуту.soulprint.where("<предикат>")— данные хостов по стабильной группе (cross-host lookup).incarnation.host_count— число хостов в таргете; используется в идиоме полноты probe (см. ниже).
Важно не путать две позиции:
where:— ключ шага: «на каких хостах выполнить».soulprint.where(...)/soulprint.hosts— функция в выражении: «откуда взять данные».
Destiny эти аксессоры напрямую не видит — он получает топологию только через явный проброс apply: input:.
Probe и обработка ошибок
Заголовок раздела «Probe и обработка ошибок»Probe — это обычный шаг, не особая конструкция: read-only модуль (core.exec.run) + register: + changed_when: false. Чтобы безопасно таргетировать разрушительные операции по результату probe, probe должен утверждать полноту ответа через failed_when::
failed_when: size(register.redis_role) < incarnation.host_countБез этой проверки частичный probe (не все хосты ответили) молча привёл бы разрушительную операцию только к «ответившей» части. С проверкой частичный probe делает шаг упавшим, прогон останавливается, состояние не коммитится.
Вся обработка ошибок в сценарии — те же механизмы, что в Destiny: retry:, onfail:, failed_when:.
Запись состояния и атомарность
Заголовок раздела «Запись состояния и атомарность»Что сценарий пишет в incarnation.state, объявляется в блоке state_changes:
state_changes: sets: redis_version: "${ input.version }" leader_host: "${ register.elect.stdout }"Коммит состояния — это cross-host барьер:
- сценарий дожидается завершения всех задач на всех хостах прогона;
- только после барьера
state_changesкоммитятся в базу; - если хоть одна задача хоть на одном хосте упала — состояние не коммитится, incarnation переходит в
error_locked.
Это инвариант, а не опция: зафиксированное состояние всегда соответствует факту на хостах.
Полный пример
Заголовок раздела «Полный пример»input: drain_timeout: type: string
state_changes: {}
tasks: - name: Detect actual redis role per host module: core.exec.run register: redis_role changed_when: false failed_when: size(register.redis_role) < incarnation.host_count params: command: "redis-cli role | head -1"
- name: Rolling restart of replicas, two at a time module: core.service.restarted where: register.redis_role.stdout == 'slave' serial: 2 params: name: redis-serverЧто дальше
Заголовок раздела «Что дальше»- Destiny — ядро задач, наследуемое сценарием.
- Essence — параметры, доступные сценарию напрямую.
- Каталог core-модулей — модули, в том числе keeper-side (
on: keeper).