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

Написание снапшот-воркфлоу

Вы уже достаточно часто пользовались dwe snapshot create и restore, чтобы знать их с точки зрения потребителя (см. switching-tasks-with-snapshots.md). Теперь вы хотите написать workspace/snapshot.yml, который ими управляет, — решить, что захватывается, что восстанавливается, что переживает восстановление и что подчищается при удалении.

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

Достаточно snapshot.yml с одним шагом, чтобы захватить и восстановить базу:

workspace/snapshot.yml
create:
description: Capture the main DB
steps:
- command: db.dump
with: { out: ${snapshot.path}/db/main.sql.gz }
restore:
description: Restore the main DB
steps:
- command: db.restore
with: { in: ${snapshot.path}/db/main.sql.gz }

db.dump и db.restore — это ваши собственные пользовательские команды; DWE не поставляет бэкенд для баз данных (см. author-project-commands.md, если вы их ещё не написали). Подсистема снапшотов лишь оркеструет процесс: берёт локи проекта, создаёт ./snapshots/<имя>/, запускает ваши create-шаги с подставленным ${snapshot.path} и затем пишет манифест.

Справочник: ../reference/config/snapshot.md.

${snapshot.path} — это абсолютный путь к ./snapshots/<имя>/; именно туда ваш create-воркфлоу пишет артефакты и откуда restore-воркфлоу их читает. Воркфлоу должны писать внутри этого пути; симлинки, положенные внутрь каталога снапшота, отклоняются при сканировании сразу после создания.

Полный неймспейс:

Переменнаяcreaterestore / remove
${snapshot.name}
${snapshot.path}
${snapshot.description}
${snapshot.variant}
${snapshot.created_at}ошибка (ещё не существует)

Вне блоков снапшот-воркфлоу ${snapshot.*} вызывает ошибку на этапе компиляции: проверка области видимости срабатывает до рендера шаблона. Случайно прочитать ${snapshot.path} из обычной команды db.dump, вызванной вне контекста снапшота, не получится; та же db.dump подхватывает ${snapshot.path}, только когда её вызывают через блок with: снапшот-воркфлоу.

Воркфлоу переиспользуют ваши пользовательские команды

Заголовок раздела «Воркфлоу переиспользуют ваши пользовательские команды»

Шаги снапшот-воркфлоу имеют ту же форму WorkflowStep, что и у пользовательских команд type: workflow: command:, with:, when:, confirm:, parallel:, continue_on_error:. Никакого особого синтаксиса для снапшотов и никакой отдельной модели исполнения нет: всё, что вы заложили в свои пользовательские команды (параметры, валидация, уведомления), работает и здесь.

Это значит, что снапшот с несколькими хранилищами строится просто как больше шагов, вызывающих больше команд:

create:
description: Capture full env
steps:
- command: db.dump
with: { out: ${snapshot.path}/db/main.sql.gz }
- command: opensearch.snapshot
with: { out: ${snapshot.path}/search/index.tar }
- command: redis.dump
with: { out: ${snapshot.path}/redis/dump.rdb }

restore: устроен так же, но вызывает соответствующие пользовательские команды *.restore. Условное восстановление — «только если файл есть» — задаётся предикатом when: на шаге:

restore:
steps:
- command: opensearch.restore
when: file-exists ${snapshot.path}/search/index.tar
with: { in: ${snapshot.path}/search/index.tar }

Полный справочник workflow-шага — в ../reference/config/commands/types.md.

Блок воркфлоу может объявить именованные альтернативные списки шагов в variants:. Используйте их, когда «захватить всё» и «захватить только базу» должны сосуществовать, не дублируя родительский воркфлоу копипастой:

create:
description: Capture full env
steps:
- command: db.dump
with: { out: ${snapshot.path}/db/main.sql.gz }
- command: opensearch.snapshot
with: { out: ${snapshot.path}/search/index.tar }
variants:
db-only:
description: Capture DB only
steps:
- command: db.dump
with: { out: ${snapshot.path}/db/main.sql.gz }
with-search:
description: Capture DB + search
steps:
- command: db.dump
with: { out: ${snapshot.path}/db/main.sql.gz }
- command: opensearch.snapshot
with: { out: ${snapshot.path}/search/index.tar }

Выбор варианта на create:

Окно терминала
dwe snapshot create wip-x # дефолтный блок
dwe snapshot create wip-x --using=db-only

Выбранный вариант фиксируется в манифесте снапшота, так что dwe snapshot restore wip-x автоматически берёт restore.variants.db-only, если он есть. Если соответствующего варианта восстановления нет, restore откатывается к блоку restore: по умолчанию — удобно, когда асимметрия сводится к «захватить меньше, восстановить тем же способом».

Имена вариантов должны соответствовать [a-z0-9][a-z0-9._-]{0,30}. Запрос несуществующего варианта при create завершается ошибкой ещё до каких-либо изменений в файловой системе, поэтому создать наполовину сломанный снапшот невозможно.

Каждый снапшот фиксирует config_hash проекта на момент создания (дайджест от итогового deploy-конфига). При восстановлении DWE сравнивает его с хешем текущего состояния деплоя. По умолчанию несовпадение — мягкое предупреждение в stderr.

Включите строгий режим, когда восстановление поверх несовпадающего конфига было бы заведомо опасным — например, если ваш db.restore-воркфлоу рассчитан на конкретную версию схемы:

require_matching_config: true

При строгой проверке restore прерывается (exit 1), если хеши расходятся. Пустой config_hash в манифесте — снапшот создан до того, как прошёл хоть один деплой — считается совпадением и никогда не блокирует.

Несовпадение config_hash на практике означает одно из:

  • снапшот сделан на другой ветке, где содержимое workspace/deploy.yml или service.yml отличается,
  • снапшот коллеги сделан на другой версии проектного конфига,
  • проект был перебазирован вперёд или назад через коммит, меняющий deploy-конфиг.

Чтобы разобраться с расхождением, используйте require_matching_config: true вместе с dwe snapshot inspect <name> (она показывает зафиксированный хеш).

Снапшоты фиксируют итоговый набор сервисов (имя + флаг enabled, отсортированные по имени) на момент создания. При восстановлении этот набор сравнивается с текущим итоговым набором проекта. Блок политики определяет, что делать, когда есть различия:

services_mismatch:
policy: warn # warn (по умолчанию) | block | ignore
ПолитикаПоведение
warn (по умолчанию)Восстановление продолжается. Различия выводятся в запросе подтверждения; с -y они идут в stderr, и restore продолжается.
blockЛюбые различия прерывают восстановление ещё до изменения workspace/local.yml (exit 1).
ignoreРазличия не показываются; восстановление продолжается молча.

Различия группируются в три категории — только в снапшоте, только локально, различается enabled — и та же группировка появляется в dwe snapshot inspect и валидаторе snapshot.<name>.services_diff. Выбирайте block, когда снапшот используется неинтерактивно (CI или скрипты) и молчаливое продолжение при несовпадении приведёт к сбою дальше по цепочке, который трудно диагностировать.

local_yml.preserve_keys — сохранить машинно-локальные оверрайды

Заголовок раздела «local_yml.preserve_keys — сохранить машинно-локальные оверрайды»

workspace/local.yml входит в снапшот. В большинстве случаев это и нужно: восстановление снапшота должно вернуть локальные переключатели, которые были у коллеги на момент создания. Но машинно-локальные значения (порты, переназначенные из-за того, что 5432 локально занят; хосты, указывающие на приватный DNS) переноситься не должны.

local_yml.preserve_keys — это список dot-путей, чьи текущие локальные значения переживают восстановление:

local_yml:
preserve_keys:
- services.main.ports
- services.db.ports
- vars.host.shell
  • Dot-пути указывают на вложенные ключи маппингов. Сегменты с индексами массива (services[0].ports) не поддерживаются.
  • Пути, которых нет ни с одной из сторон, просто молча игнорируются.
  • Порядок и YAML-комментарии на нетронутых узлах сохраняются там, где их удерживает yaml.v3; flow/block-стиль и отступы при сериализации могут нормализоваться.

При создании перечисленные пути вырезаются из захваченного local.yml. При восстановлении захваченный local.yml накладывается поверх текущей копии, а сохраняемые ключи вставляются обратно из вашего рабочего файла. Если снапшот не содержит local.yml вовсе, но у вас локально есть сохраняемые значения, записывается минимальный local.yml только с сохраняемыми ключами.

Типичный выбор: что-нибудь под services.<name>.ports, что-нибудь под services.<name>.hosts, пути к локальным инструментам разработки, различающиеся в зависимости от ОС.

pack.exclude — не включать временные файлы в архивы

Заголовок раздела «pack.exclude — не включать временные файлы в архивы»

dwe snapshot pack <name> создаёт один файл ./snapshots/<name>.tar.gz. Если ваш create-воркфлоу оставляет временные файлы, промежуточные дампы или что-то ещё, чем вы не хотите делиться с коллегой, исключите их с помощью doublestar-шаблонов:

pack:
exclude:
- "**/*.tmp"
- ".cache/**"
- "**/*.log"

Шаблоны вычисляются относительно каталога снапшота. CLI-флаг dwe snapshot pack --exclude=<glob> дополняет этот список, а не заменяет его — то есть exclude в snapshot.yml задаёт базовый набор, который можно расширять при каждом вызове, а не значение по умолчанию, которое можно переопределить.

При распаковке архив проверяется по содержимому относительно manifest.yml. Исключённые файлы не попадают в манифест и не участвуют в проверке целостности, поэтому исключать временный вывод безопасно.

Если один из снапшотов — это эталонное «безопасное» состояние (обычно baseline, снятый сразу после чистого деплоя), объявите его целью отката:

rollback_target: baseline

Тогда dwe snapshot rollback — это краткая форма dwe snapshot restore baseline. Команда явно завершается ошибкой, если целевого снапшота нет, а валидатор snapshot.rollback_target_exists выдаёт предупреждение при dwe validate snapshot, когда rollback_target задан, но указанного снапшота на диске нет.

baseline — это просто соглашение; подойдёт любое существующее имя снапшота. Соглашение окупается тем, что каждое руководство, runbook и привычка могут однозначно ссылаться на «откат». См. switching-tasks-with-snapshots.md — взгляд со стороны потребителя.

dwe snapshot remove <name> удаляет ./snapshots/<name>/ с диска. Если снапшоту соответствует ещё и внешнее состояние — объекты в S3-бакете, строки в таблице метаданных, тег в registry — объявите воркфлоу remove:, который запускается перед удалением каталога:

remove:
description: Drop external artifacts for this snapshot
steps:
- command: s3.remove
with: { prefix: snapshots/${snapshot.name}/ }
- command: registry.untag
when: file-exists ${snapshot.path}/registry-tag
with: { tag: "${snapshot.name}" }

remove: опционален. Без него dwe snapshot remove просто вызывает os.RemoveAll(snapshotDir) и сбрасывает указатель current, если тот ссылался на этот снапшот. С ним сначала запускается воркфлоу — ${snapshot.*} доступны в области видимости restore (поэтому ${snapshot.created_at} тоже доступен), — а затем удаляется каталог. Сбой воркфлоу прерывает удаление: каталог остаётся на месте, чтобы вы могли разобраться и перезапустить.

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