Настройка CI/CD для монорепозитория (Turborepo/Nx/Bazel)

Сборка монорепозитория растёт вместе с числом команд: правка в одном пакете запускает пересборку всего, релизы ждут очереди, а ошибки всплывают у заказчика. Мы настраиваем CI/CD для монорепозитория под ключ: единый конвейер собирает только изменившееся, проверяет и выпускает по готовности. Время сборки сокращается в разы, а выпуск версий становится предсказуемым.
Часто задаваемые вопросы

CI/CD для монорепозитория: где ломается сборка

Монорепозиторий выигрывает на старте и начинает тормозить выпуск на объёме. Первое узкое место — сборка всего дерева пакетов на каждое изменение: правка в одном сервисе тянет тесты, линтеры и сборку остальных, и время сборки растёт от числа пакетов, а не от размера правки. Ожидание сборки перед выпуском становится частью релизного цикла, а не его подготовкой.

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

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

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

Эффект единого конвейера: скорость и предсказуемость

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

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

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

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

Порядок внедрения: от аудита до передачи

Порядок работ не произвольный: каждый следующий шаг опирается на данные предыдущего. Сначала снимаем факты — время сборки по задачам, состав повторных прогонов, поведение кэша, — и только потом меняем конфигурацию. Так правки в конвейере опираются на замеры, а не на предположения о том, где «тормозит».

  1. Аудит сборочных шагов. Прогоняем действующий конвейер, замеряем время каждой задачи и считаем, сколько работы выполняется повторно при неизменённых файлах.
  2. Схема зависимостей пакетов. Строим граф: что от чего зависит, какие пакеты собираются параллельно, где циклические связи мешают разделению на независимые задачи.
  3. Проектирование правил запуска. Фиксируем, какие задачи поднимаются на изменённый пакет и его зависимых, как считается ключ кэша, что происходит на ветках и релизных метках.
  4. Настройка окружений сборки. Сводим версии инструментов на агентах к одному набору, чтобы локальный прогон и конвейер давали одинаковый результат и сбои воспроизводились.
  5. Проверка на реальных изменениях. Прогоняем конвейер на настоящих коммитах: правка в листовом пакете, обновление общей библиотеки, откат на предыдущий релиз.
  6. Передача конвейера команде. Отдаём конфигурации с комментариями, разбираем типовые сбои и порядок их устранения, открываем доступы и дополняем документацию.

Что остаётся в репозитории после внедрения

На руки заказчик получает не «настроенный сервер», а комплект артефактов, по которому команда развивает сборку без нас — для монорепозитория на Turborepo, Nx или Bazel. Каждый пункт лежит в репозитории рядом с кодом и проходит ревью как обычное изменение.

  • Конфигурация конвейера — какие задачи запускаются на изменение, что кешируется между запусками, какие шаги идут параллельно, а какие только на основной ветке, как добавить новую задачу.
  • Схема зависимостей — граф пакетов и приложений: что от чего зависит, какие задачи затрагивает правка одного файла, где граница между общими библиотеками и продуктами.
  • Правила обязательных проверок — что блокирует слияние, что работает как предупреждение, на каких путях срабатывает, кто и на каких условиях может обойти блокировку.
  • Политики доступа к секретам — где хранятся ключи, какие задачи и ветки их видят, что закрыто для запросов из внешних копий, как проходит ротация.
  • Регламент выпуска — версионирование пакетов, порядок публикации артефактов, последовательность выката и откат на предыдущий релиз.
  • Документация для команды — структура репозитория, типовые операции (добавить пакет, обновить общую библиотеку, ускорить сборку) и разбор частых ошибок.

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

Кейс: сокращение времени сборки на крупном репозитории

Монорепозиторий на 42 пакета и четыре продуктовые команды: каждая правка в общем модуле запускала полный прогон CI/CD, который занимал 55 минут. Разработчики перестали ждать результат и уходили в другие задачи, а очередь прогонов росла быстрее, чем закрывались изменения.

Что было

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

Что изменили

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

Что стало

Полный прогон сократился с 55 до 14 минут, типовой прогон по одной правке — до 3–4 минут. Доля ложных падений упала с 30% до 4%: проверка снова стала сигналом, а не поводом перезапустить. Откат неудачного выпуска занимает около трёх минут вместо сорока минут ручных действий — возвращаем предыдущую сборку одной командой.

Чем отличается CI/CD для монорепозитория от сборки по частям?

В наборе отдельных репозиториев единица изменения — репозиторий, поэтому задача, затрагивающая несколько пакетов, распадается на несколько независимых наборов изменений. Их приходится согласовывать вручную: порядок слияния, совместимость версий, момент выпуска. В монорепозитории единица изменения — весь граф.

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

Единая версия общих библиотек снимает отдельный класс отказов. Когда пакеты лежат порознь, общая библиотека расходится по версиям и в эксплуатации одновременно живут несколько её вариантов — несовместимость всплывает уже на работающем контуре. В монорепозитории библиотека одна, и изменение проходит проверки всех потребителей до выпуска.

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

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

Практические сомнения: откаты, кэш, изоляция окружений

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

Как откатить неудачный выпуск?

Каждый релиз — неизменяемый артефакт с меткой версии. Откат — переключение указателя на предыдущий артефакт: пересборка не нужна, состояние возвращается за минуты. Миграции схемы делаем обратно совместимыми, чтобы код прошлой версии работал с текущей базой.

Что делать, если кэш сборки отдаёт неверный результат?

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

Как разводятся окружения?

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

Кто имеет права доступа к конвейеру?

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

Почему проверки перед вливанием идут так долго?

Обязательный набор собирает и тестирует только затронутые пакеты по графу зависимостей — это минуты, а не часы. Полный прогон уходит в ночное расписание и не блокирует работу над следующей задачей.

Обсудим задачу и вернёмся с планом внедрения

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

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

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

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

Опишите задачу — какие приложения в монорепозитории и что сейчас мешает выпускать чаще. Вернёмся с оценкой сроков и планом, который можно включать поэтапно.

Критерий Раздельные репозитории Монорепозиторий, полный проход Монорепозиторий, сборка по затронутым пакетам
Что проверяется при правке только один репозиторий все пакеты целиком изменённый пакет и всё, что от него зависит
Время проверки по мере роста проекта складывается из проверок каждой части остаётся высоким и не зависит от размера правки пропорционально объёму правки, а не размеру репозитория
Согласованность версий между частями синхронизация вручную, расхождения копятся единые версии внутри одного репозитория единые версии внутри одного репозитория
Повторное использование результатов прошлых сборок почти невозможно: у каждой части своя среда возможно, но легко теряется при общей правке основной механизм ускорения
Изоляция изменений высокая: части живут отдельно низкая: правка затрагивает общий конвейер средняя: границы задаёт описание зависимостей
Последствия ошибки в общей части ломается одна служба останавливается весь конвейер ограничены затронутыми пакетами
Когда подход оправдан независимые команды с разными циклами выпуска небольшой репозиторий, редкие правки крупный репозиторий с частыми правками
Этап Что происходит Ориентировочный срок Что остаётся у заказчика
Аудит сборки замеряем, какие шаги занимают время, где теряются повторные проходы, какие зависимости не описаны 3–5 рабочих дней карта сборки и список узких мест с оценкой эффекта от каждого исправления
Разделение на затронутые пакеты описываем зависимости между частями, настраиваем отбор того, что нужно проверять после правки 1–2 недели правила отбора и описание граф зависимостей
Кэш и повторное использование настраиваем сохранение результатов шагов между запусками и между машинами исполнителей 1–2 недели повторные сборки без изменений проходят без работы
Режимы выпуска разделяем быструю проверку правки и полную проверку перед выпуском, настраиваем откат 1–2 недели предсказуемый выпуск и понятная процедура отката
Передача и наблюдение обучение команды, разбор типовых сбоев, показатели времени сборки и доли успешных запусков 1 неделя документация, дежурная инструкция, дашборд показателей
Роль в конвейере Вариант решения Когда оправдан Что даёт
Отбор задач и запуск шагов облегчённый оркестратор по описанию зависимостей средний репозиторий, команда уже знает инструмент быстрый старт, низкий порог входа, прозрачные правила
Отбор задач и запуск шагов многофункциональная система сборки с надстройками репозиторий растёт, нужны расширения и единый стиль задач гибкая настройка шагов, готовые расширения под типовые языки
Хранение результатов и граф зависимостей строгая система сборки с полным описанием зависимостей большая команда, высокая цена ошибки в общей части жёсткая дисциплина описания, воспроизводимость, точный отбор
Разделение сред общие исполнители с сохранением результатов большинство проектов на старте меньше простоя, результаты шагов переиспользуются между запусками
Разделение сред отдельные среды под каждую задачу чувствительные к изоляции проекты, редкие тяжёлые проверки полная изоляция, предсказуемое поведение, дороже в поддержке
Работа с оборудованием собственные исполнители нужен доступ к внутренним ресурсам и закрытым данным контроль над окружением, стабильный результат сборки

Наши компетенции:

Последние работы

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1543
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1394
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1374
  • Разработка логотипа компании B2B Advance
    Разработка логотипа компании B2B Advance
    816
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1142