Как я настраиваю раннеров для GitLab CI

Настроил несколько раннеров под свои проекты и понял: вопрос не «shell или docker», а «где этот раннер физически живёт и что ему позволено трогать».

На каком хосте размещать

Мой принцип сейчас такой: раннер для деплоя живёт на том же хосте, куда деплоит. Это позволяет использовать DooD — job-контейнер управляет хостовым докером через смонтированный /var/run/docker.sock, SSH не нужен вообще.

Раннеры для линта и тестов можно хоть на другом континенте — им хост не нужен, им нужны только CPU и сеть.

Какой экзекьютор выбирать

Docker executor — 90% случаев

Job выполняется в чистом контейнере из образа image: node:22 и т.п. Состояние между job'ами не течёт, чисто и предсказуемо.

[[runners]]
  name = "shared-check"
  executor = "docker"
  [runners.docker]
    image = "alpine"
    volumes = ["/cache"]

Docker executor с DooD — для деплоя

Добавляем сокет и, для группового раннера, каталог релизов:

[[runners]]
  name = "deployer"
  executor = "docker"
  [runners.docker]
    image = "docker:28"
    user = "runner"                    # uid:gid — чтобы job не был root на сокете
    volumes = [
      "/var/run/docker.sock:/var/run/docker.sock",
      "/opt/apps:/mnt/deploy:ro",      # /opt/apps — общий каталог релизов всех проектов
      "/cache"
    ]

Что потребуется дополнительно:

Shell executor — только для особых случаев

Job выполняется прямо на хосте, без изоляции. Я его почти не использую: нечисто, job'ы видят всё. Исключение — когда надо собирать что-то, что упорно не лезет в контейнер.

Как я ограничиваю раздувание кеша

Ранеры жрут диск тремя способами: кеш GitLab (/cache или S3), кеш слоёв сборки buildkit, сами образы. Моя система защиты:

  1. Кеш GitLab — в S3 или с лимитом. Локальный /cache на диске хоста я ограничиваю таймером:
    # /etc/systemd/system/gitlab-cache-cleanup.service
    ExecStart=/bin/sh -c "find /cache -maxdepth 3 -type d -mtime +7 -exec rm -rf {} +"
    
    и таймер раз в сутки (OnCalendar=daily, Persistent=true).
  2. Кеш сборки — в after_script build-джоб: docker builder prune -af --filter until=72h.
  3. Образыdocker image prune -f там же + раз в неделю docker system prune -af руками по ssh, когда смотрю на алерты.
  4. Алерт по диску на 80% — без него любые таймеры рано или поздно проигрывают.

Ещё одна мелочь, которая сэкономила мне нервы: теги раннеров называю по смыслу (check, test, build, deploy), а в пайплайне привязываю job'ы к тегам — тогда deploy случайно не уедет на раннер без сокета, а линт не будет ждать освобождения деплойной машины.