{ "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У релиза есть несколько разных объектов. commit фиксирует исходный revision. artifact содержит собранное содержимое и digest. migration меняет схему или данные и должна назвать целевую версию и совместимость. rollout описывает намерение отправить конкретный digest. return point указывает версию и digest, к которым можно вернуться.
В Git tag является ссылкой в пространстве имён refs/tags/. Он удобен для имени релиза, но запись с одним tag не заменяет зафиксированный commit: ссылку нужно разрешить и сохранить полный идентификатор. Для контейнерного образа digest — content identifier: OCI описывает его как хеш содержимого, который можно независимо проверить.
Поэтому rollout должен ссылаться на digest, а не только на имя, которое может разрешиться иначе. Эти связи проверяют согласованность записей, но не являются разрешением на выкладку: они не проверяют права, политики CI, состояние registry или здоровье сервиса.
\nНиже приведён учебный пример с вымышленными значениями. Код не обращается к Git, registry, CI, Kubernetes API или базе данных. Он только сравнивает записи. Положительный результат означает согласованность этих записей, а не готовность реальной среды.
\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 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 всё ещё будет выглядеть правильно, но содержимое уже не совпадёт. Отрицательный путь важнее зелёной строки: система не выбирает за инженера «примерно подходящую» запись.
Название readyForReview намеренно не означает readyForDeploy. Код не проверяет подпись, права, конфигурацию среды, состояние базы, доступность сервиса или факт доставки. Он лишь открывает следующий этап проверки, если четыре связи согласованы.
Сначала соберите evidence в режиме чтения. Команды ниже используют примерные имена и не изменяют удалённый Git или Kubernetes. Выполняйте их только в репозитории и namespace, к которым у вас есть разрешение. Git разрешает tag до полного commit; Kubernetes показывает image reference в Pod template, историю ревизий и состояние rollout.
\ntag=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. Последняя команда подтверждает состояние контроллера, но не происхождение образа, совместимость данных или пользовательский результат.
| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| 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. | Не обещать возврат, пока обе величины не записаны. |
Таблица разделяет разные классы риска. Ошибка commit относится к происхождению artifact. Ошибка digest относится к содержимому, выбранному для rollout. Ошибка migration относится к совместимости данных и кода. Неопределённый return point относится к возможности безопасно назвать действие после сбоя. Одно поле release=green не заменяет эти проверки.
В Kubernetes новая ревизия Deployment создаётся при изменении Pod template, например image или label. Rollback возвращает часть template к предыдущей ревизии. Он не отменяет произвольный SQL, удалённую запись, заполненное поле, отправленное событие или изменение во внешней системе. Поэтому успех kubectl rollout undo нельзя называть откатом данных.
Поэтому return point должен содержать две границы. Первая говорит, какой artifact можно запустить. Вторая говорит, что разрешено делать с данными. Возможны обратная migration, совместимый промежуточный код, восстановление из резервной копии или запрет автоматического возврата. Пока выбранный путь не проверен, честная формулировка звучит так: «возврат artifact определён; откат данных не доказан».
\nТа же граница действует для provenance и attestation. Provenance описывает происхождение сборки. Attestation может подтверждать утверждение об этом происхождении. Ни одно из них само по себе не доказывает совместимость migration, approval rollout или здоровье сервиса. Эти вопросы требуют собственных источников и проверок.
\nСхема ловит расхождения между названными записями. Она не доказывает правдивость каждого значения. Она не проверяет историю Git, содержимое образа, подпись, policy CI, права на кластер, runtime configuration, состояние базы, трафик, метрики или факт доставки.
\nЕсли commit неизвестен, digest отсутствует, migration не содержит compatibility statement или return point не назван, результат должен быть отрицательным. Не подставляйте «последний main». Не ищите образ по tag. Не объявляйте rollback данных по факту возврата Pod template. Остановитесь на первой неизвестной границе и назначьте источник, который может её подтвердить.
\nЗапись готова к передаче на авторизованную проверку, если второй инженер без устных пояснений может показать exact commit id, artifact digest, связь artifact с commit, migration target и compatibility, rollout digest, migration version и return point. Для каждой строки есть источник, владелец и действие при mismatch. Проверка даёт либо все утверждения true, либо конкретный stop с названием ложной связи.
В учебном примере все значения вымышлены и не описывают production-результат. В реальном выпуске критерий нужно применять к доступным и разрешённым записям. Если одна строка не имеет источника или действия, выпуск не готов: сначала уточните contract, затем повторите сверку.
\n