{ "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. Цена ошибки — ручное сравнение каталогов, задержка отката и риск повторно отправить тот же неизвестный результат.
Для первого контура не нужно строить большой release-комплекс. Достаточно назвать входы, один раз создать результат и передать его следующему job. Ниже — учебная схема GitLab CI/CD в историческом контексте марта 2020 года: образ node:12-alpine, npm ci, три стадии и ручной staging deploy. Она не изображает реальный production-релиз и не заменяет проверку версии GitLab, Runner, shell, прав и команд конкретного проекта.
Входом служат revision исходников и lockfile зависимостей. Job verify запускает lint и тесты; любой ненулевой exit code останавливает следующий этап. Job build создаёт dist/, записывает туда CI_COMMIT_SHA и список контрольных сумм. Job deploy_staging получает только этот результат, проверяет его до сетевого вызова и не собирает проект заново.
Такой контракт отвечает на два разных вопроса. REVISION связывает каталог с commit текущего pipeline. SHA256SUMS показывает, что файлы внутри полученного artifact не изменились между сборкой и проверкой. Ни один из файлов не является подписью релиза: они не защищают runner, сервер, registry или секреты. Их роль уже: сделать подмену наблюдаемой и остановить job до побочного эффекта.
Cache хранит данные, которые можно получить заново. В Node-проекте это, например, каталог npm-кэша. Его отсутствие должно увеличить время установки, но не менять заявленный результат выпуска. Artifact — файл или каталог, который конкретный job сохраняет для следующих job. Если deploy читает cache вместо artifact, pipeline теряет владельца результата: неизвестно, кто создал каталог и к какому запуску он относится.
\nПорядок стадий задаёт маршрут verify → build → release. При этом одного порядка недостаточно: deploy должен явно указать dependencies: - build, чтобы получить artifact именно этого job. В build можно записать dependencies: [], тем самым не рассчитывать на файлы предыдущих job. Если проекту понадобится передать отчёт из verify, это следует добавить как отдельный, названный контракт, а не использовать общий рабочий каталог.
Пример рассчитан на приложение, которое публикует каталог dist/. Имена команд lint, test и ./scripts/deploy-staging условны. Перед применением конфигурацию нужно проверить через CI Lint и выполнить на том образе и Runner, которые используются в проекте.
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, наоборот, создаётся до расчёта сумм и входит в проверяемый набор.
Build повторяет npm ci, хотя verify уже устанавливал зависимости. Это осознанный обмен: jobs не делят случайный node_modules и каждый начинает с checkout и lockfile. В реальном проекте повтор можно сократить после измерения и явной передачи проверенного набора зависимостей. Нельзя делать cache носителем dist/ только ради экономии нескольких минут.
Проверка должна идти в deploy до команды, которая меняет staging. Сначала сравнивается revision, затем контрольные суммы, затем наличие минимального ожидаемого файла. Вынесем последовательность в отдельный фрагмент, чтобы её можно было повторить локально на учебном каталоге или внутри job без доступа к production:
\nset -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 и выводить в диагностический лог.
| Симптом | Гипотеза | Проверка | Действие |
|---|---|---|---|
| Зелёный build, но другой bundle | deploy пересобирает 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 и расследовать источник изменения |
Таблица задаёт порядок проверки, а не список советов на все случаи. Сначала определяется потерянный объект, затем проверяется его граница. Retry до этого шага скрывает нестабильность: следующий запуск может использовать другой cache или окружение и не ответит, почему первый результат отличался.
\nnpm ci на чистой среде. Несогласованный lockfile исправить до настройки deploy.verify с реальными lint и test-командами; убедиться, что ненулевой exit code не запускает build.dist/ один раз, добавить REVISION и checksum-manifest, затем объявить каталог artifact.dependencies: - build и проверками до вызова delivery-скрипта.Pipeline не доказывает, что staging принял файлы, что миграция базы безопасна или что пользовательский сценарий работает. Нужны отдельные smoke-проверки, мониторинг, правила отката и контроль доступа. Недельный expire_in в примере — срок хранения учебного artifact, а не политика релизов. Для rollback артефакт следует хранить в подходящем registry или хранилище с понятным именованием.
Checksum не защищает от скомпрометированного Runner и не подтверждает серверную конфигурацию. npm ci не фиксирует версию Node.js и состояние внешнего registry. Если ручной job должен блокировать pipeline, поведение when: manual и allow_failure: false нужно проверить на установленной версии GitLab и с реальными правами запуска.
Минимальная реализация готова, когда один staging-запуск позволяет назвать commit и состав artifact, а deploy не содержит команды сборки. Три отрицательных проверки обязательны: сбой verify блокирует build; отсутствие или несовпадение artifact блокирует deploy; mismatch revision или checksum не вызывает сетевой скрипт. Это проверяемая граница учебного pipeline, а не обещание production-надёжности.
\nstages, artifacts, dependencies и when; актуальная документация, поэтому поддержку на старой установке нужно сверять отдельно.