{ "index": 282, "slug": "editorial-2020-03-practice-ci-pipeline", "title": "Минимальный CI/CD pipeline: как не отправить непроверенную сборку", "excerpt": "Практический разбор GitLab CI/CD: отделяем проверки от сборки, передаём deploy один артефакт и останавливаем выпуск, если его происхождение нельзя подтвердить.", "contentHtml": "

Симптом появляется в момент, когда кажется, что всё уже прошло: job verify и build зелёные, а после ручного deploy на staging оказывается другой набор файлов. Иногда deploy снова вызывает npm run build. Иногда он читает каталог из cache. Иногда в логе нет ответа на вопрос, из какого commit собран bundle. Цена ошибки — ручное сравнение каталогов, задержка отката и риск повторно отправить тот же неизвестный результат.

\n

Для первого контура не нужно строить большой release-комплекс. Достаточно назвать входы, один раз создать результат и передать его следующему job. Ниже — учебная схема GitLab CI/CD в историческом контексте марта 2020 года: образ node:12-alpine, npm ci, три стадии и ручной staging deploy. Она не изображает реальный production-релиз и не заменяет проверку версии GitLab, Runner, shell, прав и команд конкретного проекта.

\n
\"Схема
Проверка и доставка разделены одним артефактом. Cache ускоряет установку, но не становится источником файлов для deploy.
\n

Сначала фиксируем контракт выпуска

\n

Входом служат revision исходников и lockfile зависимостей. Job verify запускает lint и тесты; любой ненулевой exit code останавливает следующий этап. Job build создаёт dist/, записывает туда CI_COMMIT_SHA и список контрольных сумм. Job deploy_staging получает только этот результат, проверяет его до сетевого вызова и не собирает проект заново.

\n

Такой контракт отвечает на два разных вопроса. REVISION связывает каталог с commit текущего pipeline. SHA256SUMS показывает, что файлы внутри полученного artifact не изменились между сборкой и проверкой. Ни один из файлов не является подписью релиза: они не защищают runner, сервер, registry или секреты. Их роль уже: сделать подмену наблюдаемой и остановить job до побочного эффекта.

\n

Cache и artifact отвечают за разное

\n

Cache хранит данные, которые можно получить заново. В Node-проекте это, например, каталог npm-кэша. Его отсутствие должно увеличить время установки, но не менять заявленный результат выпуска. Artifact — файл или каталог, который конкретный job сохраняет для следующих job. Если deploy читает cache вместо artifact, pipeline теряет владельца результата: неизвестно, кто создал каталог и к какому запуску он относится.

\n

Порядок стадий задаёт маршрут verify → build → release. При этом одного порядка недостаточно: deploy должен явно указать dependencies: - build, чтобы получить artifact именно этого job. В build можно записать dependencies: [], тем самым не рассчитывать на файлы предыдущих job. Если проекту понадобится передать отчёт из verify, это следует добавить как отдельный, названный контракт, а не использовать общий рабочий каталог.

\n

Учебная конфигурация GitLab CI/CD

\n

Пример рассчитан на приложение, которое публикует каталог dist/. Имена команд lint, test и ./scripts/deploy-staging условны. Перед применением конфигурацию нужно проверить через CI Lint и выполнить на том образе и Runner, которые используются в проекте.

\n
image: node:12-alpine\n\nstages:\n  - verify\n  - build\n  - release\n\ncache:\n  key: \"$CI_COMMIT_REF_SLUG\"\n  paths:\n    - .npm/\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 && find . -type f ! -name SHA256SUMS -print0 | sort -z | xargs -0 sha256sum > SHA256SUMS)\n  artifacts:\n    paths:\n      - dist/\n    expire_in: 1 week\n\ndeploy_staging:\n  stage: release\n  when: manual\n  allow_failure: false\n  dependencies:\n    - build\n  script:\n    - set -eu\n    - test \"$(cat dist/REVISION)\" = \"$CI_COMMIT_SHA\"\n    - (cd dist && sha256sum -c SHA256SUMS)\n    - ./scripts/deploy-staging dist/
\n

Важная деталь находится в команде создания manifest: SHA256SUMS исключён из списка входных файлов. Если сначала открыть этот файл на запись, а затем включить его в find, manifest начнёт считать сам себя; проверка станет зависеть от момента чтения и может завершиться ошибкой. REVISION, наоборот, создаётся до расчёта сумм и входит в проверяемый набор.

\n

Build повторяет npm ci, хотя verify уже устанавливал зависимости. Это осознанный обмен: jobs не делят случайный node_modules и каждый начинает с checkout и lockfile. В реальном проекте повтор можно сократить после измерения и явной передачи проверенного набора зависимостей. Нельзя делать cache носителем dist/ только ради экономии нескольких минут.

\n

Проверяем artifact до сетевого действия

\n

Проверка должна идти в deploy до команды, которая меняет staging. Сначала сравнивается revision, затем контрольные суммы, затем наличие минимального ожидаемого файла. Вынесем последовательность в отдельный фрагмент, чтобы её можно было повторить локально на учебном каталоге или внутри job без доступа к production:

\n
set -eu\n\nprintf 'commit=%s\\n' \"$CI_COMMIT_SHA\"\ntest -f dist/REVISION\ntest -f dist/SHA256SUMS\ntest \"$(cat dist/REVISION)\" = \"$CI_COMMIT_SHA\"\n(cd dist && sha256sum -c SHA256SUMS)\ntest -f dist/index.html\n\n# Только после проверок:\n./scripts/deploy-staging dist/
\n

Команда sha256sum -c проверяет целостность перечисленных файлов, но не бизнес-логику приложения. Для вложенного output, другого shell или Windows Runner понадобятся другие команды. Секреты и адрес staging должны приходить из защищённых настроек CI; их нельзя добавлять в YAML и выводить в диагностический лог.

\n
Наблюдаемый симптом и безопасное действие
СимптомГипотезаПроверкаДействие
Зелёный build, но другой bundledeploy пересобирает checkout или читает cacheНайти команды сборки в deploy и источник dist/Передать artifact build и убрать вторую сборку
В deploy нет dist/Artifact не создан, истёк или не скачанПроверить artifacts:paths, срок хранения и dependenciesОстановить job и исправить передачу результата
REVISION отличаетсяСмешаны pipeline, ветка или каталогСравнить файл с CI_COMMIT_SHA в том же jobНе отправлять файлы; создать pipeline нужного revision
npm ci падаетManifest и lockfile расходятсяЗапустить чистую установку тем же образомСогласовать lockfile и manifest отдельным commit
Checksum не проходитФайл изменился либо manifest неполонПроверить состав dist/ и команду его созданияОстановиться до upload и расследовать источник изменения
\n

Таблица задаёт порядок проверки, а не список советов на все случаи. Сначала определяется потерянный объект, затем проверяется его граница. Retry до этого шага скрывает нестабильность: следующий запуск может использовать другой cache или окружение и не ответит, почему первый результат отличался.

\n

Порядок внедрения

\n
  1. Записать commit, lockfile, образ Runner, команду build и фактический путь результата.
  2. Проверить npm ci на чистой среде. Несогласованный lockfile исправить до настройки deploy.
  3. Добавить verify с реальными lint и test-командами; убедиться, что ненулевой exit code не запускает build.
  4. Собрать dist/ один раз, добавить REVISION и checksum-manifest, затем объявить каталог artifact.
  5. Настроить deploy с dependencies: - build и проверками до вызова delivery-скрипта.
  6. На staging отдельно проверить провал verify, отсутствие artifact, неверный revision и изменение файла.
\n

Что эта схема не доказывает

\n

Pipeline не доказывает, что staging принял файлы, что миграция базы безопасна или что пользовательский сценарий работает. Нужны отдельные smoke-проверки, мониторинг, правила отката и контроль доступа. Недельный expire_in в примере — срок хранения учебного artifact, а не политика релизов. Для rollback артефакт следует хранить в подходящем registry или хранилище с понятным именованием.

\n

Checksum не защищает от скомпрометированного Runner и не подтверждает серверную конфигурацию. npm ci не фиксирует версию Node.js и состояние внешнего registry. Если ручной job должен блокировать pipeline, поведение when: manual и allow_failure: false нужно проверить на установленной версии GitLab и с реальными правами запуска.

\n

Критерий готовности

\n

Минимальная реализация готова, когда один staging-запуск позволяет назвать commit и состав artifact, а deploy не содержит команды сборки. Три отрицательных проверки обязательны: сбой verify блокирует build; отсутствие или несовпадение artifact блокирует deploy; mismatch revision или checksum не вызывает сетевой скрипт. Это проверяемая граница учебного pipeline, а не обещание production-надёжности.

\n

Проверяемые источники

\n" }