Регистрация раннера на практике

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

Регистрация раннера — процедура, при которой агент получает токен проекта/группы и начинает опрашивать сервер на наличие джоб.

Установка

Раннер — отдельный бинарник. На Linux его ставят пакетом, а проще всего — запустить как Docker-контейнер:

docker run -d --name gitlab-runner --restart always \
  -v /srv/gitlab-runner/config:/etc/gitlab-runner \
  -v /var/run/docker.sock:/var/run/docker.sock \
  gitlab/gitlab-runner:latest

Том с config хранит настройки между перезапусками, а проброс docker.sock позволит раннеру запускать контейнеры джоб на хосте.

Стоит остановиться на этих двух томах — за ними прячется половина типичных проблем. Первый, /etc/gitlab-runner, — это «память» раннера: туда после регистрации ляжет config.toml с токеном и параметрами. Если не пробросить этот каталог наружу контейнера, при первом же docker rm вся регистрация испарится, и раннер придётся заводить заново. Второй том, docker.sock, отдаёт раннеру управление демоном Docker хоста: благодаря ему docker-executor может поднимать контейнеры джоб «по соседству». Это удобно, но имеет цену: раннер с доступом к сокету фактически получает права root на хосте, поэтому такой раннер нельзя выставлять на ненадёжные публичные проекты — чужой .gitlab-ci.yml сможет через него хозяйничать на машине.

Флаг --restart always здесь не косметика: раннер должен переживать перезагрузку сервера, иначе после ночного апдейта ОС команда обнаружит, что пайплайны молча перестали запускаться. А -d запускает контейнер в фоне — раннер живёт как демон, тихо опрашивая сервер.

Регистрация

В современных версиях GitLab сначала создают раннер в интерфейсе (Settings → CI/CD → Runners) и получают токен аутентификации, затем регистрируют агента этим токеном:

gitlab-runner register \
  --url https://gitlab.com \
  --token glrt-XXXXXXXXXXXXXXXXXXXX \
  --executor docker \
  --docker-image alpine:3.19 \
  --description "my-docker-runner"

Здесь мы выбрали executor docker и образ по умолчанию alpine:3.19 — он используется, если джоба не указала свой image. После регистрации раннер появится в списке проекта со статусом онлайн.

Параметр --url указывает, к какому инстансу GitLab подключаться: для облака это https://gitlab.com, для корпоративной установки — её внутренний адрес. Токен glrt-… — это не пароль пользователя и не «токен регистрации» из старых версий, а authentication token, заранее созданный в интерфейсе под конкретный раннер. Такой токен уже несёт в себе область видимости (проект, группа или весь инстанс) и список тегов — поэтому современная регистрация короче и безопаснее: вы не передаёте секрет, дающий право плодить новые раннеры, а лишь подключаете один заранее описанный.

Почему по умолчанию выбирают docker, а не shell

При регистрации executor можно было бы поставить shell — тогда команды джобы выполнялись бы прямо в оболочке хоста, без всяких контейнеров. Соблазнительно простой вариант, но коварный: всё, что джоба наставила в систему (глобальные пакеты, временные файлы, переменные окружения), остаётся и влияет на следующие джобы. Рано или поздно это даёт классическое «у меня собиралось, а на CI падает» — потому что у вас на машине что-то было доустановлено руками, а в чистом окружении этого нет. Docker-executor лечит проблему радикально: каждая джоба стартует в свежем контейнере из заявленного образа, состояние не накапливается, и пайплайн воспроизводим хоть через год. Именно поэтому в подавляющем большинстве промышленных установок выбирают docker, а shell оставляют для редких задач вроде управления самим хостом.

Образ по умолчанию тоже выбран не случайно. alpine — крошечный дистрибутив в несколько мегабайт, он быстро скачивается и поднимается, поэтому удобен как запасной для джоб, не указавших свой image. Но помните: в alpine нет привычных bash и многих утилит, так что для реальной сборки джоба почти всегда задаёт собственный, более богатый образ, а дефолтный служит лишь подстраховкой.

Теги: направляем джобы на раннер

У раннера могут быть теги — метки вроде docker, gpu, production. Джоба с ключом tags попадёт только на раннер с такими же тегами. Это способ маршрутизации: тяжёлую сборку — на мощный раннер, деплой — на раннер с доступом в прод-сеть.

build-heavy:
  tags:
    - gpu
  script:
    - echo "Сборка на мощном раннере"

Если у раннера снят флажок «запускать джобы без тегов», то джобы без tags на него не попадут вовсе. Это частая причина зависших в pending пайплайнов.

Полезно держать в голове наглядную картину того, как сервер раздаёт джобы по тегам. Сервер сопоставляет требования джобы со «способностями» каждого онлайн-раннера и отдаёт работу только совпавшему:

джоба tags:[gpu]   ┌───────────────┐
      ───────────►│   GitLab      │
                  │  очередь джоб │
                  └──────┬────────┘
                         │ подбор по тегам
        ┌────────────────┼────────────────┐
        ▼                ▼                ▼
  runner[docker]   runner[gpu]      runner[deploy]
     не подходит    ВЫПОЛНЯЕТ        не подходит

Теги стоит проектировать как «возможности» раннера, а не как его имя. Хорошие теги — gpu, linux, arm64, prod-network: они описывают, что раннер умеет, и джоба явно заявляет, что ей нужно. Плохие теги — вроде runner-7 или ivan-laptop: они привязывают пайплайн к конкретной железке, и при её замене придётся править весь .gitlab-ci.yml. Когда тегов несколько, джоба попадёт только на раннер, у которого есть все перечисленные теги — это логическое И, а не ИЛИ, и об этом часто забывают, потом удивляясь зависшему пайплайну.

Как работает под капотом

После регистрации раннер пишет свой токен и настройки в config.toml. Дальше он в цикле шлёт серверу запрос «дай джобу для моих тегов». Сервер сопоставляет ожидающие джобы с тегами и возможностями раннеров и отдаёт подходящую. Раннер скачивает образ, поднимает контейнер и выполняет джобу, периодически отправляя логи обратно — поэтому вывод в интерфейсе появляется в реальном времени.

Если сравнить с GitHub Actions, регистрация там устроена похоже, но с другими акцентами. Self-hosted раннер в GitHub тоже скачивают, распаковывают и привязывают токеном к репозиторию или организации, однако маршрутизация делается через labels и поле runs-on, а сам агент по умолчанию выполняет шаги прямо на хосте, без обязательного контейнера. В GitLab же связка «executor + config.toml + теги» делает поведение раннера более явным и настраиваемым: вы прямо в конфиге задаёте, как изолировать джобу, какой образ брать по умолчанию и сколько джоб крутить параллельно. Эта явность — общая черта GitLab CI: меньше «магии по умолчанию», больше строк, которые вы контролируете сами.

Частые ошибки

  • Джоба висит в pending: либо нет онлайн-раннера с нужными тегами, либо раннер не принимает джобы без тегов.
  • Забыть проброс docker.sock или настроить privileged-режим — тогда docker-команды внутри джоб не работают (об этом подробнее в разделе про Docker-in-Docker).
  • Регистрировать раннер устаревшей командой с --registration-token: в новых версиях используется токен аутентификации, созданный в UI.

Итоги

  • Раннер ставится бинарником или контейнером, настройки живут в config.toml.
  • Регистрация связывает агента с проектом через токен и задаёт executor и образ по умолчанию.
  • Теги маршрутизируют джобы на конкретные раннеры; несоответствие тегов — частая причина зависших джоб.
Проверьте себя
1. Зачем нужны теги раннера?
AДля подсветки синтаксиса YAML
BЧтобы направлять джобы на конкретные раннеры по совпадению меток
CДля шифрования секретов
DЧтобы ускорить git clone
2. Джоба зависла в статусе pending. Какая причина наиболее вероятна?
AОшибка в SQL-запросе
BНет онлайн-раннера с нужными тегами или раннер не принимает джобы без тегов
CСлишком короткий script
DНе указан correct_index