Зачем использовать выделенный сервер для CI/CD?
Системы непрерывной интеграции и непрерывной доставки компилируют код, запускают тесты, собирают образы контейнеров, сканируют зависимости и публикуют артефакты. Эти задачи часто создают кратковременные периоды очень высокой нагрузки на CPU, память, дисковый ввод-вывод и сеть. Агенты Jenkins и GitLab Runner также выполняют код из множества веток, поэтому конкуренция за ресурсы может сделать длительность конвейеров непредсказуемой.
Выделенный сервер предоставляет CI-системе исключительный доступ к ядрам CPU, памяти, NVMe-накопителю и сетевому порту. Такая изоляция уменьшает влияние соседних нагрузок и упрощает оценку времени сборки. Она также позволяет администратору настроить операционную систему, хранилище Docker, кэширование, политику использования swap и средства безопасности с учётом нагрузки runner.
VPS или выделенный сервер?
VPS достаточно, если
- Команда запускает менее 5 параллельных заданий.
- Большинство сборок завершается менее чем за 10 минут и не компилирует крупные проекты.
- Образы контейнеров и зависимости достаточно малы, чтобы уместиться в 80–160 ГБ NVMe-хранилища.
- Сбои сборки, вызванные конкуренцией за CPU или диск, не оказывают существенного влияния на бизнес.
- CI-сервис отделён от производственных баз данных и приложений, доступных клиентам.
VPS с 4 vCPU, 8 ГБ ОЗУ и 160 ГБ NVMe-хранилища может поддерживать небольшой контроллер Jenkins, один лёгкий агент или GitLab Runner для веб-приложений и репозиториев инфраструктуры. По возможности разделяйте контроллер и задания сборки; контроллеры Jenkins должны планировать работу, а не выполнять тяжёлую компиляцию.
Переходите на bare metal, если
- Одновременно выполняется более 10 заданий или задержка в очереди регулярно превышает 5 минут.
- Сборки длительное время используют все выделенные vCPU.
- Проекты компилируют крупные кодовые базы на Java, Rust, C++, Android или .NET.
- Сборки образов Docker создают много гигабайт временных слоёв.
- Нескольким командам нужны изолированные runner’ы или выделенные исполнители.
- Для выпусков, тестирования или окон развёртывания требуется предсказуемое время выполнения сборок.
- Система хранит большие кэши артефактов, зеркала пакетов или реестры контейнеров.
Bare metal также хорошо подходит для привилегированных нагрузок. Docker-in-Docker, вложенная виртуализация, тестирование оборудования и высокообъёмные интеграционные тесты могут создавать проблемы с «шумными соседями», безопасностью или вводом-выводом на общем виртуальном хосте.
Рекомендуемые характеристики выделенного сервера
Для общего хоста Jenkins или GitLab Runner начните с 8 физических ядер, 32 ГБ ECC-памяти и 480 или 960 ГБ корпоративного NVMe-хранилища. Выбирайте современный серверный процессор с высокой производительностью одного потока, а также достаточным количеством ядер для параллельных заданий. Компиляция и наборы тестов выигрывают от обоих этих свойств.
- CPU: от 8 до 16 физических ядер для средних команд; от 16 до 32 ядер для крупных ферм сборки. Не определяйте конфигурацию только по количеству потоков, поскольку многопоточность не равна второму физическому ядру.
- ОЗУ: 32 ГБ для 10–20 параллельных заданий, 64 ГБ для 20–40 заданий и 128 ГБ или больше для крупных языковых сборок, сборок Android, браузерного тестирования или большого количества контейнеров.
- Хранилище: предпочтителен NVMe SSD. Используйте 480 ГБ для компактного runner и 960 ГБ–1,92 ТБ для локальных кэшей, артефактов и слоёв контейнеров. RAID 1 защищает доступность, но не заменяет резервное копирование за пределами сервера.
- Сеть: 1 Гбит/с достаточно для большинства команд. Выбирайте 10 Гбит/с, если runner’ы загружают большие образы, зеркалируют пакеты или часто передают артефакты размером в несколько гигабайт. Ежемесячный лимит трафика 10 ТБ — практичная отправная точка.
- Операционная система: Debian 12, Ubuntu Server 24.04 LTS или другой поддерживаемый дистрибутив Linux. Держите хост минимальным, а сервисы приложений размещайте на отдельных машинах.
Не размещайте Jenkins, GitLab, реестр контейнеров и производственную базу данных на одном сервере только потому, что у CPU есть свободные ядра. Задания CI выполняют изменяющийся код и должны иметь чёткую границу доверия с системами управления исходным кодом и производственными системами.
Для 10 и менее параллельных заданий обычно достаточно VPS с 4 vCPU; при более чем 20 параллельных заданиях или длительной компиляции используйте выделенный сервер с 16 ядрами, 64 ГБ ОЗУ и 960 ГБ NVMe.
| Параллельные задания CI | vCPU или ядра | ОЗУ | Диск | Ежемесячный трафик |
|---|---|---|---|---|
| До 5 | 4 vCPU | 8 ГБ | 160 ГБ NVMe | 2 ТБ |
| От 6 до 20 | 8 физических ядер | 32 ГБ | 480 ГБ NVMe | 5 ТБ |
| От 21 до 50 | 16 физических ядер | 64 ГБ | 960 ГБ NVMe, RAID 1 | 10 ТБ |
| От 51 до 100 | 32 физических ядра | 128 ГБ | 1,92 ТБ NVMe, RAID 1 | 20 ТБ |
Пошаговая настройка
1. Подготовьте DNS, доступ и чистый хост
Создайте отдельную DNS-запись, например ci.example.com, и используйте SSH-ключи вместо аутентификации по паролю. Установите поддерживаемый образ Linux и создайте администратора без прав root. Ограничьте SSH с помощью межсетевого экрана и разрешите доступ только с доверенных офисных, VPN- или административных адресов.
2. Обновите операционную систему
Установите обновления безопасности до добавления Jenkins или GitLab Runner:
sudo apt update && sudo apt full-upgrade -y
Установите правильный часовой пояс и включите автоматические обновления безопасности, если это допускается вашей политикой изменений. Перезагрузите систему после обновлений ядра.
3. Настройте межсетевой экран
Откройте только необходимые порты. Jenkins обычно использует TCP 8080 за обратным прокси, тогда как GitLab Runner обычно устанавливает исходящие соединения и не нуждается во входящем сервисном порте.
sudo apt install -y ufw sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow from 203.0.113.10 to any port 22 proto tcp sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable
4. Установите Docker для изолированных заданий
Задания на основе Docker обеспечивают воспроизводимые зависимости и упрощают очистку. Установите Docker из репозитория, поддерживаемого дистрибутивом, или из официального репозитория Docker, затем проверьте работу демона:
sudo apt install -y docker.io sudo systemctl enable --now docker sudo docker run --rm hello-world
Не предоставляйте ненадёжным пользователям неограниченный доступ к сокету Docker. Членство в группе docker фактически означает доступ root к хосту. Для менее доверенного кода используйте доверенные репозитории, защищённые ветки, изолированные runner’ы или альтернативы без прав root.
5. Установите и зарегистрируйте GitLab Runner
Установите пакет runner с помощью репозитория поставщика, затем зарегистрируйте его с токеном проекта или группы. Используйте такие теги, как docker, linux или privileged, только когда они требуются конвейеру.
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash sudo apt install -y gitlab-runner sudo gitlab-runner register
При появлении запроса выберите исполнитель Docker. Установите ограничение параллелизма в /etc/gitlab-runner/config.toml, чтобы на хосте сохранялся запас памяти и CPU для системных задач.
6. Установите Jenkins на отдельном контроллере или агенте
Для небольшой установки Jenkins может работать на том же хосте, что и один агент. Для крупных установок разместите контроллер на небольшом VPS, а выделенный сервер используйте как входящий или исходящий агент сборки. Установите Java, поддерживаемую выбранным выпуском Jenkins, затем установите Jenkins из подписанного репозитория.
sudo apt install -y fontconfig openjdk-21-jre sudo systemctl enable --now jenkins sudo systemctl status jenkins
Разместите Jenkins за обратным HTTPS-прокси, включите обязательную аутентификацию, отключите анонимные сборки и ограничьте права агентов. Для ненадёжных запросов на слияние отдавайте предпочтение эфемерным агентам или одноразовым контейнерам.
7. Добавьте кэширование и хранение артефактов
Кэшируйте зависимости пакетов и результаты компиляции только тогда, когда инвалидация кэша надёжна. Настройте правила хранения журналов сборки и артефактов; бессрочное хранение всех артефактов в конечном итоге заполнит NVMe-том. Храните выпускаемые артефакты в объектном хранилище или выделенном реестре с политиками жизненного цикла.
8. Проверьте параллелизм до использования в рабочей среде
Одновременно запустите несколько показательных конвейеров и измерьте загрузку CPU, давление на память, задержку диска, передачу данных по сети и время ожидания в очереди:
sudo apt install -y sysstat iotop mpstat -P ALL 5 iostat -xz 5 free -h
Увеличивайте количество исполнителей только после подтверждения того, что объём ОЗУ и задержка диска остаются в допустимых пределах.
Оптимизация производительности
Сопоставьте исполнителей с физическими ресурсами
Слишком большое количество исполнителей создаёт конвейеры без очередей, но с низкой скоростью. Начните с одного исполнителя на физическое ядро для ресурсоёмкой компиляции, затем тщательно протестируйте конфигурацию. Браузерные тесты, сетевые тесты и задания с интенсивным вводом-выводом могут поддерживать больший параллелизм, тогда как сборкам Android и крупным сборкам C++ может потребоваться несколько гигабайт ОЗУ на одного исполнителя.
Используйте быстрое контролируемое хранилище Docker
Разместите корневой каталог данных Docker на NVMe и контролируйте использование с помощью docker system df. Очищайте неиспользуемые слои по расписанию, а не во время пиковых сборок:
docker system df sudo docker image prune -af --filter until=168h
При необходимости используйте зеркала реестров или прокси зависимостей GitLab. Не удаляйте активные тома и никогда не запускайте широкие команды очистки без подтверждения требований к хранению.
Защитите память и дисковое пространство
Установите ограничения памяти на уровне заданий для контейнеров, зарезервируйте ОЗУ для операционной системы и настройте оповещения при заполнении файловой системы более чем на 80 процентов. Небольшой swap-файл может предотвратить аварийное завершение процесса, но swap не заменяет достаточный объём ОЗУ:
sudo fallocate -l 8G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile
Сократите ненужную работу конвейера
- Используйте lock-файлы зависимостей и зафиксированные базовые образы.
- Запускайте модульные тесты при каждом коммите, а дорогие интеграционные тесты — в защищённых ветках или по расписанию.
- Используйте поверхностное клонирование Git, если полная история не нужна.
- Собирайте один раз, затем перемещайте протестированный артефакт между окружениями.
- Распараллеливайте независимые этапы, сохраняя параллелизм ниже проверенной производительности сервера.
Контролируйте хост CI
Отслеживайте длительность ожидания в очереди, длительность заданий, использование исполнителей, среднюю нагрузку, давление на память, задержку диска, ёмкость файловой системы и неудачные задания. Настройте оповещения при устойчивой нагрузке выше доступного количества физических ядер, использовании диска более чем на 80 процентов и повторяющихся событиях нехватки памяти. Централизуйте журналы, чтобы сбой хоста не уничтожил данные, необходимые для диагностики.
Распространённые ошибки, которых следует избегать
- Запуск сборок на контроллере Jenkins: оставьте исполнителей контроллера отключёнными или установите их небольшое количество, а тяжёлую работу назначайте агентам.
- Избыточное количество исполнителей: 32 логических потока не гарантируют 32 быстрых задания компиляции. Тестируйте конфигурацию на реальных репозиториях.
- Использование одного общего привилегированного runner: разделяйте доверенные задания выпуска и задания запросов на слияние, ограничивайте привилегированный доступ к Docker.
- Игнорирование роста дискового пространства: слои контейнеров, каталоги рабочих пространств, отчёты тестов и артефакты могут быстро заполнить диск объёмом 480 ГБ.
- Отказ от резервного копирования: создавайте резервные копии конфигурации Jenkins, метаданных учётных данных, определений конвейеров и конфигурации runner, сохраняя секреты в зашифрованном виде.
- Открытые интерфейсы управления: разместите Jenkins за HTTPS, включите MFA через поставщика удостоверений и ограничьте административные пути.
- Использование неприпопиненных зависимостей: пакеты и базовые образы без фиксации версий затрудняют воспроизведение сборок и могут создавать риски для цепочки поставок.
- Смешивание CI и рабочей среды: скомпрометированное задание сборки не должно иметь прямого сетевого доступа к производственным базам данных или учётным данным для развёртывания.
Когда добавлять дополнительные серверы
Один большой сервер не всегда является лучшим решением. Добавляйте отдельные агенты, если проектам нужны разные операционные системы, архитектуры CPU, доступ к GPU или уровни доверия. Контроллер и два или более выделенных агентов могут сократить время ожидания в очереди и позволить выполнять обслуживание без остановки всех конвейеров. Используйте метки и теги runner, чтобы направлять задания Android, ARM, браузерного тестирования, выпуска и сканирования безопасности на подходящее оборудование.
Для высокой доступности независимо создавайте резервные копии системы управления исходным кодом и метаданных CI, поддерживайте образ для замены runner и документируйте процедуры регистрации и ротации секретов. Доступность CI определяется не только временем работы сервера, но и возможностью воспроизвести чистую сборку после сбоя.