{ "index": 280, "slug": "editorial-2020-03-field-ci-pipeline", "title": "CI/CD: как не отправить в deploy результат другой сборки", "excerpt": "После зелёного build на стенд попадает другой каталог. Разбираем границу между сборкой и deploy, передаём один artifact, сверяем commit и останавливаем выпуск до сетевого действия.", "contentHtml": "
После merge job verify и build завершаются успешно, но на staging нет ожидаемого файла. Иногда deploy завершается зелёным, а приложение открывает старую версию. Иногда падает команда доставки с сообщением о пропущенном каталоге. Повторный запуск может убрать симптом и одновременно скрыть причину.
Цена ошибки — потеря связи между проверенным и отправленным результатом. Команда не знает, какой commit собрался, какой каталог попал в deploy и какие зависимости использовал runner. В таком состоянии нельзя уверенно повторить сбой или доказать, что исправление относится к нужному релизу.
\nТезис простой: результат сборки должен создаваться один раз, передаваться как artifact и проверяться перед внешним действием. Job deploy не должна заново получать исходники и выполнять npm ci или npm run build. Она должна получить конкретный output от job build, сверить его с commit pipeline и остановиться при любом расхождении.
В плохой конфигурации build собирает приложение, а deploy снова делает checkout, устанавливает зависимости и собирает каталог. Две строки build passed тогда относятся к разным запускам. Между ними могут измениться cache, образ runner, версия package manager, переменные окружения и рабочая директория.
Совпадение commit не доказывает совпадение output. Сборка зависит не только от Git-дерева. Важны lockfile, версия Node.js, настройки bundler, переменные окружения и порядок команд. Поэтому повторная сборка в deploy создаёт вторую точку производства релизного результата.
\nВ GitLab job artifact задаёт явную границу: ранняя job сохраняет каталог, поздняя job получает копию этого каталога. Поле dependencies ограничивает список job, чьи artifacts нужно скачать. Это делает происхождение файлов видимым в YAML и в логах.
Ниже учебный пример. Он показывает механизм, но не описывает реальный production-инцидент и не доказывает длительность, надёжность или успешность выкладки.
\nbuild:\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/\nВ этом варианте deploy_staging не использует dist/ от build. Он создаёт новый каталог. Даже если команда обычно получает тот же результат, pipeline не хранит доказательство этого равенства.
Исправление переносит единственную сборку в build. Там же создаются идентификатор commit и контрольные суммы. Deploy скачивает artifact, проверяет его и только потом вызывает скрипт, который меняет внешнюю среду.
build:\n stage: build\n script:\n - npm ci\n - npm run build\n - printf '%s\\n' \"$CI_COMMIT_SHA\" > dist/REVISION\n - (cd dist && find . -type f -print0 | sort -z | xargs -0 sha256sum > 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 && sha256sum -c SHA256SUMS)\n - test -f dist/index.html\n - ./deploy-staging.sh dist/\nКоманды в примере предполагают POSIX shell и каталог dist/. Название входного файла, способ доставки и формат checksum нужно заменить на правила конкретного приложения. Сам принцип не меняется: artifact создаёт один job, deploy только читает и проверяет его.
| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
dist/REVISION отсутствует | Build не создаёт контрактный файл или artifact не содержит путь | Проверить script build и artifacts:paths | Остановить deploy, исправить состав результата |
Revision отличается от $CI_COMMIT_SHA | Deploy читает старый или чужой каталог | Сравнить файл из artifact с переменной job | Создать pipeline нужного commit и найти источник каталога |
sha256sum -c завершается с ошибкой | Файл изменился после manifest или передан неполный набор | Проверить момент создания manifest и список files | Не вызывать delivery-script; собрать новый artifact |
Deploy запускает npm run build | Результат build не передаётся как dependency | Прочитать YAML и список скачанных artifacts | Убрать повторную сборку, указать dependencies: [build] |
| Verify не прошёл | Ошибка теста, install или окружения | Посмотреть exit code и отчёт job | Исправить причину; build не использовать как обход |
«Вероятная причина» в таблице не равна доказанной. Например, checksum может не сойтись из-за того, что manifest создали до появления последнего файла. Проверка должна отделить этот случай от подмены каталога. Пока причина неизвестна, deploy остаётся заблокированным.
\nПоследняя команда job имеет побочный эффект: она отправляет файлы, вызывает API или изменяет staging. До неё pipeline должен проверить четыре свойства. Artifact пришёл от ожидаемого job. В нём есть revision. Revision совпадает с commit pipeline. Контрольные суммы и обязательные файлы сходятся.
\nПроверки должны быть жёсткими. test -f, сравнение строк и sha256sum -c должны возвращать ненулевой код при ошибке. Не стоит превращать mismatch в предупреждение или добавлять || true. Иначе лог покажет проблему, но pipeline продолжит движение к сетевой команде.
Manual deploy не заменяет эти проверки. Ручное подтверждение отвечает на вопрос «можно ли сейчас запускать этот шаг», но не доказывает происхождение каталога. Оно полезно после автоматических gate, а не вместо них.
\nnpm ci, npm install и npm run build. Для релизного каталога должен остаться один владелец.REVISION и checksum-manifest после появления всех файлов.artifacts:paths. В deploy-job указать dependency на build и удалить повторную сборку.Artifact не делает сборку воспроизводимой сам по себе. Он сохраняет уже созданный результат. Для воспроизводимости дополнительно нужны зафиксированные зависимости, контролируемый образ runner и понятные переменные окружения.
\nChecksum подтверждает целостность набора файлов после создания manifest. Он не подтверждает права доступа, безопасность секретов, корректность бизнес-логики или совместимость приложения со staging. Smoke-тесты и rollback решают другие задачи.
\ndependencies подходит для простой последовательной схемы. При переходе к needs, нескольким build-job, child pipeline или межпроектным artifacts нужно отдельно проверить, откуда deploy получает файлы. Название job само по себе не является доказательством правильного источника.
Изменение готово, если для одного pipeline можно показать commit, job-источник, список artifact и checksum-manifest. Deploy не выполняет сборку повторно. При отсутствии файла, mismatch revision или неверной checksum он завершается до delivery-script. При корректном artifact он выполняет только согласованный staging-шаг.
\nЭто проверяемый критерий, а не обещание production-результата. Он показывает, что pipeline знает происхождение отправляемого каталога и умеет остановиться до внешнего действия.
\ndependencies и других ключей pipeline.