Чистка build-агентов Gitlab
Tags: infra, gitlab, cleaner, devops
Published: 2022-08-26 08:48:27

---
title: Чистка build-агентов Gitlab
published: true
date: 2022-08-11 09:52:28 UTC
tags: infra, gitlab, cleaner, devops
canonical_url: https://blog.rnds.pro/030-gitlab-janitor?utm_source=teletype&utm_medium=feed_rss&utm_campaign=rnds
---

![](https://img4.teletype.in/files/37/38/37383f40-cc8b-4523-b616-a1705311242e.jpeg)

_Оригинал: [https://blog.rnds.pro/030-gitlab-janitor](https://blog.rnds.pro/030-gitlab-janitor?utm_source=devto&utm_medium=feed_rss&utm_campaign=rnds)_

Спешим поделиться с вами нашим инструментом для поддержания чистоты и порядка на наших (RNDSOFT) сборочных серверах [gitlab-janitor](https://github.com/RND-SOFT/gitlab-janitor). О том, как мы к нему пришли, и каков первый опыт - далее по тексту.

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

## Проблемы

Вся терминология будет опираться на [Gitlab](https://docs.gitlab.com/), но всё это применимо и к другим решениям, опирающимся на docker.

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

- сборка docker-образа (обычно одного на проект, но бывает и больше).
- тестирование. Тут мы используем подход, когда тестируем не сам код, а весь контейнер. Конечно, есть юнит-тесты, интеграционные, графические и прочие, но запускаем мы их непосредственно внутри боевого контейнера. А что? У нас ruby - можем себе позволить 💎 :)
- тэгирование (image promoting). Этап в зависимости от результатов тестов и QA перетегируется во что-то вроде `stable` или `release`, или как-то еще, в зависимости от конкретного проекта, команды и принятых процессов.
- доставка (deploy). Тут всё как обычно - дев/тест стенды, QA-стенды, динамические стенды для [MR](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) и всякое разное вроде документации.
<section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="kswC">Давно хочу написать отдельно про процесс image promotiong, про то, как мы таскаем образы между стадиями, но руки не доходят. Если кого сильно интересует - пишите в комментариях, и мне придётся найти в себе силы :)</p>
  </section>

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

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

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

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

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

<section style="background-color:hsl(hsl(323, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="LCgm">В день мы генерим примерно 75GB образов. ~25GB на каждом из трех сборщиков. И это летом, когда многие в отпуске :)</p>
  </section>

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

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

Многие контейнеры при создании аллоцируют в докере временные анонимные тома/диски (мы же всегда про докер говорим, верно?) и оставляют их после себя. Также сам Gitlab, если вы не используете распределённые кеш (shared cache) [на базе S3](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runnerscaches3-section), всё равно создаёт кеш-тома, [если ему явно не запретить это делать](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runnersdocker-section) через `disable_cache`. Проблема!

* * *

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

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

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

Нам нужно хорошее решение, желательно с баристой и массажисткой! Встречаем - [gitlab-janitor](https://github.com/RND-SOFT/gitlab-janitor)!

## Gitlab-janitor

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

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

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

Написав своё решение, мы решили опубликовать его в открытом виде - и [зеркало на github](https://github.com/RND-SOFT/gitlab-janitor), и [образы на docker-hub](https://hub.docker.com/r/rnds/gitlab-janitor), и даже документация с примерами. Ну и не мог я удержаться от разнообразных бейджиков :)

![](https://img3.teletype.in/files/62/47/6247a285-8416-4c43-be16-c2317598a279.png)

Проще всего запускать его в докере на каждом сборщике (для этого мы используем [nomad](https://www.nomadproject.io/) 🥂 ):

```
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
```

Заметили `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
```

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

## Результаты

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

![](https://img2.teletype.in/files/55/ae/55aecb6b-d59b-4bdd-b6e1-b079fcfb4805.png)

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