Dry-run как предохранитель между кодом и реальными изменениями
Знаете, что общего между Terraform, AWS CLI и моей бабушкой? Все трое перед тем, как что-то сломать, предпочитают спросить: «А точно надо?»
Бабушка спрашивает перед выбрасыванием вещей из холодильника. AWS CLI спрашивает, если добавить флаг
--dry-run. А Terraform вообще не делает ничего, пока ты не скажешь
terraform apply — до этого он только показывает план в terraform plan.
В статье обсуждается концепция Henrik Warne
про --dry-run. Становится понятно, что это один из тех паттернов, которые кажутся «лишней
работой» на старте проекта, но спасают репутацию и нервные клетки через три месяца эксплуатации.
Что такое dry-run простым языком
Представьте, что вы пишете скрипт, который чистит старые логи. Он должен пройтись по директории
/var/log, найти файлы старше 90 дней и удалить их. Без --dry-run
вы запускаете скрипт — и молитесь, чтобы регулярное выражение внутри не сожрало важные данные.
С --dry-run скрипт говорит: «Я бы удалил 147 файлов, включая
/var/log/important-production-db.log. Вы уверены?» — и вы материтесь, исправляете фильтр,
и только потом запускаете «боевой» режим.
Dry-run (пробный прогон) — это режим, при котором программа показывает, что она собирается сделать, но не делает этого. Никаких побочных эффектов (side effects — изменений в базе данных, файловой системе или отправки писем). Только логи и выход в терминал.
Почему это важнее, чем кажется
Henrik описывал reporting-приложение, которое генерирует отчеты, пакует их в zip, загружает
на SFTP-сервер (Secure File Transfer Protocol — способ безопасной передачи файлов по сети) и рассылает
уведомления. Он добавил --dry-run в самом начале разработки и использовал его десятки раз
в день.
Представим похожую систему — автоматическую рассылку инвойсов. Без dry-run отладка превращалась в ад: каждый тестовый запуск создавал реальные PDF-ки, ложил их в S3, отправлял письма клиентам (ну, «тестовым» клиентам, но всё равно неприятно) и двигал файлы по директориям. Цикл «написал код — проверил — откатил изменения» занимал минуты.
Когда добавляешь флаг --dry-run, всё изменилось. Можно проверить логику генерации отчетов
за секунды, не дожидаясь рендеринга тяжелых PDF. Видно, какие именно файлы улетят на SFTP,
не подключаясь к серверу. Это как разница между прыжком с парашютом и прыжком на батуте —
отрабатываешь движения без последствий.
Выполнение скрипта
──────────────────
Без dry-run:
┌─────────┐ ┌──────────┐ ┌─────────┐
│ Код │───▶│ Действие │───▶│ База │
│ │ │ (delete) │ │ 💥 │
└─────────┘ └──────────┘ └─────────┘
С dry-run:
┌─────────┐ ┌──────────────┐ ┌─────────┐
│ Код │───▶│ Проверка │───▶│ "Would │
│ │ │ if dryRun: │ │ delete │
└─────────┘ │ log(..) │ │ 5 rows" │
└──────────────┘ └─────────┘
│
▼
┌──────────────┐
│ База цела │
│ ✓ │
└──────────────┘
Как это реализовать (и почему это не больно)
Главный аргумент против dry-run, который я слышу: «Это же загрязнит код кучей if-ов!» Да, придется добавить проверки. Но, как заметил Henrik, они не проникают глубоко в бизнес-логику.
Вот проверенный паттерн:
- На уровне CLI (Command Line Interface — интерфейс командной строки) парсим аргументы и кладем флаг
dry_runв конфигурацию - На границе системы (там, где код встречается с внешним миром) проверяем флаг
- Глубокая логика (генерация отчетов, расчеты) не знает о dry-run — она просто не вызывается
Архитектура с dry-run
─────────────────────
┌─────────────────────────┐
│ CLI Parser |
| --dry-run flag |
└───────────┬─────────────┘
|
▼
┌─────────────────────────┐
| Orchestrator |
| if config.dry_run: |
| logger.info("Would |
| upload to SFTP") |
| else: |
| sftp.upload() |
└───────────┬─────────────┘
|
▼
┌─────────────────────────┐
| Report Generator |
| (чистая функция, |
| не знает о dry-run) |
└─────────────────────────┘
Когда это не работает
Dry-run бесполезен для реактивных систем (reactive applications—тех, что ждут событий или сообщений, а
не запускаются командой). Если у вас микросервис, который слушает Kafka-топики и обрабатывает сообщения, флаг
--dry-run не имеет смысла — вы не запускаете его вручную каждый раз.
Также сложно сделать dry-run для операций, которые нельзя откатить транзакционно. Например, если ваш скрипт вызывает внешний API с числением денег — даже «тестовый» вызов может списать реальные средства или заблокировать счет. Тут помогут только моки (mocks—заглушки, имитирующие внешние сервисы) в тестовом окружении, а не production dry.
Психологический эффект
Самое ценное в сухом пробеге — психологическая сторона. Когда знаешь, что можно запустить команду без риска, запускаешь её чаще. Чем чаще запускаю—тем быстрее отлавливаю баги. Это замыкает положительную обратную связь.
Без dry-run каждый запуск—это стресс. «А вдруг сейчас удалит production базу? „Стресс ведёт к редким запускам. Редкие запуски ведут к накоплению ошибок. Накопление ошибок ведёт к катастрофе.
Henrik прав: добавлять --dry-run нужно в первый же день проекта. Потом будет лень
возвращаться и рефакторить. А когда он уже есть, вы используете его инстинктивно, как
git diff перед git commit.
Ссылки
- In Praise of –dry-run — оригинальная статья Henrik Warne
- Terraform plan — как работает dry-run в инфраструктурном коде
- AWS CLI dry-run — пример реализации в облачных инструментах
- Subversion — система контроля версий, упомянутая
в оригинале как один из первых инструментов с
--dry-run