{ "index": 125, "slug": "editorial-2024-07-mechanism-release-engineering", "title": "Инженерия релиза: как связать артефакт, миграцию и откат", "excerpt": "Один номер версии не доказывает, что команда выпускает нужный код. Разбираем цепочку commit → artifact → migration → rollout и ставим проверяемый стоп перед ошибочным deploy.", "contentHtml": "
После выкладки сервис отвечает старым поведением, хотя в CI и карточке релиза стоит одна версия — 2024.07.0. Откат возвращает прежний контейнер, но ошибка в данных остаётся. Команда повторяет deploy, меняет таймаут и смотрит на зелёный статус job. Это не исправляет расхождение. Цена ошибки — потерянное время, спор о том, что именно работает, и риск усугубить миграцию данных.
Тезис простой: релиз нужно проверять как цепочку связей, а не как строку с версией. Commit должен быть источником артефакта. Rollout должен ссылаться на точный digest артефакта. Миграция должна называть целевую версию и границу совместимости. Для возврата нужно заранее назвать версию и digest. Если хотя бы одна связь не сходится, процесс останавливается до deploy.
\nТег отвечает на вопрос «как назвали выпуск». Он не отвечает на вопросы «из какого commit собрали образ», «какой образ запросил rollout» и «для какой схемы написана миграция». Для этих вопросов нужны неизменяемые значения и явные предикаты.
\nartifact.sourceCommitId === commit.id — артефакт собран из заявленного commit.rollout.requestedArtifactDigest === artifact.digest — намерение выкладки указывает тот же контент.migration.targetReleaseVersion === release.version — миграция относится к этому выпуску.returnPoint.version и returnPoint.digest заполнены — у возврата есть конкретная точка.Эти условия проверяют согласованность записей. Они не доказывают, что deploy завершился, что registry доступен или что миграция обратима. Execution result и release evidence — разные вещи. Успешный rollout может работать с неправильным артефактом. Согласованный record может ещё не быть разрешением на выкладку.
\nНиже — синтетические записи. Они не получены из production и не описывают реальную доставку.
\nconst commit = {\n id: 'synthetic-commit-91',\n releaseVersion: '2024.07.0'\n};\n\nconst artifact = {\n digest: 'sha256:synthetic-artifact-42',\n sourceCommitId: 'synthetic-commit-other-91'\n};\n\nconst migration = {\n targetReleaseVersion: '2024.07.0',\n compatibleWith: '2024.06.x'\n};\n\nconst rollout = {\n requestedArtifactDigest: 'sha256:synthetic-artifact-42'\n};\n\nconst checks = {\n sourceMatches: artifact.sourceCommitId === commit.id,\n artifactMatches: rollout.requestedArtifactDigest === artifact.digest,\n migrationMatches: migration.targetReleaseVersion === commit.releaseVersion\n};\n\nconst canDeploy = Object.values(checks).every(Boolean);\n// false: остановить процесс и сверить записи\n\nДве проверки проходят. Артефакт и rollout называют один digest, миграция нацелена на правильную версию. Но source commit не совпадает. Поэтому canDeploy равен false. Нельзя делать вывод, что контейнер содержит код из synthetic-commit-91. Нельзя лечить это повторным запуском того же deploy. Сначала нужно найти источник расхождения и заново зафиксировать запись.
Обратный путь важен не меньше. Возврат контейнера к предыдущему digest не отменяет изменение схемы или данных. Если миграция уже прошла, прежний код может не поддерживать новую схему. В карточке возврата нужно разделить два действия: вернуть code artifact и решить, что делать с data effect. Если второго решения нет, честный статус — «возврат артефакта подготовлен, откат данных не определён».
\n| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| Везде одна версия, но поведение разное | Тег используют как единственный идентификатор | Сравнить exact commit id и artifact sourceCommitId | Остановить выпуск и пересобрать evidence chain |
| Rollout зелёный, но загружен не тот образ | Карточка хранит tag вместо digest | Сравнить requestedArtifactDigest с digest артефакта | Исправить intent record, не повторять deploy |
| После возврата код падает на данных | Rollback контейнера приняли за rollback данных | Проверить target schema, compatibility и migration status | Передать data effect отдельному владельцу и остановить автоматический возврат |
| Миграция прошла для другой версии | План миграции следует ветке или последнему main | Сравнить targetReleaseVersion с release.version | Закрыть gate и выпустить новый migration review |
| Невозможно объяснить, что вернётся | Return point описана словом «предыдущий» | Проверить конкретные version и digest | Не давать approval, пока точка возврата не названа |
Таблица полезна только тогда, когда каждая проверка имеет владельца и stop action. Строка «все jobs зелёные» недостаточна: она не связывает job с содержимым артефакта и контрактом данных. Строка «digest совпал» тоже недостаточна: она не подтверждает доступность сервиса после выкладки. Не смешивайте semantic consistency с результатом исполнения.
\nУ релиза есть как минимум два состояния: code state и data state. Deployment обычно управляет шаблоном Pod или другим runtime artifact. Миграция меняет схему, записи или внешний контракт. Эти операции могут иметь разные владельцы, журналы и способы возврата.
\nБезопасный порядок требует compatibility window. Новый код сначала должен работать со старой и новой формой данных, если это возможно. Затем миграция меняет данные. После проверки трафика команда может удалить старую ветку совместимости. В такой схеме возврат на старый код возможен только до закрытия окна. После него нужен отдельный план: обратная миграция, восстановление из backup или сохранение нового кода с исправлением.
\nЭто не универсальная стратегия миграций. Некоторые изменения нельзя отменить. Некоторые системы разрешают только forward migration. Статья не утверждает, что любой Kubernetes Deployment или любой image digest можно безопасно вернуть. Она требует назвать границу действия и не приписывать rollback то, чего он не делает.
\nfalse верните статус stop-and-reconcile-records. Не запускайте новую попытку ради зелёного job.Эта модель не проверяет настоящий Git history, подпись, identity builder, provenance, registry, environment configuration, secrets, права доступа, database state, трафик и telemetry. Она не выдаёт уровень SLSA и не доказывает, что конкретный attestation заслуживает доверия. Для этого нужны отдельные политики, хранилища и проверяющие компоненты.
\nУчебный код также не является CI-конфигурацией. Синтетические id и digest нужны, чтобы показать рассуждение на закрытом наборе данных. Реальные значения нельзя подменить в этом примере и затем считать результат производственным evidence. Практический перенос начинается с одного разрешённого release record и read-only проверки, а не с подключения fixture к deploy.
\nРелиз готов к авторизованному review, если второй проверяющий без устных пояснений находит в одной записи:
\nПроверяющий должен назвать результат каждой связи: true или false, stop action при false и владельца следующего вопроса. Если он может только сказать «job зелёный», критерий не выполнен. Это проверяемый предел статьи: согласовать записи до действия, не объявить production success.