惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Threatpost
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
T
The Blog of Author Tim Ferriss
S
SegmentFault 最新的问题
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 司徒正美
T
Tailwind CSS Blog
The Cloudflare Blog
The Last Watchdog
The Last Watchdog
PCI Perspectives
PCI Perspectives
博客园 - 聂微东
Stack Overflow Blog
Stack Overflow Blog
TaoSecurity Blog
TaoSecurity Blog
云风的 BLOG
云风的 BLOG
C
Cybersecurity and Infrastructure Security Agency CISA
O
OpenAI News
Recorded Future
Recorded Future
GbyAI
GbyAI
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Y
Y Combinator Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
量子位
博客园 - 叶小钗
V
Vulnerabilities – Threatpost
F
Full Disclosure
Recent Announcements
Recent Announcements
Vercel News
Vercel News
S
Schneier on Security
H
Heimdal Security Blog
Cisco Talos Blog
Cisco Talos Blog
V2EX - 技术
V2EX - 技术
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
B
Blog RSS Feed
宝玉的分享
宝玉的分享
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
P
Privacy & Cybersecurity Law Blog
T
Threat Research - Cisco Blogs
G
Google Developers Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
爱范儿
爱范儿
IT之家
IT之家
大猫的无限游戏
大猫的无限游戏
C
Check Point Blog
N
Netflix TechBlog - Medium
S
Security @ Cisco Blogs
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Microsoft Azure Blog
Microsoft Azure Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Cyberwarzone
Cyberwarzone

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Настройка self-hosted gitlab runner (CI/CD)
gtosss · 2026-05-07 · via Все публикации подряд на Хабре

Уровень сложностиПростой

Время на прочтение8 мин

Охват и читатели12K

Туториал

Настройка self-hosted gitlab runner

Вторая часть материала о CI/CD в котором мы рассмотрим настройку self hosted gitlab-runner через docker.

О серии статей

Все найденные мной русскоязычные гайды не дают базового понимания того, как это работает, по большому счету это просто инструкции по настройке, причем под какой-то конкретный продукт и кейс: .net, Java, Node JS, etc.

Целью серии статей является детальное и схематичное описание того, как все это устроено. Главная задача — вооружить читателя фундаментальным пониманием, что конкретно ему требуется сделать в его конкретном случае. Помимо самой инструкции по настройке, это будет так же справочник для погружения в DevOps, охватывающий:

  • Максимально много функционала, которыми обладает Gitlab (общие абстракции актуальны и для его аналогов).

  • Инструменты которые нужны: Bash, Docker, Kubernetes и другие.

  • Общую теорию и практику с конкретными сценариями.


Материал разбит на 4 части.

  1. Настройка GitLab CI/CD: понимаем принципы работы и запускаем первый pipeline

  2. [Вы здесь → ] Настройка self-hosted gitlab runner (CI/CD)

  3. [in progress 70%] Работа с registry (CI/CD)

  4. [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 для проекта

Настраиваем 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 -0400

Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root) — владелец root.

Стоит обратить внимание на Context: Context: unconfined_u:object_r:var_t:s0

sudo 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:s0system_u:object_r:container_file_t:s0.

После запуска контейнера с gitlab-runner переходим в gitlab и смотрим есть ли соединение:


Спасибо, что дочитали!

Кто уже подписан на телеграм-канал — отдельная благодарность. Помимо технических статей поднимаем и социально важные темы.

Если материал полезен и изложен понятным языком — не стесняйтесь ставить плюсы. Это поможет выделиться на фоне остальных статей, так же это хороший способ поддержать меня.

С вами был Тимофей. Кто я?

Разрабатываю с 2015 года. Стартовал как front-end разработчик на React, после 6-лет переключился на full-stack, последние годы — чаще DevOps. Мой публичный WakaTime.