Собранный пайплайн выкатывает код — но выкатывать можно по-разному: с осмысленными версиями и changelog или тегом latest и молитвой. Release engineering — это дисциплина того, как код превращается в именованный, воспроизводимый релиз, а метрики DORA — способ честно ответить, здорова ли ваша доставка или это только ощущается. Эта статья — про две вещи: как наводить порядок в релизах (semver, автоматизация, монорепо) и как измерять доставку пятью актуальными метриками DORA — не теми четырьмя, что до сих пор кочуют по докладам, — не скатываясь в театр ради красивых чисел.
Финальная статья серии «CI/CD на практике». Замыкает конвейер (CI → деплой) вопросами версий и измерения.
В статье
- Semver: что обещает номер версии
- Conventional commits: версия и changelog из истории
- Релизы в монорепо
- DORA: пять метрик доставки
- KPI-театр: как накрутить одну метрику
- Связи: восстановление, отказы, error budget
- Демо и версии
- Документация
Semver: что обещает номер версии
Версия — это обещание потребителю, а не порядковый номер. Semantic versioning (MAJOR.MINOR.PATCH) кодирует в трёх числах, чего ждать от обновления:
- PATCH (
1.2.0 → 1.2.1) — исправления без изменений публичного API. Обещание тут именно про совместимость интерфейса, а не про отсутствие регрессий: патч-релиз может принести и новую ошибку, semver этого не исключает; - MINOR (
1.2.0 → 1.3.0) — новое, но обратно совместимое; старый код продолжит работать; - MAJOR (
1.2.0 → 2.0.0) — есть ломающее изменение; обновление требует внимания.
Ценность в том, что обещание читается без чтения кода. Увидел смену MAJOR — знаешь, что надо проверить свою интеграцию, ещё не открыв changelog. Нарушенное обещание (ломающее изменение под видом PATCH) дороже отсутствия версий вовсе — на него полагаются.
Важная оговорка: semver осмыслен там, где есть объявленный публичный интерфейс — библиотека, API, формат данных, CLI-контракт. Именно относительно него определяется, что считать ломающим изменением. Для приложения, которое никто не подключает как зависимость и которое разворачивается непрерывно, semver часто оказывается ритуалом: там полезнее календарные версии или просто идентификатор сборки, однозначно отображающийся в коммит. Первый вопрос не «какую версию поставить», а «что именно мы обещаем и кому». Что считать ломающим на уровне API — соседний сюжет, разобранный в статье про версионирование APIСкоро.
Conventional commits: версия и changelog из истории
Если версия — обещание, то решать вручную, PATCH это или MAJOR, — значит регулярно ошибаться. Автоматизация начинается с дисциплины сообщений коммитов. Conventional commits задают формат: feat: — новая функциональность (MINOR), fix: — исправление (PATCH), feat!: или BREAKING CHANGE: — ломающее (MAJOR). Тогда следующая версия и changelog выводятся из истории, а не назначаются на глаз.
В digital-cookbook/infrastructure/cicd это отдельное демо: репозиторий-фикстура с историей коммитов, тег v1.2.0, а после него — набор коммитов, среди которых один feat!. git-cliff считает следующую версию и собирает changelog:
git-cliff вычислил следующую версию: v2.0.0
Коммитов после тега v1.2.0: 4
### Bug Fixes
- Корректная пагинация на последней странице
### Documentation
- Описание эндпоинтов заказов
### Features
- Фильтр заказов по статусу
- Переименовать amount в total в ответе APIВерсия поднялась до v2.0.0, а не v1.3.0, именно из-за feat!-коммита: одно ломающее изменение перевешивает любое число фич и правок. Ручное решение здесь ошиблось бы легко — «ну, в основном же фичи, пусть будет MINOR».
Обратная сторона автоматизации — она ровно настолько хороша, насколько дисциплинированы сообщения. Коммит без conventional-префикса демо намеренно проверяет:
Падающий вариант: неконвенциональный коммит «почини там по-быстрому»
в changelog НЕ попал.Сообщение без типа отбрасывается: оно не поднимает версию и не попадает в changelog. Уточнение, важное для практики: это настройка инструмента, а не свойство самого стандарта — в cliff.toml за него отвечает filter_unconventional. Другие генераторы (и другая конфигурация того же git-cliff) могут складывать такие коммиты в раздел «прочее» или считать их патчами. Выбирать стоит осознанно: тихо отбрасывать — значит терять изменения в changelog; включать всё подряд — значит получить свалку. В любом случае дисциплина сообщений и есть то, из чего собирается всё остальное. Тег фиксирует, какой коммит уехал в прод; release notes собираются из тех же сообщений; воспроизводимость релиза держится на том, что версия однозначно отображается в состояние кода.
Релизы в монорепо
Один репозиторий не обязан быть одним релизом. В монорепо живёт несколько пакетов, и у каждого своя версия и свой темп: библиотека обновилась до 2.0.0, сервис рядом остался на 1.4.x. Задача релиз-инжиниринга здесь — определить, что публиковать при изменении, особенно когда меняется общий код, от которого зависят несколько пакетов.
Границу проводят по зависимостям: изменение в общем модуле может потребовать поднять версии пакетов, которые от него зависят, а не только того, где правился код. Именно «может»: нужно ли это и насколько, зависит от режима версионирования (единая версия на монорепо против независимых) и от характера изменения — правка внутренней детали общего модуля не обязана двигать версии потребителей, а изменение его публичного контракта обязана. Инструменты релиз-оркестрации (changesets, nx, release-please и подобные) автоматизируют обход графа зависимостей по выбранным правилам — но принцип тот же, что и с одним пакетом: версия выводится из того, что изменилось, а не назначается вручную.
DORA: пять метрик доставки
Порядок в релизах отвечает на вопрос «что уехало». Метрики DORA отвечают на другой — «здорова ли доставка в принципе».
Сразу важное, потому что «четыре ключевые метрики DORA» до сих пор кочуют по статьям и докладам: модель изменилась. Метрик теперь пять, и одна из прежних переопределена:
- в 2023 году MTTR (mean time to restore) была переименована и переопределена как failed deployment recovery time — прежнее определение не отличало отказ, вызванный изменением, от отказа по внешней причине вроде аварии в дата-центре, и мерило, по сути, две разные вещи;
- в 2024 году добавилась пятая — deployment rework rate.
Изменилась и группировка: восстановление после неудачного деплоя относится к пропускной способности, а не к стабильности.
Throughput (пропускная способность):
- Deployment frequency — как часто выкатываете.
- Change lead time — сколько проходит от коммита до прода.
- Failed deployment recovery time — сколько занимает восстановление именно после неудачного деплоя.
Instability (нестабильность):
- Change fail rate — какая доля деплоев приводит к отказу.
- Deployment rework rate — какая доля деплоев была незапланированной работой по устранению последствий инцидента в проде.
Считаются они из данных, которые уже есть: деплои с метками времени (когда закоммичено, когда выкачено, плановый или аварийный) и инциденты (когда начался, когда закрыт, каким деплоем вызван). В стенде это калькулятор на CSV-фикстурах:
деплоев: 8 за 14 дн.
-- throughput --
1. Deployment frequency: 0.571 деплоя/день (= 8 / 14)
2. Change lead time (медиана): 3h30m0s
3. Failed deployment recovery (мед): 2h0m0s
-- instability --
4. Change fail rate: 12.5% (1 упавших деплоев из 8)
5. Deployment rework rate: 12.5% (1 незапланированных из 8)Взятые вместе, пять метрик описывают доставку с двух сторон: быстро ли и устойчиво ли. По отдельности каждая обманчива — и именно на этом ломается их наивное применение.
KPI-театр: как накрутить одну метрику
Как только метрика становится целью, её начинают двигать напрямую, а не через то, что она должна отражать. Это закон Гудхарта, и на DORA он виден особенно наглядно.
Возьмём deployment frequency как цель — «деплоим чаще». Простейший способ поднять её без всякого улучшения доставки: дробить деплои. Те же изменения, но выкаченные вдвое большим числом релизов. Стенд считает метрики на такой «театральной» фикстуре — те же коммиты, каждый деплой разбит надвое — и сравнивает с честной:
Есть тонкость, из-за которой это стоит считать в двух вариантах. Дробление создаёт новые деплои, и кто-то должен решить, как их классифицировать: аварийная выкатка, разбитая надвое, — это два аварийных деплоя или один аварийный и один плановый? Определение метрики этого не предписывает. Поэтому в стенде считаются оба варианта:
метрика честно театр A театр B
Deployment frequency 0.571 1.143 1.143
Change lead time 3h30m0s 3h30m0s 3h30m0s
Failed deployment recovery time 2h0m0s 2h0m0s 2h0m0s
Change fail rate 12.5% 6.2% 6.2%
Deployment rework rate 12.5% 12.5% 6.2%Накрутка уверенно двигает две метрики:
- deployment frequency удваивается — знаменатель (дни) прежний, числитель раздут;
- change fail rate уполовинивается — инцидент тот же, а деплоев вдвое больше, то есть вырос только знаменатель. (Считается она по числу различных деплоев, вызвавших хотя бы один инцидент, а не по числу инцидентов.)
Устойчивы в обоих вариантах разметки только две: change lead time — реальное время от коммита до прода не меняется, потому что дробление релизов не ускоряет разработку, — и failed deployment recovery time: тот же инцидент, то же восстановление. Именно их несогласованность с выросшей частотой и выдаёт подлог.
А deployment rework rate на роль такого детектора не годится, и это стоит отдельного абзаца, потому что первая редакция этой статьи утверждала обратное. Там было написано, что она устойчива «по построению»: дробление делит и числитель, и знаменатель. Столбец «театр B» показывает, что это неверно — стоит классифицировать вторую копию как плановую, и доля падает вдвое, ровно как change fail rate. Устойчивость в варианте A следовала не из определения метрики, а из того, как размечены данные. Разметку же выбирает тот, кто отчитывается.
Отсюда честная граница вывода. Демонстрация показывает узкое: эта стратегия накрутки — дробление деплоев — обнаруживается несогласованностью метрик, опирающихся на разные события. Она не доказывает, что метрики нельзя подделать вообще: границу деплоя, классификацию аварийных выкаток, привязку инцидентов к релизам и момент фиксации коммита определяет тот же, кто предъявляет результат. DORA и сама предупреждает, что её метрики не должны становиться целями и осмысленны только в контексте конкретного приложения или сервиса. Стоит сделать любую из них KPI с премией — и получишь ровно этот театр, просто в другой форме.
Связи: восстановление, отказы, error budget
Метрики нестабильности не висят в воздухе — они смыкаются с соседними практиками.
Failed deployment recovery time измеряет скорость восстановления после неудачного деплоя, а улучшается она не гонкой за числом, а разбором причин: постмортемы превращают каждый инцидент в устранённый класс проблем. Падает не метрика как цель — падает частота и тяжесть инцидентов, а метрика лишь отражает это. Здесь же видна польза переопределения: прежняя MTTR смешивала «мы выкатили сломанное» и «у провайдера упал дата-центр», хотя лечатся эти две ситуации совершенно по-разному.
Change fail rate — это про качество того, что выкатывается: доля деплоев, потребовавших отката или хотфикса. Снижают её не отчётностью, а тем, что доезжает до прода, — тестами, ревью, прогрессивной выкаткой.
Deployment rework rate — про то, сколько выкаток вообще уходит на разгребание последствий вместо продвижения продукта. Высокая доля — сигнал, что команда живёт в режиме тушения пожаров, даже если частота деплоев при этом выглядит здоровой.
Связывает всё это error budget: если надёжность формализована как SLO, то допустимая доля отказов становится бюджетом, который тратят осознанно — быстрее катим, пока бюджет есть, притормаживаем, когда исчерпан. Это переводит DORA из отчётного дашборда в рычаг принятия решений; подробнее — в статье про SLO и error budget.
Демо и версии
Рабочий пример — digital-cookbook/infrastructure/cicd, раздел release-dora: changelog-demo.sh (версия и changelog из conventional commits через git-cliff на репозитории-фикстуре) и dora-demo.sh (калькулятор пяти метрик на честной и «театральной» фикстурах). Калькулятор покрыт тестами, включая случаи, которые молча искажают результат: инцидент со ссылкой на несуществующий деплой (иначе change fail rate способен превысить 100%), рваный CSV, неразбираемая дата, повторяющийся идентификатор. Все числа — детерминированный расчёт по фикстурам, в FIXTURES.md стенда.
Версии на момент замеров: git-cliff 2.13.1, Go 1.26.5. DORA-числа — арифметика на входных данных, не замер живой системы; ценность в методе расчёта и в том, как связка из пяти метрик ловит накрутку одной.
Документация
- Semantic Versioning 2.0.0 — спецификация semver.
- Conventional Commits — формат сообщений.
git-cliff— генератор changelog из conventional commits.- DORA: актуальные метрики доставки — первоисточник, текущая пятиметровая модель.
- История метрик DORA — когда и почему MTTR стала failed deployment recovery time и откуда взялась deployment rework rate.
- Закон Гудхарта — почему метрика-цель перестаёт быть метрикой.
Это финальная статья серии «CI/CD на практике». Начало — дизайн CI-пайплайна, середина — деплой без простоя и миграции схемы.
Комментарии