8 lines
15 KiB
JSON
8 lines
15 KiB
JSON
{
|
||
"index": 125,
|
||
"slug": "editorial-2024-07-mechanism-release-engineering",
|
||
"title": "Инженерия релиза: как связать артефакт, миграцию и откат",
|
||
"excerpt": "Один номер версии не доказывает, что команда выпускает нужный код. Разбираем цепочку commit → artifact → migration → rollout и ставим проверяемый стоп перед ошибочным deploy.",
|
||
"contentHtml": "<p>После выкладки сервис отвечает старым поведением, хотя в CI и карточке релиза стоит одна версия — <code>2024.07.0</code>. Откат возвращает прежний контейнер, но ошибка в данных остаётся. Команда повторяет deploy, меняет таймаут и смотрит на зелёный статус job. Это не исправляет расхождение. Цена ошибки — потерянное время, спор о том, что именно работает, и риск усугубить миграцию данных.</p>\n<p>Тезис простой: релиз нужно проверять как цепочку связей, а не как строку с версией. Commit должен быть источником артефакта. Rollout должен ссылаться на точный digest артефакта. Миграция должна называть целевую версию и границу совместимости. Для возврата нужно заранее назвать версию и digest. Если хотя бы одна связь не сходится, процесс останавливается до deploy.</p>\n<h2>Механизм: четыре связи вместо одного тега</h2>\n<p>Тег отвечает на вопрос «как назвали выпуск». Он не отвечает на вопросы «из какого commit собрали образ», «какой образ запросил rollout» и «для какой схемы написана миграция». Для этих вопросов нужны неизменяемые значения и явные предикаты.</p>\n<ul><li><code>artifact.sourceCommitId === commit.id</code> — артефакт собран из заявленного commit.</li><li><code>rollout.requestedArtifactDigest === artifact.digest</code> — намерение выкладки указывает тот же контент.</li><li><code>migration.targetReleaseVersion === release.version</code> — миграция относится к этому выпуску.</li><li><code>returnPoint.version</code> и <code>returnPoint.digest</code> заполнены — у возврата есть конкретная точка.</li></ul>\n<p>Эти условия проверяют согласованность записей. Они не доказывают, что deploy завершился, что registry доступен или что миграция обратима. Execution result и release evidence — разные вещи. Успешный rollout может работать с неправильным артефактом. Согласованный record может ещё не быть разрешением на выкладку.</p>\n<h2>Учебный пример расхождения</h2>\n<p>Ниже — синтетические записи. Они не получены из production и не описывают реальную доставку.</p>\n<pre><code>const 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</code></pre>\n<p>Две проверки проходят. Артефакт и rollout называют один digest, миграция нацелена на правильную версию. Но source commit не совпадает. Поэтому <code>canDeploy</code> равен <code>false</code>. Нельзя делать вывод, что контейнер содержит код из <code>synthetic-commit-91</code>. Нельзя лечить это повторным запуском того же deploy. Сначала нужно найти источник расхождения и заново зафиксировать запись.</p>\n<p>Обратный путь важен не меньше. Возврат контейнера к предыдущему digest не отменяет изменение схемы или данных. Если миграция уже прошла, прежний код может не поддерживать новую схему. В карточке возврата нужно разделить два действия: вернуть code artifact и решить, что делать с data effect. Если второго решения нет, честный статус — «возврат артефакта подготовлен, откат данных не определён».</p>\n<figure><img src=\"/assets/editorial/2024/release-engineering-2024-rollback-table.svg\" alt=\"Связи между артефактом, миграцией, rollout и точкой возврата\" /><figcaption>Учебная схема показывает границу: code rollback возвращает названный артефакт, но не обещает отмену миграции.</figcaption></figure>\n<h2>Симптом → причина → проверка → действие</h2>\n<table><caption>Диагностика рассогласованного релиза</caption><thead><tr><th>Симптом</th><th>Причина</th><th>Проверка</th><th>Действие</th></tr></thead><tbody><tr><td>Везде одна версия, но поведение разное</td><td>Тег используют как единственный идентификатор</td><td>Сравнить exact commit id и artifact sourceCommitId</td><td>Остановить выпуск и пересобрать evidence chain</td></tr><tr><td>Rollout зелёный, но загружен не тот образ</td><td>Карточка хранит tag вместо digest</td><td>Сравнить requestedArtifactDigest с digest артефакта</td><td>Исправить intent record, не повторять deploy</td></tr><tr><td>После возврата код падает на данных</td><td>Rollback контейнера приняли за rollback данных</td><td>Проверить target schema, compatibility и migration status</td><td>Передать data effect отдельному владельцу и остановить автоматический возврат</td></tr><tr><td>Миграция прошла для другой версии</td><td>План миграции следует ветке или последнему main</td><td>Сравнить targetReleaseVersion с release.version</td><td>Закрыть gate и выпустить новый migration review</td></tr><tr><td>Невозможно объяснить, что вернётся</td><td>Return point описана словом «предыдущий»</td><td>Проверить конкретные version и digest</td><td>Не давать approval, пока точка возврата не названа</td></tr></tbody></table>\n<p>Таблица полезна только тогда, когда каждая проверка имеет владельца и stop action. Строка «все jobs зелёные» недостаточна: она не связывает job с содержимым артефакта и контрактом данных. Строка «digest совпал» тоже недостаточна: она не подтверждает доступность сервиса после выкладки. Не смешивайте semantic consistency с результатом исполнения.</p>\n<h2>Почему миграция меняет смысл отката</h2>\n<p>У релиза есть как минимум два состояния: code state и data state. Deployment обычно управляет шаблоном Pod или другим runtime artifact. Миграция меняет схему, записи или внешний контракт. Эти операции могут иметь разные владельцы, журналы и способы возврата.</p>\n<p>Безопасный порядок требует compatibility window. Новый код сначала должен работать со старой и новой формой данных, если это возможно. Затем миграция меняет данные. После проверки трафика команда может удалить старую ветку совместимости. В такой схеме возврат на старый код возможен только до закрытия окна. После него нужен отдельный план: обратная миграция, восстановление из backup или сохранение нового кода с исправлением.</p>\n<p>Это не универсальная стратегия миграций. Некоторые изменения нельзя отменить. Некоторые системы разрешают только forward migration. Статья не утверждает, что любой Kubernetes Deployment или любой image digest можно безопасно вернуть. Она требует назвать границу действия и не приписывать rollback то, чего он не делает.</p>\n<h2>Порядок действий перед deploy</h2>\n<ol><li><strong>Зафиксируйте release version и owner проверки.</strong> Owner отвечает за сравнение записей, но это не делает его автоматически исполнителем deploy.</li><li><strong>Проверьте связь commit → artifact.</strong> Сравните exact id. Название ветки, последний merge и номер задачи не заменяют идентификатор commit.</li><li><strong>Проверьте artifact → rollout.</strong> Сравните immutable digest. Не подставляйте digest по тегу и не считайте совпадение имён доказательством.</li><li><strong>Опишите migration target и compatibility.</strong> Назовите версию, допустимый предыдущий контракт и отдельный data effect.</li><li><strong>Проверьте все предикаты.</strong> При первом <code>false</code> верните статус <code>stop-and-reconcile-records</code>. Не запускайте новую попытку ради зелёного job.</li><li><strong>Подготовьте return point.</strong> Запишите version и digest артефакта для возврата. Рядом укажите, что произойдёт с данными.</li><li><strong>Отделите approval от исполнения.</strong> Проверка записи разрешает перейти к авторизованному review, но сама не вызывает registry, cluster или deploy runner.</li></ol>\n<h2>Ограничения</h2>\n<p>Эта модель не проверяет настоящий Git history, подпись, identity builder, provenance, registry, environment configuration, secrets, права доступа, database state, трафик и telemetry. Она не выдаёт уровень SLSA и не доказывает, что конкретный attestation заслуживает доверия. Для этого нужны отдельные политики, хранилища и проверяющие компоненты.</p>\n<p>Учебный код также не является CI-конфигурацией. Синтетические id и digest нужны, чтобы показать рассуждение на закрытом наборе данных. Реальные значения нельзя подменить в этом примере и затем считать результат производственным evidence. Практический перенос начинается с одного разрешённого release record и read-only проверки, а не с подключения fixture к deploy.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Релиз готов к авторизованному review, если второй проверяющий без устных пояснений находит в одной записи:</p>\n<ul><li>точный commit id, из которого заявлен artifact;</li><li>точный artifact digest, который запросил rollout;</li><li>target версии миграции и её compatibility boundary;</li><li>version и digest для возврата;</li><li>отдельное решение по data effect.</li></ul>\n<p>Проверяющий должен назвать результат каждой связи: <code>true</code> или <code>false</code>, stop action при <code>false</code> и владельца следующего вопроса. Если он может только сказать «job зелёный», критерий не выполнен. Это проверяемый предел статьи: согласовать записи до действия, не объявить production success.</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://slsa.dev/spec/v1.0/\" target=\"_blank\" rel=\"noopener noreferrer\">SLSA v1.0 specification</a> — термины provenance и verification.</li><li><a href=\"https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/about-artifact-attestations\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub: About artifact attestations</a> — официальное описание attestations и их границ.</li><li><a href=\"https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#rolling-back-a-deployment\" target=\"_blank\" rel=\"noopener noreferrer\">Kubernetes: Rolling back a Deployment</a> — что именно возвращает Deployment rollback.</li></ul>"
|
||
}
|