Как установить 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.

Поэтому перед установкой полезно зафиксировать исходный объем свободного пространства и дальше сравнивать его с фактическим потреблением.

Теперь все исходные проверки можно выполнить на сервере.

Команды подготовки сервера

Сначала обновим индекс пакетов и установленные пакеты:

    sudo apt update
sudo apt upgrade -y

Установим базовые зависимости:

    sudo apt install -y \
  curl \
  ca-certificates \
  openssh-server \
  tzdata \
  perl

Если планируем использовать локальную отправку почты через Postfix:

    sudo apt install -y postfix

Для тестового стенда этот шаг можно пропустить и позже подключить внешний SMTP.

Проверим SSH:

    systemctl is-active ssh

Теперь посмотрим hostname:

    hostnamectl

При необходимости зададим более понятное имя:

    sudo hostnamectl set-hostname gitlab-prod-01

Проверим DNS публичного домена:

    getent hosts gitlab.example.com

или:

    dig +short gitlab.example.com

Если dig отсутствует, его можно установить вместе с dnsutils.

Проверим системное время:

    timedatectl

Нас интересует, чтобы часы были корректными, а синхронизация времени — активной.

Теперь еще раз посмотрим свободное место:

    df -h

И основные точки монтирования:

    lsblk

Если на сервере используется UFW, проверим его состояние:

    sudo ufw status

Для стандартной схемы понадобятся правила:

    sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

После этого можно включить UFW, если он еще не используется:

    sudo ufw enable

Перед включением firewall особенно важно убедиться, что SSH уже разрешен, иначе можно случайно закрыть себе доступ к VPS.

Дальше нас ждёт наш стандартный якорь-таблица.

Основные команды подготовки VPS

Задача Зачем Команда 
Обновить пакеты Начать установку с актуальной системы sudo apt update && sudo apt upgrade -y 
Установить зависимости Подготовить базовое окружение GitLab sudo apt install -y curl ca-certificates openssh-server tzdata perl 
Проверить SSH Убедиться, что удаленный доступ работает systemctl is-active ssh 
Проверить hostname Увидеть текущее имя сервера hostnamectl 
Проверить DNS Убедиться, что домен указывает на VPS dig +short gitlab.example.com 
Проверить время Исключить проблемы с TLS и токенами timedatectl 
Проверить диск Убедиться, что есть запас под GitLab и данные df -h 
Проверить firewall Убедиться, что нужные порты доступны sudo ufw status 

После этих проверок сервер готов уже не в теории, а практически: сеть доступна, домен известен, время синхронизировано, а свободное место зафиксировано.

Следующий шаг — установить сам 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.

Что делает параметр external_url

external_url определяет публичный адрес GitLab.

Например: external_url "https://gitlab.example.com"

Именно этот адрес GitLab воспринимает как основной URL своего инстанса.

Он используется при формировании:

  • Ссылок в веб-интерфейсе;
  • HTTP(S) clone URL;
  • Redirect;
  • Некоторых callback URL;
  • Адресов, которые GitLab показывает пользователям.

При первоначальной установке external_url можно передать сразу:

    sudo EXTERNAL_URL="https://gitlab.example.com" apt install gitlab-ce

В этом случае установщик сам запишет нужное значение в /etc/gitlab/gitlab.rb.

Позже, если адрес изменится, достаточно отредактировать файл вручную и снова применить конфигурацию.

И здесь появляется одна из главных административных команд GitLab — gitlab-ctl reconfigure.

Что происходит при gitlab-ctl reconfigure

Команда:

    sudo gitlab-ctl reconfigure

приводит рабочую конфигурацию GitLab к состоянию, описанному в gitlab.rb.

Проще говоря, мы сначала указываем, что хотим получить:

    external_url "https://gitlab.example.com"

а затем выполняем:

    sudo gitlab-ctl reconfigure

После этого GitLab обновляет связанные настройки своих компонентов.

Во время reconfigure могут:

  • Создаваться и обновляться конфигурационные файлы;
  • Проверяться каталоги;
  • Меняться права;
  • Применяться параметры сервисов;
  • Перезапускаться необходимые компоненты.

Поэтому reconfigure и обычный restart — не одно и то же.

restart просто перезапускает уже настроенные сервисы:

    sudo gitlab-ctl restart

А reconfigure сначала обновляет конфигурацию, после чего приводит сервисы в соответствие с ней.

Эту разницу стоит запомнить: дальше мы еще несколько раз будем менять gitlab.rb.

Теперь можно переходить к самой установке.

Практика: устанавливаем и запускаем GitLab

В статье будем использовать GitLab Community Edition.

Сначала добавим официальный repository GitLab:

    curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash

После этого устанавливаем пакет и сразу задаем публичный адрес:

    sudo EXTERNAL_URL="https://gitlab.example.com" apt install gitlab-ce

Установка может занять несколько минут.

После завершения проверим основной конфигурационный файл:

    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:

    letsencrypt['enable'] = true

При необходимости укажем контактный адрес:

    letsencrypt['contact_emails'] = ['admin@example.com']

Итоговый минимальный фрагмент может выглядеть так:

    external_url "https://gitlab.example.com"
letsencrypt['enable'] = true
letsencrypt['contact_emails'] = ['admin@example.com']

Сохраняем файл и применяем настройки:

    sudo gitlab-ctl reconfigure

После этого проверим состояние сервисов:

    sudo gitlab-ctl status

Теперь посмотрим HTTP-заголовки:

    curl -I https://gitlab.example.com

Если сертификат выпущен корректно, 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:

    git@gitlab.example.com:user/demo-app.git

На рабочем компьютере клонируем проект:

    git clone git@gitlab.example.com:user/demo-app.git

Переходим в каталог:

    cd demo-app

Если repository был создан полностью пустым, добавим простой README:

    echo "# Demo App" > README.md

Проверим состояние:

    git status

Добавим файл в staging area:

    git add README.md

Создадим первый commit:

    git commit -m "Initial commit"

Если Git еще не знает имя и email пользователя, он предложит их настроить. Сделать это глобально можно так:

    git config --global user.name "Developer"
git config --global user.email "developer@example.com"

После этого повторяем git commit.

Теперь отправим изменения:

    git push

Для нового 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.

Процесс выглядит так:

  1. Создаем Runner в интерфейсе GitLab;
  2. Задаем его параметры;
  3. Получаем authentication token;
  4. Запускаем gitlab-runner register на VPS;
  5. Передаем URL GitLab и token;
  6. Выбираем executor;
  7. Локальная конфигурация сохраняется в config.toml.

После регистрации Runner использует этот token, чтобы аутентифицироваться перед GitLab и получать доступные задания.

Сам token нужно считать секретом. Если посторонний получит доступ к конфигурации Runner, его нельзя публиковать в статье, screenshot или repository.

При выполнении конкретного Job используется уже отдельный job token. Благодаря этому CI environment не обязательно получает постоянный authentication token самого Runner.

Теперь остается понять, как GitLab решает, какие именно Job отдавать этому исполнителю.

Что такое tags и зачем они нужны

Tags позволяют связать определенные Job с определенными Runner.

Например, Runner можно создать с tags:

    docker
linux

А в .gitlab-ci.yml указать:

    test:
  tags:
    - docker
  script:
    - echo "Running tests"

Такой Job должен получить Runner, подходящий по указанным tags.

Это особенно удобно, когда Runner несколько.

Например:

Вместо того чтобы GitLab отправлял каждое задание первому свободному Runner, tags позволяют явно описать необходимое окружение.

Есть и обратная сторона: если Job требует tag docker, а ни один доступный Runner такого tag не имеет, Job останется в pending.

Поэтому tags позже станут одной из первых вещей, которые мы будем проверять при диагностике зависшего CI.

Для нашего стенда используем один простой tag:

    docker

И зарегистрируем Runner как project runner для demo-app.

Практика: устанавливаем и регистрируем Runner

Так как мы выбрали Docker executor, сначала на VPS должен быть установлен и запущен Docker.

Проверим:

    docker --version

и:

    sudo systemctl is-active docker

Если Docker еще не установлен, его нужно установить до регистрации Runner с Docker executor.

Теперь добавим официальный repository GitLab Runner.

Скачаем установочный скрипт:

    curl -L \
  "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" \
  -o script.deb.sh

Перед выполнением его можно просмотреть:

    less script.deb.sh

Затем запускаем:

    sudo bash script.deb.sh

И устанавливаем GitLab Runner:

    sudo apt install -y gitlab-runner

Проверим версию:

    gitlab-runner --version

Теперь переходим в интерфейс нашего проекта GitLab.

Открываем: Settings → CI/CD → Runners

Создаем новый project runner.

Для него укажем, например:

    Description: demo-docker-runner
Tag: docker

После создания GitLab покажет authentication token. Подставлять его прямо в статью не нужно — используем переменную:

    export RUNNER_TOKEN="glrt-REPLACE_WITH_REAL_TOKEN"

Теперь регистрируем Runner:

    sudo gitlab-runner register \
  --non-interactive \
  --url "https://gitlab.example.com" \
  --token "$RUNNER_TOKEN" \
  --executor "docker" \
  --docker-image "alpine:latest" \
  --description "demo-docker-runner"

После успешной регистрации конфигурация появится в:

    /etc/gitlab-runner/config.toml

Проверим ее:

    sudo cat /etc/gitlab-runner/config.toml

Базовая конфигурация будет выглядеть примерно так:

    concurrent = 1
check_interval = 0

[[runners]]
  name = "demo-docker-runner"
  url = "https://gitlab.example.com"
  executor = "docker"
  [runners.docker]
    image = "alpine:latest"
    privileged = false
    disable_cache = false

Здесь нас пока интересуют несколько вещей.

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 дополнительно проверяет их.

Например:

    test:
  tags:
    - docker
  script:
    - echo "Running tests"

Такой 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 все доступные ресурсы было бы плохой идеей.

Представим, что сервер имеет:

  1. 8 vCPU
  2. 16 ГБ RAM

Если один Job сможет занять все 8 vCPU и почти всю память, GitLab в этот момент тоже останется без запаса.

Поэтому для нашего тестового Runner разумно начать с консервативных ограничений.

Например:

  1. CPU: до 2 vCPU
  2. RAM: до 4 ГБ
  3. 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

Перед изменением удобно сохранить резервную копию:

    sudo cp /etc/gitlab-runner/config.toml \
  /etc/gitlab-runner/config.toml.bak

Теперь откроем файл:

    sudo nano /etc/gitlab-runner/config.toml

После регистрации там уже должна присутствовать секция нашего Runner.

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

    concurrent = 1
check_interval = 0

[[runners]]
  name = "demo-docker-runner"
  url = "https://gitlab.example.com"
  executor = "docker"
  limit = 1
  [runners.docker]
    image = "alpine:latest"
    privileged = false
    cpus = "2"
    memory = "4g"
    disable_cache = false
    volumes = ["/cache"]

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

Заменим предыдущую тестовую конфигурацию на более полную:

    stages:
  - build
  - test
  - deploy
build_demo:
  stage: build
  image: alpine:3.22
  tags:
    - docker
  script:
    - echo "Building demo application"
    - mkdir -p build
    - echo "Demo artifact" > build/app.txt
  artifacts:
    paths:
      - build/
    expire_in: 1 hour
test_demo:
  stage: test
  image: alpine:3.22
  tags:
    - docker
  script:
    - echo "Checking build artifact"
    - test -f build/app.txt
    - cat build/app.txt
deploy_demo:
  stage: deploy
  image: alpine:3.22
  tags:
    - docker
  script:
    - echo "Demo deploy completed"

Здесь уже появляется первая связь между stages.

Job build_demo создает файл:

    build/app.txt

и сохраняет каталог build/ как artifact.

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;
  • Может быть видно другим участникам проекта;
  • Сохраняется даже после последующего удаления строки;
  • Может случайно попасть в fork, backup или export.

Например, такой вариант плохой:

    deploy:
  script:
    - export API_TOKEN="super-secret-token"
    - ./deploy.sh

Даже если позже удалить token из файла, он останется в Git history.

То же самое касается:

    variables:
  DB_PASSWORD: "secret-password"

или:

    script:
  - 'curl -H "Authorization: Bearer abc123..."'

Секреты лучше хранить отдельно от 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 и диск.

Поэтому полезнее сравнивать три состояния:

  1. Чистый VPS до установки GitLab;
  2. GitLab работает, pipeline не выполняется;
  3. 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. Но даже здесь остаются два файла, без которых восстановление может превратиться в серьезную проблему:

    /etc/gitlab/gitlab.rb
/etc/gitlab/gitlab-secrets.json

Их разберем отдельно.

Почему отдельно нужно сохранять конфигурацию и 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.

Например:

    gitlab_rails['backup_path'] = '/var/opt/gitlab/backups'

После изменения конфигурации потребуется:

    sudo gitlab-ctl reconfigure

Но хранить единственную копию 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

Например:

    sudo cp 1760000000_2026_10_02_18.x.y_gitlab_backup.tar \
  /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 останавливают процессы приложения:

    sudo gitlab-ctl stop puma
sudo gitlab-ctl stop sidekiq

После этого проверяют:

    sudo gitlab-ctl status

PostgreSQL и остальные необходимые сервисы должны продолжать работать.

Логика здесь простая: пользовательские и фоновые операции временно прекращаются, но сама инфраструктура, нужная утилите восстановления, остается доступной.

После этого уже можно безопаснее менять состояние инстанса.

Что происходит с текущими данными при restore

Restore не объединяет backup с текущим GitLab.

Он возвращает состояние из резервной копии.

Это означает, что данные, появившиеся после момента создания backup, могут быть потеряны.

Представим:

  1. 12:00 — создан backup
  2. 13:00 — появился новый commit
  3. 14:00 — создан новый issue
  4. 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.

Затем остановим Puma и Sidekiq:

    sudo gitlab-ctl stop puma
sudo gitlab-ctl stop sidekiq

Не переживайте: после восстановления мы выполним gitlab-ctl restart, и ранее остановленные Puma и Sidekiq будут запущены снова вместе с остальными компонентами GitLab.

Проверим состояние сервисов:

    sudo gitlab-ctl status

Теперь запускаем restore:

    sudo gitlab-backup restore \
  BACKUP=1760000000_2026_10_02_18.x.y

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-операции отдельно.

Откроем наш demo-app и посмотрим:

  • Историю commits;
  • Ветки;
  • Файлы;
  • Последний commit до backup.

Затем на рабочем компьютере попробуем выполнить:

    git clone git@gitlab.example.com:user/demo-app.git

Если локальная копия уже есть:

    cd demo-app
git fetch

После этого можно проверить историю:

    git log --oneline -5

В ней должны присутствовать commits, которые существовали на момент создания backup.

Полезно также сделать тестовое изменение:

    echo "restore check" >> README.md
git add README.md
git commit -m "Check repository after restore"
git push

Если 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:

    sudo journalctl -u gitlab-runner -n 100 --no-pager

Для наблюдения в реальном времени:

    sudo journalctl -u gitlab-runner -f

При Docker executor отдельно проверяем Docker:

    sudo systemctl is-active docker

Запущенные containers:

    sudo docker ps

Все containers, включая уже остановленные:

    sudo docker ps -a

Проверим ресурсы:

    free -h
df -h
uptime

И саму конфигурацию Runner:

    sudo cat /etc/gitlab-runner/config.toml

При этом 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:

    sudo journalctl -u gitlab-runner -n 100 --no-pager

Для наблюдения в реальном времени:

    sudo journalctl -u gitlab-runner -f

В логах уже можно увидеть ошибки чтения конфигурации, проблемы с сетью или другие причины завершения процесса.

Если 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:

    sudo gitlab-runner unregister --name "demo-docker-runner"

После этого выполняем новую регистрацию:

    sudo gitlab-runner register

и снова задаем:

  • URL GitLab;
  • Новый authentication token;
  • Executor;
  • Параметры Runner.

При этом повторная регистрация нужна не при любой ошибке. Если проблема всего лишь в остановленном service или DNS, пересоздавать Runner бессмысленно.

Поэтому сначала всегда проверяем текущую регистрацию через verify.

Команды восстановления Runner

Для начала проверим service:

    sudo systemctl status gitlab-runner

Если он не работает:

    sudo systemctl restart gitlab-runner

Проверяем регистрацию:

    sudo gitlab-runner verify

Если проблема остается, смотрим logs:

    sudo journalctl -u gitlab-runner -n 100 --no-pager

Проверяем GitLab URL:

    curl -I https://gitlab.example.com

И конфигурацию:

    sudo nano /etc/gitlab-runner/config.toml

При необходимости посмотрим список зарегистрированных Runner:

    sudo gitlab-runner list

Если регистрация больше не действует, удаляем старую:

    sudo gitlab-runner unregister --name "demo-docker-runner"

и выполняем новую:

    sudo gitlab-runner register

После изменений перезапускаем сервис:

    sudo systemctl restart gitlab-runner

и выполняем финальную проверку:

    sudo systemctl is-active gitlab-runner
sudo gitlab-runner verify

Основную последовательность удобно держать в виде короткой шпаргалки:

Этап Что проверяем Команда 
Status Работает ли локальный service sudo systemctl status gitlab-runner 
Verify Может ли Runner связаться с GitLab sudo gitlab-runner verify 
Logs Почему service или соединение не работают sudo journalctl -u gitlab-runner -n 100 --no-pager 
Restart Повторно запускаем Runner после исправления sudo systemctl restart gitlab-runner 

Если после этого 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 начал работу.

То есть базовая цепочка до Runner уже исправна.

Дальше искать проблему нужно внутри Job.

Например, такой pipeline:

    test:
  stage: test
  image: alpine:3.22
  tags:
    - docker
  script:
    - ./run-tests.sh

может завершиться ошибкой по совершенно разным причинам:

  • Файла run-tests.sh нет;
  • У него нет execute permission;
  • Внутри скрипта произошла ошибка;
  • Отсутствует нужный пакет;
  • Command not found;
  • Закончилась память;
  • Нет сетевого доступа;
  • Секретная variable не передалась.

Поэтому статус failed сам по себе почти ничего не говорит. Нужен Job log.

Где смотреть Job log

Открываем: Build → Pipelines → нужный pipeline → нужный Job

Внутри находится полный log выполнения.

Обычно Runner показывает несколько последовательных этапов:

  • Подготовка executor;
  • Получение source code;
  • Восстановление cache или artifacts;
  • Выполнение script;
  • Загрузка artifacts;
  • Cleanup.

Нас прежде всего интересует последняя успешная операция и первая ошибка после нее.

Например:

    $ ./run-tests.sh
/bin/sh: ./run-tests.sh: not found
ERROR: Job failed: exit code 127

Здесь проблема практически локализована сразу: Runner работает, container запустился, но нужная команда или файл недоступны.

Другой пример:

    $ npm test
npm: not found

Это уже не проблема GitLab. В выбранном container image просто нет npm.

Именно поэтому Job log стоит читать сверху вниз только до первого реального сбоя, а не пытаться анализировать каждую служебную строку Runner.

Как отличить ошибку команды от проблемы окружения

Самый простой ориентир — посмотреть, на чем именно падает Job.

Если команда запускается, но возвращает ненулевой exit code:

    Tests failed
exit code 1

скорее всего, проблема находится в самой логике приложения или тестов.

Если получаем:

    command not found

это уже проблема окружения.

Например:

    python: not found

означает, что используемый image не содержит Python.

Если ошибка выглядит так:

    Permission denied

нужно проверять permissions.

Если:

    connection refused

или:

    Could not resolve host

проблема уже ближе к сети или внешнему сервису.

Условно можно разделить ошибки так:

Такое разделение часто быстрее приводит к причине, чем попытка сразу менять Runner.

Почему pipeline может работать локально и падать на Runner

Это очень распространенная ситуация.

На компьютере разработчика уже могут быть установлены:

  • Нужная версия Node.js;
  • Python;
  • Java;
  • Package managers;
  • Environment variables;
  • Локальная database;
  • SSH keys;
  • Credentials;
  • Системные библиотеки.

А Docker container Runner начинает работу в гораздо более чистом окружении.

Например, локально команда:

npm test

работает, потому что Node.js уже установлен.

Но pipeline использует:

    image: alpine:3.22

где npm вообще отсутствует.

Получаем:

    npm: not found

Это не значит, что Runner неисправен. Просто локальная среда и CI environment различаются.

Еще один типичный случай — environment variables.

Локально приложение может читать:

    .env

а в CI этого файла нет.

Тогда тесты начинают падать из-за отсутствующих credentials или URL.

То же касается рабочей директории. Разработчик может запускать команду из нужного каталога вручную, а Runner начинает Job в корне repository.

Поэтому хороший CI должен явно описывать свое окружение, а не рассчитывать на то, что на сервере «что-то уже установлено».

Практика: базовая цепочка диагностики

Допустим, Job завершился со статусом failed.

Первым делом открываем Job log и ищем первую содержательную ошибку.

Если видим:

    command not found

проверяем image в .gitlab-ci.yml.

Например:

    image: node:22-alpine

вместо:

    image: alpine:3.22

если Job действительно требует Node.js.

Если проблема связана с файлом:

    No such file or directory

проверяем содержимое repository:

    git ls-files

и путь в CI.

Если:

    Permission denied

можно посмотреть permissions файла:

    ls -l

Для shell script при необходимости:

    chmod +x run-tests.sh
git add run-tests.sh
git commit -m "Fix script permissions"
git push

Если Job падает на variable, лучше проверить наличие значения без вывода самого секрета:

    script:
  - test -n "$API_TOKEN"

Если ошибка с сетью, можно временно проверить DNS и HTTPS прямо внутри Job:

    script:
  - wget -S --spider https://example.com

или использовать подходящий image с curl.

Если подозрение на ресурсы, смотрим VPS:

    free -h
df -h
uptime

а Docker:

    sudo docker stats

и:

    sudo docker ps -a

Если container неожиданно завершился, полезно проверить Runner logs:

    sudo journalctl -u gitlab-runner -n 100 --no-pager

В итоге цепочка диагностики получается довольно короткой:

  1. Смотрим статус Job;
  2. Открываем Job log;
  3. Находим первую реальную ошибку;
  4. Определяем уровень;
  5. Проверяем image, variables, files, network или resources;
  6. Исправляем;
  7. Запускаем 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 полезно:

  1. Проверить текущую версию;
  2. Изучить допустимый upgrade path;
  3. Создать свежий backup;
  4. Сохранить /etc/gitlab;
  5. Убедиться, что на диске достаточно места;
  6. Провести обновление;
  7. Проверить сервисы и 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. 

Список источников

  1. GitLab Docs — Install GitLab using the Linux package
  2. GitLab Docs — Registering runners
  3. GitLab Docs — Tutorial: Create, register, and run your own project runner
  4. GitLab Docs — Back up and restore / Restore GitLab
  5. GitLab Docs — GitLab Runner commands / Get started with GitLab Runner

Подпишитесь на нашу рассылку и получайте статьи и новости

    Ознакомьтесь с другими нашими материалами