Симптом: после merge job verify и build отмечены зелёным, но на staging попал каталог без ожидаемого файла. Первая реакция — перезапустить deploy или добавить в него ещё одну сборку. Это усиливает проблему: новая сборка может уже использовать другой cache, другой образ runner или изменившийся набор зависимостей. Цена — команда теряет связь между тем, что проверяла, и тем, что отправила, а следующий сбой невозможно воспроизвести по логам.
Ниже учебный сценарий, а не история реального production-инцидента. В нём нет настоящих credentials, длительностей job, идентификаторов pipeline и факта выкладки. Он нужен, чтобы отработать маршрут расследования в марте 2020 года: обнаружить повторную сборку в deploy, передать один artifact от build, сверить revision и остановить job до сетевого вызова. Такая фикстура полезна именно своей границей: она показывает, какой вывод можно сделать, а какой ещё нельзя.
Что было сломано в учебном YAML
В антипримере job deploy получает новый checkout и снова вызывает npm ci с npm run build. В результате строка «build passed» выше по pipeline относится к одному каталогу, а команда доставки — к другому. Даже если оба commit совпадают, повторное выполнение не доказывает одинаковость output: у сборки есть зависимости, образ job, настройки и внешние входы. Сначала нужно убрать эту вторую точку создания результата, а не искать случайный флаг cache.
# Учебный антипример: deploy снова собирает checkout.
deploy_staging:
stage: release
script:
- npm ci
- npm run build
- ./scripts/deploy-staging dist/
# Здесь deploy не получает результат job build.
# Лог зелёного build не доказывает, что выгружен тот же каталог.
Такой файл не обязательно вызывал бы ошибку на каждом запуске. Это делает его опаснее. В хорошие дни build и deploy могут дать похожий output, и привычка закрепится. В плохой день проявится различие: пропущенный artifact, stale cache, версия package manager, переменная сборки или ручное изменение рабочей директории. Поэтому диагностировать нужно не редкий симптом «нет файла», а нарушенный контракт: deploy не получил конкретный результат build-job.
Собираем факты до изменения конфигурации
Начинаю не с редактирования YAML, а с короткой карточки фактов. Какой $CI_COMMIT_SHA у pipeline? В каком job впервые появился dist/? Назван ли этот каталог в artifacts:paths? Кто получает его через dependencies? До какой строки script можно гарантировать, что сетевого действия не было? Эти вопросы не требуют доступа к production. Их можно проверить по конфигурации и логам учебного job, не выводя секреты.
| Наблюдение | Вероятная причина | Безопасная проверка | Блокирует deploy? | Следующее действие |
|---|---|---|---|---|
dist/REVISION отсутствует | build не записал контрактный файл или artifact не содержит путь | проверить script build и artifacts:paths | да | исправить build; не запускать delivery-script |
revision отличается от $CI_COMMIT_SHA | deploy читает старый или чужой каталог | сравнить файл из artifact с переменной job | да | создать pipeline нужного commit и выяснить источник каталога |
| checksum не проходит | передан неполный набор файлов или manifest не покрывает путь | выполнить sha256sum -c до deploy | да | остановиться и сузить artifact/исправить manifest |
deploy запускает npm run build | результат build не передаётся как dependency | прочитать YAML, не нажимая retry | да | убрать повторную сборку, добавить dependencies: [build] |
| verify не прошёл | тест или install сообщает об ошибке входа | прочитать exit code и отчёт job | да | исправить причину, build не делать обходом |
Таблица говорит «вероятная», а не «доказанная» причина. Например, checksum может не пройти потому, что manifest создан до добавления последнего файла, а не потому, что artifact подменён. Поэтому каждая строка содержит маленькую проверку и точку остановки. До тех пор пока unknown состояние не объяснено, deploy не должен превращать его в сетевой эффект.
Исправление: переносим границу в artifact
Минимальное исправление не требует переписывать pipeline. Job build создаёт dist/, записывает REVISION и SHA256SUMS, затем прикладывает каталог как artifacts. Job deploy_staging объявляет dependencies: [build] и не содержит ни npm ci, ни npm run build. Так в конфигурации появляется простое правило ревью: создавать файл релиза разрешено одному job, отправлять — другому.
Проверка перед deploy может оставаться обычным POSIX-shell. Ниже команды намеренно печатают revision и список файлов, но не адрес стенда и не переменные доступа. Их нужно выполнить внутри job, которому GitLab уже передал artifacts. Это не инструкция для ручного запуска на production-сервере и не журнал настоящего pipeline.
# Команды для job deploy, не для локального production-доступа.
set -eu
printf "commit=%s\n" "$CI_COMMIT_SHA"
find dist -maxdepth 1 -type f -print | sort
test -f dist/REVISION
test "$(cat dist/REVISION)" = "$CI_COMMIT_SHA"
(cd dist && sha256sum -c SHA256SUMS)
Если find показал неожиданный файл, не нужно сразу добавлять его в artifacts. Сначала решаю, является ли он частью релизного контракта. Если нет — оставляю его в рабочей директории или исключаю из output. Если да — build обязан добавить его до создания manifest, а deploy обязан проверить наличие. Так список файлов становится предметом ревью, а не побочным продуктом cache.
Проверки идут до ручной остановки
Полезно представить deploy как последовательность, где последняя команда имеет побочный эффект. До неё должны пройти: получен artifact нужного job, существует REVISION, revision равен переменной pipeline, checksum-manifest сходится, обязательный вход вроде index.html присутствует. Только потом оператор запускает manual-job на staging. Если любой шаг даёт ненулевой exit code, pipeline остаётся на диагностике; «попробовать ещё раз» не исправляет неизвестный результат.
В GitLab CI/CD список dependencies ограничивает, чьи artifacts job скачивает. Это важнее, чем надежда на порядок стадий: stages задают маршрут выполнения, но не делают содержимое рабочих директорий очевидным. Для первой поставки я оставляю linear stages и один dependency. Ускорять граф needs, добавлять параллельные deploy или строить общую платформенную схему здесь преждевременно — сначала нужен один понятный ответ на вопрос происхождения artifact.
Маршрут расследования и исправления
- Остановить ручной deploy до сетевой команды и записать revision pipeline. Не перезапускать job, пока не названо, какой контракт нарушен.
- Открыть YAML и отметить все места, где выполняется
npm ciилиnpm run build. Для релизного output должен остаться один владелец. - В build-job создать
REVISION, checksum-manifest и списокartifacts:paths. Проверить, что manifest формируется после всех файлов output. - В deploy-job указать
dependencies: [build], убрать повторную сборку и добавить короткую проверку файлов доdeploy-staging. - Провести учебные отрицательные проверки: убрать artifact, подменить revision в fixture, испортить файл после manifest. Во всех трёх случаях job должна остановиться до внешней команды.
- После этого выполнить отдельный согласованный staging-прогон и записать, что он подтвердил. Не переносить его длительность или успех на production без отдельной проверки доступа, отката и поведения приложения.
Что остаётся за пределами разбора
Учебная фикстура не подтверждает конфигурацию конкретного runner, реальное хранение artifacts, скорость npm, работу secrets, доступ к staging или rollback. Она также не делает checksum заменой контроля доступа. Эти ограничения важны: хороший разбор не превращает один небольшой технический факт в отчёт о надёжности всего релиза.
Но после такого исправления меняется важное свойство: если deploy снова получит неизвестный каталог, он остановится до внешнего действия и покажет, где искать причину — build, artifact, dependency или manifest. Это меньше, чем полноценная система доставки, но достаточно для следующего шага: добавить реальный smoke-сценарий на staging и оформить отдельный путь отката для предыдущего известного artifact.
Проверяемые источники
- GitLab Docs: CI/CD YAML syntax reference — справочник ключей stages, dependencies, artifacts и when; точную поддержку нужно сверять с версией GitLab на своей установке
- GitLab Docs: Job artifacts — описывает передачу файлов между job и ограничение входящих артефактов через dependencies
- npm CLI v6: npm ci — фиксирует установку из существующего lockfile и ошибку при расхождении manifest и lockfile
- GitLab 12.9.0 CI YAML reference, March 2020 — официальный справочник версии, выпущенной в марте 2020 года; в нём есть базовые artifacts, dependencies и ручной when из примера