Files

8 lines
19 KiB
JSON
Raw Permalink 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": 280,
"slug": "editorial-2020-03-field-ci-pipeline",
"title": "CI/CD: как связать проверенный build с deploy",
"excerpt": "После зелёного build на staging оказывается другой каталог. Разбираем границу между сборкой и deploy: передаём один artifact, сверяем commit и останавливаем выпуск до сетевого действия.",
"contentHtml": "<p>В понедельник утром дежурный инженер открыл staging после merge и увидел старый CSS. Job <code>verify</code> и <code>build</code> были зелёными, а <code>deploy</code> тоже завершился без ошибки. В логах не было ответа на простой вопрос: какой каталог реально отправили на стенд.</p>\n<p>Сначала команда повторила deploy и получила тот же зелёный статус. После этого обнаружилось, что deploy-job снова делала checkout, запускала <code>npm ci</code> и собирала <code>dist/</code>. Цена такой схемы — потеря связи между проверенным и отправленным результатом: нельзя доказать, какой commit собрался, какой output попал в deploy и какие зависимости использовал runner.</p>\n<p>Надёжная граница выглядит так: job <code>build</code> один раз создаёт output, сохраняет его как artifact и записывает идентификатор commit. Job <code>deploy</code> получает этот artifact, проверяет revision, состав файлов и checksum, а затем выполняет одну команду с внешним эффектом. Повторная сборка в deploy должна считаться отдельной ошибкой схемы.</p>\n<h2>Сценарий: зелёный build и чужой каталог</h2>\n<p>В учебном сценарии pipeline состоит из <code>verify</code>, <code>build</code> и <code>deploy_staging</code>. Сначала <code>verify</code> проверяет код. Затем <code>build</code> устанавливает зависимости и создаёт <code>dist/</code>. После этого deploy получает рабочую директорию, но не обязан получить каталог от предыдущей job, если конфигурация не описывает передачу artifact.</p>\n<p>В этот момент два запуска могут выглядеть одинаково в интерфейсе: оба сообщают <code>passed</code>, оба относятся к одному merge request. Но первый запуск произвёл каталог в build-job, а второй мог собрать другой каталог уже после checkout. Между ними меняются cache, образ runner, версия package manager, переменные окружения и рабочая директория.</p>\n<p>Совпадение commit не доказывает совпадение output. Для результата важны lockfile, версия Node.js, настройки bundler, переменные окружения и порядок команд. В 2020 году это уже была практическая граница CI/CD: проверять нужно не только исходное дерево, но и тот набор файлов, который пересекает границу delivery.</p>\n<h2>Четыре состояния, которые нельзя смешивать</h2>\n<p>Расследование становится короче, если назвать объект каждой операции. Checkout даёт исходное дерево, install — окружение зависимостей, build — новый каталог, artifact — сохранённую копию этого каталога. Deploy не должен молча становиться ещё одним build-job.</p>\n<table><caption>Что именно проверяем на границе между build и deploy</caption><thead><tr><th>Состояние</th><th>Что в нём находится</th><th>Какая ошибка возможна</th><th>Проверка</th></tr></thead><tbody><tr><td>Checkout</td><td>Дерево commit pipeline</td><td>Взята другая ветка или revision</td><td>Сверить commit job с <code>CI_COMMIT_SHA</code></td></tr><tr><td>Build output</td><td>Собранный каталог <code>dist/</code></td><td>Каталог неполный или собран повторно</td><td>Проверить обязательный файл и владельца output</td></tr><tr><td>Artifact</td><td>Файлы, сохранённые job и скачанные позже</td><td>Путь не включён или выбран не тот источник</td><td>Проверить <code>artifacts:paths</code> и <code>dependencies</code></td></tr><tr><td>Deploy</td><td>Команда, меняющая staging</td><td>Внешнее действие запущено до gate</td><td>Оставить delivery-script последней командой</td></tr></tbody></table>\n<p>Эта таблица не утверждает, что artifact защищён от всех угроз. Она только разделяет места, где можно проверить происхождение и целостность результата. Права доступа, секреты, бизнес-проверки и откат требуют отдельных механизмов.</p>\n<h2>Антипример: deploy производит результат заново</h2>\n<p>Ниже учебная конфигурация. Она показывает дефект границы; это не запись о реально запущенной job и не обещание, что команды выполнялись в конкретном проекте.</p>\n<pre><code>build:\n stage: build\n script:\n - npm ci\n - npm run build\n artifacts:\n paths:\n - dist/\n\ndeploy_staging:\n stage: deploy\n script:\n - npm ci\n - npm run build\n - ./deploy-staging.sh dist/</code></pre>\n<p>В этом варианте <code>deploy_staging</code> создаёт собственный <code>dist/</code>. Он не использует output от <code>build</code>, даже если в рабочей директории случайно появляется каталог с тем же именем. Две сборки могут иметь один commit и разные зависимости, переменные или содержимое cache.</p>\n<p><code>npm ci</code> здесь не является плохой командой сама по себе: она предназначена для чистой установки в автоматизированной среде и требует согласованного lockfile. Ошибка в том, что deploy одновременно устанавливает зависимости и заново производит релизный output вместо чтения проверенного artifact.</p>\n<h2>Рабочая схема: один владелец output</h2>\n<p>Исправление переносит производство каталога в <code>build</code>. После сборки job записывает revision и создаёт manifest контрольных сумм. Deploy ограничивает загрузку artifact списком <code>dependencies</code>, проверяет файлы и только затем вызывает скрипт доставки.</p>\n<pre><code>build:\n stage: build\n script:\n - npm ci\n - npm run build\n - printf '%s\\n' &quot;$CI_COMMIT_SHA&quot; &gt; dist/REVISION\n - (cd dist &amp;&amp; find . -type f -print0 | sort -z | xargs -0 sha256sum &gt; SHA256SUMS)\n artifacts:\n paths:\n - dist/\n\ndeploy_staging:\n stage: deploy\n dependencies:\n - build\n script:\n - test -f dist/REVISION\n - test &quot;$(cat dist/REVISION)&quot; = &quot;$CI_COMMIT_SHA&quot;\n - (cd dist &amp;&amp; sha256sum -c SHA256SUMS)\n - test -f dist/index.html\n - ./deploy-staging.sh dist/</code></pre>\n<p>В GitLab artifact — это файлы и каталоги, прикреплённые к job. По умолчанию job более поздней стадии получает artifacts предыдущих стадий, а <code>dependencies</code> сужает список источников. Поэтому в примере важно не только наличие <code>dist/</code>, но и явная связь deploy с job <code>build</code>.</p>\n<p>Команды предполагают POSIX shell, GNU-совместимые <code>find</code>, <code>sort</code> и <code>sha256sum</code>, а также каталог <code>dist/</code>. В другом shell или на Windows runner их нужно заменить и отдельно проверить. <code>REVISION</code> и <code>SHA256SUMS</code> — диагностический контракт учебного примера, а не подпись релиза.</p>\n<figure><img src=\"/assets/editorial/2020/ci-pipeline-diagnosis-2020.svg\" alt=\"Схема проверки artifact перед deploy: build создаёт результат, deploy сверяет revision и checksum, а при ошибке останавливается до внешнего действия\"><figcaption>Граница между build и deploy: неизвестный artifact не должен становиться сетевым действием.</figcaption></figure>\n<h2>Как читать симптом и проверять гипотезу</h2>\n<p>У каждой строки ошибки должна быть проверка, которая отделяет одну гипотезу от другой. Например, отсутствие <code>REVISION</code> ещё не доказывает подмену каталога: файл мог не создаваться или мог быть исключён из <code>artifacts:paths</code>. Лог и YAML нужно читать вместе.</p>\n<table><caption>Диагностическая матрица для остановленного deploy</caption><thead><tr><th>Симптом</th><th>Гипотеза</th><th>Проверка</th><th>Действие</th></tr></thead><tbody><tr><td><code>dist/REVISION</code> отсутствует</td><td>Build не создаёт файл или artifact не содержит путь</td><td>Проверить script build и состав скачанного artifact</td><td>Остановить deploy и исправить контракт output</td></tr><tr><td>Revision отличается от <code>$CI_COMMIT_SHA</code></td><td>Получен старый или чужой каталог</td><td>Сравнить файл из artifact с переменной job</td><td>Не вызывать delivery-script; найти источник и создать нужный pipeline</td></tr><tr><td><code>sha256sum -c</code> завершается с ошибкой</td><td>Файл изменён или manifest создан слишком рано</td><td>Сравнить список файлов и порядок команд</td><td>Собрать новый artifact после завершения output</td></tr><tr><td>Deploy запускает <code>npm run build</code></td><td>Deploy стал вторым владельцем результата</td><td>Найти все команды сборки в YAML</td><td>Удалить повторную сборку и оставить dependency на build</td></tr><tr><td>Verify не прошёл</td><td>Ошибка теста, install или окружения</td><td>Посмотреть exit code и отчёт job</td><td>Исправить причину; не обходить gate повторным deploy</td></tr></tbody></table>\n<p>Если checksum не сходится, сначала нужно проверить собственный manifest: в него должны попасть все файлы после завершения сборки, а сам файл manifest не должен хешироваться до момента его создания. Пока причина не установлена, staging-команда остаётся недостижимой.</p>\n<h2>Gate перед внешним действием</h2>\n<p>Последняя команда job имеет побочный эффект: она отправляет файлы, вызывает API или изменяет staging. До неё pipeline проверяет четыре свойства. Artifact пришёл от ожидаемого job. В нём есть revision. Revision совпадает с commit pipeline. Контрольные суммы и обязательные файлы сходятся.</p>\n<p><code>test -f</code>, сравнение строк и <code>sha256sum -c</code> должны возвращать ненулевой код при ошибке. Нельзя превращать mismatch в предупреждение или добавлять <code>|| true</code>: тогда лог покажет проблему, но shell продолжит движение к сетевой команде.</p>\n<p>Ручное подтверждение deploy отвечает на вопрос «можно ли сейчас запускать этот шаг», но не доказывает происхождение каталога. Оно может стоять после автоматических gate. Контроль artifact, revision и checksum должно пройти до ручного или автоматического delivery.</p>\n<h2>Порядок действий при исправлении</h2>\n<ol><li>Остановить deploy до команды, меняющей внешнюю среду. Записать имя job, revision и стадию сбоя.</li><li>Найти в YAML все вызовы <code>npm ci</code>, <code>npm install</code> и <code>npm run build</code>. Для релизного каталога оставить одного владельца.</li><li>В build-job создать output, добавить <code>REVISION</code> и построить checksum-manifest после появления всех файлов.</li><li>Включить каталог в <code>artifacts:paths</code> и в deploy-job явно указать dependency на build.</li><li>Удалить повторную сборку из deploy. До delivery-script оставить проверки существования, revision, checksum и обязательного файла.</li><li>Проверить отрицательные пути: убрать artifact, изменить revision и испортить файл после создания manifest. В каждом случае job должна завершиться до внешнего действия.</li><li>Провести staging-проверку с согласованными доступами и откатом. Результат staging не переносить на production без новой проверки.</li></ol>\n<h2>Ограничения и критерий готовности</h2>\n<p>Artifact сохраняет уже созданный результат, но не делает сборку воспроизводимой автоматически. Для этого дополнительно нужны зафиксированные зависимости, контролируемый образ runner и явные переменные окружения. Cache следует считать ускорителем, а не источником истины для release output.</p>\n<p>Checksum подтверждает целостность набора файлов после создания manifest. Он не подтверждает права доступа, безопасность секретов, корректность бизнес-логики, совместимость со staging или возможность отката. Smoke-тесты и rollback решают другие задачи.</p>\n<p><code>dependencies</code> подходит для линейной схемы с build-job в предыдущей стадии. При переходе к <code>needs</code>, нескольким build-job, child pipeline или межпроектным artifacts нужно отдельно проверить, откуда deploy получает файлы; эти варианты не следует смешивать в одном примере без проверки версии GitLab.</p>\n<p>Изменение готово, если для одного pipeline можно показать commit, job-источник, список artifact и checksum-manifest. Deploy не выполняет сборку повторно. При отсутствии файла, несовпадении revision или неверной checksum он завершается до delivery-script. При корректном artifact он выполняет только согласованный staging-шаг.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://docs.gitlab.com/ci/jobs/job_artifacts/\" target=\"_blank\" rel=\"noopener\">GitLab Docs: Job artifacts</a> — создание artifacts и получение их последующими job.</li><li><a href=\"https://docs.gitlab.com/ci/yaml/\" target=\"_blank\" rel=\"noopener\">GitLab Docs: CI/CD YAML syntax reference</a> — назначение <code>stages</code>, <code>artifacts</code> и <code>dependencies</code>.</li><li><a href=\"https://docs.gitlab.com/ci/yaml/lint/\" target=\"_blank\" rel=\"noopener\">GitLab Docs: Validate CI/CD configuration</a> — проверка конфигурации до запуска pipeline.</li><li><a href=\"https://docs.npmjs.com/cli/v6/commands/npm-ci/\" target=\"_blank\" rel=\"noopener\">npm Docs: npm ci</a> — чистая установка и требования к lockfile.</li></ul>"
}