Конвейер доставки
От коммита до развёрнутого образа. Схема соответствует проекту 4.
Порядок этапов
коммит
│
▼
┌─────────────┐ секунды падает часто
│ линтер │─────────────────────────────► падение здесь дешевле всего
└─────┬───────┘
▼
┌─────────────┐ минуты падает часто
│ тесты │
└─────┬───────┘
▼
┌─────────────┐ минуты падает редко
│ сборка │◄──── кэш из registry
└─────┬───────┘
▼
┌─────────────────┐ секунды
│ проверка образа │ ← проверяется ОБРАЗ, не исходники
└─────┬───────────┘
▼
┌─────────────┐ минуты
│сканирование │
└─────┬───────┘
▼
┌─────────────┐ секунды
│ SBOM │
└─────┬───────┘
▼
┌──────────────┐
│ все = 0 ? │── нет ──► НЕ публиковать
└──────┬───────┘
│ да
▼
публикация
Как читать
Порядок не назначен, а выведен: сортировка по отношению «время выполнения / вероятность отказа» ставит вперёд то, что падает часто и стоит секунды.
Сканирование — исключение из этого правила. Оно стоит после сборки не по времени, а по смыслу: проверяется образ, а между исходниками и образом лежит Dockerfile.
Три состояния этапа
┌─────────────────────────────────────────────────┐
│ 0 проверено, чисто ──► публиковать │
│ 1 проверено, есть находки ──► исправлять │
│ 3 НЕ ПРОВЕРЕНО ──► разобраться, │
│ почему нет │
│ инструмента │
└─────────────────────────────────────────────────┘
Без третьего кода:
сканер не установился
│
▼
этап вернул 0
│
▼
конвейер зелёный
│
▼
зелёный цвет означает «мы перестали проверять»,
и узнать об этом неоткуда
Сводка обязана различать три исхода, а публикация — требовать кода 0, а не «не провалено».
Где живёт логика
.github/workflows/pipeline.yaml ← только вызовы и окружение
│
│ run: ci/lint.sh
│ run: ci/test.sh
│ run: ci/build.sh
▼
ci/lib.sh коды возврата, теги, журналирование
ci/lint.sh быстрые проверки
ci/test.sh тесты
ci/build.sh сборка с кэшем
ci/verify-image.sh проверки собранного образа
ci/scan.sh сканирование
ci/sbom.sh состав
ci/pipeline.sh порядок и сводка
ci/deploy.sh развёртывание по digest
ci/rollback.sh откат
Три следствия, каждое проверяемо:
| Следствие | Проверка |
|---|---|
| Отлаживается локально | ci/pipeline.sh работает без CI |
| Перенос на другую платформу механический | Шаги YAML — только run: ci/*.sh |
| Те же скрипты проверяют образ в тестах | ci/verify-image.sh вызывается оттуда |
Теги и развёртывание
коммит a1b2c3d4e5f6
│
▼
тег: ga1b2c3d4e5f6 ← неизменяемый
│ (+ «-dirty», если дерево не чисто)
▼
публикация ──► registry
│
▼
digest: sha256:9f8e7d6c… ← вычислен из содержимого
│
▼
развёртывание ПО DIGEST ← не по тегу
Почему не по тегу:
собрали ──── время ────► запустили
│ │
│ за это время тег │
│ мог указать на другое │
▼ ▼
образ A образ B
Три места расхождения: pull без пересоздания container'а; тег перезаписан между pull и запуском; опубликовано не то, что собрано.
Откат
artifacts/deployed-production.txt ← что развёрнуто сейчас
artifacts/previous-production.txt ← на что откатиться
│
▼
ci/rollback.sh production
«Развернуть предыдущую версию» — не план отката. Планом он становится, когда предыдущий digest записан и команда выполнима без раздумий.
Два разных кэша
кэш СЛОЁВ cache mount
────────── ───────────
--cache-to type=registry RUN --mount=type=cache
переносится между запусками живёт на машине сборки
нужен mode=max НЕ переносится в CI
Их путают чаще всего. mode=min — значение по умолчанию — экспортирует только последнюю стадию, и слой с зависимостями в кэш не попадает.
Что проверить в своём конвейере
| Проверка | Как |
|---|---|
| Падает быстро на частой ошибке | Сломать линтер, замерить время |
| Отсутствие сканера даёт 3 | Убрать сканер из PATH |
| Провал перевешивает непроверенное | Подать оба состояния в сводку |
| Публикация требует кода 0 | Условие в шаге публикации |
| Тег меняется при грязном дереве | Создать неотслеживаемый файл |
| Развёртывание по тегу отвергается | Передать тег вместо digest |
| Откат без записи отказывает | Удалить файл с предыдущим digest |
Подробнее
Урок 16.1. Проектирование конвейера
Урок 16.3. Кэш в CI
Урок 16.4. Сканирование и SBOM
Проект 4. Production pipeline