Files
progcode/editorial/agent-rewrites/280.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

8 lines
14 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 280,
"slug": "editorial-2020-03-field-ci-pipeline",
"title": "CI/CD: как не отправить в deploy результат другой сборки",
"excerpt": "После зелёного build на стенд попадает другой каталог. Разбираем границу между сборкой и deploy, передаём один artifact, сверяем commit и останавливаем выпуск до сетевого действия.",
"contentHtml": "<p>После merge job <code>verify</code> и <code>build</code> завершаются успешно, но на staging нет ожидаемого файла. Иногда deploy завершается зелёным, а приложение открывает старую версию. Иногда падает команда доставки с сообщением о пропущенном каталоге. Повторный запуск может убрать симптом и одновременно скрыть причину.</p>\n<p>Цена ошибки — потеря связи между проверенным и отправленным результатом. Команда не знает, какой commit собрался, какой каталог попал в deploy и какие зависимости использовал runner. В таком состоянии нельзя уверенно повторить сбой или доказать, что исправление относится к нужному релизу.</p>\n<p>Тезис простой: результат сборки должен создаваться один раз, передаваться как artifact и проверяться перед внешним действием. Job deploy не должна заново получать исходники и выполнять <code>npm ci</code> или <code>npm run build</code>. Она должна получить конкретный output от job <code>build</code>, сверить его с commit pipeline и остановиться при любом расхождении.</p>\n<h2>Где ломается граница</h2>\n<p>В плохой конфигурации <code>build</code> собирает приложение, а <code>deploy</code> снова делает checkout, устанавливает зависимости и собирает каталог. Две строки <code>build passed</code> тогда относятся к разным запускам. Между ними могут измениться cache, образ runner, версия package manager, переменные окружения и рабочая директория.</p>\n<p>Совпадение commit не доказывает совпадение output. Сборка зависит не только от Git-дерева. Важны lockfile, версия Node.js, настройки bundler, переменные окружения и порядок команд. Поэтому повторная сборка в deploy создаёт вторую точку производства релизного результата.</p>\n<p>В GitLab job artifact задаёт явную границу: ранняя job сохраняет каталог, поздняя job получает копию этого каталога. Поле <code>dependencies</code> ограничивает список job, чьи artifacts нужно скачать. Это делает происхождение файлов видимым в YAML и в логах.</p>\n<h2>Антипример и рабочая схема</h2>\n<p>Ниже учебный пример. Он показывает механизм, но не описывает реальный production-инцидент и не доказывает длительность, надёжность или успешность выкладки.</p>\n<pre><code>build:\n stage: build\n script:\n - npm ci\n - npm run build\n artifacts:\n paths:\n - dist/\n\ndeploy_staging:\n stage: deploy\n script:\n - npm ci\n - npm run build\n - ./deploy-staging.sh dist/</code></pre>\n<p>В этом варианте <code>deploy_staging</code> не использует <code>dist/</code> от <code>build</code>. Он создаёт новый каталог. Даже если команда обычно получает тот же результат, pipeline не хранит доказательство этого равенства.</p>\n<p>Исправление переносит единственную сборку в <code>build</code>. Там же создаются идентификатор commit и контрольные суммы. Deploy скачивает artifact, проверяет его и только потом вызывает скрипт, который меняет внешнюю среду.</p>\n<pre><code>build:\n stage: build\n script:\n - npm ci\n - npm run build\n - printf '%s\\n' \"$CI_COMMIT_SHA\" &gt; dist/REVISION\n - (cd dist &amp;&amp; find . -type f -print0 | sort -z | xargs -0 sha256sum &gt; SHA256SUMS)\n artifacts:\n paths:\n - dist/\n\ndeploy_staging:\n stage: deploy\n dependencies:\n - build\n script:\n - test -f dist/REVISION\n - test \"$(cat dist/REVISION)\" = \"$CI_COMMIT_SHA\"\n - (cd dist &amp;&amp; sha256sum -c SHA256SUMS)\n - test -f dist/index.html\n - ./deploy-staging.sh dist/</code></pre>\n<p>Команды в примере предполагают POSIX shell и каталог <code>dist/</code>. Название входного файла, способ доставки и формат checksum нужно заменить на правила конкретного приложения. Сам принцип не меняется: artifact создаёт один job, deploy только читает и проверяет его.</p>\n<figure><img src=\"/assets/editorial/2020/ci-pipeline-diagnosis-2020.svg\" alt=\"Схема проверки artifact перед deploy: build создаёт результат, deploy сверяет revision и checksum, а при ошибке останавливается до внешнего действия\"><figcaption>Граница между build и deploy: неизвестный artifact не должен становиться сетевым действием.</figcaption></figure>\n<h2>Как читать симптом</h2>\n<table><thead><tr><th>Симптом</th><th>Причина</th><th>Проверка</th><th>Действие</th></tr></thead><tbody><tr><td><code>dist/REVISION</code> отсутствует</td><td>Build не создаёт контрактный файл или artifact не содержит путь</td><td>Проверить script build и <code>artifacts:paths</code></td><td>Остановить deploy, исправить состав результата</td></tr><tr><td>Revision отличается от <code>$CI_COMMIT_SHA</code></td><td>Deploy читает старый или чужой каталог</td><td>Сравнить файл из artifact с переменной job</td><td>Создать pipeline нужного commit и найти источник каталога</td></tr><tr><td><code>sha256sum -c</code> завершается с ошибкой</td><td>Файл изменился после manifest или передан неполный набор</td><td>Проверить момент создания manifest и список files</td><td>Не вызывать delivery-script; собрать новый artifact</td></tr><tr><td>Deploy запускает <code>npm run build</code></td><td>Результат build не передаётся как dependency</td><td>Прочитать YAML и список скачанных artifacts</td><td>Убрать повторную сборку, указать <code>dependencies: [build]</code></td></tr><tr><td>Verify не прошёл</td><td>Ошибка теста, install или окружения</td><td>Посмотреть exit code и отчёт job</td><td>Исправить причину; build не использовать как обход</td></tr></tbody></table>\n<p>«Вероятная причина» в таблице не равна доказанной. Например, checksum может не сойтись из-за того, что manifest создали до появления последнего файла. Проверка должна отделить этот случай от подмены каталога. Пока причина неизвестна, deploy остаётся заблокированным.</p>\n<h2>Проверка до внешнего действия</h2>\n<p>Последняя команда job имеет побочный эффект: она отправляет файлы, вызывает API или изменяет staging. До неё pipeline должен проверить четыре свойства. Artifact пришёл от ожидаемого job. В нём есть revision. Revision совпадает с commit pipeline. Контрольные суммы и обязательные файлы сходятся.</p>\n<p>Проверки должны быть жёсткими. <code>test -f</code>, сравнение строк и <code>sha256sum -c</code> должны возвращать ненулевой код при ошибке. Не стоит превращать mismatch в предупреждение или добавлять <code>|| true</code>. Иначе лог покажет проблему, но pipeline продолжит движение к сетевой команде.</p>\n<p>Manual deploy не заменяет эти проверки. Ручное подтверждение отвечает на вопрос «можно ли сейчас запускать этот шаг», но не доказывает происхождение каталога. Оно полезно после автоматических gate, а не вместо них.</p>\n<h2>Порядок действий</h2>\n<ol><li>Остановить deploy до команды, которая меняет внешнюю среду. Записать имя job, revision и стадию сбоя.</li><li>Найти в YAML все вызовы <code>npm ci</code>, <code>npm install</code> и <code>npm run build</code>. Для релизного каталога должен остаться один владелец.</li><li>В build-job создать output, <code>REVISION</code> и checksum-manifest после появления всех файлов.</li><li>Включить каталог в <code>artifacts:paths</code>. В deploy-job указать dependency на build и удалить повторную сборку.</li><li>Добавить проверки существования, revision, checksum и обязательного файла. Каждую проверку оставить до delivery-script.</li><li>Провести отрицательные проверки: убрать artifact, изменить revision и испортить файл после создания manifest. Во всех случаях job должна завершиться до внешнего действия.</li><li>Отдельно проверить staging с согласованными доступами и откатом. Результат staging не переносить на production без новой проверки.</li></ol>\n<h2>Ограничения</h2>\n<p>Artifact не делает сборку воспроизводимой сам по себе. Он сохраняет уже созданный результат. Для воспроизводимости дополнительно нужны зафиксированные зависимости, контролируемый образ runner и понятные переменные окружения.</p>\n<p>Checksum подтверждает целостность набора файлов после создания manifest. Он не подтверждает права доступа, безопасность секретов, корректность бизнес-логики или совместимость приложения со staging. Smoke-тесты и rollback решают другие задачи.</p>\n<p><code>dependencies</code> подходит для простой последовательной схемы. При переходе к <code>needs</code>, нескольким build-job, child pipeline или межпроектным artifacts нужно отдельно проверить, откуда deploy получает файлы. Название job само по себе не является доказательством правильного источника.</p>\n<h2>Критерий готовности</h2>\n<p>Изменение готово, если для одного pipeline можно показать commit, job-источник, список artifact и checksum-manifest. Deploy не выполняет сборку повторно. При отсутствии файла, mismatch revision или неверной checksum он завершается до delivery-script. При корректном artifact он выполняет только согласованный staging-шаг.</p>\n<p>Это проверяемый критерий, а не обещание production-результата. Он показывает, что pipeline знает происхождение отправляемого каталога и умеет остановиться до внешнего действия.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://docs.gitlab.com/ci/jobs/job_artifacts/\" target=\"_blank\" rel=\"noopener\">GitLab Docs: Job artifacts</a> — создание artifacts и управление тем, какие job их получают.</li><li><a href=\"https://docs.gitlab.com/ci/yaml/\" target=\"_blank\" rel=\"noopener\">GitLab Docs: CI/CD YAML syntax reference</a> — назначение <code>dependencies</code> и других ключей pipeline.</li><li><a href=\"https://docs.gitlab.com/ci/yaml/lint/\" target=\"_blank\" rel=\"noopener\">GitLab Docs: Validate CI/CD configuration</a> — проверка синтаксиса и моделирование создания pipeline.</li></ul>"
}