8 lines
18 KiB
JSON
8 lines
18 KiB
JSON
{
|
||
"index": 125,
|
||
"slug": "editorial-2024-07-mechanism-release-engineering",
|
||
"title": "Инженерия релиза: как доказать связь артефакта, миграции и отката",
|
||
"excerpt": "Одинаковая версия в CI не доказывает, что будет доставлен нужный код. Разбираем цепочку commit → artifact → migration → rollout и ставим проверяемый stop до deploy.",
|
||
"contentHtml": "<p>Симптом после выкладки: сервис отвечает старым поведением, хотя в CI и карточке релиза написано <code>2024.07.0</code>. Команда повторяет deploy, увеличивает таймаут и смотрит на зелёный job. Если перед этим миграция изменила данные, откат контейнера может вернуть старый код, но не вернуть прежнее состояние базы. В итоге спорят не о причине сбоя, а о том, какой именно артефакт вообще работает.</p>\n<p>Номер версии удобен человеку, но слаб как единственное доказательство. Надёжнее проверять цепочку: заявленный commit связан с provenance сборки, rollout указывает на неизменяемый digest образа, миграция называет целевую границу совместимости, а для возврата записана конкретная ревизия. Это не разрешение на выкладку и не обещание успешного production deploy. Это stop-проверка, которая не даёт продолжить при расхождении записей.</p>\n<h2>Что должна доказать запись о релизе</h2>\n<p>Сначала разделим четыре разных вопроса. <strong>Источник.</strong> Из какого точного commit получены входы сборки? <strong>Артефакт.</strong> Какой digest соответствует результату сборки? <strong>Изменение данных.</strong> Какую схему или форму записи ожидает новая версия? <strong>Исполнение.</strong> Какой digest запросил rollout и чем подтверждено его завершение?</p>\n<p>Эти вопросы связаны, но не взаимозаменяемы. Provenance — это подписанная или иным образом проверяемая информация о том, как получен артефакт; она не заменяет политику потребителя. Digest идентифицирует содержимое образа, но не подтверждает, что образ запущен в нужном окружении. Успешный статус rollout говорит о состоянии Deployment, а не об обратимости миграции. Поэтому release record должен хранить отдельные поля, а проверка — отдельные результаты.</p>\n<table><caption>Границы доказательств в цепочке релиза</caption><thead><tr><th>Связь</th><th>Проверяемое утверждение</th><th>Что не следует из успеха проверки</th></tr></thead><tbody><tr><td>commit → provenance</td><td>В provenance зафиксирован ожидаемый источник или dependency с точным идентификатором.</td><td>Сборка безопасна от всех атак и полностью воспроизводима.</td></tr><tr><td>provenance → artifact</td><td>Subject attestation относится к конкретному артефакту, а signer и builder входят в доверенную политику.</td><td>Артефакт без уязвимостей и подходит каждому окружению.</td></tr><tr><td>artifact → rollout</td><td>В намерении выкладки указан тот же immutable digest.</td><td>Контейнер уже запущен и прошёл проверки доступности.</td></tr><tr><td>migration → release</td><td>Миграция называет совместимую целевую версию и известный data effect.</td><td>Миграцию можно безопасно отменить одной командой.</td></tr><tr><td>return point → rollback</td><td>Названы точные version, digest и действие для данных.</td><td>Старый код поддерживает новую схему после закрытия окна совместимости.</td></tr></tbody></table>\n<h2>Маленький gate на синтетических данных</h2>\n<p>Ниже — самодостаточная проверка для командной строки. Записи вымышлены: значение примера в том, что одна связь намеренно сломана. Файл не вызывает registry, cluster или deploy runner; он только вычисляет логический результат из локального release record.</p>\n<pre><code>cat > release-record.json <<'JSON'\n{\n \"releaseVersion\": \"2024.07.0\",\n \"commit\": { \"id\": \"commit-91\" },\n \"provenance\": {\n \"sourceCommitId\": \"commit-91\",\n \"artifactDigest\": \"sha256:artifact-42\",\n \"builderId\": \"ci.example/build\"\n },\n \"rollout\": { \"requestedDigest\": \"sha256:artifact-other\" },\n \"migration\": {\n \"targetVersion\": \"2024.07.0\",\n \"compatibleWith\": [\"2024.06.x\"],\n \"dataEffect\": \"adds nullable profile.locale\"\n },\n \"returnPoint\": {\n \"version\": \"2024.06.4\",\n \"digest\": \"sha256:previous-17\",\n \"dataAction\": \"keep forward-compatible column\"\n }\n}\nJSON\n\njq -e '\n .provenance.sourceCommitId == .commit.id\n and .provenance.artifactDigest == .rollout.requestedDigest\n and .migration.targetVersion == .releaseVersion\n and (.returnPoint.version != \"\" and .returnPoint.digest != \"\")\n' release-record.json\n# jq: false, exit code 1 — rollout digest не совпал\n</code></pre>\n<p>Команда завершится с кодом <code>1</code>, потому что <code>sha256:artifact-other</code> не равен digest, записанному в provenance. Три прочие части примера согласованы, но этого недостаточно: gate должен остановить действие при одном <code>false</code>. После исправления записи полезно повторить ту же команду и отдельно проверить, что исполнение rollout действительно завершилось. Логический <code>true</code> не подменяет runtime evidence.</p>\n<figure><img src=\"/assets/editorial/2024/release-engineering-2024-rollback-table.svg\" alt=\"Схема проверки релиза: commit и provenance связаны с digest артефакта, rollout сверяется с ним, а migration и точка возврата проверяются отдельно\" /><figcaption>Схема разделяет проверку кода и проверку данных: возврат Deployment касается шаблона Pod, но не обещает отменить уже выполненную миграцию.</figcaption></figure>\n<h2>Как читать расхождение</h2>\n<p>Если в карточке и в CI одна версия, а поведение разное, первым делом не запускайте новый deploy. Получите фактический digest запущенного образа и сравните его с digest в намерении выкладки. Затем сопоставьте provenance с ожидаемым commit и доверенным builder. Здесь важно не восстановить «примерно ту же сборку», а найти конкретную точку, где цепочка перестала быть доказуемой.</p>\n<p>Если rollout указывает на правильный digest, но сервис всё ещё ведёт себя иначе, это уже другой класс проверки: конфигурация окружения, feature flag, кеш, трафик, версия зависимого сервиса и само состояние приложения. Цепочка артефакта не доказывает идентичность всех этих входов. Она лишь не позволяет списать любой эффект на слово «релиз».</p>\n<p>При расхождении миграции с release version нужно остановить и повторно согласовать план данных. Подмена значения в карточке задним числом стирает след ошибки. Зафиксируйте, какая миграция уже запущена, какие записи она изменила и с какой версией остаётся совместимой. Если этих сведений нет, статус должен быть «данные не классифицированы», а не «rollback готов».</p>\n<h2>Почему откат образа не откатывает данные</h2>\n<p>У приложения есть минимум два состояния: кодовый артефакт и состояние данных. Kubernetes Deployment хранит историю ревизий и позволяет вернуть Pod template к предыдущей ревизии, если она ещё доступна. Это полезно при проблеме с образом или параметрами запуска. Но команда <code>kubectl rollout undo</code> не отменяет SQL-миграцию, изменение документа или уже отправленное внешнему сервису событие.</p>\n<p>Поэтому миграцию стоит проектировать с окном совместимости, когда новый код умеет читать старую и новую форму данных. Сначала выкладывается код, способный работать в этом окне, затем выполняется изменение данных, после наблюдения удаляется старая ветка. Конкретный порядок зависит от хранилища и миграционного инструмента; это не универсальная лицензия на обратную миграцию.</p>\n<p>До закрытия окна точка возврата может быть обычным предыдущим артефактом. После закрытия нужно отдельное решение: forward fix, обратная миграция, восстановление резервной копии или сохранение нового кода с исправлением. В release record это должен быть явный <code>dataAction</code>, а не слово «откат» без объекта действия.</p>\n<h2>Порядок проверки перед approval</h2>\n<ol><li><strong>Снимите исходные идентификаторы.</strong> Запишите release version, точный commit SHA, digest артефакта, builder и окружение. Ветка, номер задачи и tag остаются удобными ссылками, но не заменяют SHA и digest.</li><li><strong>Проверьте provenance.</strong> Сверьте subject артефакта, source или dependency, signer и builder с политикой команды. Если используется attestation, проверяйте её криптографически, а не только открывайте страницу с метаданными.</li><li><strong>Сверьте намерение и исполнение.</strong> В manifest или release record должен быть тот же digest, который был разрешён. После deploy сохраните отдельное подтверждение состояния и времени rollout.</li><li><strong>Опишите data effect.</strong> Назовите таблицу, поля, документы или события, которые изменятся, а также допустимую старую форму. Не ставьте approval, если миграция существует только как название job.</li><li><strong>Назовите return point.</strong> Укажите конкретные version и digest для кода и отдельное решение для данных. Проверьте, что требуемая ревизия не удалена политикой хранения истории.</li><li><strong>Остановитесь при первом false.</strong> Верните запись на reconcile, не запускайте повторную попытку ради зелёного CI. Approval относится к проверенной записи; он не является доказательством фактического успеха выкладки.</li></ol>\n<h2>Границы применимости</h2>\n<p>Этот маршрут подходит как минимальный контроль согласованности для релиза, где команда может получить commit, provenance, digest, план миграции и запись rollout. Он не заменяет сканирование уязвимостей, review кода, проверку секретов, контроль прав, тесты совместимости, резервное копирование, мониторинг или процедуру incident response.</p>\n<p>SLSA описывает модель provenance и требования к её проверке, но не объявляет конкретный артефакт безопасным. GitHub отдельно предупреждает, что artifact attestation связывает артефакт с источником и инструкциями сборки, а решение о доверии требует собственной политики. В частном registry, другой CI-системе или без доверенного корня проверки команды и поля будут другими.</p>\n<p>Синтетический <code>release-record.json</code> нельзя подключать к настоящему deploy без адаптации схемы, прав и источников фактов. Kubernetes-команды требуют доступа к конкретному кластеру и работают с историей, которую можно ограничить настройкой <code>revisionHistoryLimit</code>. Если миграция необратима или внешний эффект уже ушёл, честный результат может быть «код возвращён, data effect остаётся».</p>\n<h2>Проверяемый результат</h2>\n<p>Перед авторизованной выкладкой второй инженер должен без устных пояснений найти в записи пять вещей: точный источник сборки, digest артефакта, digest в rollout, границу совместимости миграции и раздельный план возврата кода и данных. Для каждой связи должен быть результат <code>true</code> или <code>false</code>, а для <code>false</code> — владелец сверки и стоп-действие.</p>\n<p>После выкладки добавьте к этим записям фактический результат rollout и наблюдаемый сигнал приложения. Только тогда можно обсуждать поведение окружения. Такая последовательность возвращает разговор к исходному симптому: мы проверяем не красивую строку версии, а то, что именно собрано, что именно запрошено, что изменилось в данных и что реально можно вернуть.</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://slsa.dev/spec/v1.2/provenance\" target=\"_blank\" rel=\"noopener noreferrer\">SLSA v1.2: Provenance</a> — модель provenance, subject, build definition и требования к проверке.</li><li><a href=\"https://docs.github.com/en/actions/concepts/security/artifact-attestations\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub Docs: Artifact attestations</a> — назначение attestations и ограничение: они не являются гарантией безопасности артефакта.</li><li><a href=\"https://kubernetes.io/docs/tasks/run-application/update-deployment-rolling/#rolling-back-to-a-previous-revision\" target=\"_blank\" rel=\"noopener noreferrer\">Kubernetes Docs: Rolling back to a previous revision</a> — команды просмотра истории и возврата Deployment к ревизии.</li><li><a href=\"https://kubernetes.io/docs/concepts/workloads/controllers/deployment/\" target=\"_blank\" rel=\"noopener noreferrer\">Kubernetes Docs: Deployments</a> — граница rollback: возвращается Pod template, а не произвольное состояние данных.</li></ul>"
|
||
}
|