Высокопроизводительные и высокодоступные vps/vds с автоматической установкой и полным root-доступом к ОС. Заказанные ресурсы гарантировано закреплены за вами.
Универсальные и масштабируемые среды виртуальных серверов, разработанные с учетом потребностей вашего бизнеса и предлагающие гибкое распределение ресурсов.
Набор сетевых сервисов, обеспечивающих повышенную безопасность, масштабируемость и высокую доступность ресурсов для оптимизации вашей облачной экосистемы.
Обеспечьте непрерывность своей работы с помощью наших надежных решений по восстановлению после сбоев, которые гарантируют быстрое восстановление и минимальное время простоя в случае непредвиденных проблем.
Как установить GitLab на VPS и подключить собственный GitLab Runner
Как установить GitLab на VPS и подключить собственный GitLab Runner
Валерий Волков
Время прочтения 67 минут
Прежде чем переходить к установке, коротко разберемся, что именно мы будем разворачивать и зачем вообще может понадобиться собственный GitLab.
GitLab — это платформа для совместной разработки и DevOps, построенная вокруг Git-репозиториев. Помимо хранения исходного кода, она предоставляет управление пользователями и доступом, issues, merge requests, CI/CD, container registry и другие инструменты, которые позволяют собрать значительную часть рабочего процесса разработки в одном месте.
GitLab можно использовать как готовый облачный сервис, но существует и self-hosted-вариант, когда вся платформа разворачивается на собственном сервере. В таком случае администратор сам контролирует данные, пользователей, резервные копии, обновления и ресурсы, на которых работает система.
Однако одного GitLab для CI/CD недостаточно. GitLab хранит проект и создает pipeline, но сами команды из .gitlab-ci.yml выполняет отдельный компонент — GitLab Runner.
Именно эту связку мы сегодня и соберем. GitLab будет хранить репозитории, пользователей и CI/CD-конфигурацию, а Runner — получать задания pipeline и выполнять их на сервере.
Базовая цепочка выглядит так: разработчик отправляет изменения в GitLab, GitLab создает pipeline, после чего подходящий Runner получает Job и выполняет команды из .gitlab-ci.yml.
Для небольшой self-hosted установки GitLab и собственный Runner можно разместить на одном VPS. Это удобно для учебного стенда, небольшой команды или проекта с умеренной нагрузкой. По мере роста инфраструктуры Runner уже можно вынести на отдельный сервер или несколько отдельных узлов.
В статье разберем установку GitLab, регистрацию Runner, тестовый pipeline, HTTPS, потребление ресурсов, backup/restore и диагностику зависших CI-заданий.
Для production важно не только запустить GitLab, но и проверить, как он ведет себя под нагрузкой, где хранит резервные копии и что происходит, если Runner перестает забирать Job.
Как будет устроена схема GitLab и Runner
Перед установкой полезно один раз разобрать архитектуру целиком. GitLab и Runner часто воспринимают как одну систему, но на практике у них разные роли: GitLab управляет проектами и pipeline, а Runner непосредственно выполняет CI-задания.
В нашей схеме начнем с одного VPS, чтобы не усложнять инфраструктуру раньше времени. Позже отдельно посмотрим, когда Runner уже стоит вынести на другой сервер.
Какие компоненты участвуют в развертывании
Главный компонент — сам GitLab.
Он хранит репозитории, пользователей, проекты, настройки доступа, CI/CD-конфигурацию и историю pipeline.
Отдельно работает GitLab Runner. Его задача — получить Job от GitLab, подготовить среду выполнения и запустить команды, описанные в .gitlab-ci.yml.
Между ними находится pipeline. Он определяет, какие этапы нужно выполнить после push, merge request или другого события.
Например, pipeline может состоять из трех частей:
Сборка приложения;
Запуск тестов;
Deployment.
При этом GitLab управляет порядком и статусами, а сами команды выполняет Runner.
Кроме этих двух основных компонентов, понадобятся домен, HTTPS, место для backup и достаточный запас ресурсов VPS.
Теперь важно понять, где именно будут работать GitLab и Runner.
Где будет работать GitLab, а где GitLab Runner
Для небольшой установки оба компонента можно разместить на одном VPS.
Это удобно для личного GitLab, тестового стенда или небольшой команды с простой инфраструктурой, где CI запускается не слишком часто.
В таком случае один сервер одновременно выполняет две роли:
GitLab обслуживает веб-интерфейс, Git-операции и внутренние сервисы;
Runner запускает CI-задания.
Такой вариант проще в настройке и дешевле, потому что не требует второго VPS.
Но у него есть ограничение: GitLab и Runner используют одни и те же CPU, RAM и диск. Если pipeline начнет активно компилировать код или запускать тяжелые тесты, это может повлиять на работу самого GitLab.
Поэтому в этой работе мы сначала используем один сервер, а позже измерим реальное потребление ресурсов и посмотрим, насколько Runner влияет на основной инстанс.
Почему GitLab и Runner лучше разделять логически
Даже если физически они находятся на одном VPS, их полезно считать двумя отдельными частями системы.
GitLab в основном управляет данными и состоянием: хранит repositories, пользователей, pipeline и результаты Job.
Runner, наоборот, исполняет команды.
Это принципиальная разница.
Если в .gitlab-ci.yml написано установить зависимости, запустить тесты или собрать приложение, эти действия выполняются именно на стороне Runner.
Поэтому Runner ближе к непосредственному исполнению кода и обычно требует более осторожного отношения к правам и изоляции.
Такое логическое разделение дает несколько преимуществ:
Проще понимать, где искать проблему;
Легче контролировать ресурсы;
Удобнее ограничивать права;
Runner можно позже перенести на отдельный VPS без миграции самого GitLab;
Можно подключить несколько Runner под разные типы задач.
То есть даже в минимальной схеме лучше сразу мыслить так: GitLab управляет, Runner выполняет.
Отсюда уже становится проще понять, что происходит после обычного git push.
Как проходит путь от push до выполнения CI job
Разработчик изменяет код и отправляет commit в GitLab.
GitLab принимает push, обновляет repository и проверяет CI/CD-конфигурацию проекта.
Если в репозитории есть .gitlab-ci.yml и условия запуска выполнены, GitLab создает pipeline.
После этого внутри pipeline появляются Job — конкретные задачи, которые нужно выполнить.
Например:
build;
test;
deploy.
GitLab сам эти команды не запускает. Он ждет подходящий Runner.
Runner связывается с GitLab, получает доступный Job, выполняет команды и отправляет обратно результат.
В итоге цепочка выглядит так:
После выполнения GitLab получает статус Job и показывает его в интерфейсе как passed, failed, canceled или другое состояние.
Именно по этой цепочке мы позже будем диагностировать зависшие CI-задания: сначала проверять pipeline, затем Runner, а потом уже окружение самого Job.
Остается подготовить исходные данные для установки.
Что подготовить перед установкой
До начала лучше заранее определить домен, ресурсы VPS и место для хранения резервных копий. Это позволит дальше пройти установку без постоянных остановок на недостающих настройках.
Вот таблица для облегчения:
Компонент
Зачем нужен
Где работает
Что подготовить
VPS
Размещение GitLab и Runner
Основной сервер
SSH-доступ, CPU, RAM, SSD
GitLab
Репозитории, пользователи и CI/CD
VPS
Домен и системное окружение
GitLab Runner
Выполнение CI Job
Сначала тот же VPS
Ресурсы и доступ к GitLab
Домен
Публичный адрес GitLab
DNS
A/AAAA-запись на VPS
HTTPS
Защищенный доступ
GitLab/VPS
Рабочий домен
.gitlab-ci.yml
Описание pipeline
Git repository
Создадим в тестовом проекте
Backup storage
Восстановление GitLab
Лучше вне основного VPS
Отдельный диск или Object Storage
На этом этапе схема уже понятна: GitLab будет центральной точкой, а Runner — отдельным исполнителем CI-задач.
Выбираем VPS и оцениваем ресурсы
Теперь нужно понять, какой VPS способен нормально обслуживать оба компонента.
Здесь лучше сразу оставить запас. GitLab заметно тяжелее обычного сайта, а Runner создает дополнительную нагрузку именно в тот момент, когда начинается сборка или тестирование. Поэтому сначала оценим сам GitLab, затем добавим требования Runner и только после этого зафиксируем конфигурацию стенда.
Почему GitLab требовательнее обычного веб-приложения
GitLab Self-Managed — это не один веб-процесс.
Даже при установке на один сервер внутри работает целый набор компонентов. Помимо самого веб-интерфейса, GitLab использует PostgreSQL для данных, Redis, фоновые процессы Sidekiq и Gitaly для работы с Git repositories.
То есть один запрос пользователя может затрагивать сразу несколько частей системы.
Например, открытие проекта требует работы веб-приложения и базы данных, а git clone, fetch или push дополнительно нагружают хранилище репозиториев. Фоновые операции выполняются отдельно и тоже используют CPU и RAM.
Особенно чувствителен GitLab к диску. Gitaly активно работает с repositories, поэтому официальная документация GitLab рекомендует SSD-backed storage и не советует использовать хранилища с непредсказуемой производительностью.
Кроме того, потребление ресурсов меняется со временем. Пока никто не работает с проектами, сервер может выглядеть почти свободным. Но одновременно запущенные Git-операции, фоновые задачи и CI быстро меняют картину.
Поэтому конфигурацию VPS правильнее выбирать не только по числу пользователей, но и по характеру нагрузки.
Сколько CPU, RAM и диска нужно небольшому GitLab
Для обычной single-node установки GitLab сейчас указывает 8 vCPU и 16 ГБ RAM как базовую конфигурацию. Для application-части требуется как минимум около 40 ГБ дискового пространства, а дополнительно нужно учитывать место под PostgreSQL и сами repositories.
То есть VPS на 2 vCPU и 2 ГБ RAM лучше не воспринимать как обычную отправную точку для production GitLab.
Для ограниченных окружений GitLab действительно допускает более компактную настройку. В актуальной документации для memory-constrained single-node среды указан минимум около 8 ГБ RAM, но для этого уже приходится специально уменьшать потребление отдельных компонентов. Однако GitLab предупреждает, что такая оптимизация может ухудшить производительность и поведение отдельных функций.
Поэтому разделим два сценария:
8 vCPU / 16 ГБ RAM — нормальная базовая точка для обычного single-node GitLab;
около 8 ГБ RAM — уже специальный constrained-сценарий для личного или очень небольшого инстанса.
С диском запас особенно важен. Кроме самой установки место будут занимать repositories, база данных, логи, uploads, CI artifacts, packages и резервные копии.
Если выделить серверу ровно столько места, сколько занимает GitLab сразу после установки, проблема появится позже — когда начнут расти проекты и CI.
Сам GitLab мы оценили. Теперь добавим вторую часть схемы — Runner.
Какие ресурсы потребляет GitLab Runner
У GitLab Runner нет одного универсального требования по CPU или RAM.
Причина проста: Runner сам по себе сравнительно легкий менеджер, а основную нагрузку создают выполняемые CI jobs.
Например, Job, который проверяет несколько файлов и выводит строку в консоль, почти не заметен для сервера. А сборка большого проекта, установка тысяч зависимостей или запуск тестов могут надолго занять несколько CPU и гигабайты памяти.
GitLab рекомендует рассчитывать ресурсы Runner с учетом:
Нагрузки одного CI job;
Потребления памяти Job;
Числа одновременно выполняющихся заданий;
Количества активных проектов;
Количества разработчиков, запускающих CI параллельно.
Особенно важна параллельность.
Если Runner выполняет только один Job, серверу нужно пережить нагрузку одной сборки. Если разрешить одновременно четыре тяжелых задания, они уже начинают конкурировать за CPU, RAM и disk I/O.
Поэтому для нашего первого Runner ограничимся одним одновременно выполняемым Job. Так проще контролировать нагрузку и позже сравнить состояние VPS до запуска pipeline и во время него.
Это заодно отвечает на следующий вопрос: обязательно ли для Runner сразу покупать отдельный сервер?
Когда GitLab и Runner можно держать на одном VPS
Для небольшого личного GitLab, учебного стенда или маленькой команды один VPS вполне может выполнять обе роли.
Такой вариант особенно удобен, если pipeline запускаются не постоянно, CI jobs сравнительно легкие, а параллельное выполнение ограничено.
Плюс очевидный: инфраструктура проще. Не нужно обслуживать второй сервер только ради нескольких коротких сборок в день.
Но компромисс тоже понятен.
Если Runner во время Job заберет почти весь CPU или память, GitLab останется на том же сервере и начнет конкурировать с ним за ресурсы. Тяжелая сборка может отразиться на веб-интерфейсе, Git-операциях и фоновых процессах.
Поэтому общий VPS хорошо подходит, пока нагрузка умеренная. Выносить Runner отдельно уже имеет смысл, если:
Сборки регулярно загружают CPU;
Jobs требуют много RAM;
Одновременно нужно выполнять несколько задач;
CI активно работает с диском;
Runner исполняет код, которому не стоит давать доступ к серверу GitLab;
Необходимо разместить Runner в иной от Gitlab инфраструктуре, например на другой площадке;
Сам GitLab должен оставаться стабильным независимо от состояния CI.
В нашей статье начнем с одного VPS. Это позволит не только упростить установку, но и позже на практике измерить, насколько запуск Runner меняет потребление ресурсов.
Какую конфигурацию будем использовать в статье
Для нашего стенда возьмем базовую single-node конфигурацию GitLab и не будем пытаться искусственно ужимать ее до минимально возможного сервера.
Используем:
8 vCPU;
16 ГБ RAM;
100 ГБ SSD;
Поддерживаемую версию Ubuntu Server;
Один GitLab instance;
Один GitLab Runner;
Не более одного CI Job одновременно.
8 vCPU и 16 ГБ RAM соответствуют текущему базовому ориентиру GitLab для single-node установки. 100 ГБ SSD — уже выбранный нами запас: он не является фиксированным требованием GitLab, потому что реальный объем зависит от количества и размера repositories, artifacts, packages и backup.
Для тяжелого CI этого запаса может оказаться мало. Тогда правильным решением будет не бесконечно увеличивать общий VPS, а рассмотреть отдельный или даже отдельные Runner-ы.
Перед установкой полезно свести варианты в одну таблицу:
Ресурс
Ограниченный стенд
Рекомендуемая база
Конфигурация статьи
CPU
Зависит от нагрузки и оптимизации
8 vCPU
8 vCPU
RAM
От 8 ГБ при memory-constrained настройке
16 ГБ
16 ГБ
Диск
Зависит от данных
От 40 ГБ + repositories и БД
100 ГБ SSD
Runner
Только легкие задачи
С запасом под CI
1 Runner
Параллельные Job
Минимум
По ресурсам сервера
1
Назначение
Lab / personal
Single-node GitLab
GitLab + небольшой CI
Остается проверить сам VPS и сохранить исходные показатели до установки.
Команды проверки ресурсов VPS
Сначала посмотрим количество доступных процессоров:
nproc
Более подробные характеристики CPU можно получить через:
lscpu
Память проверим так:
free -h
Эта команда показывает общий объем RAM, используемую и доступную память, а также swap.
Для отдельной проверки swap:
swapon --show
Теперь посмотрим диски и разделы:
lsblk
А фактически свободное место:
df -h
Наконец, зафиксируем текущую нагрузку VPS:
uptime
На чистом сервере эти показатели станут точкой отсчета. Позже повторим замеры после установки GitLab и еще раз во время выполнения pipeline.
Получится три состояния для сравнения:
Для быстрой проверки основные команды оставим отдельной шпаргалкой. Своеобразный «якорь» для облегчения восприятия.
Проверяем ресурсы VPS
Что проверяем
Зачем
Команда
CPU
Проверить количество доступных процессоров
nproc
Характеристики CPU
Посмотреть архитектуру и модель процессора
lscpu
RAM
Узнать общий и доступный объем памяти
free -h
Swap
Проверить наличие активного swap
swapon --show
Диски
Посмотреть устройства и разделы
lsblk
Свободное место
Оценить запас под GitLab и CI
df -h
Текущая нагрузка
Зафиксировать исходный load average
uptime
Теперь перейдем к подготовке самого VPS: установим системные зависимости, проверим hostname, время и сетевые порты, а затем уже начнем установку GitLab.
Подготавливаем VPS для GitLab
Какие системные зависимости понадобятся
GitLab Linux package ставит большую часть внутренних компонентов сам, поэтому вручную собирать PostgreSQL, Redis или веб-сервер отдельно для базовой установки не требуется.
Но операционной системе все равно нужны несколько базовых пакетов и сервисов.
В первую очередь пригодятся:
curl — для загрузки установочного скрипта и проверки HTTP-запросов;
ca-certificates — для корректной работы с HTTPS;
openssh-server — для SSH-доступа к VPS и Git-операций по SSH;
tzdata — для системных часовых поясов;
perl — используется частью системных инструментов и зависимостей;
postfix — опционально, если GitLab должен отправлять уведомления через локальный mail transfer agent.
На VPS у провайдера SSH обычно уже установлен и запущен. Остальные пакеты тоже могут частично присутствовать в образе Ubuntu, поэтому повторная установка просто подтвердит их наличие.
С почтой отдельный нюанс: для тестового стенда postfix вообще не обязателен. GitLab можно позже настроить на внешний SMTP, что для production часто удобнее и предсказуемее.
Когда базовые пакеты понятны, можно перейти к параметру, который влияет уже на адрес самого GitLab.
Зачем GitLab нужен корректный hostname и домен
GitLab работает не только как веб-сайт. Он постоянно формирует ссылки на проекты, clone URL, callback URL и другие адреса, поэтому публичный URL лучше определить заранее.
Допустим, GitLab будет доступен по адресу:
gitlab.example.com
Тогда DNS-запись этого домена должна указывать на публичный IP нашего VPS.
Hostname самого Linux-сервера при этом может быть, например:
gitlab-prod-01
Эти два понятия не обязательно должны совпадать.
Hostname нужен в первую очередь администратору и самой операционной системе, а публичный домен используется пользователями и конфигурацией GitLab.
Позже в GitLab мы зададим этот адрес как external_url. Поэтому лучше не устанавливать приложение сначала на случайный IP, а потом без необходимости переделывать все ссылки под домен.
Перед установкой достаточно убедиться, что DNS уже разрешается правильно: gitlab.example.com → IP VPS
Следующий вопрос — пропустит ли сеть необходимые подключения.
Какие порты должны быть доступны
Для базового GitLab нужны несколько внешних портов.
22/TCP используется для SSH. Через него администратор подключается к VPS, а пользователи могут работать с Git repositories по SSH.
80/TCP нужен для HTTP. Даже если в production будет использоваться только HTTPS, этот порт может понадобиться для первоначального доступа, redirect и проверки домена при выпуске сертификата.
443/TCP используется для HTTPS.
Получается базовый набор:
Порт
Назначение
22/TCP
SSH и Git over SSH
80/TCP
HTTP
443/TCP
HTTPS
Если SSH на сервере перенесен на нестандартный порт, конечно, нужно использовать фактическое значение.
Открывать наружу внутренние сервисы GitLab вроде PostgreSQL или Redis не требуется. В single-node установке они работают внутри сервера и не должны быть публично доступны без отдельной причины.
Проверять нужно сразу два уровня: firewall самой Ubuntu и сетевой firewall/security group у облачного провайдера. Если порт разрешен только в одном из них, снаружи он все равно может оставаться недоступным.
Разумеется, в более безопасной инсталляции необходимо ограничивать подключения к административным интерфейсам - как минимум по внешним адресам, а в идеале - используя VPN. Но Security Hardening останется за рамками этой статьи, мы разберем базовую установку.
С сетью разобрались. Перед установкой остаются две менее заметные, но важные вещи — время и диск.
Почему важно проверить время и свободное место
Корректное время влияет на TLS, authentication, логи, CI/CD и работу токенов.
Если часы VPS заметно отличаются от реального времени, могут появляться ошибки, которые на первый взгляд вообще не связаны с системными часами: проблемы с сертификатами, сроком действия токенов или временем событий в GitLab.
Ubuntu обычно синхронизирует часы автоматически, поэтому достаточно проверить, что синхронизация действительно работает.
Со свободным местом ситуация еще важнее.
GitLab постепенно накапливает не только repositories. Диск занимают:
База данных;
Логи;
Uploads;
CI artifacts;
Packages;
Временные файлы;
Резервные копии.
Причем часть данных может расти незаметно. Например, несколько pipeline регулярно сохраняют artifacts, и через несколько месяцев они занимают больше места, чем сами Git repositories.
Поэтому перед установкой полезно зафиксировать исходный объем свободного пространства и дальше сравнивать его с фактическим потреблением.
Теперь все исходные проверки можно выполнить на сервере.
Команды подготовки сервера
Сначала обновим индекс пакетов и установленные пакеты:
После этих проверок сервер готов уже не в теории, а практически: сеть доступна, домен известен, время синхронизировано, а свободное место зафиксировано.
Следующий шаг — установить сам GitLab Linux package, задать external_url и разобраться, что именно происходит при первой gitlab-ctl reconfigure.
Устанавливаем GitLab на VPS
Что входит в GitLab Linux package
GitLab Linux package содержит не только веб-интерфейс.
Вместе с ним устанавливаются и настраиваются компоненты, которые нужны для работы self-managed GitLab. В их числе:
Основное приложение GitLab;
PostgreSQL;
Redis;
Sidekiq;
Gitaly;
Встроенный Nginx;
Служебные процессы и утилиты управления.
У каждого компонента своя роль.
PostgreSQL хранит данные проектов, пользователей и настроек. Redis используется для внутренних операций и очередей. Sidekiq выполняет фоновые задачи. Gitaly отвечает за работу с Git repositories. Nginx принимает HTTP- и HTTPS-запросы.
Именно поэтому GitLab не приходится собирать вручную из множества отдельных сервисов.
Для небольшой single-node установки это удобно: администратор получает готовый стек, который можно управлять через инструменты GitLab.
Следующий важный момент — где хранится его основная конфигурация.
Как GitLab хранит собственную конфигурацию
Главный файл Linux package находится здесь:
/etc/gitlab/gitlab.rb
В нем задаются параметры всего инстанса.
Через gitlab.rb можно настраивать:
Публичный URL;
Встроенный Nginx;
HTTPS;
Параметры PostgreSQL;
Redis;
SMTP;
Backup;
Storage;
Лимиты;
Отдельные внутренние сервисы.
То есть вместо ручного редактирования десятков конфигураций мы описываем нужное состояние GitLab в одном месте.
Например: external_url "https://gitlab.example.com" или gitlab_rails['gitlab_email_from'] = 'gitlab@example.com'
После изменения файла конфигурацию нужно применить:
sudo gitlab-ctl reconfigure
GitLab заново формирует необходимые внутренние настройки на основе gitlab.rb.
Поэтому сгенерированные конфигурационные файлы лучше не править напрямую. При следующем reconfigure такие изменения могут быть перезаписаны.
Из всех параметров первым нам понадобится external_url.
После завершения проверим основной конфигурационный файл:
sudo nano /etc/gitlab/gitlab.rb
Нас пока интересует строка:
external_url "https://gitlab.example.com"
Если она уже добавлена установщиком, ничего менять не нужно.
Если конфигурацию редактировали вручную, применяем изменения:
sudo gitlab-ctl reconfigure
Теперь проверим, запущены ли внутренние сервисы:
sudo gitlab-ctl status
В выводе должны присутствовать основные компоненты GitLab со статусом run.
Дополнительно можно посмотреть информацию о текущем окружении:
sudo gitlab-rake gitlab:env:info
Она пригодится и позже при диагностике проблем.
Для первого входа GitLab создает временный пароль пользователя root.
Посмотреть его можно так:
sudo cat /etc/gitlab/initial_root_password
После первого входа этот пароль лучше сразу заменить.
Теперь открываем в браузере:
https://gitlab.example.com
и входим под пользователем root.
На этом этапе GitLab уже установлен и управляется через стандартные команды gitlab-ctl.
Ну и по закону жанра подготовили снова для вас таблицу:
Задача
Команда
Открыть основную конфигурацию
sudo nano /etc/gitlab/gitlab.rb
Применить изменения
sudo gitlab-ctl reconfigure
Проверить сервисы
sudo gitlab-ctl status
Перезапустить GitLab
sudo gitlab-ctl restart
Посмотреть информацию о среде
sudo gitlab-rake gitlab:env:info
Посмотреть временный пароль root
sudo cat /etc/gitlab/initial_root_password
Сам GitLab уже работает, но публичный доступ пока нельзя считать полностью подготовленным.
Подключаем домен и HTTPS
GitLab уже установлен и открывается на сервере. Теперь нужно довести публичный доступ до нормального production-вида: привязать домен, включить HTTPS и убедиться, что GitLab корректно работает по защищенному адресу.
Здесь есть два основных подхода. Можно позволить самому GitLab управлять HTTPS через встроенный Nginx и автоматическое получение сертификата, а можно поставить перед ним отдельный reverse proxy. Для нашей схемы первый вариант проще и логичнее, но разберем оба.
Куда должна указывать DNS-запись
Публичный домен GitLab должен разрешаться в IP-адрес VPS.
Например, если используем:
gitlab.example.com
то в DNS нужна запись: gitlab.example.com → публичный IP VPS
Обычно для IPv4 это A-запись, а для IPv6 — AAAA.
После изменения DNS полезно проверить, что домен уже разрешается правильно: dig +short gitlab.example.com или getent hosts gitlab.example.com
Если в ответе возвращается IP нашего сервера, можно двигаться дальше.
Сама по себе DNS-запись HTTPS не включает. Она только связывает имя хоста с сервером. Следующий шаг — настроить GitLab так, чтобы он обслуживал этот домен по TLS.
Как GitLab работает с HTTPS
В базовой single-node установке GitLab использует встроенный Nginx.
Он принимает входящие HTTP- и HTTPS-запросы и передает их внутренним компонентам GitLab.
Публичный адрес задается через:
external_url "https://gitlab.example.com"
Когда в external_url используется https://, GitLab понимает, что инстанс должен работать по защищенному протоколу.
Дальше есть два варианта.
Первый — GitLab сам получает и обслуживает сертификат.
Второй — HTTPS терминируется на внешнем reverse proxy, а GitLab работает за ним.
Для одного VPS первый вариант обычно проще.
Когда использовать встроенное получение сертификата, а когда внешний reverse proxy
Если GitLab работает как самостоятельный сервис на одном VPS, удобнее использовать встроенный механизм.
В этом случае GitLab сам:
Обслуживает домен;
Получает TLS-сертификат;
Настраивает встроенный Nginx;
Следит за продлением сертификата.
Это уменьшает число отдельных компонентов и упрощает поддержку.
Внешний reverse proxy имеет смысл, если на одном сервере уже размещается несколько приложений или инфраструктура централизованно управляет TLS.
Например:
Такой вариант может быть полезен, если сертификаты для всех сервисов выпускаются в одном месте или GitLab работает внутри более сложной архитектуры.
Но здесь появляется дополнительный слой настройки: нужно корректно передавать заголовки, протокол клиента и реальный IP, а также следить, чтобы GitLab понимал, что снаружи соединение использует HTTPS.
Для нашего стенда это лишнее. Поэтому оставим встроенный Nginx GitLab и встроенное управление TLS.
Что происходит при продлении TLS
TLS-сертификат имеет ограниченный срок действия, поэтому его недостаточно выпустить один раз и забыть. Мы будем настраивать работу с центром выдачи бесплатных короткоживущих сертификатов Let's Encrypt.
При использовании встроенного механизма GitLab может автоматически проверять сертификат и обновлять его до истечения срока действия.
Для этого важно, чтобы домен продолжал указывать на VPS, а сервер оставался доступным снаружи по необходимым портам.
Если DNS изменился, порт закрыли firewall или домен перестал вести на этот сервер, автоматическое продление может завершиться ошибкой.
Поэтому после первоначальной настройки стоит проверить не только наличие сертификата, но и сам механизм renewal.
Это особенно важно для production: истекший сертификат может оставить GitLab технически работающим, но браузеры, интеграции и Git-клиенты начнут считать соединение небезопасным.
Теперь подключим HTTPS на практике.
Практика: подключаем домен и проверяем HTTPS
Сначала еще раз проверим DNS:
dig +short gitlab.example.com
Результат должен совпадать с публичным IP VPS.
Теперь откроем конфигурацию GitLab:
sudo nano /etc/gitlab/gitlab.rb
Проверим публичный URL:
external_url "https://gitlab.example.com"
Для встроенного получения сертификата можно включить Let's Encrypt:
Если сертификат выпущен корректно, curl должен установить TLS-соединение без ошибки проверки сертификата.
Можно отдельно проверить редирект с HTTP:
curl -I http://gitlab.example.com
В нормальной конфигурации пользователь должен попадать на HTTPS-версию сайта.
После этого открываем в браузере:
https://gitlab.example.com
и проверяем, что страница GitLab загружается без предупреждений о сертификате.
Для проверки всей последовательности достаточно четырех шагов:
Этап
Что проверяем
Команда / параметр
DNS
Домен указывает на VPS
dig +short gitlab.example.com
GitLab config
Публичный адрес использует HTTPS
external_url "https://gitlab.example.com"
Применение
GitLab перестроил конфигурацию
sudo gitlab-ctl reconfigure
Проверка
HTTPS отвечает без TLS-ошибок
curl -I https://gitlab.example.com
Теперь GitLab доступен по нормальному публичному HTTPS-адресу.
Следующий логичный шаг — создать тестовый проект и проверить уже не инфраструктуру, а сам Git workflow: repository, первый commit и push в наш новый GitLab.
Создаем проект и проверяем работу GitLab
Подключать Runner сразу не будем. Сначала убедимся, что GitLab нормально создает проекты, принимает commits и обрабатывает push. Тогда, если позже возникнет проблема с CI, мы уже будем знать, что базовый Git-уровень работает и искать причину нужно дальше по цепочке.
Как устроены project и repository в GitLab
В GitLab проект — это больше, чем просто Git repository.
Repository хранит историю файлов и commits, а project объединяет вокруг него дополнительные возможности:
Участников и права доступа;
Issues;
Merge requests;
CI/CD;
Variables;
Artifacts;
Packages;
Настройки Runner;
Историю pipeline.
То есть repository является одной из частей проекта.
Например, для тестового приложения можно создать проект:
demo-app
а внутри него будет Git repository, с которым разработчик работает обычными командами:
git clone
git add
git commit
git push
Сам Git при этом остается обычным Git. GitLab добавляет поверх него управление доступом, веб-интерфейс и CI/CD.
И это удобно для дальнейшей диагностики. Если git push работает, а pipeline не запускается, проблема уже не в самом repository. Значит, нужно смотреть .gitlab-ci.yml, Runner или настройки CI.
Но прежде чем отправлять первый commit, нужно выбрать способ аутентификации.
Какие способы аутентификации использовать для Git
Работать с repository в GitLab можно по HTTPS или SSH.
При HTTPS адрес обычно выглядит примерно так:
https://gitlab.example.com/user/demo-app.git
При SSH:
git@gitlab.example.com:user/demo-app.git
HTTPS удобно использовать там, где проще работать через токены и учетные данные GitLab. При этом обычный пароль учетной записи не стоит воспринимать как универсальную замену токену: для Git over HTTPS обычно применяют personal access token с подходящими правами.
SSH работает немного иначе.
На компьютере разработчика создается пара ключей:
private key остается у пользователя;
public key добавляется в профиль GitLab.
После этого GitLab может идентифицировать пользователя при подключении по SSH без постоянного ввода пароля.
Для постоянной работы с собственным GitLab SSH обычно удобнее, поэтому дальше воспользуемся именно им.
Если ключа еще нет, создать его можно на компьютере разработчика:
ssh-keygen -t ed25519 -C "developer@example.com"
После этого публичную часть:
cat ~/.ssh/id_ed25519.pub
нужно добавить в настройках профиля GitLab в раздел SSH Keys.
Проверить соединение можно так:
ssh -T git@gitlab.example.com
При первом подключении SSH попросит подтвердить fingerprint сервера. Его желательно сверить, а не принимать вслепую.
Когда аутентификация работает, можно переходить к тестовому repository.
Зачем нужен тестовый репозиторий перед подключением CI
Может показаться, что проще сразу зарегистрировать Runner и запускать pipeline. Но тогда в случае ошибки одновременно появляются слишком многие неизвестные.
Например, Job не запускается. Причиной может оказаться:
Ошибка .gitlab-ci.yml;
Неподходящий Runner;
Неправильные tags;
Проблемы с executor;
Ошибка самого скрипта;
Или вообще проблема с repository и доступом к нему.
Поэтому инфраструктуру удобнее проверять постепенно.
Сейчас наша контрольная точка простая:
Если эта цепочка работает, значит мы уже проверили домен, HTTPS, Git repository, учетную запись и базовую аутентификацию.
После этого можно спокойно добавлять CI как следующий уровень.
Практика: создаем проект и выполняем первый push
Откроем веб-интерфейс GitLab и создадим новый blank project.
Для примера назовем его:
demo-app
Visibility для закрытого рабочего проекта можно оставить Private.
После создания GitLab покажет адрес repository. В нашем случае будем использовать SSH:
Для нового repository Git при необходимости может потребовать явно указать branch и upstream. Например:
git push -u origin main
После успешного push обновляем страницу проекта в GitLab. В repository должны появиться README.md и созданный commit.
На этом базовую Git-цепочку можно считать проверенной.
Для дальнейшей работы пригодятся четыре основные команды:
Действие
Команда
Что происходит
Клонировать repository
git clone <repository-url>
Создается локальная копия проекта
Добавить изменения
git add <file>
Файлы попадают в staging area
Создать commit
git commit -m "message"
Изменения сохраняются в локальной истории
Отправить commit
git push
Новые commits передаются в GitLab
Теперь у нас есть работающий GitLab, собственный домен и первый repository, который принимает изменения от разработчика. Базовая часть готова.
Разбираемся, как работает GitLab Runner
Теперь можно подключать CI/CD — и здесь появляется GitLab Runner.
На этом этапе важно понять какова роль сервиса. Иначе потом легко запутаться: GitLab показывает pipeline, Runner отображается online, но Job все равно висит в pending. Чтобы таких ситуаций было меньше, сначала разберем сам механизм.
Что такое GitLab Runner
GitLab Runner — это отдельный сервис, который выполняет CI/CD Job.
GitLab хранит .gitlab-ci.yml, создает pipeline и определяет, какие задания нужно выполнить. Но сами команды из Job запускаются не внутри основного GitLab-процесса, а на стороне Runner.
Например, в pipeline может быть указано:
test:
script:
- echo "Run tests"
- ./run-tests.sh
GitLab создаст Job test, а Runner получит его и выполнит команды из script.
Проще всего воспринимать Runner как исполнителя: GitLab решает, что нужно выполнить, а Runner выполняет это на практике.
На одном GitLab может быть несколько Runner. Один может обслуживать конкретный проект, другой — группу проектов, третий — более широкий набор задач. Это позволяет распределять CI-нагрузку и использовать разные окружения под разные типы Job.
Чем Runner отличается от самого GitLab
GitLab и Runner связаны между собой, но выполняют разные задачи.
GitLab отвечает за управление:
repositories;
users;
branches;
merge requests;
pipeline;
CI/CD variables;
историю и статусы Job.
Runner отвечает за исполнение.
Когда начинается CI, именно Runner:
получает Job;
подготавливает рабочее окружение;
забирает исходный код;
выполняет команды;
собирает logs;
при необходимости загружает artifacts;
возвращает GitLab итоговый статус.
То есть GitLab знает, что Job должен быть выполнен, но не обязан выполнять его сам.
Это разделение полезно еще и потому, что Runner можно переносить и масштабировать независимо. Если CI становится тяжелее, можно добавить второй Runner или вынести их на отдельные VPS, не трогая основной GitLab.
Теперь посмотрим, как именно Job попадает от GitLab к Runner.
Как Runner получает CI-задания
Runner не получает команды «вслепую» от любого проекта.
Сначала он регистрируется в GitLab и связывается с определенной областью использования. После этого Runner регулярно обращается к GitLab и проверяет, есть ли подходящие Job.
Если появляется Job, GitLab учитывает несколько условий:
Доступен ли Runner;
Разрешено ли ему работать с этим проектом;
Совпадают ли tags;
Допускается ли выполнение untagged Job;
Подходит ли Runner под параметры задания.
Если условия совпадают, Runner забирает Job и начинает выполнение.
Упрощенно цепочка выглядит так:
Здесь есть еще один важный слой — executor. Он определяет, где именно и каким способом Runner будет запускать команды.
Что такое executor
Runner сам по себе не определяет среду исполнения.
Для этого используется executor — механизм, который говорит Runner, в каком окружении выполнять Job.
В зависимости от выбранного executor команды могут запускаться:
напрямую в shell операционной системы;
внутри Docker container;
в Kubernetes;
через SSH;
в других поддерживаемых средах.
То есть Runner — это управляющий процесс, а executor — способ исполнения.
Например:
или:
От executor зависит изоляция, воспроизводимость окружения и то, насколько сильно CI влияет на сам сервер.
Для небольшого self-hosted GitLab чаще всего рассматривают два варианта: Shell и Docker.
Чем shell executor отличается от Docker executor
Shell executor запускает команды прямо в операционной системе Runner.
Если Runner установлен на том же VPS, где работает GitLab, Job фактически выполняется на этом сервере.
Плюс такого подхода — простота. Не нужен Docker, окружение легко понять, а команды можно запускать почти так же, как вручную по SSH.
Но есть и важный минус: Job получает доступ к окружению хоста в рамках прав пользователя Runner. Поэтому ошибочный или недоверенный CI-скрипт может затронуть файловую систему сервера или установленные инструменты.
Docker executor работает иначе.
Для каждого Job создается container на основе указанного image. Команды выполняются внутри него, а после завершения окружение можно удалить.
Это дает более чистую и воспроизводимую модель:
Каждый Job получает отдельное окружение, а зависимости проекта не нужно заранее ставить прямо на VPS.
Сравним оба подхода:
Параметр
Shell executor
Docker executor
Где выполняется Job
На хосте Runner
В container
Изоляция
Низкая
Выше
Подготовка
Проще
Требуется Docker
Воспроизводимость
Зависит от состояния VPS
Зависит от image
Риск затронуть хост
Выше
Ниже, но не исчезает полностью
Очистка окружения
Нужно контролировать вручную
Container обычно удаляется после Job
Подходит для
Простых доверенных задач
Большинства типичных CI-сценариев
Важно понимать, что Docker executor не превращает Runner в полностью безопасную песочницу. Например, если дать container привилегированный режим или пробросить чувствительные host paths, изоляция резко снижается.
Но для обычной CI-схемы он все равно дает гораздо более удобное разделение между Job и самим VPS.
Какой executor используем в статье
В статье будем использовать Docker executor.
Для нашей задачи он удобнее по нескольким причинам.
Во-первых, каждый Job будет выполняться в предсказуемом container environment. Не придется устанавливать все зависимости тестового проекта прямо на GitLab VPS.
Во-вторых, после завершения Job меньше шансов оставить на сервере случайные файлы или измененное окружение.
В-третьих, такая схема ближе к тому, как Runner часто используют в реальном CI.
При этом сам Runner пока останется на том же VPS, что и GitLab. Так мы сохраним инфраструктуру компактной, а позже сможем измерить, сколько ресурсов начинает потреблять CI во время выполнения Job.
Получится следующая схема:
На этом теория Runner уже понятна. Следующий шаг — установить сам GitLab Runner, зарегистрировать его в нашем GitLab и проверить, что он отображается online и готов принимать задания.
Устанавливаем собственный GitLab Runner
Где лучше размещать Runner
Для небольшого self-hosted GitLab есть два основных варианта.
Первый — установить Runner на тот же VPS:
Плюс такого подхода — минимальная инфраструктура. Не нужен второй сервер, проще следить за сервисами и быстрее собрать рабочий стенд.
Минус мы уже знаем: GitLab и CI начинают делить CPU, RAM и диск.
Второй вариант — вынести Runner отдельно:
Эта схема лучше масштабируется и уменьшает влияние сборок на основной GitLab.
Для production с тяжелыми pipeline или недоверенным кодом отдельный Runner обычно предпочтительнее. Но для нашей статьи пока оставим один VPS и ограничим параллельность одним Job.
Так мы сначала построим компактную рабочую конфигурацию, а уже потом сможем понять по реальным замерам, нужен ли второй сервер.
Почему Runner нельзя считать доверенным по умолчанию
Runner выполняет содержимое CI Job.
А это значит, что любой разрешенный ему pipeline фактически передает Runner набор команд для исполнения.
Например:
job:
script:
- ./build.sh
- ./run-tests.sh
Для GitLab это просто описание Job. Для Runner — реальные команды, которые нужно выполнить.
Поэтому важно разделять две ситуации.
Если Runner обслуживает только собственный закрытый проект и все изменения проходят через доверенных разработчиков, риск сравнительно невелик.
Если же тот же Runner доступен множеству проектов или способен выполнять недоверенный код, требования к изоляции становятся значительно выше.
В нашем случае Docker executor уменьшит прямое влияние Job на операционную систему VPS, но полностью проблему безопасности не решает. Особенно опасны конфигурации, где контейнеру выдаются расширенные привилегии или доступ к чувствительным ресурсам хоста.
Поэтому на практике Runner лучше рассматривать как отдельную вычислительную среду, которой дают только те права, которые действительно нужны для CI.
Следующий вопрос — как GitLab вообще узнает, что этот Runner принадлежит нашему проекту.
Что Runner получает от GitLab при регистрации
В современном workflow сначала создается Runner в интерфейсе GitLab, а затем сервер получает runner authentication token.
Такой токен обычно начинается с:
glrt-
Он используется для связи конкретного Runner с GitLab.
Важно не путать его со старым registration token. Legacy-схема с registration token постепенно выводится из использования, поэтому для новой установки лучше сразу использовать authentication token.
Процесс выглядит так:
Создаем Runner в интерфейсе GitLab;
Задаем его параметры;
Получаем authentication token;
Запускаем gitlab-runner register на VPS;
Передаем URL GitLab и token;
Выбираем executor;
Локальная конфигурация сохраняется в config.toml.
После регистрации Runner использует этот token, чтобы аутентифицироваться перед GitLab и получать доступные задания.
Сам token нужно считать секретом. Если посторонний получит доступ к конфигурации Runner, его нельзя публиковать в статье, screenshot или repository.
При выполнении конкретного Job используется уже отдельный job token. Благодаря этому CI environment не обязательно получает постоянный authentication token самого Runner.
Теперь остается понять, как GitLab решает, какие именно Job отдавать этому исполнителю.
Что такое tags и зачем они нужны
Tags позволяют связать определенные Job с определенными Runner.
concurrent = 1 ограничивает количество одновременно выполняемых Job для всей конфигурации Runner.
executor = "docker" включает выбранный ранее Docker executor.
image = "alpine:latest" задает default image, если конкретный Job не укажет собственный.
privileged = false оставляет контейнер без privileged mode, который для нашего тестового pipeline не нужен.
Authentication token также хранится в локальной конфигурации, поэтому содержимое реального config.toml нельзя бездумно публиковать или вставлять в screenshot.
Теперь проверим сервис:
sudo systemctl status gitlab-runner
Быстрая проверка состояния:
sudo systemctl is-active gitlab-runner
Сам Runner может дополнительно проверить регистрацию:
sudo gitlab-runner verify
После этого возвращаемся в GitLab: Settings → CI/CD → Runners
Наш demo-docker-runner должен отображаться как доступный Runner проекта.
Основные команды на этом этапе можно свести к короткой таблице:
Действие
Зачем
Команда
Установить Runner
Добавить сервис на VPS
sudo apt install -y gitlab-runner
Зарегистрировать
Связать Runner с GitLab
sudo gitlab-runner register
Проверить регистрацию
Убедиться, что Runner доступен GitLab
sudo gitlab-runner verify
Проверить сервис
Убедиться, что процесс запущен
sudo systemctl is-active gitlab-runner
Посмотреть конфигурацию
Проверить executor и параметры
sudo cat /etc/gitlab-runner/config.toml
Далее у нас идёт настройка.
Настраиваем Runner для выполнения CI-заданий
Runner уже зарегистрирован и отображается в GitLab как доступный. Теперь нужно проверить его рабочую конфигурацию перед первым pipeline.
В прошлом разделе мы разобрали регистрацию и связали Runner с проектом. Здесь задача уже другая: понять, какие Job он сможет получать, где будут выполняться команды и сколько ресурсов CI разрешено забрать у VPS. Это особенно важно в нашей схеме, потому что GitLab и Runner пока находятся на одном сервере.
Как GitLab выбирает подходящий Runner
Когда pipeline создает новый Job, GitLab не отправляет его случайному Runner.
Сначала он ищет исполнителя, который подходит по нескольким условиям. Runner должен быть активен, иметь доступ к нужному проекту и соответствовать требованиям Job.
Если для задания указаны tags, GitLab дополнительно проверяет их.
Такой Job сможет получить Runner, которому назначен tag docker.
Если подходящего исполнителя нет, Job не обязательно завершится ошибкой. Он может просто остаться в состоянии pending и ждать, пока нужный Runner появится.
Это важное различие:
failed обычно означает, что Job уже начал выполняться и столкнулся с ошибкой;
pending часто указывает, что подходящий Runner еще не забрал задачу.
Поэтому при диагностике CI сначала стоит проверить не команды внутри script, а сам факт назначения Runner.
Один из основных механизмов такого выбора — tags.
Как работают tags
С назначением tag мы уже столкнулись при создании Runner. Теперь посмотрим именно на логику сопоставления.
Допустим, Runner имеет tags:
docker
linux
А Job требует:
tags:
- docker
Такой Runner подходит.
Если Job требует сразу два tag:
tags:
- docker
- linux
Runner тоже подходит, потому что имеет оба.
Но если написать:
tags:
- docker
- gpu
наш Runner уже не подойдет, поскольку gpu ему не назначен.
Это позволяет довольно точно разделять инфраструктуру.
Например, в будущем можно создать несколько Runner:
docker — для обычной сборки;
deploy — только для deployment;
gpu — для задач с GPU;
arm64 — для ARM-сборок.
При этом tag сам по себе не создает изоляцию и не выдает права. Это всего лишь механизм выбора подходящего Runner.
Для нашего стенда пока достаточно одного tag:
docker
Следующий момент менее заметен, но не менее важен: что происходит с файлами проекта после того, как Runner получил Job.
Что происходит с рабочим каталогом Job
После получения задания Runner должен подготовить место, где будут выполняться команды.
При Docker executor он запускает container и подготавливает внутри него рабочий каталог проекта. Именно туда GitLab Runner получает исходный код repository перед выполнением script.
Внутри Job путь обычно находится под:
/builds/
а дальше формируется каталог конкретного проекта.
То есть pipeline работает не с файлами из /var/www или какого-либо постоянного каталога приложения на VPS, а со своей рабочей копией repository.
Это важно по двум причинам.
Во-первых, CI Job не должен рассчитывать, что какие-то файлы «остались с прошлого раза», если они явно не сохранены через cache или artifacts.
Во-вторых, изменения внутри рабочего окружения не превращаются автоматически в изменения самого Git repository.
Например:
npm install
npm test
npm run build
Они могут создать сотни временных файлов, но после завершения Job они не становятся commits и не появляются в GitLab сами по себе.
Если результат нужно передать дальше по pipeline, обычно используют artifacts. Если нужно ускорить повторную загрузку зависимостей — cache.
К этому мы вернемся в следующих разделах. Пока достаточно понимать главное: каждый Job получает рабочее окружение, а Runner управляет его подготовкой и очисткой.
Остается ограничить аппетиты этого окружения.
Какие ограничения ресурсов стоит задать Runner
Так как GitLab и Runner находятся на одном VPS, отдавать CI все доступные ресурсы было бы плохой идеей.
Представим, что сервер имеет:
8 vCPU
16 ГБ RAM
Если один Job сможет занять все 8 vCPU и почти всю память, GitLab в этот момент тоже останется без запаса.
Поэтому для нашего тестового Runner разумно начать с консервативных ограничений.
Например:
CPU: до 2 vCPU
RAM: до 4 ГБ
Concurrent jobs: 1
Это не универсальные production-значения. Они нужны именно для нашего стенда, чтобы CI не мог случайно вытеснить GitLab с собственного VPS.
Здесь работают два уровня ограничений.
concurrent задает общее число Job, которые экземпляр GitLab Runner может выполнять одновременно.
Например:
concurrent = 1
Если Runner несколько, дополнительно можно использовать limit внутри конкретной секции [[runners]].
А для Docker executor можно ограничить ресурсы контейнера:
cpus = "2"
memory = "4g"
В результате один CI Job не сможет бесконтрольно занять весь сервер.
Если позже выяснится, что сборке действительно нужно больше ресурсов, лимиты можно увеличить — или вынести Runner на отдельный VPS.
Теперь соберем эти параметры в рабочую конфигурацию.
Практика: проверяем и настраиваем config.toml
Основной конфигурационный файл Runner находится здесь:
/etc/gitlab-runner/config.toml
Перед изменением удобно сохранить резервную копию:
Authentication token в реальном файле также будет присутствовать, но его не нужно копировать в документацию, repository или screenshots.
Разберем только параметры, которые действительно важны для нашего стенда.
concurrent = 1 запрещает этому экземпляру Runner выполнять несколько Job одновременно.
limit = 1
дополнительно ограничивает конкретный Runner одним Job.
Параметр:
executor = "docker"
оставляет выбранный ранее Docker executor.
Default image:
image = "alpine:latest"
будет использоваться, если в .gitlab-ci.yml Job не задаст другой image.
Для production-проекта лучше фиксировать конкретный tag image, например версию Alpine, а не постоянно следовать за latest. Так окружение CI будет воспроизводимее.
Дальше идут ограничения:
cpus = "2"
memory = "4g"
Они не дают контейнеру одного Job использовать весь VPS.
privileged = false оставляем без изменений:
privileged = false
Privileged mode дает контейнеру существенно более широкие возможности и без необходимости включать его не стоит.
После сохранения файла проверим конфигурацию:
sudo gitlab-runner verify
Затем перезапустим сервис:
sudo systemctl restart gitlab-runner
И убедимся, что он снова активен:
sudo systemctl is-active gitlab-runner
Если нужно посмотреть последние сообщения сервиса:
sudo journalctl -u gitlab-runner -n 50 --no-pager
Для быстрого контроля ключевые параметры можно свести в таблицу:
Параметр
Назначение
Подход для нашего стенда
concurrent
Общее число одновременно выполняемых Job
1
limit
Лимит конкретного Runner
1
executor
Среда выполнения
docker
image
Default container image
Alpine; в реальном проекте лучше фиксировать версию
cpus
Ограничение CPU одного container
2 vCPU
memory
Ограничение RAM одного container
4g
privileged
Расширенные права container
false, если они не нужны
volumes
Каталоги/volumes, доступные Job
Оставляем только необходимое
Теперь Runner не просто зарегистрирован, а подготовлен к реальной работе: он получает только подходящие Job, выполняет их через Docker и не может занять все ресурсы VPS одной сборкой.
Следующий шаг — наконец проверить эту цепочку целиком. Создадим первый .gitlab-ci.yml, отправим его в repository и посмотрим, как GitLab создаст pipeline, Runner заберет Job, а Docker executor выполнит команды внутри container.
Создаем первый GitLab CI pipeline
Что такое .gitlab-ci.yml
Основная конфигурация GitLab CI/CD хранится в файле:
.gitlab-ci.yml
Обычно он находится в корне repository.
Именно в нем описывается, что должно происходить во время pipeline: какие Job запускать, в каком порядке, в каком container image и какие команды выполнять.
Например:
test:
script:
- echo "Hello from GitLab CI"
Здесь определен один Job с именем test.
Когда GitLab обнаруживает такой файл и условия запуска выполнены, он создает pipeline и передает Job подходящему Runner.
Поэтому .gitlab-ci.yml можно воспринимать как сценарий CI/CD, который хранится вместе с кодом проекта и изменяется через обычные commits.
Это удобно еще и тем, что история pipeline-конфигурации остается в Git. Если кто-то изменил процесс сборки, можно увидеть, когда и в каком commit это произошло.
Но даже в простом .gitlab-ci.yml есть несколько уровней: pipeline, stages и отдельные Job. Их лучше сразу разделить.
Как связаны pipeline, stage и job
Pipeline — это весь запуск CI/CD для конкретного события.
Например, после git push GitLab может создать один новый pipeline.
Внутри него находятся stages — логические этапы выполнения.
Типичная последовательность выглядит так:
А уже внутри каждого stage находятся Job.
Например:
stages:
- build
- test
build_app:
stage: build
script:
- echo "Build"
run_tests:
stage: test
script:
- echo "Tests"
Здесь:
pipeline — весь запуск;
build и test — stages;
build_app и run_tests — Job.
По умолчанию следующий stage начинается после успешного завершения предыдущего. При этом несколько Job одного stage могут выполняться параллельно, если для этого есть доступные Runner и разрешена необходимая concurrency.
Для первого теста нам пока не нужна сложная цепочка. Достаточно одного stage и одного Job — так легче проверить именно связь GitLab с Runner.
Как Runner получает команды из pipeline
Когда GitLab читает .gitlab-ci.yml, он не отправляет Runner весь файл целиком с указанием «разберись сам».
GitLab сначала обрабатывает CI-конфигурацию, создает pipeline и формирует конкретные Job.
После этого Runner получает уже задание, которое подходит ему по настройкам.
В нашем случае в Job будет tag:
tags:
- docker
А собственный Runner ранее получил такой же tag.
Когда Runner заберет Job, Docker executor создаст container на основе указанного image и выполнит команды из script.
Если написать:
image: alpine:3.22
test:
tags:
- docker
script:
- echo "Runner is working"
- uname -a
Runner последовательно выполнит обе команды внутри Alpine container.
Результат их работы попадет обратно в Job log GitLab.
Именно поэтому лог pipeline становится одним из главных инструментов диагностики: там видно не только итоговый статус, но и то, на каком шаге остановилось выполнение.
Теперь посмотрим, что произойдет после обычного push.
Что происходит с pipeline после push
Допустим, мы добавили .gitlab-ci.yml, создали commit и выполнили:
git push
GitLab получает новый commit и проверяет CI-конфигурацию.
Если файл корректен, создается pipeline.
Дальше возможны несколько состояний.
Сначала Job может быть pending: GitLab уже создал задание, но Runner еще не начал его выполнять.
Когда Runner забирает Job, статус меняется на running.
После завершения получаем, например:
passed — Job выполнен успешно;
failed — одна из команд завершилась с ошибкой;
canceled — выполнение отменено;
skipped — Job пропущен по условиям конфигурации.
То есть сам факт появления pipeline уже говорит, что GitLab прочитал .gitlab-ci.yml. А успешное выполнение Job дополнительно подтверждает, что Runner найден, executor работает и container смог выполнить команды.
Именно это сейчас и проверим.
Практика: создаем тестовый .gitlab-ci.yml
Переходим в локальный каталог нашего demo-app:
cd demo-app
Создаем файл:
nano .gitlab-ci.yml
Для первого pipeline используем такую конфигурацию:
stages:
- test
runner_check:
stage: test
image: alpine:3.22
tags:
- docker
script:
- echo "GitLab Runner is working"
- 'echo "Project: $CI_PROJECT_PATH"'
- 'echo "Commit: $CI_COMMIT_SHORT_SHA"'
- uname -a
Здесь происходит несколько вещей.
stages объявляет единственный этап:
stages:
- test
runner_check — имя Job.
Параметр:
stage: test
привязывает его к нашему stage.
Дальше выбираем container image:
image: alpine:3.22
Мы специально фиксируем конкретную версию вместо latest, чтобы тестовое окружение не изменилось неожиданно после обновления image.
Tag:
tags:
- docker
указывает GitLab, что этому Job нужен Runner с соответствующим tag.
Наконец, script содержит сами команды.
Переменные:
CI_PROJECT_PATH
CI_COMMIT_SHORT_SHA
GitLab передает Job автоматически. Поэтому в log мы сразу увидим, для какого проекта и commit выполнялся pipeline.
Перед push полезно проверить CI-конфигурацию через встроенный CI/CD editor или CI Lint в GitLab. Это позволяет найти ошибки YAML и часть ошибок конфигурации еще до запуска Runner.
Если проверка проходит успешно, добавляем файл в Git:
git add .gitlab-ci.yml
Создаем commit:
git commit -m "Add first GitLab CI pipeline"
И отправляем изменения:
git push
После push открываем проект в GitLab и переходим в раздел: Build → Pipelines
Должен появиться новый pipeline.
Откроем его и перейдем в Job runner_check.
В log должны появиться строки примерно такого вида:
GitLab Runner is working
Project: user/demo-app
Commit: a1b2c3d
а затем вывод uname -a.
Если Job завершился со статусом passed, основная CI-цепочка работает:
Основную последовательность можно сохранить в небольшой таблице:
Этап
Что делаем
Результат
Validate
Проверяем .gitlab-ci.yml через CI Lint
GitLab принимает конфигурацию
Commit
git add + git commit
CI-файл сохраняется в истории Git
Push
git push
GitLab получает новый commit
Pipeline
Открываем Build → Pipelines
Job выполняется собственным Runner
На этом первая полноценная CI-цепочка готова. Но один Job мало похож на реальную разработку: обычно сначала приложение собирают, затем тестируют и только после этого разворачивают.
В следующем разделе усложним pipeline совсем немного — добавим несколько stages и посмотрим, как GitLab управляет последовательностью build → test → deploy, не превращая .gitlab-ci.yml в громоздкую конфигурацию.
Добавляем несколько stages в pipeline
Зачем разделять build, test и deploy
Если сложить все команды в один Job, технически pipeline тоже может работать.
Например:
Но такой подход быстро становится неудобным.
Во-первых, становится сложнее понять, где именно произошла ошибка. Если Job упал, приходится искать нужное место в длинном log.
Во-вторых, разные этапы часто требуют разных окружений. Для сборки может понадобиться один image, для тестов — другой, а deployment вообще может выполняться только на отдельном Runner.
Поэтому pipeline обычно делят на логические stages: build → test → deploy
Каждый stage отвечает за свою часть процесса.
build готовит приложение или artifacts.
test проверяет результат.
deploy выполняется только после того, как предыдущие проверки завершились успешно.
Такой pipeline проще читать и поддерживать, особенно когда количество Job начинает расти.
В каком порядке выполняются stages
Порядок stages задается явно:
GitLab идет по этому списку сверху вниз.
Сначала выполняются Job из build.
После их успешного завершения начинается test.
И только затем — deploy.
Если в одном stage несколько Job, следующий stage обычно ждет завершения всех обязательных Job текущего этапа.
Например:
Так можно разделять работу внутри одного этапа, но сохранять общий порядок pipeline.
Следующий вопрос — будут ли эти Job выполняться последовательно или одновременно.
Когда jobs внутри одного stage выполняются параллельно
Job внутри одного stage могут выполняться параллельно, если для этого есть доступные Runner и разрешена соответствующая concurrency.
Например:
unit_tests:
stage: test
script:
- echo "Unit tests"
lint:
stage: test
script:
- echo "Lint"
Оба Job относятся к test.
Если есть два свободных Runner или один Runner способен выполнять два задания одновременно, GitLab может запустить их параллельно.
Но в нашей конфигурации ранее было задано: concurrent = 1 и limit = 1
Поэтому даже два Job одного stage будут выполняться по очереди.
Это сделано специально: GitLab и Runner находятся на одном VPS, и пока мы не хотим, чтобы два container одновременно начали конкурировать с GitLab за CPU и RAM.
Позже, если Runner переедет на отдельный сервер, concurrency можно увеличить.
Что происходит, если один Job завершается ошибкой
По умолчанию ошибка в обязательном Job останавливает дальнейшее продвижение pipeline.
Допустим, схема такая:
Если build завершился со статусом failed, stage test обычно уже не запускается.
А если ошибка возникла на этапе test, до deploy pipeline не дойдет.
Это и есть одна из главных причин разделять процесс на stages: deployment не должен выполняться, если приложение не собрано или тесты не прошли.
При необходимости поведение можно изменить, например через allow_failure, но для обычных проверок это используют осторожно.
Например:
lint:
stage: test
allow_failure: true
script:
- ./lint.sh
Такой Job может завершиться с ошибкой, но pipeline продолжит выполнение.
Для критичных тестов это обычно не подходит. Если тест подтверждает, что приложение вообще не работает, ошибку лучше считать блокирующей.
Теперь соберем простой pipeline из трех stages и посмотрим на его граф.
Практика: создаем pipeline из нескольких stages
Откроем .gitlab-ci.yml:
nano .gitlab-ci.yml
Заменим предыдущую тестовую конфигурацию на более полную:
Artifact нужен потому, что следующий Job запускается в новом окружении. Он не должен рассчитывать, что рабочие файлы предыдущего container каким-то образом останутся на месте.
В test_demo проверяем, что artifact действительно доступен:
test -f build/app.txt
Если файла нет, команда завершится с ошибкой, а Job получит статус failed.
До deploy_demo pipeline в таком случае уже не дойдет.
Последний stage пока ничего реально не разворачивает:
echo "Demo deploy completed"
Это сделано специально. Пока нам важно проверить сам порядок build → test → deploy, не добавляя в тестовый pipeline доступ к production-серверу.
Сохраняем файл и проверяем изменения:
git diff
Добавляем конфигурацию:
git add .gitlab-ci.yml
Создаем commit:
git commit -m "Add multi-stage CI pipeline"
И отправляем его:
git push
После push переходим в GitLab: Build → Pipelines
Открываем новый pipeline.
В графе должны появиться три последовательных stage: build → test → deploy
А внутри них: build_demo → test_demo → deploy_demo
Теперь pipeline уже напоминает реальный CI-процесс: один Job создает результат, следующий его проверяет, а deployment запускается только после успешных предыдущих этапов.
Работаем с CI/CD variables и secrets
Многоэтапный pipeline уже работает: GitLab принимает commit, создает stages, Runner выполняет Job, а artifacts переходят между этапами. Теперь можно добавить следующую важную часть — переменные и секреты.
В реальном CI почти всегда нужны значения, которые нельзя бездумно хранить рядом с кодом: токены, пароли, адреса deployment-среды, API keys и другие чувствительные параметры. GitLab позволяет передавать их в Job отдельно от .gitlab-ci.yml.
Почему секреты нельзя хранить в .gitlab-ci.yml
Файл .gitlab-ci.yml находится в Git repository.
Это значит, что все записанное в нем:
Попадает в историю commits;
Может быть видно другим участникам проекта;
Сохраняется даже после последующего удаления строки;
Секреты лучше хранить отдельно от repository и передавать в Job только во время выполнения.
Для этого GitLab использует CI/CD variables.
Что такое CI/CD variables
CI/CD variable — это значение, которое GitLab передает Job как переменную окружения.
Например, в настройках проекта можно создать:
DEPLOY_ENV=staging
А внутри .gitlab-ci.yml обратиться к ней так:
script:
- echo "$DEPLOY_ENV"
В Linux container Runner увидит ее как обычную environment variable.
Так можно передавать:
API tokens;
Passwords;
Deployment URLs;
Environment names;
Feature flags;
Service credentials;
Другие параметры, которые отличаются между окружениями.
Кроме пользовательских variables, GitLab автоматически предоставляет большой набор predefined variables.
С некоторыми мы уже работали:
CI_PROJECT_PATH
CI_COMMIT_SHORT_SHA
Они создаются самим GitLab и содержат информацию о текущем project, commit, pipeline и Job.
Пользовательские CI/CD variables решают другую задачу: позволяют передать свои значения без записи их непосредственно в repository.
Но не все variables одинаковы. Для чувствительных данных особенно важны свойства masked и protected.
Чем обычные variables отличаются от masked и protected
Обычная CI/CD variable просто передается Job.
Если создать:
APP_ENV=staging
ее можно использовать в script как обычную переменную окружения.
Для несекретных значений этого достаточно.
С секретами ситуация другая. Если Job случайно выведет token через:
echo "$API_TOKEN"
значение может попасть в Job log.
Для таких случаев используется Masked.
Masked variable GitLab старается скрывать в log, заменяя совпадающее значение маской. Это снижает риск случайно показать secret в интерфейсе CI.
Но masking не нужно воспринимать как абсолютную защиту. Если пользователь может изменить .gitlab-ci.yml и выполнять произвольный код, он потенциально способен использовать переданный secret другими способами. Поэтому права на редактирование pipeline остаются критически важными.
Второе свойство — Protected.
Protected variable доступна только pipeline, которые выполняются для protected branches или protected tags.
Например, deployment token можно сделать protected и использовать только из main, если эта ветка настроена как protected.
Тогда обычная feature branch не получит секрет даже при наличии такого variable в настройках проекта.
Условно разделение выглядит так:
Обычная variable — просто значение для CI;
Masked — значение дополнительно скрывается из log;
Protected — значение доступно только защищенным refs;
Masked + protected — типичный вариант для чувствительного production secret.
Теперь посмотрим, как эти значения вообще оказываются внутри container.
Как Runner получает переменные во время Job
Когда GitLab передает Runner конкретный Job, вместе с заданием он формирует набор доступных CI/CD variables.
Туда могут входить:
Predefined variables GitLab;
Variables проекта;
Variables группы;
Variables конкретного environment;
Значения, заданные непосредственно в .gitlab-ci.yml.
Runner получает этот набор и передает переменные в окружение Job.
Для Docker executor они становятся environment variables внутри container.
Например, если в GitLab создано:
DEPLOY_ENV=staging
то внутри Job можно выполнить:
echo "$DEPLOY_ENV"
и получить:
staging
Секретные значения проверять таким способом не стоит. Если variable содержит token или password, специально выводить ее в log — плохая практика даже при включенном masking.
Вместо этого лучше проверять сам факт наличия значения.
Например:
test -n "$API_TOKEN"
Команда завершится успешно, если переменная непустая, но не покажет ее содержимое.
Этим способом и воспользуемся на практике.
Практика: добавляем и проверяем CI/CD variable
Откроем проект в GitLab и перейдем в настройки CI/CD variables.
Создадим тестовую переменную:
Key: DEMO_SECRET
Value: demo-secret-value-123
Для нее включим Masked.
Protected пока оставим выключенным, чтобы variable была доступна pipeline из нашей текущей ветки независимо от ее protected-статуса.
Теперь добавим в .gitlab-ci.yml отдельный Job:
check_variable:
stage: test
image: alpine:3.22
tags:
- docker
script:
- test -n "$DEMO_SECRET"
- echo "CI/CD variable is available"
Обратите внимание: само значение нигде не выводится.
Команда:
test -n "$DEMO_SECRET"
проверяет только то, что переменная существует и не пуста.
Если все настроено правильно, следующая строка выполнится:
echo "CI/CD variable is available"
Теперь сохраняем изменения:
git add .gitlab-ci.yml
Создаем commit:
git commit -m "Add CI/CD variable check"
И отправляем его:
git push
После запуска pipeline открываем Job check_variable.
В log должна появиться строка:
CI/CD variable is available
При этом значение DEMO_SECRET нигде не отображается.
Для наглядности можно дополнительно протестировать masking на временной учебной переменной:
script:
- echo "$DEMO_SECRET"
GitLab должен скрыть подходящее значение в Job log. После проверки такую строку лучше удалить, потому что реальные secrets специально выводить в console не нужно.
Ключевые варианты CI/CD variables можно свести в короткую таблицу:
Тип
Что делает
Когда использовать
Обычная variable
Передает значение в Job
Несекретные параметры: environment, URL, flags
Masked
Скрывает совпадающее значение в Job log
Tokens, passwords, API keys
Protected
Передает значение только protected branches/tags
Production credentials, deploy secrets
Masked + Protected
И скрывает значение, и ограничивает область использования
Чувствительные production secrets
Теперь pipeline умеет получать чувствительные параметры отдельно от repository, а .gitlab-ci.yml остается пригодным для хранения в Git.
Дальше посмотрим во что вся эта схема обходится по ресурсам. Мы уже снимали показатели чистого VPS, поэтому теперь можно сравнить их с GitLab в idle и с сервером во время реального CI Job.
Измеряем потребление ресурсов GitLab и Runner
Почему измерять ресурсы нужно после запуска реального pipeline
Одного idle-состояния недостаточно.
Если просто открыть top сразу после установки GitLab, можно получить вполне спокойную картину и решить, что сервер имеет огромный запас.
Но Runner создает нагрузку не постоянно. Она появляется именно во время Job.
Причем характер нагрузки зависит от самого pipeline. Простая команда echo почти ничего не покажет, а установка зависимостей, тесты или сборка приложения уже заметно используют CPU, RAM и диск.
Поэтому полезнее сравнивать три состояния:
Чистый VPS до установки GitLab;
GitLab работает, pipeline не выполняется;
GitLab и Runner одновременно работают во время CI Job.
Так мы увидим не только постоянное потребление GitLab, но и дополнительную нагрузку от Runner.
Какие показатели нас интересуют
Для небольшого VPS достаточно следить за четырьмя группами показателей.
В первую очередь — CPU.
Нас интересуют текущая загрузка и load average. Если во время pipeline процессоры постоянно заняты, Job уже конкурирует с GitLab за вычислительные ресурсы.
Второй показатель — RAM.
Здесь важно смотреть не только на used, но и на available. Linux активно использует свободную память под cache, поэтому само по себе большое значение used еще не означает нехватку RAM.
Третья группа — disk space.
GitLab постепенно накапливает repositories, logs, artifacts и backup. Runner дополнительно создает рабочие файлы, Docker layers и cache.
Наконец, полезно смотреть на сами процессы и containers, чтобы понять, кто именно потребляет ресурсы.
В итоге нам понадобятся:
CPU и load average;
RAM и swap;
Свободное место на диске;
Размер каталогов GitLab и Runner;
Самые тяжелые процессы;
Состояние Docker во время Job.
Этого достаточно, чтобы получить базовую картину без отдельной monitoring-системы.
Чем idle-потребление отличается от нагрузки во время CI
В idle GitLab продолжает работать.
Даже когда никто не открывает веб-интерфейс и pipeline не запускаются, активны база данных, Redis, Sidekiq, Gitaly и другие компоненты.
Поэтому часть RAM и CPU занята постоянно.
Runner в этот момент в основном ожидает задания и обычно создает намного меньшую нагрузку.
Картина меняется после запуска pipeline.
Docker executor должен:
Получить container image;
Создать container;
подготовить рабочий каталог;
Скачать repository;
Выполнить команды Job;
Сохранить cache или artifacts;
Удалить временное окружение после завершения.
Из-за этого нагрузка становится кратковременной, но заметно выше.
Например, первый запуск container image может сильнее нагружать сеть и диск, потому что Docker еще должен скачать layers. Следующий Job с тем же image уже может пройти быстрее за счет локального cache.
Поэтому один единственный замер не всегда показателен. Лучше сравнить хотя бы idle и один реальный pipeline, а при необходимости повторить тест еще раз.
Как Runner влияет на CPU, RAM и disk
Влияние Runner зависит прежде всего от того, что делает Job.
CPU сильнее загружают:
Компиляция;
Тесты;
Compression;
Сборка packages;
Container builds.
RAM активно используется при:
Больших dependency trees;
Параллельных тестах;
Сборке крупных приложений;
Работе нескольких Job одновременно.
Диск нагружают:
Clone repository;
Docker images;
Temporary build files;
Cache;
Artifacts;
Package managers.
Именно поэтому ранее мы ограничили Runner:
concurrent = 1
а Docker container:
cpus = "2"
memory = "4g"
Эти ограничения не делают Job легким, но не дают одному pipeline бесконтрольно занять весь VPS.
При анализе результатов стоит смотреть не только на максимальные цифры, но и на поведение самого GitLab. Если во время Job веб-интерфейс остается отзывчивым, memory pressure не возникает, а диск имеет нормальный запас, схема для небольшой нагрузки может быть вполне приемлемой.
Если же каждый pipeline заметно тормозит GitLab, это уже аргумент в пользу отдельного Runner VPS.
Теперь снимем реальные показатели.
Практика: измеряем ресурсы до, во время и после pipeline
Зафиксируем ее в таблице как исходную:
Состояние
CPU / load
RAM
Disk
Комментарий
Чистый VPS
2 vCPU
437 MiB / 3.8 GiB
4.6 GiB / 19 GiB (25%)
До установки GitLab
GitLab idle
load average 0.63
3.5 GiB / 3.8 GiB; swap 1.6 / 2.0 GiB
9.6 GiB / 19 GiB (53%)
GitLab запущен, CI не выполняется
Pipeline running
load average 1.35
3.5 GiB / 3.8 GiB; swap 1.5 / 2.0 GiB
9.6 GiB / 19 GiB (53%)
Runner выполняет Job
После pipeline
load average 0.61
3.5 GiB / 3.8 GiB; swap 1.5 / 2.0 GiB
9.6 GiB / 19 GiB (53%)
Job завершён, Runner снова простаивает
Сначала снимем состояние GitLab в idle.
Проверим память:
free -h
Нагрузку:
uptime
Свободное место:
df -h
Для просмотра самых ресурсоемких процессов:
ps aux --sort=-%mem | head
и отдельно по CPU:
ps aux --sort=-%cpu | head
Для наблюдения в реальном времени удобно открыть:
top
Теперь можно оценить, сколько места уже занимает GitLab.
Например:
sudo du -sh /var/opt/gitlab
Отдельно посмотрим Docker:
sudo docker system df
После этого запускаем новый pipeline.
Чтобы нагрузка была заметнее обычного echo, можно временно добавить в тестовый Job несколько простых операций, не создающих опасной нагрузки. Но значения лучше снимать уже на том pipeline, который действительно будет использоваться в статье.
Пока Job выполняется, повторяем:
free -h
uptime
ps aux --sort=-%cpu | head
и:
df -h
Если хотим отдельно увидеть запущенные containers:
sudo docker ps
А для текущего потребления ресурсов Docker:
sudo docker stats
Эта команда особенно полезна во время Job: она показывает CPU и RAM конкретного container почти в реальном времени.
После завершения pipeline снимем показатели еще раз:
free -h
uptime
df -h
И посмотрим, изменился ли объем Docker data:
sudo docker system df
Если использовались artifacts или cache, можно дополнительно проверить рост каталогов GitLab:
sudo du -sh /var/opt/gitlab
Основные команды для измерения удобно оставить в одном месте:
Что смотрим
Команда
RAM и swap
free -h
Load average
uptime
Процессы по RAM
ps aux --sort=-%mem | head
Процессы по CPU
ps aux --sort=-%cpu | head
Нагрузка в реальном времени
top
Свободное место
df -h
Размер данных GitLab
sudo du -sh /var/opt/gitlab
Docker disk usage
sudo docker system df
Ресурсы container
sudo docker stats
После реального теста сюда останется подставить фактические значения и коротко описать результат: насколько выросла RAM, что произошло с load average и появился ли заметный расход диска.
На этом мы уже получим практический ответ на вопрос, выдерживает ли один VPS одновременно GitLab и небольшой Runner. Дальше можно переходить к другой критичной части эксплуатации — резервному копированию самого GitLab.
Настраиваем резервное копирование GitLab
Что входит в GitLab backup
Стандартная команда GitLab создает резервную копию основных данных инстанса.
В нее могут входить:
PostgreSQL database;
Git repositories;
project и group wikis;
uploads и attachments;
CI job logs;
CI artifacts;
Git LFS objects;
GitLab Pages;
package registry data;
Terraform state;
project-level Secure Files;
Container Registry data, если оно хранится локально и соответствует используемой конфигурации.
То есть backup охватывает не только исходный код.
Это важно, потому что значительная часть состояния GitLab находится в PostgreSQL. Там хранятся пользователи, проекты, permissions, issues, merge requests, CI/CD metadata и многие другие объекты.
Упрощенно резервная копия объединяет несколько типов данных:
Поэтому копирование одного каталога с repositories не заменяет полноценный backup.
Но и сам gitlab-backup не сохраняет абсолютно весь сервер.
Что не входит в обычный backup автоматически
Есть несколько важных исключений.
Стандартный GitLab backup не включает:
/etc/gitlab;
gitlab.rb;
gitlab-secrets.json;
TLS certificates и private keys;
SSH host keys;
другие системные файлы;
Redis state, включая текущие Sidekiq jobs;
данные, вынесенные во внешнее Object Storage, в стандартном сценарии Linux package.
Последний пункт особенно важен.
Если artifacts, uploads, LFS или Registry уже перенесены в S3-compatible Object Storage, одного:
sudo gitlab-backup create
может быть недостаточно. Само объектное хранилище нужно резервировать своим способом.
Для нашей небольшой single-node установки пока можно работать с локальным storage. Но даже здесь остаются два файла, без которых восстановление может превратиться в серьезную проблему:
Почему отдельно нужно сохранять конфигурацию и secrets
gitlab.rb содержит конфигурацию самого инстанса.
Именно там находятся параметры вроде:
external_url "https://gitlab.example.com"
а также настройки backup, Nginx, SMTP, storage и других компонентов.
Без этого файла данные можно восстановить, но придется заново воспроизводить конфигурацию сервера.
Еще важнее: /etc/gitlab/gitlab-secrets.json
Этот файл содержит криптографические secrets, которые GitLab использует для работы с зашифрованными данными.
Например, база может содержать значения в зашифрованном виде. Если восстановить database, но потерять соответствующие ключи, приложение уже не сможет корректно расшифровать часть этих данных.
Поэтому ситуация:
есть gitlab_backup.tar
нет gitlab-secrets.json
может оказаться намного хуже, чем кажется.
При этом secrets специально не складываются внутрь основного backup archive. Хранить зашифрованные данные и ключ, которым они расшифровываются, в одном и том же месте — плохая идея.
Поэтому делаем два независимых набора:
backup данных GitLab;
защищенную копию конфигурации и secrets.
И желательно хранить их отдельно друг от друга.
Где GitLab хранит резервные копии
Для Linux package стандартный каталог backup:
/var/opt/gitlab/backups
Созданный архив будет выглядеть примерно так:
<backup-id>_gitlab_backup.tar
Проверить содержимое каталога можно командой:
sudo ls -lh /var/opt/gitlab/backups
Конкретное место хранения можно изменить через /etc/gitlab/gitlab.rb.
Но хранить единственную копию backup на том же VPS — слабая стратегия.
Если сервер потерян вместе с диском, исчезнут и GitLab, и его резервная копия.
Поэтому локальный каталог лучше рассматривать как промежуточную точку, откуда архив потом переносится в другое хранилище.
Как планировать хранение backup
Для небольшого GitLab достаточно простой схемы.
Например:
Ежедневный backup;
Несколько последних ежедневных копий;
Одна или несколько более старых контрольных копий;
Отдельное внешнее хранилище;
Отдельная защищенная копия /etc/gitlab.
Это может быть:
Второй сервер;
NAS;
S3-compatible Object Storage;
Отдельный backup volume.
Главный принцип здесь простой: backup не должен исчезнуть вместе с GitLab VPS.
Полезно учитывать и объем данных. Если GitLab занимает десятки гигабайт, ежедневное бесконечное накопление архивов быстро заполнит storage.
Для Linux package можно задать срок хранения старых локальных backup.
Например:
gitlab_rails['backup_keep_time'] = 604800
604800 секунд — 7 суток.
Это помогает автоматически удалять старые локальные архивы, но не заменяет retention policy во внешнем storage.
И еще один важный момент: сам факт успешного создания .tar не доказывает, что backup действительно пригоден к восстановлению. Полноценную проверку мы сделаем в следующей главе через restore.
А пока создадим сам архив.
Практика: создаем резервную копию
Перед началом полезно посмотреть текущее свободное место:
df -h
Backup может временно потребовать заметный объем диска, особенно если repositories и artifacts уже выросли.
Теперь запускаем стандартную команду:
sudo gitlab-backup create
GitLab последовательно обработает компоненты и создаст итоговый archive.
После завершения проверяем каталог:
sudo ls -lh /var/opt/gitlab/backups
В нем должен появиться файл вида:
<backup-id>_gitlab_backup.tar
Теперь отдельно сохраним конфигурацию.
Можно создать защищенный архив /etc/gitlab:
sudo tar -czf /root/gitlab-config-$(date +%F).tar.gz /etc/gitlab
После этого проверим:
sudo ls -lh /root/gitlab-config-*.tar.gz
Такой archive содержит чувствительные данные, включая secrets и потенциально TLS private keys. Его нельзя хранить в публичном repository или обычной общей папке.
Еще лучше — после создания перенести основной backup и конфигурационный archive на отдельное защищенное хранилище.
Для нашей установки схема получается такой:
Что резервируем
Чем
Где хранить
Database, repositories, artifacts и основные данные GitLab
gitlab-backup create
Сначала /var/opt/gitlab/backups, затем внешнее storage
gitlab.rb
Копия /etc/gitlab
Отдельное защищенное storage
gitlab-secrets.json
Копия /etc/gitlab
Особенно защищенное storage
TLS и SSH keys
Системный/config backup
Отдельно от основного VPS
Данные во внешнем Object Storage
Средствами самого storage
Независимая backup/retention policy
На этом резервная копия создана, но пока мы проверили только первую половину процесса.
Восстанавливаем GitLab из резервной копии
Теперь нужно проверить вторую, более важную половину процесса — можно ли из нее действительно восстановить GitLab.
Сам restore технически несложен, но здесь есть несколько принципиальных нюансов. Нельзя просто взять любой .tar, развернуть его поверх произвольной версии GitLab и ожидать, что все заработает. Перед восстановлением нужно проверить версию, вернуть конфигурацию и secrets, остановить часть сервисов и понимать, что произойдет с текущими данными.
Почему версия GitLab при восстановлении имеет значение
GitLab backup привязан не только к данным, но и к версии приложения.
Для корректного восстановления целевой инстанс должен работать на той же версии и редакции GitLab, на которой был создан backup.
Например, если резервная копия была сделана на:
GitLab CE 18.x.y
то восстанавливать ее нужно сначала на такой же GitLab CE 18.x.y, а уже после успешного restore обновлять инстанс обычным способом.
Нельзя рассчитывать на сценарий: старый backup → совсем новая версия GitLab → restore напрямую
Причина в том, что между версиями меняются структура database, migrations, форматы данных и внутренняя логика приложения.
Поэтому вместе с backup полезно сохранять информацию о версии:
sudo gitlab-rake gitlab:env:info
или хотя бы:
sudo gitlab-rake gitlab:env:info | grep "GitLab information" -A 10
Это сильно упрощает восстановление после полного выхода VPS из строя.
Но одной подходящей версии недостаточно. Перед restore нужно вернуть еще несколько вещей.
Что подготовить перед restore
Для восстановления понадобятся три основных компонента:
Чистая установка GitLab нужной версии;
Основной backup archive;
Сохраненная конфигурация из /etc/gitlab.
Особенно важен файл: /etc/gitlab/gitlab-secrets.json
Если он сохранился, его нужно вернуть до полноценной проверки восстановленного GitLab.
Также желательно восстановить: /etc/gitlab/gitlab.rb
чтобы вернуть тот же external_url, настройки storage, SMTP, backup и другие параметры инстанса и не настраивать их вручную.
Если TLS certificates и SSH host keys резервировались отдельно, их тоже лучше вернуть на прежние места.
Сам backup archive нужно скопировать в каталог GitLab: /var/opt/gitlab/backups
Имя реального файла будет зависеть от версии и времени создания backup.
Проверим:
sudo ls -lh /var/opt/gitlab/backups
GitLab должен иметь возможность читать этот файл, поэтому при переносе с другого сервера стоит проверить owner и permissions.
Когда файлы подготовлены, можно переходить к остановке сервисов.
Почему перед восстановлением останавливают часть сервисов
Во время restore GitLab меняет database и возвращает данные из резервной копии.
Если в этот момент пользователи продолжают отправлять commits, создавать issues или запускать pipeline, появляется риск смешать текущее состояние с восстанавливаемым.
Поэтому запись данных нужно временно остановить.
При этом полностью выключать весь GitLab до запуска restore не требуется. Для процесса восстановления должны оставаться доступны необходимые внутренние компоненты, в частности PostgreSQL.
Обычно перед restore останавливают процессы приложения:
PostgreSQL и остальные необходимые сервисы должны продолжать работать.
Логика здесь простая: пользовательские и фоновые операции временно прекращаются, но сама инфраструктура, нужная утилите восстановления, остается доступной.
После этого уже можно безопаснее менять состояние инстанса.
Что происходит с текущими данными при restore
Restore не объединяет backup с текущим GitLab.
Он возвращает состояние из резервной копии.
Это означает, что данные, появившиеся после момента создания backup, могут быть потеряны.
Представим:
12:00 — создан backup
13:00 — появился новый commit
14:00 — создан новый issue
15:00 — выполняем restore backup от 12:00
После восстановления GitLab возвращается к состоянию резервной копии. Commit и issue, появившиеся позже, автоматически туда не добавятся.
Именно поэтому restore нельзя воспринимать как безобидную диагностическую команду на рабочем production-инстансе.
Перед восстановлением обычно:
Останавливают пользовательскую активность;
Убеждаются, что выбран правильный backup;
При необходимости делают дополнительную копию текущего состояния;
Только после этого запускают restore.
Для тестового стенда риск небольшой, но последовательность действий стоит отработать сразу правильно.
Практика: выполняем восстановление
Сначала проверим, какой backup доступен:
sudo ls -lh /var/opt/gitlab/backups
Допустим, файл называется:
1760000000_2026_10_02_18.x.y_gitlab_backup.tar
Для restore GitLab обычно нужен backup ID без суффикса _gitlab_backup.tar.
То есть используем:
1760000000_2026_10_02_18.x.y
Перед восстановлением еще раз убедимся, что версия GitLab совпадает с версией backup.
Не переживайте: после восстановления мы выполним gitlab-ctl restart, и ранее остановленные Puma и Sidekiq будут запущены снова вместе с остальными компонентами GitLab.
GitLab предупредит, что текущие данные database будут заменены. Нужно внимательно проверить выбранный backup и подтвердить операцию.
После завершения restore восстановим конфигурацию, если она была потеряна вместе со старым VPS.
Например, из заранее сохраненного архива:
sudo tar -xzf /root/gitlab-config-2026-10-02.tar.gz -C /
Перед таким распаковыванием нужно понимать содержимое архива: он должен действительно содержать ожидаемый /etc/gitlab, а не неизвестные файлы из стороннего источника.
Проверим наличие основных файлов:
sudo ls -l /etc/gitlab/gitlab.rb
sudo ls -l /etc/gitlab/gitlab-secrets.json
Теперь применяем конфигурацию:
sudo gitlab-ctl reconfigure
После этого перезапускаем GitLab:
sudo gitlab-ctl restart
И проверяем состояние:
sudo gitlab-ctl status
Полезно выполнить и встроенные проверки:
sudo gitlab-rake gitlab:check SANITIZE=true
После восстановления логика получается такой:
Основные команды можно оставить в короткой таблице:
Этап
Команда
Посмотреть backup
sudo ls -lh /var/opt/gitlab/backups
Остановить веб-приложение
sudo gitlab-ctl stop puma
Остановить фоновые задачи
sudo gitlab-ctl stop sidekiq
Запустить restore
sudo gitlab-backup restore BACKUP=<backup-id>
Применить конфигурацию
sudo gitlab-ctl reconfigure
Перезапустить GitLab
sudo gitlab-ctl restart
Проверить сервисы
sudo gitlab-ctl status
Проверить GitLab
sudo gitlab-rake gitlab:check SANITIZE=true
На уровне команд восстановление завершено, но этого еще недостаточно, чтобы объявить процедуру успешной.
В следующем разделе проверим уже само содержимое GitLab: убедимся, что проекты и repositories вернулись, Runner по-прежнему доступен, а новый pipeline действительно запускается после restore.
Проверяем GitLab после восстановления
Что проверить в первую очередь
Начнем с самого инстанса.
Откроем GitLab по привычному адресу: https://gitlab.example.com
Нужно проверить, что:
Страница открывается по HTTPS;
Можно войти под существующим пользователем;
Проекты отображаются;
Группы и настройки на месте;
Нет явных ошибок интерфейса.
Затем посмотрим состояние внутренних сервисов:
sudo gitlab-ctl status
Если какой-то компонент не запущен, уже на этом этапе стоит остановиться и проверить его отдельно.
Полезно также выполнить:
sudo gitlab-rake gitlab:check SANITIZE=true
Эта команда помогает быстро заметить проблемы с конфигурацией, permissions и внутренними компонентами.
Если базовый GitLab работает, можно переходить к repositories.
Как убедиться, что repositories восстановлены
Одного наличия проекта в веб-интерфейсе недостаточно.
Проект может отображаться в database, но repository при этом быть поврежден или недоступен. Поэтому лучше проверить Git-операции отдельно.
Если push проходит успешно, значит GitLab не только читает восстановленный repository, но и принимает новые изменения после restore.
Теперь можно проверять CI-часть.
Что проверить у Runner после restore
Сам GitLab Runner обычно является отдельным сервисом со своей локальной конфигурацией:
/etc/gitlab-runner/config.toml
Поэтому восстановление GitLab не означает автоматическое восстановление файлов Runner.
Если Runner остался на том же VPS и его конфигурация не была удалена, сервис может продолжить работать как раньше.
Проверим:
sudo systemctl is-active gitlab-runner
Затем:
sudo gitlab-runner verify
После этого стоит открыть Settings → CI/CD → Runners и убедиться, что demo-docker-runner отображается как доступный.
Если Runner online, это уже хороший признак, но окончательная проверка — новый Job.
И здесь возникает важный вопрос: всегда ли после restore Runner нужно регистрировать заново?
Нужно ли повторно регистрировать Runner
Не обязательно.
Если GitLab Runner сохранил свой:
/etc/gitlab-runner/config.toml
а восстановленный GitLab содержит прежнюю запись Runner и связанные с ней данные, существующая регистрация может продолжить работать.
То есть при восстановлении того же инстанса обычно сначала стоит проверить текущий Runner, а не сразу регистрировать новый.
Повторная регистрация понадобится, если:
VPS Runner был потерян;
Config.toml не сохранился;
Runner был удален из GitLab;
Authentication token больше не действует;
Меняется сам GitLab instance;
Старую регистрацию намеренно решили заменить.
Это важно, потому что лишняя повторная регистрация может просто создать дубликат Runner в интерфейсе.
Поэтому правильная последовательность такая:
После этого остается проверить pipeline целиком.
Команды проверки после восстановления
Начнем с сервисов GitLab:
sudo gitlab-ctl status
Проверим внутреннее состояние:
sudo gitlab-rake gitlab:check SANITIZE=true
Посмотрим информацию о среде:
sudo gitlab-rake gitlab:env:info
Проверим Runner:
sudo systemctl is-active gitlab-runner
и:
sudo gitlab-runner verify
Теперь создадим новый commit в demo-app:
echo "CI after restore" >> README.md
git add README.md
git commit -m "Test CI after restore"
git push
После push переходим в: Build → Pipelines
Новый pipeline должен пройти ту же цепочку, что и до восстановления.
Если stages завершаются со статусом passed, значит мы проверили сразу несколько вещей:
GitLab принимает push;
Repository доступен;
CI-конфигурация читается;
Runner связан с GitLab;
Docker executor запускается;
Job выполняются после restore.
Коротко всю проверку можно свести к четырем уровням:
Уровень
Что проверяем
Пример проверки
GitLab
Интерфейс и внутренние сервисы
gitlab-ctl status
Repository
Clone, fetch и push
git clone, git fetch, git push
Runner
Доступность и регистрация
gitlab-runner verify
Pipeline
Полная CI-цепочка
Новый commit и pipeline
Если все четыре уровня работают, восстановление можно считать подтвержденным не только на уровне команды restore, но и на уровне реальной эксплуатации.
Теперь у нас есть рабочая backup/restore-цепочка. Следующий блок будет уже про эксплуатационные проблемы: разберем, почему CI Job может застрять в pending, почему Runner иногда не забирает задание и как быстро локализовать такую ситуацию.
Диагностируем зависшие CI-задания
Что значит job в состоянии pending
Статус pending означает, что GitLab уже создал Job, но выполнение еще не началось.
То есть .gitlab-ci.yml был обработан, pipeline существует, однако Runner пока не забрал задание.
Сам по себе короткий pending нормален. Runner может быть занят предыдущим Job или просто еще не успел запросить новое задание.
Проблема начинается, когда Job остается в этом состоянии долго.
Тогда нужно проверить несколько вещей:
Есть ли вообще активный Runner;
Доступен ли он этому project;
Совпадают ли tags;
Разрешено ли выполнять untagged Job;
Не достигнут ли лимит concurrency;
Не paused ли Runner;
Нет ли ограничений для protected branches.
Например, если наш Runner уже выполняет один Job, а в config.toml установлено:
concurrent = 1
второй Job может ждать освобождения Runner. Это не ошибка — просто очередь.
Если же свободный Runner есть, а Job все равно не запускается, нужно искать причину глубже.
Почему Runner может не забирать Job
Сам факт, что GitLab показывает Runner в интерфейсе, еще не гарантирует, что он сможет выполнить конкретное задание.
Одна из первых проверок — состояние локального сервиса:
sudo systemctl is-active gitlab-runner
Если сервис остановлен, GitLab не сможет передать ему новый Job.
Следом проверяем регистрацию:
sudo gitlab-runner verify
Если здесь появляется ошибка связи или аутентификации, проблема уже находится между Runner и GitLab.
Runner также может быть доступен, но занят другим заданием. Для нашей конфигурации это особенно актуально, потому что мы сознательно ограничили параллельность одним Job.
Проверить текущие процессы можно через:
ps aux | grep gitlab-runner
а Docker containers:
sudo docker ps
Если активный Job уже выполняется, следующий вполне закономерно будет ждать.
Еще одна группа причин связана не с самим сервисом Runner, а с условиями, которые GitLab использует при его выборе.
Как tags могут оставить Job без подходящего Runner
Tags — одна из самых частых причин бесконечного pending.
В нашем стенде Runner получил tag:
docker
А Job использует:
tags:
- docker
Эти значения должны совпадать.
Если случайно написать:
tags:
- docker-runner
или:
tags:
- linux
при отсутствии соответствующего tag у Runner GitLab не сможет назначить Job.
Еще один типичный случай — несколько tags одновременно:
tags:
- docker
- linux
Runner должен подходить по всему требуемому набору.
Если у него есть только:
docker
такой Job ему уже не назначается.
Поэтому при зависшем pending стоит открыть настройки Runner в GitLab и буквально сравнить список tags с .gitlab-ci.yml.
Есть и обратная ситуация: Job вообще не содержит tags, а Runner запрещено выполнять untagged Job. Тогда он также останется без исполнителя.
То есть первое правило диагностики здесь простое: если Job еще не начал выполняться, сначала проверяем соответствие Runner, а не команды внутри script.
Что значит stuck job
Термин stuck job обычно используют для Job, который не может быть назначен подходящему Runner.
То есть фактически Job существует, но условия для его выполнения не выполняются.
GitLab может прямо указывать, что Job stuck из-за отсутствия Runner, подходящего по tags или другим параметрам.
Типичные причины:
Все Runner offline;
Runner paused;
Нет Runner с нужными tags;
Runner не разрешено выполнять untagged Job;
Runner недоступен этому project;
Job относится к protected branch, а Runner не настроен соответствующим образом;
Все подходящие Runner заняты;
Достигнут concurrency limit.
Здесь полезно разделять два уровня:
Stuck/pending до старта — проблема выбора или доступности Runner;
Running, но ничего не происходит — Job уже получил Runner, значит искать нужно внутри выполнения.
Это сильно сокращает диагностику.
Почему Job может зависнуть уже после запуска
Если статус изменился на running, GitLab уже нашел Runner и передал ему задание.
Значит tags, регистрация и базовая связь с GitLab, скорее всего, отработали.
Дальше проблема может быть уже внутри Job.
Например, команда ожидает пользовательский ввод:
some-command
и программа открывает interactive prompt. В CI никто не нажмет Y, поэтому Job может стоять бесконечно.
Похожая ситуация возникает с package managers, если забыть non-interactive режим.
Еще одна частая причина — сетевое ожидание.
Job может пытаться:
Скачать dependencies;
Подключиться к API;
Обратиться к database;
Получить package из внешнего registry;
Разрешить DNS-имя.
Если внешний сервис не отвечает, команда может долго ждать timeout.
Для Docker executor возможны и отдельные проблемы:
Image не скачивается;
Docker daemon недоступен;
Недостаточно места;
Container уперся в memory limit;
Завис filesystem I/O;
Registry требует authentication.
Например, состояние Docker полезно проверить так:
sudo systemctl is-active docker
Свободное место:
df -h
Память:
free -h
А работающие containers:
sudo docker ps
Сам Job log при этом остается главным источником информации. Последняя строка часто показывает, на какой именно операции остановилось выполнение.
Если Job завис на: Installing dependencies...
нужно проверять уже package manager и сеть, а не регистрацию Runner.
Команды диагностики Runner и CI
Начнем с самого Runner:
sudo systemctl status gitlab-runner
Для короткой проверки:
sudo systemctl is-active gitlab-runner
Теперь проверим регистрацию:
sudo gitlab-runner verify
Если сервис работает, посмотрим его последние logs:
При этом authentication token из вывода не стоит копировать в screenshots или документацию.
В GitLab со своей стороны нужно проверить:
Runner online или offline;
Не paused ли он;
Его tags;
Разрешены ли untagged Job;
Доступен ли он нужному project;
Соответствует ли protected-статус;
Какой Runner назначен проблемному Job.
Для быстрого поиска причины удобно идти по симптомам:
Симптом
Вероятная причина
Что проверить
Решение
Job долго pending
Нет подходящего Runner
Runner status, tags, project access
Подключить или исправить подходящий Runner
Runner offline
Сервис остановлен или нет связи
systemctl, gitlab-runner verify, logs
Запустить сервис, проверить URL/token/network
Job требует docker, но не запускается
Tags не совпадают
.gitlab-ci.yml и Runner tags
Привести tags к одному набору
Untagged Job остается pending
Runner не принимает untagged Job
Настройки Runner
Разрешить untagged Job или добавить tag
Второй Job ждет первый
concurrent = 1 / limit = 1
config.toml, активные Job
Дождаться выполнения или увеличить лимит
Job running, но остановился на команде
Скрипт завис или ждет input
Job log
Сделать команду non-interactive, добавить timeout
Docker Job не стартует
Проблема Docker daemon/image
systemctl docker, docker ps -a, Runner log
Исправить Docker или доступ к registry
Job неожиданно падает/зависает
Нехватка RAM или диска
free -h, df -h, logs
Освободить ресурсы или увеличить лимиты
Главное здесь — не пытаться диагностировать CI хаотично. Сначала определяем состояние Job.
Если он еще pending, проверяем GitLab → Runner → tags → ограничения.
Если он уже running, проверяем Runner → executor → container → конкретную команду из Job log.
Так проблема локализуется намного быстрее.
Что делать, если Runner offline
В прошлом разделе мы разбирали Job, которые зависают в pending или уже во время выполнения. Теперь возьмем более очевидный случай: GitLab вообще перестал считать Runner доступным и показывает его как offline.
Здесь проблема обычно находится не внутри .gitlab-ci.yml, а уровнем ниже — в самом сервисе Runner, его связи с GitLab или параметрах регистрации. Поэтому диагностику лучше начинать не с pipeline, а с локального процесса на VPS.
Почему GitLab перестает видеть Runner
GitLab считает Runner доступным, пока тот регулярно связывается с сервером и запрашивает новые задания.
Если эти обращения прекращаются, через некоторое время Runner начинает отображаться как недоступный.
Причины могут быть довольно разными:
Остановился сервис gitlab-runner;
VPS перезагрузился, а сервис не стартовал;
Пропала сеть;
Runner больше не может разрешить DNS-имя GitLab;
Возникла TLS-ошибка;
Изменился URL GitLab;
Authentication token стал недействительным;
Поврежден или удален config.toml;
Firewall или другая сетевая настройка блокирует соединение.
При этом GitLab и Runner могут находиться даже на одном VPS, но все равно связываться через указанный URL:
url = "https://gitlab.example.com"
Поэтому рабочий веб-интерфейс GitLab еще не гарантирует, что Runner способен установить соединение именно со своей стороны.
Первое, что стоит проверить, — работает ли вообще локальный сервис.
Как проверить systemd service
Runner устанавливается как systemd service, поэтому начинаем с простой команды:
sudo systemctl status gitlab-runner
Если все нормально, сервис должен находиться в состоянии:
active (running)
Для быстрой проверки без подробного вывода подойдет:
sudo systemctl is-active gitlab-runner
Если получаем: inactive или failed
Runner уже не сможет получать новые Job.
Попробуем запустить его:
sudo systemctl start gitlab-runner
И снова проверим:
sudo systemctl status gitlab-runner
Чтобы Runner автоматически стартовал после reboot:
sudo systemctl enable gitlab-runner
Если сервис запускается и сразу падает, простой restart мало поможет. Нужно посмотреть причину в logs:
В логах уже можно увидеть ошибки чтения конфигурации, проблемы с сетью или другие причины завершения процесса.
Если systemd показывает active, а GitLab все равно считает Runner offline, следующий уровень — связь между ними.
Как проверить связь Runner с GitLab
Для проверки зарегистрированных Runner используем:
sudo gitlab-runner verify
Команда проверяет, может ли локальный Runner обратиться к GitLab и пройти аутентификацию.
Если все в порядке, соответствующий Runner должен пройти verification.
Если появляется ошибка, нужно понять ее уровень.
При проблемах с DNS проверим домен:
getent hosts gitlab.example.com
или:
dig +short gitlab.example.com
Теперь HTTPS:
curl -I https://gitlab.example.com
Если curl не может установить TLS-соединение, Runner, скорее всего, столкнется с той же проблемой.
Можно отдельно проверить доступность GitLab с самого VPS:
curl -I https://gitlab.example.com/users/sign_in
Если DNS и HTTPS работают, стоит посмотреть локальную конфигурацию:
sudo nano /etc/gitlab-runner/config.toml
В секции Runner прежде всего проверяем:
[[runners]]
name = "demo-docker-runner"
url = "https://gitlab.example.com"
executor = "docker"
Особенно внимательно — url.
Даже небольшая ошибка вроде старого hostname или перехода GitLab на другой домен может полностью оборвать связь.
Если URL правильный, остается проверить регистрацию и authentication token.
Что происходит после изменения URL или токена
Здесь полезно разделить два разных изменения.
Если меняется адрес самого GitLab, например:
https://gitlab-old.example.com
на:
https://gitlab.example.com
старый Runner может продолжать обращаться по адресу, который записан в его config.toml.
Тогда нужно обновить:
url = "https://gitlab.example.com"
и перезапустить сервис:
sudo systemctl restart gitlab-runner
С authentication token ситуация строже.
Runner использует его для подтверждения своей регистрации перед GitLab. Если token был отозван или Runner пересоздан в интерфейсе, старое значение больше не позволит нормально работать.
В таком случае недостаточно просто бесконечно выполнять:
sudo systemctl restart gitlab-runner
Сервис будет запускаться, но связь с GitLab не восстановится.
Если выдан новый authentication token, нужно либо корректно обновить регистрацию, либо удалить старую конфигурацию Runner и зарегистрировать его заново.
Например, сначала можно посмотреть текущие регистрации:
sudo gitlab-runner list
Если старый Runner больше не нужен, его можно unregister:
Если после этого Runner снова отображается online, полезно запустить небольшой тестовый pipeline. Так мы проверим не только heartbeat между Runner и GitLab, но и полный путь до Docker executor и выполнения Job.
Дальше разберем противоположную ситуацию: Runner online, Job запускается, но сам pipeline завершается ошибкой. Здесь уже нужно отделить проблему инфраструктуры от ошибки внутри команд CI.
Диагностируем pipeline, который запускается, но падает
Чем ошибка pipeline отличается от проблемы Runner
Если Job вообще не стартует и остается в pending, мы проверяем Runner, tags, регистрацию и concurrency.
Если Job уже перешел в running, а потом стал failed, значит GitLab нашел Runner, Runner получил задание, а executor начал работу.
В итоге цепочка диагностики получается довольно короткой:
Смотрим статус Job;
Открываем Job log;
Находим первую реальную ошибку;
Определяем уровень;
Проверяем image, variables, files, network или resources;
Исправляем;
Запускаем pipeline повторно.
Для быстрого поиска причины можно оставить таблицу:
Ошибка
Уровень
Что проверить
command not found
Окружение
Container image и установленные packages
No such file or directory
Repository / path
Наличие файла и рабочий каталог
Permission denied
Permissions
Права на файл и executable bit
connection refused
Network / service
Адрес, порт, доступность сервиса
Could not resolve host
DNS
DNS внутри container
Variable empty
CI/CD variables
Наличие variable, scope, protected/masked
Tests return exit code 1
Application
Test log и код приложения
Job killed / OOM
Resources
RAM limit, free -h, Docker stats
Artifact missing
Pipeline
artifacts, paths и порядок stages
Если Runner online и Job стартует, в большинстве случаев проблему уже можно локализовать по Job log без вмешательства в сам сервис Runner.
На этом диагностику CI можно считать закрытой по основным сценариям. Дальше логично пройти всю установку одним контрольным маршрутом и убедиться, что GitLab, repository, Runner, pipeline, backup и restore работают как единая система.
Проверяем развертывание целиком
Начнем с самого GitLab и его системных сервисов:
Что проверяем
Что должно быть
Проверка
GitLab
Инстанс открывается по HTTPS
https://gitlab.example.com
Внутренние сервисы
Основные компоненты запущены
sudo gitlab-ctl status
Конфигурация
GitLab проходит внутреннюю проверку
sudo gitlab-rake gitlab:check SANITIZE=true
Диск
Есть запас свободного места
df -h
HTTPS
Сертификат принимается без ошибок
curl -I https://gitlab.example.com
Если этот уровень работает, значит основная платформа доступна пользователям и готова принимать Git-операции.
Следом проверяем уже CI-часть. Здесь важно не ограничиваться статусом online в интерфейсе GitLab: Runner должен действительно получить новое задание и успешно выполнить его.
Что проверяем
Что должно быть
Проверка
Runner service
Сервис активен
sudo systemctl is-active gitlab-runner
Регистрация
Runner связывается с GitLab
sudo gitlab-runner verify
Docker
Executor может создавать containers
sudo systemctl is-active docker
Repository
Новый commit отправляется в GitLab
git push
Pipeline
Job получает Runner и выполняется
Build → Pipelines
Результат
Все обязательные stages завершены
Статус passed
Самый полезный тест здесь — небольшой новый commit. Он проходит практически всю рабочую цепочку сразу: GitLab принимает push, создает pipeline, Runner забирает Job, Docker executor запускает container, а итог возвращается в интерфейс.
Но даже полностью исправный CI еще не делает развертывание надежным. Последний уровень — проверить, что GitLab можно восстановить после потери данных или самого VPS.
Что проверяем
Что должно быть
Проверка
GitLab backup
Архив успешно создается
sudo gitlab-backup create
Backup-файл
Архив присутствует и имеет ожидаемый размер
sudo ls -lh /var/opt/gitlab/backups
Config и secrets
/etc/gitlab сохраняется отдельно
Проверка внешней копии
Внешнее хранение
Backup не остается только на GitLab VPS
Проверка backup storage
Restore
Данные восстанавливаются на совместимой версии
Тестовое восстановление
После restore
Repository, Runner и pipeline работают
git push + новый pipeline
Если все три уровня проходят проверку, инфраструктуру уже можно рассматривать как цельную систему, а не просто набор запущенных сервисов.
Итоговый путь выглядит так:
GitLab принимает и хранит изменения, pipeline описывает необходимую автоматизацию, Runner забирает подходящий Job, а executor создает среду, в которой выполняются команды.
При этом вокруг основной цепочки работают еще два важных механизма: контроль ресурсов и backup. Первый не дает CI незаметно вытеснить сам GitLab с VPS, а второй позволяет восстановить систему после серьезного сбоя.
Развертывание теперь проверено целиком. Осталось уже не столько запускать новые компоненты, сколько правильно эксплуатировать существующие: ограничить права Runner, защитить CI/CD secrets, следить за диском, обновлять GitLab и регулярно проверять не только создание backup, но и возможность восстановления.
Как безопасно использовать GitLab и Runner в production
Не выполнять недоверенный код на привилегированном Runner
Runner фактически исполняет команды из .gitlab-ci.yml.
Поэтому любой пользователь, способный изменить CI-конфигурацию и запустить pipeline, потенциально влияет на среду Runner.
Особенно опасен privileged mode Docker executor: privileged = true
Он значительно расширяет возможности container и уменьшает изоляцию от host system.
Если такой режим действительно не нужен, оставляем: privileged = false
Для нашего Runner именно так и настроено.
Еще осторожнее стоит относиться к host volumes.
Например, проброс Docker socket:
/var/run/docker.sock
внутрь CI container фактически дает Job очень широкие возможности управления Docker на хосте. Для сервера, где одновременно работает сам GitLab, это особенно чувствительно.
Поэтому недоверенные проекты лучше не запускать на Runner, который имеет повышенные права или доступ к критичным ресурсам основного VPS.
Если CI становится сложнее, безопаснее вынести такие Runner на отдельные машины.
Ограничивать доступ к CI/CD variables
CI/CD variables часто содержат наиболее чувствительные данные pipeline:
API tokens;
Passwords;
Deployment keys;
Credentials Registry;
Access tokens.
Поэтому секрет лучше не просто создать, а сразу подумать, каким pipeline он вообще нужен.
Для чувствительных значений стоит использовать masking и при необходимости protected scope.
Например, production token логично выдавать только pipeline защищенной ветки:
main
а feature branches вообще не должны получать к нему доступ.
Это особенно важно для проектов с несколькими разработчиками. Если пользователь способен изменить .gitlab-ci.yml, а Job автоматически получает production secret, секрет уже нельзя считать хорошо изолированным.
То есть защита CI/CD variables — это не только флажок Masked, но и контроль того, кто способен запустить код с доступом к переменной.
Не хранить токены Runner и secrets в репозитории
Секреты не должны попадать в Git history.
Это касается не только deployment passwords, но и служебных данных самой инфраструктуры:
Runner authentication token;
Personal access tokens;
API keys;
Private SSH keys;
Содержимое gitlab-secrets.json;
Пароли database;
Private TLS keys.
Например, нельзя добавлять в repository настоящий:
/etc/gitlab-runner/config.toml
потому что реальный файл содержит authentication data Runner.
То же самое относится к:
/etc/gitlab/gitlab-secrets.json
Даже private repository не является подходящим местом для таких файлов.
Если secret случайно попал в Git, одного удаления строки недостаточно. Нужно считать значение скомпрометированным, отозвать его и выпустить новое.
Разделять protected и обычные branches
Не все branches имеют одинаковую ценность.
Обычная feature branch используется для разработки и экспериментов. main или другой production branch уже может запускать deployment и получать production secrets.
Поэтому их права тоже должны различаться.
Protected branches позволяют ограничить:
Кто может выполнять push;
Кто может merge;
Какие CI/CD variables доступны;
Какие Runner могут выполнять соответствующие Job.
Например, разработчик может создавать feature branches и запускать в них тесты, но deployment в production выполняется только после merge в protected main.
Так CI превращается не просто в автоматизацию, а в дополнительный контрольный слой между обычной разработкой и production.
Контролировать свободное место
Для GitLab свободный диск — такой же ресурс, как RAM и CPU.
Repositories растут. Pipeline создают artifacts. Docker накапливает images и layers. GitLab пишет logs. Backup также требуют места.
При этом проблема может появиться постепенно:
Сегодня: Disk usage: 45%
Через несколько месяцев: Disk usage: 82%
А после крупного backup или нескольких тяжелых pipeline сервер внезапно оказывается почти заполнен.
Поэтому регулярно стоит проверять (а ещё лучше - настроить автоматический мониторинг):
df -h
Размер данных GitLab:
sudo du -sh /var/opt/gitlab
и Docker:
sudo docker system df
Для production лучше не ждать 100% заполнения диска, а реагировать заранее.
Кроме очистки старых данных, имеет смысл настроить retention для artifacts, packages и backup в соответствии с реальными требованиями проекта.
Хранить backup вне GitLab VPS
Локальный backup удобен, но он не защищает от потери или компрометации самого сервера.
Если GitLab + backup лежат на одном virtual disk, повреждение или удаление VPS уничтожит оба.
Поэтому после создания:
sudo gitlab-backup create
архив нужно переносить во внешнее хранилище.
Подойдет:
Отдельный backup server;
NAS;
Другой volume;
S3-compatible Object Storage.
При этом /etc/gitlab с gitlab-secrets.json тоже нужно резервировать, но хранить особенно аккуратно из-за чувствительных данных.
Хороший backup должен переживать потерю основного GitLab VPS.
Проверять восстановление, а не только создание backup
Зеленый статус команды backup еще не означает, что disaster recovery действительно готов.
Проблема может обнаружиться только при restore:
Потерян gitlab-secrets.json;
Забыта версия GitLab;
Archive поврежден;
Backup находится не там;
Configuration backup устарел;
Repositories восстановились не полностью;
Runner после восстановления больше не работает.
Поэтому периодически стоит выполнять тестовый restore в изолированной среде.
После него проверяем хотя бы:
Вход в GitLab;
Наличие проектов;
Git clone;
Git push;
Runner;
Новый pipeline.
Если эти действия проходят, backup уже подтвержден практикой.
Регулярно обновлять GitLab и Runner
Self-managed GitLab нужно обслуживать самостоятельно.
Обновления приносят исправления ошибок, security fixes и новые версии внутренних компонентов. Runner тоже развивается отдельно, поэтому его не стоит годами оставлять на старой версии.
Но обновлять GitLab без подготовки тоже не стоит.
Перед upgrade полезно:
Проверить текущую версию;
Изучить допустимый upgrade path;
Создать свежий backup;
Сохранить /etc/gitlab;
Убедиться, что на диске достаточно места;
Провести обновление;
Проверить сервисы и pipeline.
Текущую информацию о GitLab можно посмотреть так:
sudo gitlab-rake gitlab:env:info
Версию Runner:
gitlab-runner --version
После обновления снова проверяем:
sudo gitlab-ctl status
sudo gitlab-rake gitlab:check SANITIZE=true
и запускаем тестовый pipeline.
Так upgrade проверяется не только по успешному завершению apt, но и по реальной работе всей цепочки.
Что проверить перед production
Перед тем как считать GitLab готовым к постоянной эксплуатации, полезно пройти три небольших чеклиста.
Сначала — сам GitLab и доступ к нему:
Проверка GitLab
Что должно быть
HTTPS
GitLab открывается только по защищенному адресу
external_url
Указан правильный production-домен
Root/admin
Используется сильный пароль и ограниченный административный доступ
Protected branches
Production branches защищены
Системные сервисы
gitlab-ctl status не показывает проблем
Диск
Есть достаточный запас свободного места
Если основная платформа защищена, переходим к Runner и CI:
Проверка Runner и CI
Что должно быть
Docker executor
Job выполняются в отдельных containers
privileged
false, если повышенные права не нужны
Concurrency
Соответствует ресурсам VPS
CI/CD variables
Secrets не хранятся в repository
Masked / Protected
Используются для чувствительных production values
Runner access
Runner доступен только нужным проектам
Tags
Явно разделяют типы Job, где это требуется
И последний блок — эксплуатация после запуска:
Backup, обновления и мониторинг
Что должно быть
GitLab backup
Создается регулярно
/etc/gitlab
Резервируется отдельно
External storage
Backup хранится вне GitLab VPS
Restore test
Восстановление периодически проверяется
Disk usage
Контролируется до критического заполнения
GitLab updates
Устанавливаются по корректному upgrade path
Runner updates
Runner тоже поддерживается в актуальном состоянии
Проверка после update
Выполняются gitlab:check и тестовый pipeline
Если эти проверки проходят, инфраструктура уже готова не только «работать сегодня», но и переживать обычные эксплуатационные события: обновления, рост проектов, сбои CI и восстановление из резервной копии.
На этом техническую часть развертывания можно считать законченной. Остается подвести итог и определить, для каких проектов схема с self-hosted GitLab и собственным Runner действительно оправдана.
Заключение
Self-hosted GitLab с собственным Runner хорошо подходит командам и отдельным проектам, которым нужен полный контроль над repositories, CI/CD и инфраструктурой. Такая схема особенно оправдана для внутренней разработки, закрытых корпоративных проектов, небольших команд и случаев, когда код или CI-secrets нежелательно выносить во внешний сервис.
На старте GitLab и Runner можно разместить на одном VPS, если pipeline запускаются умеренно, выполняется один Job за раз и CI не создает тяжелую нагрузку. Но по мере роста проекта Runner становится логичным кандидатом на отдельный сервер: так сборки перестают конкурировать с GitLab за CPU, RAM и disk I/O, а исполняемый CI-код лучше отделяется от основного инстанса.
Главное — не заканчивать настройку на первом успешном pipeline. Для production не менее важны backup с проверенным restore, контроль свободного места, защита CI/CD variables, обновления и понятная диагностика Runner. Если эти процессы настроены заранее, self-hosted GitLab остается достаточно простой инфраструктурой даже после того, как первоначальный тестовый проект превращается в рабочую систему.
Спасибо за внимание!
FAQ
Можно ли держать GitLab и GitLab Runner на одном VPS?
Да. Для небольшого GitLab, личного проекта или маленькой команды это вполне рабочая схема.
Главное — учитывать, что Runner во время CI Job использует те же CPU, RAM и диск, что и GitLab. Поэтому стоит ограничить concurrency и ресурсы containers, а затем посмотреть на реальную нагрузку.
Если pipeline становятся тяжелыми или должны выполняться параллельно, Runner лучше перенести на отдельный VPS. Для production GitLab также рекомендует рассматривать Runner как отдельную инфраструктуру для выполнения CI Job.
Можно ли подключить несколько Runner к одному GitLab?
Да. Более того, это один из основных способов масштабирования CI.
Можно использовать project, group и instance runners, а через tags распределять между ними разные типы Job. Например, один Runner оставить для обычных Docker-сборок, другой — для deployment, а третий — для специализированных задач.
Что произойдет с pipeline, если Runner выключен?
GitLab сможет создать pipeline и Job, но подходящий Runner не заберет задание.
Такой Job обычно останется в pending, пока подходящий Runner снова не станет доступен. Если Runner после запуска сервиса подключится к GitLab и все условия назначения по-прежнему выполняются, Job сможет продолжить обычный жизненный цикл.
Переживает ли Runner восстановление GitLab из backup?
Может, но это зависит от того, что именно сохранилось.
Runner хранит свою локальную конфигурацию отдельно, обычно в:
/etc/gitlab-runner/config.toml
Если сам Runner не потерян, его configuration и authentication data сохранились, а восстановленный GitLab вернул соответствующие данные инстанса, Runner может продолжить работу без новой регистрации.
После restore сначала лучше выполнить:
sudo gitlab-runner verify
и только при проблемах регистрировать Runner заново.
Можно ли перенести Runner на другой сервер или подключить его к другому GitLab?
Да, но здесь важно различать перенос машины и смену GitLab instance.
Для нового сервера Runner устанавливают заново и связывают с нужной конфигурацией. GitLab допускает несколько регистраций Runner и повторное использование подходящей runner configuration.
Если Runner должен работать уже с другим GitLab instance, нужно настроить его на URL этого инстанса и пройти соответствующую регистрацию. Просто скопировать старый config.toml и ожидать, что authentication автоматически подойдет другому GitLab, нельзя.
Что произойдет с выполняющимся Job при reboot VPS?
Процесс выполнения будет прерван.
Если Runner и Docker работают на этом же сервере, reboot остановит и сервис Runner, и активные containers. После загрузки systemd может снова запустить Runner, но уже выполнявшийся Job обычно нельзя просто продолжить с той же точки.
В GitLab такой Job в итоге потребует повторного запуска. Поэтому pipeline лучше строить так, чтобы отдельные Job были воспроизводимыми и не зависели от уникального состояния старого container.
Нужен ли Docker для GitLab Runner?
Нет.
GitLab Runner поддерживает разные executors. Docker — лишь один из вариантов. Можно использовать, например, Shell executor, который выполняет команды непосредственно на host system.
В этой статье выбран Docker executor из-за более предсказуемого окружения и лучшего отделения CI Job от основного VPS. Сам GitLab Runner при этом не требует Docker как обязательного условия своей работы.
Можно ли делать backup GitLab во время работы пользователей?
Да, штатный backup можно создавать на работающем GitLab, поэтому для обычного регулярного резервного копирования не требуется каждый раз полностью останавливать инстанс.
Но backup отражает состояние данных на определенный момент, а активный GitLab продолжает меняться. Для критичных процедур, миграций или особенно строгих требований к согласованности стоит заранее продумать maintenance window и стратегию восстановления.
И самое важное — проверять сам restore. GitLab прямо рекомендует тестировать полный процесс восстановления до того, как он понадобится в production.
В лучших традициях TLDR даём короткую последовательность по тому как: установить ClickHouse, проверить доступ, создать таблицу, загрузить публичный dataset, выполнить несколько...
Ниже — короткая последовательность настройки Redis на VPS: от установки и безопасного доступа до ограничения памяти, выбора eviction policy и проверки сохранности данных после...
Если нужен только рабочий маршрут без подробных объяснений, ниже — короткая последовательность настройки MongoDB на Ubuntu: от установки и пользователей до...
Используем cookies
Мы используем файлы cookie чтобы вам было комфортнее работать на нашем сайте. Нажимая «Далее», вы соглашаетесь на использование файлов cookie в соответствии с Политикой конфиденциальности и Соглашением о Cookies
Functional Always active
The technical storage or access is strictly necessary for the legitimate purpose of enabling the use of a specific service explicitly requested by the subscriber or user, or for the sole purpose of carrying out the transmission of a communication over an electronic communications network.
Preferences
The technical storage or access is necessary for the legitimate purpose of storing preferences that are not requested by the subscriber or user.
Statistics
The technical storage or access that is used exclusively for statistical purposes.The technical storage or access that is used exclusively for anonymous statistical purposes. Without a subpoena, voluntary compliance on the part of your Internet Service Provider, or additional records from a third party, information stored or retrieved for this purpose alone cannot usually be used to identify you.
Marketing
The technical storage or access is required to create user profiles to send advertising, or to track the user on a website or across several websites for similar marketing purposes.