# Дешёвые GitLab-раннеры: как избежать узких мест и нестабильных сборок

> Узнайте, как небольшие команды настраивают дешёвые GitLab‑раннеры с разумным автоскейлингом, правильным размещением кеша и безопасной изоляцией, чтобы снизить затраты и уменьшить число сбоев.

## Почему дешёвые раннеры ломаются под нагрузкой

Дешёвые GitLab-раннеры часто ведут себя нормально при лёгких тестах. Проблемы появляются, когда приходит реальная нагрузка команды. Одна сборка идёт гладко, но четыре задачи на одном недорогом VM могут превратить приемлемую настройку в медленную и ненадёжную.

Процессоры получают много внимания, но они обычно не первыми выходят из строя. Медленные диски тормозят установку зависимостей, слои Docker долго распаковываются, а загрузки артефактов накапливаются в очереди. Сетевая латентность вызывает те же боли, когда раннеры тянут образы, скачивают пакеты или обращаются к удалённому хранилищу кеша.

Именно поэтому небольшой VM может показывать свободный CPU и при этом ощущаться перегруженным. Раннер проводит большую часть времени в ожидании чтения/записи диска и сетевых ответов. Разработчики видят, как пайплайны растягиваются с 4 до 18 минут, а простая панель всё ещё говорит, что машина в порядке.

Общие раннеры быстро распространяют небольшие задержки. Если одна задача тянет на 3 минуты дольше, следующие задачи встают в очередь, появляются новые коммиты, и очередь растёт. Небольшая заминка превращается в час ожидания.

Случайные отказы обычно начинаются с скучных проблем, а не драматичных сбоев. Диск заполняется старыми Docker-слоями или устаревшими кешами. Загрузка образа зависает и попадает в таймаут. Одна «шумная» задача забирает I/O, память или пропускную способность, и несвязанные сборки начинают падать так, как будто их невозможно воспроизвести.

Представьте одну дешёвую машину, выполняющую четыре задачи одновременно: Docker‑сборку, набор тестов, установку пакетов и загрузку артефактов. По отдельности ни одна из них не выглядит большой. Вместе они борются за один и тот же диск и сетевой путь, и раннер перестаёт быть дешёвым, если посчитать потерянное время команды.

Это типичная причина нестабильного CI на бюджетном железе. Небольшим командам обычно не нужны сначала более мощные серверы. Им нужно меньше скрытых узких мест, меньше шаринга и настройка, которая не позволит одной плохой задаче испортить остальные.

## Подбирайте раннеры под реальные задачи

Много проблем CI начинается с одной привычки: все задачи считают одинаковые требования к раннеру. Это не так. Линтинг, unit‑тесты, сборки Docker‑образов, интеграционные тесты с базой данных и деплои нагружают разные части машины.

Начните с анализа пайплайнов за последние несколько недель и рассортируйте задачи по реальным потребностям. Одни пикируют CPU на минуту, другие быстро заполняют диск, третьи нуждаются в стабильном доступе к сети. Дешёвая машина может справиться со многими из них, но не со всеми одновременно на одном общем раннере.

Короткие задачи должны держаться подальше от тяжёлых. Если 20‑секундный линт сидит за 12‑минутной Docker‑сборкой, весь пайплайн кажется медленным, даже когда раннер технически занят. Разделяйте раннеры по нагрузке, чтобы быстрый фидбэк оставался быстрым.

Для большинства небольших команд достаточно простой схемы с тегами: `fast` для линтинга, type‑checks и лёгких тестов, `build` для Docker‑сборок и долгих прогонов тестов, `deploy` для рутинных доставок и `release` для работы только в продакшне. Держите теги простыми. Если вы создадите лабиринт из пересекающихся тегов, люди будут гадать, задачи попадут не туда, и разделение перестанет помогать.

Релизные и продакшн‑джобы требуют своего чистого пути. Дайте им раннеры, которые делают как можно меньше другого. Не позволяйте ad‑hoc тестам, половинчатым образам или экспериментальным скриптам использовать тот же маршрут. Это не требует дорогостоящего железа — требуется предсказуемое железо.

Цель не в том, чтобы все машины были одинаковы. Цель — чтобы каждая машина хорошо справлялась с одним типом работы. 2 vCPU машина, которая только выполняет линт и unit‑тесты, часто кажется быстрее, чем более крупный универсальный раннер, который делает всё плохо.

Если не знаете, с чего начать, сначала отделите Docker‑сборки от всего остального. Это изменение решает удивительно многие замедления.

## Выбирайте размер машин, опираясь на реальные узкие места

Небольшие команды часто выбирают машины по числу CPU. Это работает, пока сборки не начинают таймаутиться по причинам, которые выглядят случайными. На практике первыми страдают медленные диски, нехватка памяти или слабая сеть.

Наблюдайте за нормальной рабочей неделей перед любыми изменениями. Смотрите использование CPU, давление по RAM, время ожидания диска и сетевой трафик во время тех сборок, которые вы запускаете чаще всего. Одна шумная среда в понедельник скажет меньше, чем пять обычных рабочих дней.

Шаблон обычно проявляется быстро. Если CPU остаётся низким, а задачи ползут — процессор не проблема. Если память заполняется и машина начинает свопить, сборки резко замедляются. Если скачивание пакетов, слои Docker или загрузка артефактов отнимают вечность — проверьте диск и сеть, прежде чем тратить деньги на дополнительные ядра.

Быстрые локальные SSD важнее, чем многие команды ожидают. Папки сборки, временные файлы, распаковка зависимостей и слои контейнеров постоянно обращаются к хранилищу. Скромная машина с хорошим SSD часто обгонит большую машину с дешёвым сетевым хранилищем.

RAM обычно следующая покупка. Команды часто увеличивают concurrency, потому что одна машина выглядит недозагруженной, а затем удивляются появлению нестабильных ошибок. Если каждой задаче нужно 2–3 ГБ в пике, четыре параллельных задачи на 8 ГБ раннере уже рискованны, учитывая ОС, Docker и GitLab Runner.

Несколько простых правил помогают: добавляйте RAM до увеличения параллелизма; держите директории сборки и временные файлы на локальном SSD; рассматривайте своп как сигнал, что раннер уже слишком заполнен. Если задачи тянут большие образы или зависимости, измерьте скорость сети вместо догадок.

Разнообразие машин создаёт свои проблемы. Гора случайных одноразовых VM трудно настраивается и ещё труднее отлаживается. Используйте две‑три стабильных конфигурации — например, маленький дефолтный раннер и более крупный для памятиёмких задач.

Так бюджетные раннеры становятся более надёжными: меньше сюрпризов, понятное планирование мощности и настройка, которую команда может понять без догадок, почему один раннер ведёт себя иначе.

## Настройте автоскейлинг по шагам

Автоскейлинг лучше работает, когда путь по умолчанию остаётся простым. Держите один раннер включённым всегда для быстрых задач вроде линтинга, unit‑тестов и небольших сборок. Этот раннер даёт быстрое подтверждение вместо того, чтобы каждый пайплайн ждал поднимющуюся машину.

Добавляйте burst‑раннеры для задач, которые действительно нуждаются в большей мощности. Docker‑сборки, browser‑тесты и крупные интеграционные наборы — обычные кандидаты. Небольшие команды часто получают лучшие результаты от такого разделения, чем от бросания всех задач в одну общую pool.

Задайте жёсткий потолок перед включением автоскейлинга. Если ваш бюджет — пять машин, сделайте этот предел строжайшим в менеджере раннеров и, по возможности, в облачном аккаунте. Автоскейлинг без лимита — как кратковременный всплеск трафика превращается в ежемесячный счёт, который никто не ожидал.

Новые машины должны присоединяться быстро, иначе автоскейлинг будет казаться сломанным. Держите startup‑скрипты короткими: установите только нужное, подтяните маленький базовый образ, зарегистрируйте раннер и начните брать задачи. Если время загрузки три минуты, разработчики всё ещё будут ждать, даже если вы технически масштабируетесь.

Практический дефолт прост: держите один маленький раннер онлайн и разрешайте появление нескольких больших только при росте очереди. Команда из 10–15 разработчиков может держать одну 2 vCPU машину для повседневных задач и позволять двум‑трем временным машинам появляться для тяжёлых пайплайнов в пиковые часы.

Баланс нужен и для выключения простаивающих машин. Останавливайте лишние раннеры быстро, чтобы они не жгли деньги, но не убивайте их сразу после завершения задачи. Короткое окно простоя, обычно 5–15 минут, работает лучше, потому что ловит следующую волну коммитов.

В первую неделю смотрите три числа: среднее время в очереди, время старта раннера и пик количества машин. Если время в очереди низкое и пул редко достигает лимита — настройка, вероятно, близка к оптимальной. Если очередь пикирует каждый день днём — добавьте небольшой буфер для этого окна, а не платите за большие машины весь день.
