8 lines
16 KiB
JSON
8 lines
16 KiB
JSON
{
|
||
"index": 282,
|
||
"slug": "editorial-2020-03-practice-ci-pipeline",
|
||
"title": "Минимальный CI/CD pipeline: как не отправить непроверенную сборку",
|
||
"excerpt": "Практический разбор GitLab CI/CD: отделяем проверки от сборки, передаём deploy один артефакт и останавливаем выпуск, если его происхождение нельзя подтвердить.",
|
||
"contentHtml": "<p>Симптом знакомый: pipeline зелёный, deploy тоже завершился успешно, но на стенде оказались не те файлы. Иногда deploy заново запускает <code>npm run build</code>. Иногда он читает каталог из cache. Иногда в логе нет ответа на вопрос, из какого commit собран bundle. Цена ошибки — не только один неудачный релиз. Команда тратит время на сравнение каталогов, откладывает откат и рискует повторно отправить тот же неизвестный результат.</p>\n<p>Причина обычно не в числе job. Pipeline не назвал единственный результат выпуска. Проверка прошла над одним состоянием, а доставка взяла другое. Исправление начинается с контракта: конкретный commit и lockfile входят в pipeline; job verify проверяет код; job build один раз создаёт каталог; job deploy получает именно этот каталог и не пересобирает проект.</p>\n<h2>Тезис: deploy должен доставлять объект, а не повторять процесс</h2>\n<p>У pipeline есть четыре разных состояния. Checkout содержит исходники текущего запуска. Cache ускоряет работу и может исчезнуть без потери корректности. Артефакт — результат конкретного job, прикреплённый к запуску. Deploy создаёт побочный эффект: отправляет артефакт в среду. Эти состояния нельзя смешивать.</p>\n<p>Если deploy снова запускает сборку, он становится вторым build-job. У него могут отличаться образ, переменные, lockfile, время получения зависимостей и содержимое cache. Даже при том же SHA он способен получить другой результат. Тестировался один каталог, а отправился другой. Поэтому deploy должен читать результат build и завершаться ошибкой до сетевого вызова, если результат отсутствует или не проходит проверку.</p>\n<p>Учебная схема ниже рассчитана на GitLab CI/CD и Node.js. Она показывает границы и проверки, а не готовый production-файл. В ней нет credentials, реального сервера, измерений времени и утверждений о надёжности конкретной команды. Версии GitLab Runner, Node.js и shell нужно сверить с вашим окружением.</p>\n<figure><img src=\"/assets/editorial/2020/ci-pipeline-contract-2020.svg\" alt=\"Схема pipeline: commit и lockfile проходят verify, build создаёт dist с REVISION и SHA256SUMS, ручной deploy получает только артефакт\" loading=\"lazy\" /><figcaption>Выпуск проходит по одной цепочке: commit и lockfile, проверки, один build-артефакт, затем остановка перед доставкой.</figcaption></figure>\n<h2>Механизм: входы, доказательства и граница побочного эффекта</h2>\n<p>Сначала назовите входы. Минимальный набор — revision исходников, lockfile, образ job и команды из <code>package.json</code>. Переменные окружения тоже могут менять результат. Не обязательно стабилизировать все параметры сразу, но их нельзя прятать за фразой «на CI работает иначе».</p>\n<p><code>npm ci</code> полезен для ранней остановки. Он требует существующий lockfile и завершается ошибкой, если manifest и lockfile расходятся. Команда не чинит lockfile сама и не оставляет старый <code>node_modules</code> как доказательство корректности. Это не гарантия воспроизводимости всей сборки: внешний registry, native-модули и версия Node.js остаются отдельными входами.</p>\n<p>После verify job build создаёт <code>dist/</code>. Внутрь стоит положить файл <code>REVISION</code> со значением <code>CI_COMMIT_SHA</code> и manifest с контрольными суммами. Так deploy может ответить на два узких вопроса: какой commit породил каталог и не изменились ли его файлы после сборки. Checksum не проверяет бизнес-логику, настройки сервера или безопасность канала. Он только проверяет происхождение и целостность заявленного набора.</p>\n<h2>Конкретный пример конфигурации</h2>\n<p>Это учебный пример для простого приложения, которое публикует <code>dist/</code>. Имена job и команды нужно заменить на реальные команды проекта. Ключи и поведение следует проверить через CI Lint и документацию версии GitLab на вашей установке.</p>\n<pre><code>image: node:20-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 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 -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/</code></pre>\n<p><code>cache</code> здесь хранит только npm-кэш. Его отсутствие должно замедлить job, но не изменить контракт результата. <code>artifacts</code> прикрепляет каталог к job build. <code>dependencies</code> ограничивает вход deploy этим job. <code>when: manual</code> оставляет явную остановку перед побочным эффектом. Доступ к staging и секреты должны приходить из защищённых настроек CI, а не из YAML.</p>\n<p>В примере build повторяет <code>npm ci</code> в отдельном job. Это намеренно консервативный вариант: job не зависит от случайного рабочего каталога verify. Платформа может поддерживать другой способ передачи зависимостей, но оптимизация должна сохранять доказуемую границу. Сначала подтвердите цепочку, потом сокращайте повторную работу измерениями.</p>\n<h2>Симптомы и точечная диагностика</h2>\n<div class=\"table-scroll\"><table><caption>Что наблюдать до изменения pipeline</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>Зелёный build, другой bundle на стенде</td><td>deploy пересобирает checkout или читает cache</td><td>Найти build-команды в deploy и вывести источник dist</td><td>Передать artifact через dependencies и убрать вторую сборку</td></tr><tr><td>Deploy не находит dist</td><td>artifact не создан, истёк или не скачан</td><td>Проверить paths, срок хранения и список dependencies</td><td>Остановить deploy, исправить передачу artifact</td></tr><tr><td>REVISION не совпадает с CI_COMMIT_SHA</td><td>Смешаны pipeline, ветка или каталог</td><td>Сравнить значение файла, переменную job и commit в интерфейсе</td><td>Не отправлять файлы; запустить pipeline для нужного revision</td></tr><tr><td>npm ci падает до тестов</td><td>Manifest и lockfile расходятся или не совпали флаги npm</td><td>Запустить чистую установку тем же образом и прочитать первую ошибку</td><td>Обновить lockfile осознанно и закоммитить согласованную пару</td></tr><tr><td>Checksum не проходит</td><td>Файл изменился после build или manifest не соответствует каталогу</td><td>Проверить содержимое dist и команду создания SHA256SUMS</td><td>Завершить job до upload и расследовать источник изменения</td></tr></tbody></table></div>\n<p>Таблица не заменяет логи. Она задаёт короткий маршрут: сначала определить, какой объект потерялся, затем проверить конкретную границу. Не добавляйте retry, новый cache или вторую сборку, пока не назван симптом. Повтор запуска скрывает нестабильность и не доказывает, что проверка и доставка использовали один результат.</p>\n<h2>Порядок внедрения</h2>\n<ol><li>Зафиксируйте текущую команду build, путь результата и commit, на котором выполняется проверка.</li><li>Проверьте чистый runner: <code>npm ci</code> должен работать с сохранённым lockfile. Исправьте расхождение manifest и lockfile до настройки deploy.</li><li>Добавьте verify с реальными lint и test-командами. Ненулевой exit code должен блокировать build.</li><li>Соберите <code>dist/</code> один раз и добавьте <code>REVISION</code> и <code>SHA256SUMS</code>. Прикрепите каталог как artifact.</li><li>Настройте deploy только от build. До сетевого вызова сравните revision и контрольные суммы.</li><li>Оставьте manual gate для учебного staging-прогона. Отдельно проверьте провалы test, отсутствие artifact и несовпадение revision.</li></ol>\n<h2>Ограничения и отрицательный путь</h2>\n<p>Эта схема не решает rollback, миграции базы, стратегию production-раскатки, smoke-тесты после доставки и мониторинг. Artifact имеет срок хранения. Если он нужен для отката, его следует сохранять в подходящем registry или хранилище с правилами доступа и именованием. Нельзя считать недельный срок из примера политикой релизов.</p>\n<p>Checksum не защищает от скомпрометированного runner и не подтверждает, что сервер применил файлы. Manual job не заменяет review прав доступа. <code>npm ci</code> не фиксирует версию Node.js и состояние внешнего registry. Эти ограничения не делают минимальный pipeline бесполезным. Они показывают, какие вопросы он не закрывает.</p>\n<p>Отрицательный путь обязателен. Если lockfile расходится, verify должен остановиться. Если build не создал artifact, deploy не должен строить заново. Если revision или checksum не совпали, сетевой deploy не должен запускаться. Если manual gate не подтверждён, побочный эффект не происходит. Именно эти остановки делают ошибку наблюдаемой и ограничивают её цену.</p>\n<h2>Критерий готовности</h2>\n<p>Учебная реализация готова, когда один запуск на staging показывает цепочку «commit → verify → build → artifact → manual deploy», а журнал позволяет назвать revision и состав artifact. Отдельно должны быть подтверждены три отказа: провал verify блокирует build; отсутствие или несовпадение artifact блокирует deploy; deploy не вызывает build. Это проверяемое условие. Оно не выдаёт учебный прогон за production-результат и оставляет понятный следующий шаг для hardening.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://docs.gitlab.com/ci/yaml/\" target=\"_blank\" rel=\"noopener noreferrer\">GitLab Docs: CI/CD YAML syntax reference</a> — официальное описание stages, artifacts, dependencies и manual job.</li><li><a href=\"https://docs.gitlab.com/ci/jobs/job_artifacts/\" target=\"_blank\" rel=\"noopener noreferrer\">GitLab Docs: Job artifacts</a> — правила создания и передачи артефактов между job.</li><li><a href=\"https://docs.npmjs.com/cli/commands/npm-ci\" target=\"_blank\" rel=\"noopener noreferrer\">npm Docs: npm ci</a> — требования к lockfile и поведение чистой установки.</li></ul>"
|
||
}
|