MAATRIX / Блог / Сколько RAM нужно для Drone CI

Сколько RAM нужно для Drone CI

MAATRIX

Drone CI — контейнерный CI/CD: каждый шаг пайплайна запускается в отдельном Docker-контейнере, а не в общем процессе-воркере, как у части других систем. Это удобно с точки зрения изоляции, но заставляет по-другому считать память: мало «зарезервировать RAM под CI-сервер», нужно учитывать ещё и то, что параллельно крутится десяток контейнеров сборки. Разберём, из чего складывается расход и сколько закладывать на VPS, чтобы Drone не падал по OOM в самый неподходящий момент.

Обсудить статью, задать вопрос или начать новую тему

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

Перейти в сообщество →

Как устроен Drone CI и почему память считается иначе

Drone состоит из двух ролей: drone-server (веб-интерфейс, БД, приём вебхуков от Git-провайдера, распределение задач) и один или несколько drone-runner-docker — агентов, которые реально выполняют пайплайны. Раннер подключается к серверу по RPC, забирает задачу и для каждого шага .drone.yml поднимает отдельный Docker-контейнер через сокет /var/run/docker.sock.

Из этого вытекает главный момент: сам Drone (сервер + раннер) — лёгкие Go-бинарники, которые в простое едят немного, ориентировочно до пары сотен мегабайт суммарно. А вот память, которую съедает пайплайн, целиком зависит от того, что вы туда напихали — сборка Node.js-проекта, docker build многослойного образа или прогон интеграционных тестов с базой данных в соседнем контейнере. Поэтому вопрос «сколько RAM нужно для Drone CI» на самом деле раскладывается на два: сколько нужно самой платформе и сколько — вашим шагам с учётом параллелизма.

Из чего складывается общий расход памяти

Считайте RAM для сервера с Drone как сумму пяти слагаемых:

  • drone-server — веб-UI, API, хранилище состояния пайплайнов (SQLite или внешняя Postgres/MySQL). В простое немного, но под нагрузкой (много одновременных вебхуков, большая история билдов) растёт база и кэш.
  • drone-runner-docker — сам процесс раннера, лёгкий, но именно он инициирует все дочерние контейнеры и держит с ними связь.
  • Docker-демон — накладные расходы на управление контейнерами, сетями, слоями образов. Растут пропорционально числу одновременно живых контейнеров.
  • Контейнеры шагов пайплайна — самая тяжёлая и самая непредсказуемая часть. Один шаг docker build для образа с npm install внутри может кратковременно требовать больше памяти, чем весь остальной стек вместе взятый.
  • Docker-in-Docker (DinD), если используется — отдельный слой, который сам по себе не бесплатен: DinD-контейнер держит собственный демон, кэш слоёв, временные образы.

Ключевая ошибка при прикидке — считать только первые два пункта и забывать, что реальные шаги сборки — это полноценные процессы со своим потреблением, которые Docker не «сжимает» магическим образом просто потому, что они внутри CI.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Сколько нужно для старта: минимальная конфигурация

Если вам нужен только сам факт работающего Drone — сервер плюс раннер, без тяжёлых пайплайнов, — минимум выглядит так:

КомпонентМинимум RAMКомментарий
drone-server (SQLite)256–512 МБНа старте с SQLite Postgres не нужен
drone-runner-docker128–256 МБСам процесс лёгкий
Docker-демон (простой)100–200 МББез активных контейнеров
Запас на 1 лёгкий шаг (lint, unit-тесты на скриптовом языке)300–500 МБОриентировочно, зависит от языка
Итого работоспособный минимум~1–1.5 ГБТолько для «пощупать», без продакшен-нагрузки

Это цифры для знакомства с системой, не для реального CI, который гоняет билды команды. На практике комфортный старт для одного разработчика или маленькой команды — это 2 ГБ RAM: сервер и раннер живут спокойно, и на один-два параллельных шага остаётся запас без свопа.

Пример минимального docker-compose.yml для быстрого старта (без внешней БД, с одним раннером):

version: "3.8"

services:
  drone-server:
    image: drone/drone:2
    ports:
      - "80:80"
    volumes:
      - drone-data:/data
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - DRONE_GITEA_SERVER=https://git.example.com
      - DRONE_RPC_SECRET=change-me
      - DRONE_SERVER_HOST=ci.example.com
      - DRONE_SERVER_PROTO=https
    mem_limit: 512m
    restart: unless-stopped

  drone-runner:
    image: drone/drone-runner-docker:1
    environment:
      - DRONE_RPC_HOST=drone-server
      - DRONE_RPC_PROTO=http
      - DRONE_RPC_SECRET=change-me
      - DRONE_RUNNER_CAPACITY=2
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    mem_limit: 256m
    restart: unless-stopped

volumes:
  drone-data:

DRONE_RUNNER_CAPACITY — ключевой параметр для расчёта RAM: он задаёт, сколько пайплайнов раннер может выполнять одновременно. Именно от него, а не от абстрактных «пары гигабайт с запасом», нужно плясать при планировании памяти.

Таблица: реальные сценарии и ориентировочный расход на шаг

Ниже — ориентировочные диапазоны для типовых шагов пайплайна. Это именно ориентир: точные цифры зависят от размера проекта, версий инструментов и того, что параллельно происходит в системе. Проверяйте на своей нагрузке через docker stats во время реального билда.

Тип шагаЧто происходитОриентировочный пик RAM
Линтер / статический анализ (eslint, golangci-lint)Разбор AST небольшого проекта150–400 МБ
Unit-тесты на GoКомпиляция + прогон тестов200–500 МБ
npm ci && npm run build для среднего фронтенд-проектаУстановка зависимостей, сборка бандла (webpack/vite)800 МБ – 1.5 ГБ
docker build многослойного образа с компиляциейСборка слоёв, кэш buildkit1–2.5 ГБ
Интеграционные тесты с Postgres/Redis в соседних сервисахТестовый рантайм + соседние контейнеры1–2 ГБ суммарно
Сборка Java/Kotlin через GradleJVM-эвристики памяти по умолчанию агрессивны1.5–3 ГБ
Python-проект с тяжёлыми зависимостями (numpy, torch и т. п.)Установка колёс, компиляция нативных расширений1.5–3 ГБ

Обратите внимание на два «тяжеловеса» — Gradle и Python с ML-зависимостями. Gradle-демон по умолчанию резервирует память щедро, и если не ограничить его явно (org.gradle.jvmargs=-Xmx1g в gradle.properties), он попробует взять столько, сколько увидит в контейнере. Такая же история с Node — node --max-old-space-size стоит выставлять руками для сборок с большими бандлами, иначе процесс упрётся в дефолтный лимит V8 или, наоборот, попробует съесть всё, что найдёт.

Как считать RAM под конкурентность пайплайнов

Формула для планирования простая:

RAM_сервера ≈ RAM_drone_server + RAM_runner + Docker_overhead
            + (CAPACITY × средний_пик_шага) + буфер_20-30%

Пример для команды из 5 человек с фронтенд- и бэкенд-репозиториями, где типичный шаг — сборка Docker-образа (пик ~1.5 ГБ) и DRONE_RUNNER_CAPACITY=3:

RAM_server + runner + overhead   ≈ 0.6 ГБ
3 × 1.5 ГБ (параллельные шаги)   ≈ 4.5 ГБ
буфер 25%                        ≈ 1.3 ГБ
-----------------------------------------
Итого                            ≈ 6.4 ГБ → берём 8 ГБ

Важный нюанс: CAPACITY — это не «сколько пайплайнов», а именно сколько *шагов* может выполняться параллельно на раннере (в Drone шаги одного пайплайна по умолчанию идут последовательно, но разные пайплайны от разных коммитов вполне могут запуститься одновременно). Если у вас активная команда, где несколько человек пушат в течение дня, реальная параллельность легко достигает выставленного CAPACITY, и экономить на этом множителе не стоит — именно здесь чаще всего и вылезает OOM.

Если пайплайны сильно разные по весу (лёгкий lint и тяжёлый docker build), не берите средний пик — считайте по самому тяжёлому шагу, который реально может выполняться параллельно с другими. Недооценка здесь дороже переоценки: недостающий гигабайт RAM обваливает билд посреди сборки, а лишний просто немного простаивает.

Лимиты памяти на шаги, чтобы не поймать OOM всей системой

Без ограничений один прожорливый шаг может выесть память у соседних контейнеров и уронить не только себя, но и drone-server, если он на том же хосте. Есть три уровня защиты.

1. Лимит на сами контейнеры Drone — уже показан выше через mem_limit в docker-compose.yml. Это защищает управляющий слой: даже если шаги сборки съели всё, сервер и раннер не должны быть первыми жертвами OOM killer.

2. Лимиты на уровне шага в .drone.yml. Раннер Docker поддерживает блок resources для отдельных шагов — сверяйтесь с документацией именно вашей версии раннера, синтаксис между релизами Drone менялся:

kind: pipeline
type: docker
name: build

steps:
  - name: build-image
    image: docker:24-cli
    volumes:
      - name: dockersock
        path: /var/run/docker.sock
    commands:
      - docker build -t myapp:latest .
    resources:
      limits:
        memory: 2GiB
      requests:
        memory: 1GiB

volumes:
  - name: dockersock
    host:
      path: /var/run/docker.sock

3. Приоритет OOM killer на уровне хоста. Если совсем поджимает память, полезно понизить приоритет служебных контейнеров Drone, чтобы ядро в первую очередь убивало тяжёлые контейнеры сборки, а не сам CI-сервер:

docker inspect --format '{{.State.Pid}}' drone-server
echo -900 > /proc/<PID>/oom_score_adj

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

Что делать, когда памяти всё равно не хватает

Три рабочих направления, в порядке от дешёвого к дорогому:

  • Понизить DRONE_RUNNER_CAPACITY — самый быстрый способ снять пиковую нагрузку ценой скорости: пайплайны будут вставать в очередь вместо параллельного выполнения, но перестанут душить друг друга по памяти.
  • Разнести раннеры по разным хостам. Один drone-server может обслуживать несколько drone-runner-docker на разных машинах — тяжёлые сборки (Java, ML) уводите на отдельный сервер с большим объёмом RAM, лёгкие (lint, unit-тесты) оставляете на основном.
  • Проверить, не течёт ли память в самих шагах. Кэш зависимостей, который каждый раз качается заново вместо переиспользования volume, лишний DinD там, где хватило бы обычного Docker-социта с хоста, тестовые контейнеры, которые не гасятся между шагами — частые причины раздутого потребления, не связанные с самим Drone.

Если апгрейд RAM неизбежен — для CI с активной командой разумный ориентир: 4 ГБ на 1-2 параллельных лёгких шага, 8-16 ГБ на команду с регулярной параллельной сборкой Docker-образов, от 16 ГБ — если в пайплайнах живёт Gradle, ML-зависимости или несколько интеграционных сервисов одновременно. Общие принципы расчёта запаса под изменчивую нагрузку описаны в статье сколько оперативной памяти закладывать с запасом, а если сервер уже упирается в память — разбор конкретных действий есть в статье что делать при нехватке RAM. Общий подход к лимитам CPU и памяти в Docker подробно разобран в статье лимиты CPU и памяти в Docker, а если сравниваете VPS под CI/CD в целом — смотрите какой VPS выбрать для разработчика и CI/CD.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Хватит ли 2 ГБ RAM для Drone CI?

Для одного разработчика с лёгкими пайплайнами (lint, unit-тесты на скриптовых языках) — да, с запасом на один активный шаг. Для параллельной сборки Docker-образов или JVM-проектов уже тесно.

Нужна ли отдельная база данных вместо встроенного SQLite?

Для маленькой команды SQLite достаточно и он экономит память. Внешняя Postgres имеет смысл при большом числе одновременных вебхуков и долгой истории билдов — тогда лучше вынести БД на отдельный сервис, чтобы не конкурировать за память с раннером.

Почему один и тот же пайплайн иногда падает по памяти, а иногда нет?

Чаще всего дело в параллелизме: если несколько коммитов запускают билды одновременно, суммарный пик по памяти на хосте выше, чем при одиночном запуске. Проверьте DRONE_RUNNER_CAPACITY и реальную нагрузку через docker stats в момент падения.

Можно ли ограничить память для Docker-in-Docker отдельно от остальных шагов?

Да, DinD-контейнер — это тоже контейнер, для него так же работает mem_limit или resources.limits.memory. Но учтите, что DinD добавляет накладные расходы поверх памяти самой сборки внутри него.

Стоит ли переходить на Kubernetes-раннер Drone ради экономии памяти?

Сам по себе Kubernetes не экономит память — он даёт более гибкое масштабирование и изоляцию через namespaces/quotas, но добавляет собственные накладные расходы control plane. Для одного сервера Docker-раннер обычно проще и легче.

Обсудить статью, задать вопрос или начать новую тему

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

Перейти в сообщество →