{ "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-системе. Его задача — показать, какие факты нужно связать до разрешения релиза.

\n

Номер версии не доказывает происхождение и содержимое выпуска. Для решения нужны как минимум четыре независимые связи: artifact собран из нужного commit, rollout выбирает тот же digest, migration совместима с текущей схемой, а return point описан отдельно. Если связь неизвестна или ложна, проверка должна остановиться. Повторный deploy не превращает неизвестный факт в доказательство.

\n

Что именно проверяет инженер

\n

Commit — точная ревизия исходников. Artifact — результат сборки, например контейнерный образ, доступный по digest. Provenance — проверяемые сведения о том, где и как собран artifact. Migration — изменение схемы или данных с заявленной совместимостью. Rollout intent — запись о том, какой artifact и какую migration собираются применить. Return point — заранее известный вариант возврата к предыдущему коду и описание границы данных.

\n

Это не одна сущность с полем version. Для каждого объекта нужен источник и владелец. Поле artifact.sourceCommitId ниже — проектный контракт учебной модели, а не универсальное поле Docker или Kubernetes. Аналогично, migration.compatibleWith требует договорённости команды: инструменты миграций называют такие сведения по-разному.

\n
\"Схема
Учебная схема показывает порядок сверки и стоп-ветку. Это не лог CI, registry, кластера, базы данных или реально выполненного rollout.
\n

Почему одного tag недостаточно

\n

Tag удобен для человека, но это изменяемая ссылка. Один и тот же tag может указывать на другой образ после следующей сборки. Digest адресует содержимое образа. Docker документирует загрузку образа по форме name@sha256:... и объясняет, что такой идентификатор фиксирует выбранную версию содержимого. Поэтому в rollout-записи храните digest, а tag оставляйте только как дополнительную подпись для чтения.

\n

Digest отвечает лишь на вопрос «какое содержимое выбрано». Он не отвечает на вопросы «из какого commit оно собрано», «кто его собрал» и «совместима ли схема». Для этого нужен provenance и политика проверки. В SLSA v1.2 проверка включает сопоставление subject с digest artifact, доверенный builder, подпись и ожидаемые параметры сборки. Это отдельный gate, а не синоним успешной загрузки образа.

\n

У attestation тоже есть граница. Подписанное утверждение связывает metadata с artifact, но не утверждает, что migration выполнилась, сервис принимает трафик или решение о выкладке одобрил нужный человек. Эти факты должны появиться в собственных системах и проверках.

\n

Минимальный воспроизводимый gate

\n

Следующая команда запускается в Node.js без зависимостей. Все значения синтетические: она не обращается к Git, registry, CI, Kubernetes API или базе. Код проверяет только заранее подготовленные записи и завершает процесс с ненулевым статусом при расхождении. Сохраните его как команду через heredoc или вставьте в локальный терминал.

\n
node --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 не угадывает правильную запись.

\n

Положительный результат означает только согласованность шести полей в памяти. Он не доказывает, что digest существует в registry, provenance подписан, миграция запущена, а старый artifact умеет работать с новой схемой. Эти вопросы нельзя «досчитать» из примера.

\n

Диагностика: симптом → причина → проверка → действие

\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 записей, затем повторить только проверку.
\n

Таблица разделяет происхождение, содержимое и совместимость. Это помогает не лечить ошибку схемы заменой образа и не считать зелёный rollout доказательством корректности данных. Если один источник недоступен, статус должен быть «не проверено», а не «вероятно совпадает».

\n

Порядок проверки перед approval

\n
  1. Определите объект релиза. Зафиксируйте имя сервиса, среду, release version и владельцев commit, artifact, migration и deploy. Не смешивайте тестовые и production-записи.
  2. Сверьте исходники и сборку. Возьмите полный commit id из системы контроля версий. Проверьте source repository, builder и параметры provenance; короткий tag или название ветки их не заменяет.
  3. Зафиксируйте artifact. Получите digest из registry или результата push. В deployment-манифесте используйте ссылку с digest, если это поддерживает ваша платформа. Для Docker-пути пример выглядит как registry.example/app@sha256:....
  4. Опишите migration. Назовите текущую и целевую схему, совместимость reader/writer, порядок шагов и отдельное действие для данных. Не прячьте SQL и условия возврата в комментарии к образу.
  5. Сверьте intent. Digest artifact и digest rollout должны совпасть, как и версия migration. Несовпадение переводит запись в stop.
  6. Проверьте return point. Запишите предыдущий digest, revision, способ переключения и проверку здоровья. Отдельно укажите, можно ли вернуть данные, и кто принимает это решение.
  7. Повторите gate после исправления. Положительный результат открывает human approval, но не выдаёт автоматическое право на deploy. Авторизованный процесс всё равно должен проверить свои policy, права и наблюдаемость.
\n

Что действительно означает rollback

\n

В Kubernetes новая revision Deployment появляется, когда меняется Pod template, например image или labels. Команда kubectl rollout undo возвращает предыдущую ревизию этого шаблона. Значит, такой rollback касается описания Pod: образа, переменных и других полей template. Он не является отменой произвольного SQL, удалённой строки, уже отправленного сообщения или изменения внешней системы.

\n

Проблема особенно заметна при миграции. Если новая версия добавила поле и старый код его не ожидает, возврат image может вернуть работоспособность. Если новая версия удалила или изменила смысл поля, старый код может не запуститься на текущей схеме. До релиза нужен либо совместимый expand/contract-переход, либо проверенная обратная миграция, либо восстановление из резервной копии. Выбор зависит от базы, инструмента и договора владельцев данных.

\n

Практичная формулировка return point состоит из двух утверждений: «этот artifact можно снова запустить» и «для этой схемы есть разрешённое действие». Первое не даёт права утверждать второе. Если доказан только возврат Pod template, так и пишите в release record: «rollback кода определён; rollback данных не подтверждён».

\n

Границы применимости и безопасный stop

\n

Метод подходит как предварительная проверка связей в release record и как шаблон для CI-gate. Он не заменяет security review, проверку подписи, тест совместимости, backup/restore drill, smoke-тест, анализ метрик или approval согласно правилам организации. Названия полей и формат provenance в вашем toolchain могут отличаться; переносите инварианты, а не имена из учебного примера.

\n

SLSA проверяет provenance относительно заданных ожиданий, но сами ожидания должны быть сформированы и защищены командой. GitHub artifact attestations доступны только при соответствующей настройке workflow и permissions; команда gh attestation verify требует доступного GitHub-контекста и не проверяет вашу migration. Kubernetes хранит историю Deployment с ограничениями revision history, поэтому старый Pod template может быть недоступен, если историю сократили или образ удалён.

\n

Статус должен быть отрицательным, если отсутствует полный commit id, digest, источник provenance, compatibility statement или return point. Не подставляйте «последний main», не ищите образ по tag и не объявляйте rollback данных по факту kubectl rollout undo. Остановка на первой неизвестной границе дешевле расследования после повреждения данных.

\n

Критерий готовности

\n

Перед human approval второй инженер без устных пояснений должен найти exact commit id, source repository, artifact digest, связь artifact с commit, результат provenance verification, текущую и целевую схему, compatibility statement, rollout digest, migration version и return point. Для каждого значения указаны источник, владелец и действие при mismatch.

\n

Готовность здесь — не одно зелёное число. Это воспроизводимый набор утверждений: все обязательные связи истинны, неизвестные значения не замаскированы, а возврат коду не выдан за возврат данным. Если хотя бы одно утверждение нельзя показать, релиз остаётся на проверке с конкретной причиной остановки.

\n

Проверяемые источники

\n" }