Files
progcode/editorial/agent-rewrites/282.json
T

8 lines
16 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 282,
"slug": "editorial-2020-03-practice-ci-pipeline",
"title": "Минимальный CI/CD pipeline: как не отправить непроверенную сборку",
"excerpt": "Практический разбор GitLab CI/CD: отделяем проверки от сборки, передаём deploy один артефакт и останавливаем выпуск, если его происхождение нельзя подтвердить.",
"contentHtml": "<p>Симптом появляется в момент, когда кажется, что всё уже прошло: job <code>verify</code> и <code>build</code> зелёные, а после ручного deploy на staging оказывается другой набор файлов. Иногда deploy снова вызывает <code>npm run build</code>. Иногда он читает каталог из cache. Иногда в логе нет ответа на вопрос, из какого commit собран bundle. Цена ошибки — ручное сравнение каталогов, задержка отката и риск повторно отправить тот же неизвестный результат.</p>\n<p>Для первого контура не нужно строить большой release-комплекс. Достаточно назвать входы, один раз создать результат и передать его следующему job. Ниже — учебная схема GitLab CI/CD в историческом контексте марта 2020 года: образ <code>node:12-alpine</code>, <code>npm ci</code>, три стадии и ручной staging deploy. Она не изображает реальный production-релиз и не заменяет проверку версии GitLab, Runner, 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>Проверка и доставка разделены одним артефактом. Cache ускоряет установку, но не становится источником файлов для deploy.</figcaption></figure>\n<h2>Сначала фиксируем контракт выпуска</h2>\n<p>Входом служат revision исходников и lockfile зависимостей. Job <code>verify</code> запускает lint и тесты; любой ненулевой exit code останавливает следующий этап. Job <code>build</code> создаёт <code>dist/</code>, записывает туда <code>CI_COMMIT_SHA</code> и список контрольных сумм. Job <code>deploy_staging</code> получает только этот результат, проверяет его до сетевого вызова и не собирает проект заново.</p>\n<p>Такой контракт отвечает на два разных вопроса. <code>REVISION</code> связывает каталог с commit текущего pipeline. <code>SHA256SUMS</code> показывает, что файлы внутри полученного artifact не изменились между сборкой и проверкой. Ни один из файлов не является подписью релиза: они не защищают runner, сервер, registry или секреты. Их роль уже: сделать подмену наблюдаемой и остановить job до побочного эффекта.</p>\n<h2>Cache и artifact отвечают за разное</h2>\n<p>Cache хранит данные, которые можно получить заново. В Node-проекте это, например, каталог npm-кэша. Его отсутствие должно увеличить время установки, но не менять заявленный результат выпуска. Artifact — файл или каталог, который конкретный job сохраняет для следующих job. Если deploy читает cache вместо artifact, pipeline теряет владельца результата: неизвестно, кто создал каталог и к какому запуску он относится.</p>\n<p>Порядок стадий задаёт маршрут <code>verify → build → release</code>. При этом одного порядка недостаточно: deploy должен явно указать <code>dependencies: - build</code>, чтобы получить artifact именно этого job. В build можно записать <code>dependencies: []</code>, тем самым не рассчитывать на файлы предыдущих job. Если проекту понадобится передать отчёт из verify, это следует добавить как отдельный, названный контракт, а не использовать общий рабочий каталог.</p>\n<h2>Учебная конфигурация GitLab CI/CD</h2>\n<p>Пример рассчитан на приложение, которое публикует каталог <code>dist/</code>. Имена команд <code>lint</code>, <code>test</code> и <code>./scripts/deploy-staging</code> условны. Перед применением конфигурацию нужно проверить через CI Lint и выполнить на том образе и Runner, которые используются в проекте.</p>\n<pre><code>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\" &gt; dist/REVISION\n - (cd dist &amp;&amp; find . -type f ! -name SHA256SUMS -print0 | sort -z | xargs -0 sha256sum &gt; 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 &amp;&amp; sha256sum -c SHA256SUMS)\n - ./scripts/deploy-staging dist/</code></pre>\n<p>Важная деталь находится в команде создания manifest: <code>SHA256SUMS</code> исключён из списка входных файлов. Если сначала открыть этот файл на запись, а затем включить его в <code>find</code>, manifest начнёт считать сам себя; проверка станет зависеть от момента чтения и может завершиться ошибкой. <code>REVISION</code>, наоборот, создаётся до расчёта сумм и входит в проверяемый набор.</p>\n<p>Build повторяет <code>npm ci</code>, хотя verify уже устанавливал зависимости. Это осознанный обмен: jobs не делят случайный <code>node_modules</code> и каждый начинает с checkout и lockfile. В реальном проекте повтор можно сократить после измерения и явной передачи проверенного набора зависимостей. Нельзя делать cache носителем <code>dist/</code> только ради экономии нескольких минут.</p>\n<h2>Проверяем artifact до сетевого действия</h2>\n<p>Проверка должна идти в deploy до команды, которая меняет staging. Сначала сравнивается revision, затем контрольные суммы, затем наличие минимального ожидаемого файла. Вынесем последовательность в отдельный фрагмент, чтобы её можно было повторить локально на учебном каталоге или внутри job без доступа к production:</p>\n<pre><code>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 &amp;&amp; sha256sum -c SHA256SUMS)\ntest -f dist/index.html\n\n# Только после проверок:\n./scripts/deploy-staging dist/</code></pre>\n<p>Команда <code>sha256sum -c</code> проверяет целостность перечисленных файлов, но не бизнес-логику приложения. Для вложенного output, другого shell или Windows Runner понадобятся другие команды. Секреты и адрес staging должны приходить из защищённых настроек CI; их нельзя добавлять в YAML и выводить в диагностический лог.</p>\n<div class=\"table-scroll\"><table><caption>Наблюдаемый симптом и безопасное действие</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>Найти команды сборки в deploy и источник <code>dist/</code></td><td>Передать artifact build и убрать вторую сборку</td></tr><tr><td>В deploy нет <code>dist/</code></td><td>Artifact не создан, истёк или не скачан</td><td>Проверить <code>artifacts:paths</code>, срок хранения и <code>dependencies</code></td><td>Остановить job и исправить передачу результата</td></tr><tr><td><code>REVISION</code> отличается</td><td>Смешаны pipeline, ветка или каталог</td><td>Сравнить файл с <code>CI_COMMIT_SHA</code> в том же job</td><td>Не отправлять файлы; создать pipeline нужного revision</td></tr><tr><td><code>npm ci</code> падает</td><td>Manifest и lockfile расходятся</td><td>Запустить чистую установку тем же образом</td><td>Согласовать lockfile и manifest отдельным commit</td></tr><tr><td>Checksum не проходит</td><td>Файл изменился либо manifest неполон</td><td>Проверить состав <code>dist/</code> и команду его создания</td><td>Остановиться до upload и расследовать источник изменения</td></tr></tbody></table></div>\n<p>Таблица задаёт порядок проверки, а не список советов на все случаи. Сначала определяется потерянный объект, затем проверяется его граница. Retry до этого шага скрывает нестабильность: следующий запуск может использовать другой cache или окружение и не ответит, почему первый результат отличался.</p>\n<h2>Порядок внедрения</h2>\n<ol><li>Записать commit, lockfile, образ Runner, команду build и фактический путь результата.</li><li>Проверить <code>npm ci</code> на чистой среде. Несогласованный lockfile исправить до настройки deploy.</li><li>Добавить <code>verify</code> с реальными lint и test-командами; убедиться, что ненулевой exit code не запускает build.</li><li>Собрать <code>dist/</code> один раз, добавить <code>REVISION</code> и checksum-manifest, затем объявить каталог artifact.</li><li>Настроить deploy с <code>dependencies: - build</code> и проверками до вызова delivery-скрипта.</li><li>На staging отдельно проверить провал verify, отсутствие artifact, неверный revision и изменение файла.</li></ol>\n<h2>Что эта схема не доказывает</h2>\n<p>Pipeline не доказывает, что staging принял файлы, что миграция базы безопасна или что пользовательский сценарий работает. Нужны отдельные smoke-проверки, мониторинг, правила отката и контроль доступа. Недельный <code>expire_in</code> в примере — срок хранения учебного artifact, а не политика релизов. Для rollback артефакт следует хранить в подходящем registry или хранилище с понятным именованием.</p>\n<p>Checksum не защищает от скомпрометированного Runner и не подтверждает серверную конфигурацию. <code>npm ci</code> не фиксирует версию Node.js и состояние внешнего registry. Если ручной job должен блокировать pipeline, поведение <code>when: manual</code> и <code>allow_failure: false</code> нужно проверить на установленной версии GitLab и с реальными правами запуска.</p>\n<h2>Критерий готовности</h2>\n<p>Минимальная реализация готова, когда один staging-запуск позволяет назвать commit и состав artifact, а deploy не содержит команды сборки. Три отрицательных проверки обязательны: сбой verify блокирует build; отсутствие или несовпадение artifact блокирует deploy; mismatch revision или checksum не вызывает сетевой скрипт. Это проверяемая граница учебного pipeline, а не обещание production-надёжности.</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> — описание <code>stages</code>, <code>artifacts</code>, <code>dependencies</code> и <code>when</code>; актуальная документация, поэтому поддержку на старой установке нужно сверять отдельно.</li><li><a href=\"https://docs.gitlab.com/ci/jobs/job_artifacts/\" target=\"_blank\" rel=\"noopener noreferrer\">GitLab Docs: Job artifacts</a> — создание artifacts и ограничение их передачи между jobs.</li><li><a href=\"https://docs.gitlab.com/ci/caching/\" target=\"_blank\" rel=\"noopener noreferrer\">GitLab Docs: Caching in GitLab CI/CD</a> — различие cache для повторно получаемых зависимостей и artifacts для результатов job.</li><li><a href=\"https://docs.npmjs.com/cli/v6/commands/npm-ci\" target=\"_blank\" rel=\"noopener noreferrer\">npm CLI v6: npm ci</a> — чистая установка по lockfile и ошибка при расхождении manifest и lockfile.</li><li><a href=\"https://gitlab.com/gitlab-org/gitlab/-/blob/v12.9.0-ee/doc/ci/yaml/README.md\" target=\"_blank\" rel=\"noopener noreferrer\">GitLab 12.9.0 CI YAML reference</a> — исторический справочник версии марта 2020 года для проверки доступности использованных базовых ключей.</li></ul>"
}