{ "index": 280, "slug": "editorial-2020-03-field-ci-pipeline", "title": "CI/CD: как связать проверенный build с deploy", "excerpt": "После зелёного build на staging оказывается другой каталог. Разбираем границу между сборкой и deploy: передаём один artifact, сверяем commit и останавливаем выпуск до сетевого действия.", "contentHtml": "
В понедельник утром дежурный инженер открыл staging после merge и увидел старый CSS. Job verify и build были зелёными, а deploy тоже завершился без ошибки. В логах не было ответа на простой вопрос: какой каталог реально отправили на стенд.
Сначала команда повторила deploy и получила тот же зелёный статус. После этого обнаружилось, что deploy-job снова делала checkout, запускала npm ci и собирала dist/. Цена такой схемы — потеря связи между проверенным и отправленным результатом: нельзя доказать, какой commit собрался, какой output попал в deploy и какие зависимости использовал runner.
Надёжная граница выглядит так: job build один раз создаёт output, сохраняет его как artifact и записывает идентификатор commit. Job deploy получает этот artifact, проверяет revision, состав файлов и checksum, а затем выполняет одну команду с внешним эффектом. Повторная сборка в deploy должна считаться отдельной ошибкой схемы.
В учебном сценарии pipeline состоит из verify, build и deploy_staging. Сначала verify проверяет код. Затем build устанавливает зависимости и создаёт dist/. После этого deploy получает рабочую директорию, но не обязан получить каталог от предыдущей job, если конфигурация не описывает передачу artifact.
В этот момент два запуска могут выглядеть одинаково в интерфейсе: оба сообщают passed, оба относятся к одному merge request. Но первый запуск произвёл каталог в build-job, а второй мог собрать другой каталог уже после checkout. Между ними меняются cache, образ runner, версия package manager, переменные окружения и рабочая директория.
Совпадение commit не доказывает совпадение output. Для результата важны lockfile, версия Node.js, настройки bundler, переменные окружения и порядок команд. В 2020 году это уже была практическая граница CI/CD: проверять нужно не только исходное дерево, но и тот набор файлов, который пересекает границу delivery.
\nРасследование становится короче, если назвать объект каждой операции. Checkout даёт исходное дерево, install — окружение зависимостей, build — новый каталог, artifact — сохранённую копию этого каталога. Deploy не должен молча становиться ещё одним build-job.
\n| Состояние | Что в нём находится | Какая ошибка возможна | Проверка |
|---|---|---|---|
| Checkout | Дерево commit pipeline | Взята другая ветка или revision | Сверить commit job с CI_COMMIT_SHA |
| Build output | Собранный каталог dist/ | Каталог неполный или собран повторно | Проверить обязательный файл и владельца output |
| Artifact | Файлы, сохранённые job и скачанные позже | Путь не включён или выбран не тот источник | Проверить artifacts:paths и dependencies |
| Deploy | Команда, меняющая staging | Внешнее действие запущено до gate | Оставить delivery-script последней командой |
Эта таблица не утверждает, что artifact защищён от всех угроз. Она только разделяет места, где можно проверить происхождение и целостность результата. Права доступа, секреты, бизнес-проверки и откат требуют отдельных механизмов.
\nНиже учебная конфигурация. Она показывает дефект границы; это не запись о реально запущенной job и не обещание, что команды выполнялись в конкретном проекте.
\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/. Он не использует output от build, даже если в рабочей директории случайно появляется каталог с тем же именем. Две сборки могут иметь один commit и разные зависимости, переменные или содержимое cache.
npm ci здесь не является плохой командой сама по себе: она предназначена для чистой установки в автоматизированной среде и требует согласованного lockfile. Ошибка в том, что deploy одновременно устанавливает зависимости и заново производит релизный output вместо чтения проверенного artifact.
Исправление переносит производство каталога в build. После сборки job записывает revision и создаёт manifest контрольных сумм. Deploy ограничивает загрузку artifact списком dependencies, проверяет файлы и только затем вызывает скрипт доставки.
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В GitLab artifact — это файлы и каталоги, прикреплённые к job. По умолчанию job более поздней стадии получает artifacts предыдущих стадий, а dependencies сужает список источников. Поэтому в примере важно не только наличие dist/, но и явная связь deploy с job build.
Команды предполагают POSIX shell, GNU-совместимые find, sort и sha256sum, а также каталог dist/. В другом shell или на Windows runner их нужно заменить и отдельно проверить. REVISION и SHA256SUMS — диагностический контракт учебного примера, а не подпись релиза.
У каждой строки ошибки должна быть проверка, которая отделяет одну гипотезу от другой. Например, отсутствие REVISION ещё не доказывает подмену каталога: файл мог не создаваться или мог быть исключён из artifacts:paths. Лог и YAML нужно читать вместе.
| Симптом | Гипотеза | Проверка | Действие |
|---|---|---|---|
dist/REVISION отсутствует | Build не создаёт файл или artifact не содержит путь | Проверить script build и состав скачанного artifact | Остановить deploy и исправить контракт output |
Revision отличается от $CI_COMMIT_SHA | Получен старый или чужой каталог | Сравнить файл из artifact с переменной job | Не вызывать delivery-script; найти источник и создать нужный pipeline |
sha256sum -c завершается с ошибкой | Файл изменён или manifest создан слишком рано | Сравнить список файлов и порядок команд | Собрать новый artifact после завершения output |
Deploy запускает npm run build | Deploy стал вторым владельцем результата | Найти все команды сборки в YAML | Удалить повторную сборку и оставить dependency на build |
| Verify не прошёл | Ошибка теста, install или окружения | Посмотреть exit code и отчёт job | Исправить причину; не обходить gate повторным deploy |
Если checksum не сходится, сначала нужно проверить собственный manifest: в него должны попасть все файлы после завершения сборки, а сам файл manifest не должен хешироваться до момента его создания. Пока причина не установлена, staging-команда остаётся недостижимой.
\nПоследняя команда job имеет побочный эффект: она отправляет файлы, вызывает API или изменяет staging. До неё pipeline проверяет четыре свойства. Artifact пришёл от ожидаемого job. В нём есть revision. Revision совпадает с commit pipeline. Контрольные суммы и обязательные файлы сходятся.
\ntest -f, сравнение строк и sha256sum -c должны возвращать ненулевой код при ошибке. Нельзя превращать mismatch в предупреждение или добавлять || true: тогда лог покажет проблему, но shell продолжит движение к сетевой команде.
Ручное подтверждение deploy отвечает на вопрос «можно ли сейчас запускать этот шаг», но не доказывает происхождение каталога. Оно может стоять после автоматических gate. Контроль artifact, revision и checksum должно пройти до ручного или автоматического delivery.
\nnpm ci, npm install и npm run build. Для релизного каталога оставить одного владельца.REVISION и построить checksum-manifest после появления всех файлов.artifacts:paths и в deploy-job явно указать dependency на build.Artifact сохраняет уже созданный результат, но не делает сборку воспроизводимой автоматически. Для этого дополнительно нужны зафиксированные зависимости, контролируемый образ runner и явные переменные окружения. Cache следует считать ускорителем, а не источником истины для release output.
\nChecksum подтверждает целостность набора файлов после создания manifest. Он не подтверждает права доступа, безопасность секретов, корректность бизнес-логики, совместимость со staging или возможность отката. Smoke-тесты и rollback решают другие задачи.
\ndependencies подходит для линейной схемы с build-job в предыдущей стадии. При переходе к needs, нескольким build-job, child pipeline или межпроектным artifacts нужно отдельно проверить, откуда deploy получает файлы; эти варианты не следует смешивать в одном примере без проверки версии GitLab.
Изменение готово, если для одного pipeline можно показать commit, job-источник, список artifact и checksum-manifest. Deploy не выполняет сборку повторно. При отсутствии файла, несовпадении revision или неверной checksum он завершается до delivery-script. При корректном artifact он выполняет только согласованный staging-шаг.
\nstages, artifacts и dependencies.