{ "index": 124, "slug": "editorial-2024-07-field-release-engineering", "title": "Когда версия не доказывает релиз: проверка artifact, migration и rollback", "excerpt": "Как связать commit, provenance, digest, migration и точку возврата до approval — с воспроизводимым gate, отрицательным тестом и честными границами rollback.", "contentHtml": "
В заявке на выпуск стоит 2024.07.0. Такое же имя указано у ветки, образа, миграции и выкладки. После запуска сервис отвечает ошибкой схемы: новый код обращается к полю, которого нет в базе. Команда повторяет deploy, потому что все записи выглядят согласованными. Это учебный сценарий, а не отчёт о конкретной production-системе. Его задача — показать, какие факты нужно связать до разрешения релиза.
Номер версии не доказывает происхождение и содержимое выпуска. Для решения нужны как минимум четыре независимые связи: artifact собран из нужного commit, rollout выбирает тот же digest, migration совместима с текущей схемой, а return point описан отдельно. Если связь неизвестна или ложна, проверка должна остановиться. Повторный deploy не превращает неизвестный факт в доказательство.
\nCommit — точная ревизия исходников. Artifact — результат сборки, например контейнерный образ, доступный по digest. Provenance — проверяемые сведения о том, где и как собран artifact. Migration — изменение схемы или данных с заявленной совместимостью. Rollout intent — запись о том, какой artifact и какую migration собираются применить. Return point — заранее известный вариант возврата к предыдущему коду и описание границы данных.
\nЭто не одна сущность с полем version. Для каждого объекта нужен источник и владелец. Поле artifact.sourceCommitId ниже — проектный контракт учебной модели, а не универсальное поле Docker или Kubernetes. Аналогично, migration.compatibleWith требует договорённости команды: инструменты миграций называют такие сведения по-разному.
Tag удобен для человека, но это изменяемая ссылка. Один и тот же tag может указывать на другой образ после следующей сборки. Digest адресует содержимое образа. Docker документирует загрузку образа по форме name@sha256:... и объясняет, что такой идентификатор фиксирует выбранную версию содержимого. Поэтому в rollout-записи храните digest, а tag оставляйте только как дополнительную подпись для чтения.
Digest отвечает лишь на вопрос «какое содержимое выбрано». Он не отвечает на вопросы «из какого commit оно собрано», «кто его собрал» и «совместима ли схема». Для этого нужен provenance и политика проверки. В SLSA v1.2 проверка включает сопоставление subject с digest artifact, доверенный builder, подпись и ожидаемые параметры сборки. Это отдельный gate, а не синоним успешной загрузки образа.
\nУ attestation тоже есть граница. Подписанное утверждение связывает metadata с artifact, но не утверждает, что migration выполнилась, сервис принимает трафик или решение о выкладке одобрил нужный человек. Эти факты должны появиться в собственных системах и проверках.
\nСледующая команда запускается в Node.js без зависимостей. Все значения синтетические: она не обращается к Git, registry, CI, Kubernetes API или базе. Код проверяет только заранее подготовленные записи и завершает процесс с ненулевым статусом при расхождении. Сохраните его как команду через heredoc или вставьте в локальный терминал.
\nnode --input-type=module <<'NODE'\nconst current = { schemaVersion: '2024.06.3' };\nconst release = { version: '2024.07.0' };\nconst commit = { id: 'commit-7f4a0c1' };\nconst artifact = {\n digest: 'sha256:release-070-a1',\n sourceCommitId: 'commit-7f4a0c1',\n};\nconst migration = {\n version: '2024.07.0',\n targetSchemaVersion: '2024.07.0',\n compatibleWith: '2024.06.3',\n};\nconst rollout = {\n artifactDigest: 'sha256:release-070-a1',\n migrationVersion: '2024.07.0',\n};\nconst returnPoint = {\n artifactDigest: 'sha256:release-069-z9',\n schemaVersion: '2024.06.3',\n};\n\nconst checks = {\n source: artifact.sourceCommitId === commit.id,\n release: migration.version === release.version,\n compatibility: migration.compatibleWith === current.schemaVersion,\n artifact: rollout.artifactDigest === artifact.digest,\n migration: rollout.migrationVersion === migration.version,\n returnPoint: Boolean(returnPoint.artifactDigest && returnPoint.schemaVersion),\n};\nconst ready = Object.values(checks).every(Boolean);\n\nconsole.log(JSON.stringify({ checks, ready }, null, 2));\nif (!ready) process.exitCode = 1;\nNODE\nПри исходных данных команда печатает \"ready\": true и завершается с кодом 0. Измените rollout.artifactDigest на sha256:release-070-other: поле artifact станет false, а процесс завершится с кодом 1. Такой отрицательный тест важнее красивого положительного fixture: он показывает, что gate не угадывает правильную запись.
Положительный результат означает только согласованность шести полей в памяти. Он не доказывает, что digest существует в registry, provenance подписан, миграция запущена, а старый artifact умеет работать с новой схемой. Эти вопросы нельзя «досчитать» из примера.
\n| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| Tag совпадает, а digest другой. | Rollout использует изменяемую ссылку вместо содержимого. | Сверить digest в intent с digest из registry и записи сборки. | Остановить запуск; пересоздать intent после выбора exact artifact. |
| Artifact указывает на другой commit. | Образ собран из другой ревизии, либо provenance неполон. | Сопоставить commit, provenance subject и ожидаемый source repository. | Не объявлять образ релизом; пересобрать или исправить запись после расследования. |
| Новая схема не совместима со старым кодом. | Миграцию выполнили как необратимый шаг до перехода reader/writer. | Проверить порядок expand, переключение читателей и contract-шаг. | Остановить удаление/изменение данных; привлечь владельца схемы. |
| После stop предлагают повторить deploy. | Retry используют вместо объяснения первой ложной связи. | Найти первый mismatch и назвать источник каждого значения. | Сначала reconcile записей, затем повторить только проверку. |
Таблица разделяет происхождение, содержимое и совместимость. Это помогает не лечить ошибку схемы заменой образа и не считать зелёный rollout доказательством корректности данных. Если один источник недоступен, статус должен быть «не проверено», а не «вероятно совпадает».
\nregistry.example/app@sha256:....В Kubernetes новая revision Deployment появляется, когда меняется Pod template, например image или labels. Команда kubectl rollout undo возвращает предыдущую ревизию этого шаблона. Значит, такой rollback касается описания Pod: образа, переменных и других полей template. Он не является отменой произвольного SQL, удалённой строки, уже отправленного сообщения или изменения внешней системы.
Проблема особенно заметна при миграции. Если новая версия добавила поле и старый код его не ожидает, возврат image может вернуть работоспособность. Если новая версия удалила или изменила смысл поля, старый код может не запуститься на текущей схеме. До релиза нужен либо совместимый expand/contract-переход, либо проверенная обратная миграция, либо восстановление из резервной копии. Выбор зависит от базы, инструмента и договора владельцев данных.
\nПрактичная формулировка return point состоит из двух утверждений: «этот artifact можно снова запустить» и «для этой схемы есть разрешённое действие». Первое не даёт права утверждать второе. Если доказан только возврат Pod template, так и пишите в release record: «rollback кода определён; rollback данных не подтверждён».
\nМетод подходит как предварительная проверка связей в release record и как шаблон для CI-gate. Он не заменяет security review, проверку подписи, тест совместимости, backup/restore drill, smoke-тест, анализ метрик или approval согласно правилам организации. Названия полей и формат provenance в вашем toolchain могут отличаться; переносите инварианты, а не имена из учебного примера.
\nSLSA проверяет provenance относительно заданных ожиданий, но сами ожидания должны быть сформированы и защищены командой. GitHub artifact attestations доступны только при соответствующей настройке workflow и permissions; команда gh attestation verify требует доступного GitHub-контекста и не проверяет вашу migration. Kubernetes хранит историю Deployment с ограничениями revision history, поэтому старый Pod template может быть недоступен, если историю сократили или образ удалён.
Статус должен быть отрицательным, если отсутствует полный commit id, digest, источник provenance, compatibility statement или return point. Не подставляйте «последний main», не ищите образ по tag и не объявляйте rollback данных по факту kubectl rollout undo. Остановка на первой неизвестной границе дешевле расследования после повреждения данных.
Перед human approval второй инженер без устных пояснений должен найти exact commit id, source repository, artifact digest, связь artifact с commit, результат provenance verification, текущую и целевую схему, compatibility statement, rollout digest, migration version и return point. Для каждого значения указаны источник, владелец и действие при mismatch.
\nГотовность здесь — не одно зелёное число. Это воспроизводимый набор утверждений: все обязательные связи истинны, неизвестные значения не замаскированы, а возврат коду не выдан за возврат данным. Если хотя бы одно утверждение нельзя показать, релиз остаётся на проверке с конкретной причиной остановки.
\n