{ "index": 126, "slug": "editorial-2024-07-practice-release-engineering", "title": "Релиз без догадок: как связать commit, artifact и rollback", "excerpt": "Одинаковый tag не доказывает, что команда собирается доставить нужный код. Разбираем проверяемую цепочку от commit до rollout, отрицательный путь и границу rollback для данных.", "contentHtml": "

После выкладки сервис отвечает кодом старой версии, хотя в заявке указан новый релиз. В карточке сборки, образе и rollout стоит один tag. Команда повторяет запуск, но не может быстро ответить на три вопроса: из какого commit собран artifact, какой digest отправили и совместима ли migration с данными. Цена ошибки растёт с каждой попыткой: увеличивается окно сбоя, меняется состояние базы, а точку возврата приходится восстанавливать по разным журналам.

\n

Одинаковая версия не связывает объекты сама по себе. Релиз готов к следующему действию только тогда, когда можно сравнить exact commit id, immutable digest, migration target и return point. Если одна связь неизвестна или ложна, проверка должна остановить выпуск. Новый retry не исправляет расхождение записей.

\n

Что именно связывает релиз

\n

У релиза есть несколько разных объектов. commit фиксирует исходный revision. artifact содержит собранное содержимое и digest. migration меняет схему или данные и должна назвать целевую версию и совместимость. rollout описывает намерение отправить конкретный digest. return point указывает версию и digest, к которым можно вернуться.

\n

В Git tag является ссылкой в пространстве имён refs/tags/. Он удобен для имени релиза, но запись с одним tag не заменяет зафиксированный commit: ссылку нужно разрешить и сохранить полный идентификатор. Для контейнерного образа digest — content identifier: OCI описывает его как хеш содержимого, который можно независимо проверить.

\n

Поэтому rollout должен ссылаться на digest, а не только на имя, которое может разрешиться иначе. Эти связи проверяют согласованность записей, но не являются разрешением на выкладку: они не проверяют права, политики CI, состояние registry или здоровье сервиса.

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

Минимальный пример

\n

Ниже приведён учебный пример с вымышленными значениями. Код не обращается к Git, registry, CI, Kubernetes API или базе данных. Он только сравнивает записи. Положительный результат означает согласованность этих записей, а не готовность реальной среды.

\n
const 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  targetReleaseVersion: '2024.07.0',\n  compatibleWith: '2024.06.3',\n};\nconst rollout = {\n  requestedArtifactDigest: 'sha256:release-070-a1',\n  migrationVersion: '2024.07.0',\n};\nconst returnPoint = {\n  version: '2024.06.3',\n  digest: 'sha256:release-063-b7',\n};\n\nfunction inspect(candidateRollout) {\n  const checks = {\n    source: artifact.sourceCommitId === commit.id,\n    migration: migration.targetReleaseVersion === release.version,\n    artifact: candidateRollout.requestedArtifactDigest === artifact.digest,\n    rollout: candidateRollout.migrationVersion === migration.targetReleaseVersion,\n    returnPoint: Boolean(returnPoint.version && returnPoint.digest),\n  };\n  return { checks, ok: Object.values(checks).every(Boolean) };\n}\n\nconst aligned = inspect(rollout);\nconst broken = inspect({\n  ...rollout,\n  requestedArtifactDigest: 'sha256:release-070-b2',\n});\n\nconsole.log(JSON.stringify({ aligned, broken }, null, 2));\nif (aligned.ok !== true || broken.ok !== false) {\n  throw new Error('unexpected validator result');\n}
\n

Каждая проверка отвечает только на один вопрос. Если заменить sourceCommitId на другой id, результат станет отрицательным. Если изменить digest в rollout, tag всё ещё будет выглядеть правильно, но содержимое уже не совпадёт. Отрицательный путь важнее зелёной строки: система не выбирает за инженера «примерно подходящую» запись.

\n

Название readyForReview намеренно не означает readyForDeploy. Код не проверяет подпись, права, конфигурацию среды, состояние базы, доступность сервиса или факт доставки. Он лишь открывает следующий этап проверки, если четыре связи согласованы.

\n

Как получить точные значения

\n

Сначала соберите evidence в режиме чтения. Команды ниже используют примерные имена и не изменяют удалённый Git или Kubernetes. Выполняйте их только в репозитории и namespace, к которым у вас есть разрешение. Git разрешает tag до полного commit; Kubernetes показывает image reference в Pod template, историю ревизий и состояние rollout.

\n
tag=v2024.07.0\ngit rev-parse \"$tag^{commit}\"\ngit show -s --format='%H %s' \"$tag^{commit}\"\n\nkubectl -n production get deployment/app   -o jsonpath='{.spec.template.spec.containers[?(@.name==\"app\")].image}{\"\\n\"}'\nkubectl -n production rollout history deployment/app\nkubectl -n production rollout status deployment/app --timeout=60s
\n

Сохраните вывод рядом с release record и сравните его с заявленными значениями. Для контейнера ожидайте ссылку вида registry.example/app@sha256:...; если в Pod template остался только registry.example/app:v2024.07.0, имя ещё не доказывает выбранный digest. Последняя команда подтверждает состояние контроллера, но не происхождение образа, совместимость данных или пользовательский результат.

\n

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

\n
Диагностика расхождений до запуска
СимптомПричинаПроверкаДействие
Tag совпадает, но artifact ссылается на другой commit.Tag используют вместо точной связи с исходным revision.Сравнить artifact.sourceCommitId и commit id.Остановить выпуск. Исправить запись или пересобрать artifact после решения владельца.
Старый код не читает новую схему.Rollback образа ошибочно считают rollback данных.Проверить target migration, совместимость и обратную процедуру.Вернуть только разрешённый artifact. Изменение данных рассмотреть отдельно.
Rollout прошёл с тем же tag, но другим digest.Намерение ссылается на изменяемую метку.Сравнить requested digest с digest artifact.Не запускать rollout. Пересоздать запись после сверки.
После остановки предлагают повторить deploy.Retry используют вместо объяснения mismatch.Найти первую ложную связь и её источник.Сначала исправить записи, затем повторить только проверки.
Точку возврата называют «предыдущим релизом».У return point нет конкретного содержимого.Проверить version и immutable digest.Не обещать возврат, пока обе величины не записаны.
\n

Таблица разделяет разные классы риска. Ошибка commit относится к происхождению artifact. Ошибка digest относится к содержимому, выбранному для rollout. Ошибка migration относится к совместимости данных и кода. Неопределённый return point относится к возможности безопасно назвать действие после сбоя. Одно поле release=green не заменяет эти проверки.

\n

Порядок действий перед выпуском

\n
  1. Назовите границу проверки. Зафиксируйте release version, owner и источник каждой записи. Укажите, что сейчас выполняется сверка, а не deploy.
  2. Свяжите artifact с commit. Проверьте exact source commit и digest. Имя ветки, последний merge и короткий tag не заменяют идентификатор.
  3. Опишите migration отдельно. Назовите target version, совместимость с текущим contract и действие по данным. Не прячьте migration в комментарии к образу.
  4. Сверьте rollout. Он должен содержать immutable digest artifact и migration version. Любое расхождение переводит процесс в stop.
  5. Назовите return point. Запишите предыдущую версию и digest. Отдельно укажите, что произойдёт с данными и кто проверит этот путь.
  6. Повторите проверки после исправления. Передайте дальше только записи без ложных связей. Положительный результат открывает авторизованную проверку, но не выдаёт разрешение на deploy.
\n

Почему rollback не возвращает данные

\n

В Kubernetes новая ревизия Deployment создаётся при изменении Pod template, например image или label. Rollback возвращает часть template к предыдущей ревизии. Он не отменяет произвольный SQL, удалённую запись, заполненное поле, отправленное событие или изменение во внешней системе. Поэтому успех kubectl rollout undo нельзя называть откатом данных.

\n

Поэтому return point должен содержать две границы. Первая говорит, какой artifact можно запустить. Вторая говорит, что разрешено делать с данными. Возможны обратная migration, совместимый промежуточный код, восстановление из резервной копии или запрет автоматического возврата. Пока выбранный путь не проверен, честная формулировка звучит так: «возврат artifact определён; откат данных не доказан».

\n

Та же граница действует для provenance и attestation. Provenance описывает происхождение сборки. Attestation может подтверждать утверждение об этом происхождении. Ни одно из них само по себе не доказывает совместимость migration, approval rollout или здоровье сервиса. Эти вопросы требуют собственных источников и проверок.

\n

Ограничения и отрицательный путь

\n

Схема ловит расхождения между названными записями. Она не доказывает правдивость каждого значения. Она не проверяет историю Git, содержимое образа, подпись, policy CI, права на кластер, runtime configuration, состояние базы, трафик, метрики или факт доставки.

\n

Если commit неизвестен, digest отсутствует, migration не содержит compatibility statement или return point не назван, результат должен быть отрицательным. Не подставляйте «последний main». Не ищите образ по tag. Не объявляйте rollback данных по факту возврата Pod template. Остановитесь на первой неизвестной границе и назначьте источник, который может её подтвердить.

\n

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

\n

Запись готова к передаче на авторизованную проверку, если второй инженер без устных пояснений может показать exact commit id, artifact digest, связь artifact с commit, migration target и compatibility, rollout digest, migration version и return point. Для каждой строки есть источник, владелец и действие при mismatch. Проверка даёт либо все утверждения true, либо конкретный stop с названием ложной связи.

\n

В учебном примере все значения вымышлены и не описывают production-результат. В реальном выпуске критерий нужно применять к доступным и разрешённым записям. Если одна строка не имеет источника или действия, выпуск не готов: сначала уточните contract, затем повторите сверку.

\n

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

\n" }