Перейти к содержимому

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: определяет, на каких хостах выполнить шаг, опираясь на стабильные метки (резолвится по базе). Три формы:

ФормаСемантика
опущенвесь 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: отбирает хосты 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 — они известны из реестра.

Сценарий делегирует работу в изолированный 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: — волновое исполнение: хосты таргета бьются на последовательные волны размера ≤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 — это обычный шаг, не особая конструкция: 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 барьер:

  1. сценарий дожидается завершения всех задач на всех хостах прогона;
  2. только после барьера state_changes коммитятся в базу;
  3. если хоть одна задача хоть на одном хосте упала — состояние не коммитится, incarnation переходит в error_locked.

Это инвариант, а не опция: зафиксированное состояние всегда соответствует факту на хостах.

scenario/restart/main.yml
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).