DEV Community 👩‍💻👨‍💻

Samoilenko Yuri for RNDSOFT

Posted on • Originally published at blog.rnds.pro on

Чистка build-агентов Gitlab

Оригинал: https://blog.rnds.pro/030-gitlab-janitor

Спешим поделиться с вами нашим инструментом для поддержания чистоты и порядка на наших (RNDSOFT) сборочных серверах gitlab-janitor. О том, как мы к нему пришли, и каков первый опыт - далее по тексту.

С каждым годом инфраструктура RNDSOFT, обеспечивающая процессы CI/CD, растёт. Появляются новые проекты, усложняются процессы (pipelines) сборки и тестирования, растет количество сборщиков (build agents), и всё это приносит дополнительные накладные расходы. Сегодня мы рассмотрим конкретную проблему - чистку/освобождение ресурсов на самих сборочных серверах. Мы используем Gitlab и несколько Gitlab runners с докером (docker executor) под капотом. Вот с проблем и начнём.

Проблемы

Вся терминология будет опираться на Gitlab, но всё это применимо и к другим решениям, опирающимся на docker.

Типовой процесс сборки и доставки состоит из 4х больших этапов (stages), по несколько задач (jobs) в каждом:

  • сборка docker-образа (обычно одного на проект, но бывает и больше).
  • тестирование. Тут мы используем подход, когда тестируем не сам код, а весь контейнер. Конечно, есть юнит-тесты, интеграционные, графические и прочие, но запускаем мы их непосредственно внутри боевого контейнера. А что? У нас ruby - можем себе позволить 💎 :)
  • тэгирование (image promoting). Этап в зависимости от результатов тестов и QA перетегируется во что-то вроде stable или release, или как-то еще, в зависимости от конкретного проекта, команды и принятых процессов.
  • доставка (deploy). Тут всё как обычно - дев/тест стенды, QA-стенды, динамические стенды для MR и всякое разное вроде документации.

    Давно хочу написать отдельно про процесс image promotiong, про то, как мы таскаем образы между стадиями, но руки не доходят. Если кого сильно интересует - пишите в комментариях, и мне придётся найти в себе силы :)

В результате описанных выше процессов (особенно тестирования) на сборщиках остаётся много мусора, именно это является проблемой. Далее по порядку.

Подвисшие контейнеры (dangling containers)

Тут речь, конечно, идёт не о повисании ПО внутри контейнера, а о самих "ненужных" контейнерах. Например, для графических тестов поднимается целый стек: Postgres, Chrome, Selenium, само приложение, контейнер, из которого запускаются тесты, Redis и бог еще знает что. Если весь процесс (pipeline) или непосредственно задача (job) будут прерваны, то никто не погасит за нас эти контейнеры, и они продолжат висеть и потреблять ресурсы. Проблема!

Ненужные образы

В процессе сборки имя образа претерпевает примерно следующие изменения: image:$SHA -> image:stable -> image:release. Конечно, это примерно, шагов может больше или именования другие, но сути это не меняет. При последовательных комитах сратые старые образы остаются лежать мёртвым грузом.

<p id="LCgm">В день мы генерим примерно 75GB образов. ~25GB на каждом из трех сборщиков. И это летом, когда многие в отпуске :)</p>
Enter fullscreen mode Exit fullscreen mode

Ситуация усугубляется тем, что один и тот же образ находится (с максимальной вероятностью) сразу на всех сборщиках, поскольку разные типы тестов запускаются параллельно и попадают на все сборщики и занимают x3 места. Проблема! Дважды проблема, поскольку с образами всё не так просто и очень интересно - об этом ниже.

Безымянные и кеширующие тома (unnamed and cache volumes)

Многие контейнеры при создании аллоцируют в докере временные анонимные тома/диски (мы же всегда про докер говорим, верно?) и оставляют их после себя. Также сам Gitlab, если вы не используете распределённые кеш (shared cache) на базе S3, всё равно создаёт кеш-тома, если ему явно не запретить это делать через disable_cache. Проблема!


Конечно, эти проблемы не простой "мусор" - старые образы могут ускорить последующую сборку, кеш-тома также теоретически могут ускорять сборку, так что они не совсем бесполезны. Однако, проблемы ускорения и кеширования мы решаем немного иначе - с помощью определённых схем именования образов, используя buildkit, который умеет подтягивать кешированные слои прямо из реестра образов (в нашем случае harbor) и пр.

Что же делать?

Проблемы появляются не сразу. Когда-то у нас был один сборщик, там вообще не было проблем с перетягиванием образов между этапами, а образы чистились через docker rmi на последнем шаге процесса. Когда сборщиков стало больше, и на них докинули ресурсы, пришлось немного усложнить схему - сначала были bash-скрипты, которые по расписанию удаляли образы по шаблону имени. Затем периодически стали использовать docker system prune. Но эти решения очень негибкие, плохо поддерживаются и масштабируются и обладают фундаментальной проблемой - частыми кеш-промахами (cache miss), что периодически сильно тормозило процессы (pipelines). И вот однажды терпеть это стало невозможно 😜 :)))

Нам нужно хорошее решение, желательно с баристой и массажисткой! Встречаем - gitlab-janitor!

Gitlab-janitor

Основными задачами были:

  • консолидация всех чисток сборщика в одном месте;
  • уменьшение кеш-промахов, насколько это возможно;
  • простота работы.

Для чистки места на сборщиках уже есть проект gitlab-runner-docker-cleanup, он и явился идейным вдохновителем нашей утилиты, но, к сожалению, не выполнял всех необходимых нам функций.

Написав своё решение, мы решили опубликовать его в открытом виде - и зеркало на github, и образы на docker-hub, и даже документация с примерами. Ну и не мог я удержаться от разнообразных бейджиков :)

Проще всего запускать его в докере на каждом сборщике (для этого мы используем nomad 🥂 ):

docker run --rm \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /persistent/janitor:/store \
  -e REMOVE=true \
  -e INCLUDE="*integr*, *units*" \
  -e EXCLUDE="*gitlab*" \
  -e CONTAINER_DEADLINE="1h10m" \
  -e VOLUME_DEADLINE="4d" \
  -e IMAGE_DEADLINE="4d" \
  -e CACHE_SIZE="10G" \
  -e IMAGE_STORE="/store/images.txt" \
  rnds/gitlab-janitor:latest
Enter fullscreen mode Exit fullscreen mode

Заметили IMAGE_STORE? С остальными параметрами всё просто - шаблоны для имён образов, томов, сроки хранения, а вот для образов всё гораздо интереснее!

Докер не сохраняет в своём локальном хранилище никакой информации о времени загрузки образа, а только обычную дату создания образа. В результате нет возможности определить, какой образ очень нужен и часто используется (хоть и создан давно), а какой свеженький, но больше никогда и никому не будет нужен.

Для решения этой проблемы в gitlab-janitor сделан простенький механизм - когда образ встречается первый раз, он сохраняется в файл с временной меткой - это считается датой появления. Когда подходит срок - образ удаляется. Однако, если gitlab-janitor увидит созданный контейнер из этого образа - то временная метка в этом файле сбрасывается. Именно для этого и используется параметр IMAGE_STORE. Данный файл можно монтировать с хост-машины, а можно и нет - в этом случае история образов будет теряться при пересоздании/обновлении контейнера-чистильщика:

selenium/standalone-chrome:latest sha256:c01aea5eb0bf279df5f745e3c277e30d7d9c81f15b9d1d4e829f1075c31ed5b1 1660205645
postgres:10-alpine sha256:d7023df56cb7cbe961f0c402888f7170397c98c099fb76fbe16b5442a236ad51 1660205040
redis:latest sha256:3edbb69f9a493835e66a0f0138bed01075d8f4c2697baedd29111d667e1992b4 1660205645
Enter fullscreen mode Exit fullscreen mode

С остальными параметрами, документацией и примерами можно ознакомиться на гитхабе.

Результаты

Вот и подъехали результаты объективного контроля за работой gitlab-janitor на трех наших сборщиках:

Все чистки с помощью cron+bash, docker prune и пр. выключены. Параметры, оптимально подходящие под наши процессы, мы еще подбираем (и будем подбирать), но уже видно, что процесс вошел в стабильную фазу, и наш чистильщик выполняет свою работу!

Top comments (0)

Make Your Github Profile Stand Out

Github is great, but have you considered how to make yours more attractive for potential employers or other visitors? Even non-tech ones like recruiters!

Take a couple of hours and show your best side as a person - and a programmer.