← CI/CD: паттерны
11 мин · средне · Урок 4 из 5

Кеш и артефакты в пайплайне

Чем кеш отличается от артефакта, как кешировать зависимости и слои образа и как передавать результат между job.

Две похожие, но разные вещи

В пайплайне есть два механизма сохранить файлы между запусками: кеш и артефакт. Их легко спутать, а нужны они для разного.

Простое правило: кеш для скорости, артефакт для передачи результата.

Кеш зависимостей

Каждый job стартует в чистом окружении и заново тянет зависимости. На большом проекте это минуты. Кеш сохраняет папку зависимостей и переиспользует её:

test:
  cache:
    key:
      files:
        - package-lock.json      # кеш обновится только при смене зависимостей
    paths:
      - .npm/
  script:
    - npm ci --cache .npm --prefer-offline
    - npm test

Обрати внимание, что кешируется .npm/, а не node_modules/. Кешировать node_modules вместе с npm ci бессмысленно: npm ci первым делом удаляет каталог целиком и ставит всё заново из lock-файла. Кеш восстановится, тут же будет снесён, и в конце job GitLab запакует его обратно. Только потеря времени.

Экономия живёт на уровне npm-store. Флаг --cache .npm кладёт скачанные тарболлы в каталог проекта, --prefer-offline заставляет npm брать их оттуда вместо сети. Установка всё равно происходит, но без скачивания.

Ключ кеша тоже важен. key: $CI_COMMIT_REF_SLUG даёт свой кеш на каждую ветку, но обновляется он на каждом запуске. Привязка к хешу lock-файла точнее: кеш меняется ровно тогда, когда меняются зависимости.

Кеш слоёв образа

Docker-сборка тоже кешируется по слоям из прошлого урока про образы. Но в CI каждый job стартует на чистом раннере, где локального кеша слоёв нет вообще.

Классический совет docker build --cache-from registry/app:latest здесь работает плохо: образа нет в локальном хранилище, а сам по себе --cache-from его не скачивает. Формально команда отрабатывает, по факту процент попаданий нулевой.

Рабочий вариант — buildx с кешем, который живёт в registry:

docker buildx build \
  --cache-from type=registry,ref=$CI_REGISTRY_IMAGE:cache \
  --cache-to   type=registry,ref=$CI_REGISTRY_IMAGE:cache,mode=max \
  -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA --push .

--cache-from тянет кеш из реестра сам, --cache-to заливает обновлённый обратно. mode=max сохраняет слои всех стадий, включая промежуточные из multi-stage — без него в кеш попадёт только финальный образ, и вся тяжёлая стадия сборки будет пересчитываться каждый раз.

Артефакты между job

Job собрал бинарь, а деплой-job должен его взять. Чистое окружение не делится файлами само, поэтому результат объявляют артефактом:

build:
  stage: build
  script:
    - go build -o app
  artifacts:
    paths:
      - app
    expire_in: 1 week

deploy:
  stage: deploy
  script:
    - ./app --version       # артефакт из build уже здесь

GitLab сохранит app после build и распакует в deploy. expire_in ограничивает срок хранения, чтобы артефакты не копились вечно.

Отдельный частый случай — артефакты-отчёты: junit-результаты тестов, coverage. Их собирают, чтобы показать в UI пайплайна, даже если дальше они не нужны.

Что важно запомнить

Кеш ускоряет сборку и переживает потерю без поломки; артефакт передаёт результат между job, и его потеря ломает следующий шаг.

Кеш зависимостей привязывают к lock-файлу, чтобы он обновлялся только при смене зависимостей. Кешируют npm-store (.npm/), а не node_modules/: npm ci всё равно сносит каталог перед установкой.

Слои образа в CI кешируют через docker buildx с --cache-from/--cache-to type=registry и mode=max. Голый docker build --cache-from на чистом раннере не даёт попаданий.

Артефакты объявляют через artifacts: paths, ограничивают expire_in и используют ещё и для отчётов тестов в UI.

В следующем уроке разберём безопасность в пайплайне: проверки на уязвимости и секреты.

Прошли материал до конца? Отметьте урок пройденным.