Используем кэш в GitLab CI

GitLab CI job

Всем привет!
Сегодня я решил написать короткую заметку о своем опыте использования кэша в GitLab CI.

Зачем это нужно

Есть у меня небольшой pet-project на котором я обычно обкатываю различные нововведения. Репозиторий проекта хранится в GitLab и там же в CI настроены две задачи - на запуск тестов и на деплой.
Задача с тестами проходила довольно быстро – примерно за 2 минуты. Но каждый раз я задумывался о том что GitLab-runner каждый раз выполняет одно и то же действие с установкой Python-зависимостей.

Да, с одной стороны это гарантирует воспроизводимость сборки и бережет от возможных проблем с отсутствием нужного пакета в каталоге пакетов в самый ответственный момент (привет leftpad и mimemagick 😄).

Но с другой стороны у меня это всего лишь pet-project для которого все это некритично. Поэтому я решил прикрутить кэш для зависимостей.

Вот официальная документация с примерами кэширования зависимостей - https://docs.gitlab.com/ee/ci/caching/

Проект на котором я все тестировал написан на Django. Для управления зависимостей я использую poetry и через него же активирую virtualenv.

Как выглядел .gitlab-ci.yml до внесения изменений

1
2
3
4
5
6
7
8
9
10
11
12
13
stages:
- tests
- deploy

tests:
stage: tests
image: python:3.7-slim
script:
- apt-get update -qy && apt-get install -y build-essential
- pip --no-cache-dir install poetry
- poetry config virtualenvs.create false && poetry install --no-root
- sed 's/#DATABASE_URL/DATABASE_URL/g' telega/.env.example > telega/.env
- coverage run --source='.' manage.py test && coverage report -m

Здесь у меня устанавливаются пакеты в Debian, затем из pip устанавливается poetry и потом poetry доустанавливает необходимые пакеты для проекта.

Как стал выглядеть .gitlab-ci.yml после изменений

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
stages:
- tests
- deploy

variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"

cache:
key:
files:
- poetry.lock
- .gitlab-ci.yml
prefix: ${CI_JOB_NAME}
paths:
- .venv
- .cache/pip

tests:
stage: tests
image: python:3.7-slim
script:
- apt-get update -qy && apt-get install -y build-essential
- pip install poetry
- poetry config virtualenvs.in-project true
- poetry install --no-root
- sed 's/#DATABASE_URL/DATABASE_URL/g' telega/.env.example > telega/.env
- poetry run coverage run manage.py test && poetry run coverage report -m

Я добавил настройки указывающие для pip и poetry директории в которых нужно хранить установленные пакеты. Затем добавил эти директории в директиву cache и в качестве ключа указал два файла - poetry.lock и .gitlab-ci.yml
Это означает что в случае если эти два файла не изменялись то установленные зависимости берутся из кэша, в обратном случае мы все устанавливаем заново и кэшируем.

Что изменилось

Время работы задачи с тестами снизилось с двух минут до одной. Распаковка закэшированных директорий также занимает время, но это происходит намного быстрее чем установка пакетов из PyPi. Если бы у меня было побольше зависимостей на проекте то ускорение было бы еще более ощутимым.

На этом скриншоте можно увидеть как устанавливается poetry и все зависимости достаются из кэша.

GitLab CI job

А тут видно что poetry install рапортует о том что никаких новых зависимостей устанавливать не нужно.

GitLab CI job

Выводы

Кэширование на CI это мощная штука позволяющая экономить ресурсы и время.

Также для серьезного продакшна можно настроить более сложный конфиг и на каких-то ветках не использовать кэширование. В этом случае мы получаем и производительность и надежность.

Есть еще один вариант - это собирать Docker-контейнеры со всеми зависимостями, класть их в registry и потом использовать в CI. Я думаю на каком-то проекте стоит попробовать и его)