gtosss2 часа назад
Настройка self-hosted gitlab runner (CI/CD)
Уровень сложностиПростойВремя на прочтение8 минОхват и читатели3.1KDevOps*Системное администрирование*Linux*Git*GitHub*ТуториалНастройка self-hosted gitlab runner
Вторая часть материала о CI/CD в котором мы рассмотрим настройку self hosted gitlab-runner через docker.
О серии статейВсе найденные мной русскоязычные гайды не дают базового понимания того, как это работает, по большому счету это просто инструкции по настройке, причем под какой-то конкретный продукт и кейс: .net, Java, Node JS, etc.
Целью серии статей является детальное и схематичное описание того, как все это устроено. Главная задача — вооружить читателя фундаментальным пониманием, что конкретно ему требуется сделать в его конкретном случае. Помимо самой инструкции по настройке, это будет так же справочник для погружения в DevOps, охватывающий:
• Максимально много функционала, которыми обладает Gitlab (общие абстракции актуальны и для его аналогов).
• Инструменты которые нужны: Bash, Docker, Kubernetes и другие.
• Общую теорию и практику с конкретными сценариями.Материал разбит на 4 части.
• Настройка GitLab CI/CD: понимаем принципы работы и запускаем первый pipeline
• [Вы здесь → ] Настройка self-hosted gitlab runner (CI/CD)
• [in progress 70%] Работа с registry (CI/CD)
• [todo] Горизонтальное масштабирование CI/CD (высоко-нагруженный продакшн)
Оглавление
• Настройка собственного gitlab runner
• Запуск и проверка self hosted раннераЕсли мы запускаем pipeline на gitlab.com (SaaS-инстанс), используются так называемые shared runners (общие для всех пользователей GitLab). Но не всегда хочется делить раннеры с остальными — иногда нужен свой. Для этого случая и написана статья.
Когда вам нужен свой gitlab runner?• Полный контроль и независимость — ваше железо, ваш раннер.
• Доступ к внутренним ресурсам — развёртывание в приватную сеть, обращение к базам данных или API, недоступным извне
• Контроль над железом — нужны GPU, много RAM или определённая архитектура (ARM, RISC-V)
• Безопасность и комплаенс — код/артефакты не должны покидать инфраструктуру компании
• Стоимость — большие объёмы CI/CD дешевле на своих мощностях, чем на SaaS-минутах GitLab
• Производительность — устранение сетевых задержек, кеширование на быстрых локальных дисках
Настройка собственного gitlab runner
Есть множество способов установки gitlab runner, мы выберем вариант через docker container.
Подключаемся к серверу и создаем директорию inf-config — это будет рабочая директория в из которой мы будем запускать инфраструктурные сервисы:
mkdir inf-configСоздадим docker-compose файл с конфигурацией запуска нашего gitlab реннера:
touch docker-compose.ymlСодержимое:
services:
gitlab-runner:
image: gitlab/gitlab-runner:alpine3.21-v18.11.0
container_name: gitlab-runner
restart: always
volumes:
- /srv/gitlab-runner/config:/etc/gitlab-runner
- /var/run/docker.sock:/var/run/docker.sockЧто бы этот контейнер запустился, от имени администратора создаем директорию:
sudo mkdir -p /srv/gitlab-runner/configВ нем так же потребуется создать конфиг нашего ранера:
sudo touch config.tomlВ gitlab нужно создать раннер для нашего репозитория, в нем мы возьмем токен.
Создаем Gitlab runner для проектаНастраиваем Gitlab runnerСодержимое:
concurrent = 1 check_interval = 0
[[runners]]
name = "laptop-dc"
url = "https://gitlab.com/"
token = "ВАШ_РАННЕР_ТОКЕН_ИЗ_НАСТРОЕК_GITLAB"
executor = "docker"
[runners.docker]
image = "alpine:3.22.4"
privileged = false
disable_entrypoint_overwrite = false
disable_cache = false
volumes = ["/cache"]Детальный разбор конфигурацииДокументация по конфигурации gitlab runner в toml подробно объясняет все значения конфигурации, я кратко опишу только то, что использовал в нашем случае.
check_interval = 0 — Интервал времени в секундах для проверки раннером новых jobs. Иными словами как часто runner будет стучаться на gitlab с вопросом “есть ли для меня работа?”. Так как у нас 0, будет использовать значение по умолчанию (3 секунды).
concurrent = 1 — Лимит на кол-во параллельных jobs. Мы можем установить его внутри секции отдельного раннера что бы применить лимит на конкретный раннер. Так как мы указали значения в корне конфигурации, а не для отдельного раннера — мы установили глобальное ограничение для всех раннеров, на случай если их в будущем будет несколько, пока что у нас только 1.
url = "https://gitlab.com/" — Тут нужен URL Gitlab, если у вас self-hosted Gitlab, то нужен ваш адрес. Мы же используем Gitlab.com (SaaS-инстанс) по этому просто глобальный https://gitlab.com к которому наш раннер будет подключаться.
token = ВАШ_РАННЕР_ТОКЕН_ИЗ_НАСТРОЕК_GITLAB — сюда мы скопировали токен из настроек Gitlab (на последнем скриншоте действие 5).
executor = docker — Мы уже разобрали тему экзекьютеров выше, наш выбор Docker.
image = "alpine:3.22.4" — Образ по умолчанию который будет использован для всех джобов. Используем alpine — самая легковесная linux версия. На всякий случай конкретная версия, а не latest — так меньше вероятность что в какой-то момент что-то перестанет работать, в ранее работающих конфигурациях.
privileged = false — Это привилегированный режим запуска контейнеров. В обычном докере контейнер в привилегированном режиме запускается так: docker run --privileged alpine:3.22.4, в docker-compose.yml у сервиса ставиться флаг privileged: true. Что бы понимать все особенности этого режима, нужно хорошо ориентироваться в системе контроля доступов linux, об этом у меня есть статья на Хабре. Если совсем коротко, privileged:
• Включает все capabilities (привилегии) ядра Linux — речь о механизме ограничения отдельных низкоуровневых операций процессов. Например, управление сетевыми настройками, но не уровне изменения какого нибудь etc/network/interfaces, а напрямую на уровне операций, которые процесс может попросить выполнить ядро линукса (включить, отключить интерфейс, изменить MAC адресс и тд).
• Отключает стандартный профиль secure computing mode (seccomp) — механизм безопасности ядра, ограничивающий системные вызовы (syscalls). Примеры syscalls это: reboot, mount, open, read, write и т.д.
• Отключает AppArmor.
• Отключает метку процесса SELinux (Security-Enhanced Linux).
• Предоставляет доступ ко всем устройствам хоста.
• Делает виртуальную файловую систему (sysfs) доступной для чтения и записи.
• Делает монтирования cgroups доступными для чтения и записи.Думаю, сложилось понимание, что с привилегированным режимом нужно быть очень осторожным.
disable_entrypoint_overwrite = false — если здесь true, то нельзя переопределять entrypoint контейнеру из .gitlab-ci.yml. В shared runner стоит true, что ограничивает возможности в целях безопасности. Про entrypoint хорошо описано здесь. Кому интересно можете отследить появление этой фичи: MR c добавлением флага.
disable_cache = false — Данная опция сохраняет возможность использовать cache, который указывается в .gitlab-ci.yml. Этот кэш позволяет расшаривать данные между джобами. Например:
# Использование кэша в .gitlab-ci.yml
cache:
paths:
- node_modules/
- .m2/repository/
- .pip-cache/Кэш может работать через Docker volume или S3. Docker volume не имеет смысла в shared runners, так как там все джобы создаются в виртуалках и полностью изолированы друг от друга. В нашем случае — self-hosted runner, и мы можем использовать кэш для ускорения работы пайплайнов. Но стоит быть аккуратным и осознанно использовать кэш: без тонкого понимания жизненного цикла кэша можно создать трудноуловимые баги. Более совершенная реализация кэша — через S3. Подробнее: Advanced configuration/ the [runners.cache] section.
Запуск и проверка self hosted раннера
Необходимые конфигурации созданы:
• /home/gtosss/inf-config/docker-compose.yml
• /srv/gitlab-runner/config/config.tomlОсталось только проверить, что все работает. В директории где лежит docker-compose.yml запускаем docker compose up. В моем случае рабочей директорией является /home/gtosss/inf-config или сокращенно ~/inf-config под пользователем gtosss. Я использую fedora и вместо docker у меня podman.
Переходим в рабочую директорию
cd ~/inf-configЗапускаем podman-compose up
gtosss@laptop-dc:~/inf-config$ podman-compose up
[gitlab-runner] | Runtime platform arch=amd64 os=linux pid=2 revision=249f0215 version=18.11.0
[gitlab-runner] | Starting multi-runner from /etc/gitlab-runner/config.toml... builds=0 max_builds=0
[gitlab-runner] | Running in system-mode.
[gitlab-runner] |
[gitlab-runner] | FATAL: Service run failed error=stat /etc/gitlab-runner/config.toml: permission deniedЯ погорячился создавая config.toml от имени супер пользователя. Причина ошибки в том, что podman в fedora работает в более безопасном режиме, чем docker в Ubuntu по дефолту. Podman разделяет управление контейнерами на отдельные режимы rootles и rootful, мы работаем от имени пользователя gtosss, podman поднимая контейнер назначает текущего пользователя его владельцем — это и есть режим rootles. Контейнеру нужен доступ к toml файлу, а мы его создали от имени рута.
Исправляем ситуацию руководствуясь подсказками из статьи — Права в Linux: chown/chmod, SELinux context, символьная/восьмеричная нотация, DAC/MAC/RBAC/ABAC. Если последующая информация вызывает вопросы, ответы будут как раз в этой статье.
Для начала посмотрим, какие права вообще назначеныgtosss@laptop-dc:~/inf-config$ stat /srv/gitlab-runner
File: /srv/gitlab-runner
Size: 12 Blocks: 0 IO Block: 4096 directory
Device: 0,36 Inode: 5930632 Links: 1
Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Context: unconfined_u:object_r:var_t:s0
Access: 2026-05-05 16:45:47.714091050 -0400
Modify: 2026-04-18 09:28:48.179756650 -0400
Change: 2026-04-18 09:28:48.179756650 -0400
Birth: 2026-04-18 09:28:36.917840577 -0400Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root) — владелец root.
Стоит обратить внимание на Context: Context: unconfined_u:object_r:var_t:s0sudo chown -R gtosss:gtosss /srv/gitlab-runnerПробуем запуститься:
gtosss@laptop-dc:~/inf-config$ podman-compose up
[gitlab-runner] | cannot open `/run/user/1000/crun/78f1016f972c126ea6f02713931a3180dfee34b8ebaf40b2b8ba6e47c405939c/exec.fifo`: No such file or directory
[gitlab-runner] | Error: unable to start container 78f1016f972c126ea6f02713931a3180dfee34b8ebaf40b2b8ba6e47c405939c: `/usr/bin/crun start 78f1016f972c126ea6f02713931a3180dfee34b8ebaf40b2b8ba6e47c405939c` failed: exit status 1Решение нашлось в issues github (podman-compose репозиторий). Советуют просто compose down и затем compose up. Так себе решение, не особо понятна природа проблемы, но придется довольствоваться тем что есть.
Пояснение для тех, кого совсем уж не устраивает этот черный ящик./run/user/1000 — Это директория для временных файлов которые хранят информацию с момента загрузки системы (run-time variable data). После /user фигурирует UID (пользовательский ID), обычно это 1000. Почему именно тысяча обсуждалось например на linuxquestions.org в 2014. Есть конфигурация в /etc/login.defs, где среди прочих параметров указываются значения:
GID_MIN 1000
GID_MAX 6000Проблема решилась, но мы опять сталкиваемся с ошибкой: error=stat /etc/gitlab-runner/config.toml: permission denied
В документации к podman о волумах говориться о метке z или Z. Ее нужно указывать в SELinux. То же самое и в документации docker. Согласно документации:
• z (маленькая) - если volume разделяют несколько контейнеров (наш случай, т.к раннер будет создавать много контейнеров).
• Z (большая) - если volume использует только один контейнер.Изменяем наш docker-compose.yml добавив метку z:
services:
gitlab-runner:
image: gitlab/gitlab-runner:alpine3.21-v18.11.0
container_name: gitlab-runner
restart: always
volumes:
- /srv/gitlab-runner/config:/etc/gitlab-runner:z
- /var/run/docker.sock:/var/run/docker.sockДля docker.sock указывать не нужно, так как в нем уже настроен нужный SELinux контекст из коробки. Теперь все заработало. Кстати если сейчас посмотреть информацию о файле через stat:
gtosss@laptop-dc:~/inf-config$ stat /srv/gitlab-runner/config
File: /srv/gitlab-runner/config
Size: 56 Blocks: 0 IO Block: 4096 directory
Device: 0,36 Inode: 5930634 Links: 1
Access: (0755/drwxr-xr-x) Uid: ( 1000/ gtosss) Gid: ( 1000/ gtosss)
Context: system_u:object_r:container_file_t:s0То можно заметить, что после запуска docker-compose up Context сам поменялся: unconfined_u:object_r:var_t:s0 → system_u:object_r:container_file_t:s0.
После запуска контейнера с gitlab-runner переходим в gitlab и смотрим есть ли соединение:
Спасибо, что дочитали!
Кто уже подписан на телеграм-канал — отдельная благодарность. Помимо технических статей поднимаем и социально важные темы.
Если материал полезен и изложен понятным языком — не стесняйтесь ставить плюсы. Это поможет выделиться на фоне остальных статей, так же это хороший способ поддержать меня.
С вами был Тимофей. Кто я?Разрабатываю с 2015 года. Стартовал как front-end разработчик на React, после 6-лет переключился на full-stack, последние годы — чаще DevOps. Мой публичный WakaTime.Теги:• gitlab runner
• docker compose
• SELinux
• self-hosted
• config.toml
• volume permissions
• podman
• rootless containers
• gitlab ciХабы:• DevOps
• Системное администрирование
• Linux
• Git
• GitHub
Получайте больше инсайтов о систематизации бизнеса
Подписывайтесь на Telegram-канал Business Operations — ежедневные материалы о бизнес-процессах, операционном управлении и повышении эффективности
💬 Подписаться на канал→ Оригинальная статья