{ "index": 281, "slug": "editorial-2020-03-mechanism-ci-pipeline", "title": "Артефакт как контракт CI/CD: как не отправить непроверенную сборку", "excerpt": "Зелёные job ещё не доказывают, что deploy отправит тот же каталог, который проверял build. Разбираем границу между checkout, cache и artifact и ставим проверку до сетевого действия.", "contentHtml": "
Симптом появляется в момент поставки: job verify и build завершились успешно, а на staging нет ожидаемого файла или приложение выглядит не так, как после проверки. Лог показывает зелёные статусы, но не отвечает на главный вопрос: какой каталог проверили и какой каталог отправили. Цена ошибки — повторная сборка, потерянное время и откат, который тоже приходится собирать заново. В худшем случае команда принимает непроверенный результат за тот, что прошёл CI.
Причина обычно не в одном флаге GitLab. Pipeline смешивает checkout, cache, рабочую директорию и release artifact. Пока job deploy может заново вызвать npm run build, связь между проверкой и доставкой остаётся предположением. Надёжная граница проще: один job создаёт артефакт, следующие job получают этот артефакт, проверяют его происхождение и не собирают другой каталог.
Минимальный pipeline отвечает на четыре разных вопроса. Checkout подтверждает исходный commit. npm ci проверяет согласованность manifest и lockfile и устанавливает зависимости. Build создаёт конкретный набор файлов. Deploy отправляет именно этот набор и останавливается до сетевого вызова, если доказательство неполно.
Эти состояния нельзя подменять друг другом. Cache ускоряет установку, но может исчезнуть или устареть. Рабочая директория существует только внутри job. Успешная команда сборки говорит, что команда завершилась с нулевым кодом, но не говорит, какие файлы были приложены к следующему job. Artifact нужен как явный интерфейс между job: он переносит результат, а не надежду на одинаковое окружение.
\nОдин SHA связывает pipeline с исходниками, но не описывает все входы сборки. Результат зависит от lockfile, версии Node.js, package manager, образа runner, переменных и настроек bundler. Если deploy запускает сборку повторно, любой из этих входов может отличаться. Даже одинаковый commit не доказывает одинаковый output.
\nНа раннем шаге полезно намеренно делать установку строгой. npm ci использует существующий lockfile и завершается ошибкой при конфликте с manifest; он не обновляет lockfile. Это выгоднее молчаливого обновления зависимостей: pipeline останавливается там, где нарушен входной контракт. Cache .npm/ можно подключить для скорости, но он не становится release artifact и не должен влиять на его содержимое.
| Симптом | Вероятная причина | Проверка | Действие |
|---|---|---|---|
В deploy нет dist/index.html | Путь не указан в artifact или job не получил artifact | Сверить artifacts:paths, имя job и dependencies | Исправить передачу файлов; не запускать повторный build |
REVISION не равен $CI_COMMIT_SHA | Взята сборка другого pipeline или checkout пересобран отдельно | Вывести значение файла и переменной до delivery-команды | Остановить job и создать pipeline для нужного commit |
| Checksum не проходит | Файл изменился после создания manifest или manifest неполон | Выполнить sha256sum -c внутри полученного artifact | Не выполнять сетевой шаг; исправить состав artifact |
npm ci завершается ошибкой | Manifest и lockfile расходятся или не совпадает версия package manager | Проверить файлы и версии на чистом runner | Исправить входы; не подменять их cache |
| Job зелёный, но результат не подтверждён | Проверяли команду, а не содержимое релизного каталога | Показать список artifact и обязательные файлы | Добавить явный контракт и проверку до deploy |
Таблица задаёт отрицательный путь. Неизвестное состояние не превращается в сетевой эффект. Retry может повторить случайный результат, но не объяснит, какой вход изменился. Сначала проверяют факт, затем меняют конфигурацию.
\nКлюч stages показывает порядок работ. Например, verify идёт перед build, а build — перед release. Но стадии сами по себе не описывают файлы, которые получает job. Для этого нужен явный список artifact-зависимостей.
В минимальной схеме build собирает checkout и не получает файлы от verify. Поэтому для него уместно dependencies: []. Deploy получает artifact только от build. Если deploy содержит собственный npm ci и npm run build, граница снова исчезает: job проверяет один результат, а отправляет другой.
В примере используется только dependencies. Если проект добавляет needs, способ получения artifact нужно описать через needs:artifacts и не смешивать два механизма в одном job. Иначе визуальный порядок стадий не гарантирует тот набор файлов, который получил deploy.
image: node:12-alpine\n\nstages:\n - verify\n - build\n - release\n\nverify:\n stage: verify\n script:\n - npm ci --cache .npm --prefer-offline\n - npm run lint\n - npm test\n\nbuild:\n stage: build\n dependencies: []\n script:\n - npm ci --cache .npm --prefer-offline\n - npm run build\n - printf \"%s\\n\" \"$CI_COMMIT_SHA\" > dist/REVISION\n - (cd dist && sha256sum * > SHA256SUMS)\n artifacts:\n name: \"web-$CI_COMMIT_SHA\"\n paths:\n - dist/\n expire_in: 7 days\n\ndeploy_staging:\n stage: release\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 - ./scripts/deploy-staging dist/\n when: manual\n allow_failure: false\nКод показывает учебную структуру, а не готовый production-файл. Образ, команды, имя выходного каталога и поведение manual job нужно сверить с конкретным GitLab Runner. В строке checksum используется простой плоский каталог и GNU-команда. Для вложенных файлов, другого shell или другой операционной системы нужен отдельный вариант проверки.
\nФайл REVISION содержит ожидаемый SHA commit-а pipeline. Сравнение с $CI_COMMIT_SHA показывает, что artifact помечен тем же commit, но само по себе не доказывает происхождение всех файлов: значение можно записать вручную. Это проверка происхождения на уровне диагностики, а не цифровая подпись и не замена контролю доступа.
SHA256SUMS проверяет, что перечисленные файлы не изменились после создания manifest. Сначала build записывает обязательные файлы, затем создаёт manifest, затем прикладывает каталог. Deploy проверяет manifest после передачи artifact и до вызова delivery script. Если manifest покрывает только часть каталога, нельзя называть весь artifact проверенным.
# scripts/check-release-artifact.sh\nset -eu\n\ntest -f dist/REVISION\ntest -f dist/SHA256SUMS\ntest \"$(cat dist/REVISION)\" = \"$CI_COMMIT_SHA\"\n\n(cd dist && sha256sum -c SHA256SUMS)\ntest -f dist/index.html\nУ каждой команды есть цена отказа. Отсутствующий файл останавливает job. Несовпадение revision останавливает job. Ошибка checksum останавливает job. Поэтому проверка должна стоять перед первой командой, которая открывает соединение со staging или production. В лог можно вывести SHA и названия проверенных файлов. Секреты, токены и полные URL доступа в него попадать не должны.
\nwhen: manual создаёт паузу перед внешним действием. Оператор может посмотреть результат verify, состав artifact и revision. Но ручное нажатие не доказывает качество каталога. Если deploy получил неизвестный набор файлов, человек лишь вручную запускает неизвестный набор файлов.
Поэтому manual gate ставят после автоматических проверок. Для staging он может быть полезен как ограничитель частых изменений. Для production понадобятся отдельные правила доступа, rollback, миграции данных, наблюдение и согласование. Эта статья не утверждает, что минимальная схема покрывает их. Она закрывает более узкий вопрос: deploy не должен незаметно создавать новый результат после проверки.
\nnpm ci завершается без изменения lockfile.verify с существующими lint и test-командами. Ненулевой exit code должен блокировать следующую стадию.REVISION, создать checksum-manifest и приложить только нужный каталог.Этот механизм не доказывает, что пользовательский сценарий работает. Lint не заменяет тесты. Тесты не заменяют smoke на стенде. Checksum не заменяет авторизацию, резервное копирование и rollback. Артефакт также может истечь по сроку хранения, а синтаксис GitLab зависит от версии сервера и Runner. Эти условия нужно проверять отдельно.
\nМинимальный pipeline готов, когда один запуск показывает один commit, один job-владелец release output и один переданный artifact. Deploy не содержит повторной сборки. До сетевой команды автоматически проверяются REVISION, manifest и обязательный файл. Три отрицательные проверки — отсутствующий artifact, неверный revision и испорченный файл — останавливают job. Если эти условия нельзя показать по YAML и логам, зелёный статус ещё не означает готовый выпуск.
stages, dependencies, artifacts, needs:artifacts и when.