Симптом: очередь GitHub Actions растёт, хотя несколько Mac Runner постоянно имеют статус «online».

Быстрое решение: считайте не число разработчиков и не онлайн-узлы, а пиковый поток задач, длительность выполнения, допустимое ожидание и резерв на сбой. Базовый пул оставьте для PR, UI-тесты и публикации разделите по ролям, а кратковременный пик закрывайте временным удалённым Mac, а не постоянным запасом на весь год.

Эта статья для DevOps-инженеров, которые поддерживают самостоятельные macOS Runner и не понимают, когда пора расширять пул. Она также пригодится руководителям мобильной разработки с несколькими iOS-проектами и техническим закупщикам, планирующим аренду удалённых Mac для CI.

01

Почему число онлайн-Runner не показывает реальную ёмкость

В GitHub Actions один занятый Runner выполняет одну назначенную ему задачу. Пока job исполняется, этот узел не является свободным ресурсом для следующей задачи. GitHub выбирает подходящий Runner по меткам и группам, а не просто по факту его присутствия в сети. Это описано в документации GitHub о маршрутизации self-hosted Runner.

Поэтому пул из нескольких машин может иметь низкую полезную ёмкость:

  • часть узлов занята долгими UI-тестами;
  • одна машина доступна по сети, но уже выполняет job;
  • Runner не подходит по метке, группе или архитектуре;
  • после сбоя процесс остался в неопределённом состоянии;
  • задачи ждут освобождения конкретного пула, хотя другой Mac простаивает;
  • старые PR уже неактуальны, но продолжают занимать очередь.

Для расчёта заведите отдельные измерения по каждому workflow:

  • время поступления job;
  • время начала выполнения;
  • время окончания;
  • длительность ожидания;
  • фактическое время работы;
  • число отменённых и повторно запущенных задач;
  • тип Runner и набор меток;
  • результат завершения;
  • состояние CPU, памяти, диска и Simulator во время job.

Среднее время скрывает релизный день и периоды массовых коммитов. Базой должны стать наблюдаемые пиковые окна и высокий перцентиль длительности, а не удобное среднее за месяц. GitHub рекомендует использовать доступные средства мониторинга и диагностики self-hosted Runner; методика наблюдения описана в официальном руководстве по мониторингу Runner.

Рабочая модель расчёта

Для каждого типа нагрузки используйте простую оценку:

Требуемая параллельность ≈ поток задач за окно × типичная длительность задачи / длительность окна.

Но это только стартовая модель. В неё нужно добавить:

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

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

02

Первый сценарий: PR-сборки формируют базовый пул

PR-поток обычно состоит из проверки кода, модульных тестов и инкрементальной сборки. Эти задачи приходят неравномерно, но их ценность связана со скоростью обратной связи. Если разработчик получает результат слишком поздно, он продолжает работать поверх устаревшей ошибки, а затем создаёт новые коммиты и дополнительные job.

В Xcode CI сначала разделите workflow по трудоёмкости:

  • быстрые проверки, которые не требуют macOS;
  • проверки, которые используют Xcode;
  • инкрементальные сборки;
  • чистые сборки;
  • обязательные проверки перед слиянием;
  • необязательные задачи для веток и черновых PR.

Всё, что не зависит от Apple SDK, не должно занимать Mac Runner. Это может быть статический анализ, подготовка артефактов или проверка формата. Конкретный состав зависит от проекта, поэтому переносите шаги только после проверки воспроизводимости.

Для ускорения инкрементальных сборок полезно включить сбор данных о времени фаз. Apple описывает Build Timing Summary и анализ времени сборки Xcode. Эти данные показывают, что увеличение числа Runner не исправит узкое место внутри одной компиляции: медленная зависимость или плохо настроенный граф целей продолжит занимать слот.

Сколько задач способен выполнять один самостоятельный Mac Runner? Одновременно — одну назначенную job. Несколько потоков внутри самой команды могут создавать параллельные процессы, но это не превращает один Runner в несколько независимых машин. При этом одна job может перегружать CPU, память, диск или Simulator, поэтому фактическая производительность разных workflow будет отличаться.

Сначала отмените устаревшие задачи для одной ветки, объедините проверки, которые не требуют отдельного запуска, и исключите из Mac те шаги, которым macOS не нужна. Для управления взаимозаменяемыми запусками используйте механизм concurrency в GitHub Actions. Только после такой очистки измеряйте потребность в новых узлах.

Напоминание. Растущая очередь не всегда означает нехватку Mac. Она может быть результатом старых job, неверной метки, слишком широкого workflow или задачи, которая блокирует редкий тип Runner.

03

Как отделить UI-тесты и Simulator от обычной сборки

UI-тесты требуют отдельного расчёта. На одной машине могут одновременно работать несколько Simulator, тестовые процессы, компилятор и сборка приложения. В результате параллельность workflow и внутреннюю параллельность тестов нельзя просто сложить.

Apple документирует параметры параллельного тестирования в материалах о настройке и запуске тестов Xcode. Используйте официально поддерживаемые параметры xcodebuild, но принимайте решение по данным своего проекта:

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

Стоит ли запускать UI-тесты и компиляцию на одном Runner? Да, если тестовый workflow короткий, редко пересекается с PR-пиками и не ухудшает стабильность. Нет, если рост числа Worker замедляет каждую job, вызывает падения Simulator или блокирует обязательные сборки. В таком случае выделите тестовый пул с отдельными метками.

Что сравнить перед разделением пула

Нагрузка Что занимает ресурсы Основной риск общего Runner Предпочтительная стратегия
PR-инкрементальная сборка Компилятор, кэш, диск Увеличение времени обратной связи Базовый пул для обычных job
UI-тесты Simulator, тестовые процессы, память Нестабильность и взаимное влияние Отдельный пул при доказанном конфликте
Полная регрессионная проверка Длительная job и несколько Simulator Долгая блокировка узла Плановое окно или самостоятельный тестовый пул
Подготовка релизной сборки Xcode, архив, подпись Конкуренция с PR Изолированный релизный Runner

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

04

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

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

Поле Что записывать Как использовать
Тип workflow PR, UI, регрессия, публикация Разделить несовместимые нагрузки
Время поступления Метка создания job Найти пики потока
Ожидание От создания до запуска Проверить доступность пула
Выполнение От запуска до завершения Оценить занятость слота
Повторный запуск Причина и результат Отделить инфраструктурный шум
Runner и метки Узел, архитектура, группа Найти неправильную маршрутизацию
Ресурсы CPU, память, диск, Simulator Определить предел одного Mac

Как связать количество удалённых Mac с длительностью сборки? Для каждого типа задач умножьте число job, пришедших в пиковое окно, на длительность высокого, но наблюдаемого перцентиля. Полученную занятость сравните с продолжительностью окна и добавьте резерв для отказа. Если ограничение по ожиданию жёсткое, проверяйте не только среднюю загрузку, но и момент, когда несколько долгих задач стартуют одновременно.

Например, если PR-сборки обычно короткие, но чистая сборка иногда занимает значительно больше времени, именно длинные наблюдаемые запуски должны влиять на базовую ёмкость. Численное значение из общей отраслевой статьи здесь было бы ненадёжным: Apple и GitHub не публикуют универсальную формулу «столько-то iOS-проектов на один Mac».

Условия выбора

  • Если очередь PR растёт в рабочее время, а UI-тесты и публикации занимают отдельные узлы, выберите расширение базового пула.
  • Если большая часть ожидания вызвана устаревшими job, сначала включите отмену взаимозаменяемых запусков и пересмотрите workflow.
  • Если UI-тесты замедляют компиляцию или повышают долю нестабильных завершений, перенесите их на отдельные Runner.
  • Если публикация блокируется обычными job, назначьте ей собственную группу или резервный узел.
  • Если дефицит появляется только перед релизом, выберите краткосрочное расширение, а не постоянный пул.
  • Если даже после разделения нагрузок очередь сохраняется каждый рабочий день, планируйте долгосрочное увеличение числа Mac.
05

Подпись и публикация требуют изоляции, а не только мощности

Релизная job может быть редкой, но её цена простоя выше, чем у обычной PR-проверки. При выборе общей машины учитывайте:

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

Маршрутизацию задавайте через метки и группы. Документация GitHub о метках self-hosted Runner показывает, как направлять job к узлам с нужными характеристиками. Для более строгого разделения используйте Runner Groups.

Нужен ли отдельный Mac для публикации? Да, если обычные задачи регулярно занимают все доступные слоты, если сертификаты нельзя безопасно смешивать с PR-окружением или если сбой публикации требует быстрого восстановления. Нет, если релизный Runner имеет отдельную группу, чистое состояние, проверенную очистку и гарантированное окно доступности.

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

06

Ночные задания и релизные пики лучше закрывать эластично

Разделите нагрузку на три класса:

  1. ежедневные PR и обязательные проверки;
  2. ночные регрессии и задачи без жёсткого срока;
  3. короткие пики перед публикацией или выпуском версии.

Для первой группы нужен постоянно доступный базовый пул. Для второй можно использовать очередь и расписание. Для третьей сравните постоянный узел с временной арендой удалённого Mac.

Решение Когда подходит Ограничение Что измерить
Постоянный Runner Нагрузка стабильна каждый рабочий день Оплата простаивающей ёмкости Доля занятости и очередь
Перенос по расписанию Задачи можно завершать ночью Результат приходит позже Допустимое окно ожидания
Временное расширение Пик короткий и предсказуемый Нужно подготовить окружение Время доставки и настройки
Общий узел с резервом Нагрузки редко пересекаются Конфликт может совпасть с релизом Время восстановления

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

Опыт эксплуатации. Резерв — это не Runner со статусом «online», а узел, который проверен тем же workflow, имеет нужные метки и может принять задачу после отказа основного хоста.

07

Пошаговый план пробного запуска

Шаг первый: разделите workflow по сценарию

Создайте отдельные категории для PR-сборок, Simulator/UI, ночных задач и публикации. Не смешивайте их в одну среднюю статистику.

Шаг второй: назначьте маршрутизацию

Определите метки для архитектуры, версии Xcode, UI-тестов и релизной подписи. Затем проверьте, что workflow действительно попадает в ожидаемую группу. Синтаксис назначения Runner и меток приведён в официальной документации GitHub по workflow.

Шаг третий: соберите исходную выборку

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

Шаг четвёртый: очистите неэффективные запуски

Отмените устаревшие PR-задачи, вынесите macOS-независимые проверки и проверьте, нельзя ли объединить одинаковые действия. После изменения workflow соберите статистику заново.

Шаг пятый: проверьте внутреннюю параллельность UI-тестов

Постепенно меняйте число Worker и Simulator. Для каждого режима фиксируйте не только время полного запуска, но и стабильность, нагрузку, ошибки и восстановление. Если новый режим быстрее только на бумаге, оставьте меньше Worker и добавьте независимый узел.

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

Запустите архивирование с реальной подписью в релизной группе. Проверьте чистое состояние, повтор после сбоя и отсутствие влияния PR-задач.

Шаг седьмой: смоделируйте отказ

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

Шаг восьмой: сравните четыре показателя

Решение принимайте по очереди, эффективному времени выполнения, доле неуспешных запусков и состоянию ресурсов. GitHub также рекомендует использовать наблюдение за Runner для поиска проблем с доступностью и выполнением job.

08

Итоговая схема ёмкости для закупки

Роль узла Основная задача Можно ли делить Когда добавлять
Базовый пул PR, проверки, обычная сборка С ночными задачами — при отсутствии конфликта Очередь превышает целевое окно регулярно
Тестовый пул UI, Simulator, регрессия С PR — только после измерений Внутренняя параллельность ухудшает стабильность
Релизный резерв Архив, подпись, публикация С PR — нежелательно Публикация ждёт освобождения или требует изоляции
Отказоустойчивый резерв Приём задач после сбоя С непиковыми задачами — с ограничениями Один отказ блокирует обязательный поток

Используйте следующие правила финального решения:

  • Оставьте текущий размер, если очередь укладывается в целевое окно, ошибки не связаны с перегрузкой, а релизный маршрут проверен.
  • Оптимизируйте workflow, если много отменяемых старых запусков, macOS выполняет лишние шаги или неверные метки создают искусственное ожидание.
  • Расширьте пул на короткий срок, если дефицит появляется только в измеряемые часы релиза или регрессии.
  • Добавьте постоянный узел, если дефицит повторяется после оптимизации в каждом сопоставимом рабочем окне.
  • Разделите роли, если UI-тесты, подпись или длинные архивы ухудшают работу обычных PR.
  • Сформируйте резерв, если отказ одного Mac оставляет обязательный workflow без подходящего Runner.

Для сравнения вариантов можно отдельно изучить Mac mini с оформлением аренды для нужного региона, но сначала зафиксируйте требования к архитектуре, Xcode, меткам, сети и восстановлению. Региональный выбор не заменяет расчёт очереди.

09

Что выбрать: постоянные узлы или временный удалённый Mac

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

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

Если проблема ограничена релизным окном, ночной регрессией или временным ростом PR, аренда удалённого Mac у KVMNODE обычно лучше соответствует задаче: вы проверяете измеренную нагрузку коротким расширением, не превращая пиковое количество Runner в постоянный расход. Ограничения нужно учитывать заранее: физический доступ к конкретному Mac, задержка сети, требования к приватному контуру и длительная стабильная загрузка могут сделать собственный узел разумнее.

Начните с таблицы фактических job. Если после оптимизации и разделения пулов очередь всё ещё превышает допустимое окно, возьмите временный удалённый Mac под измеренный пик, проверьте Xcode CI, UI-тесты, подпись и восстановление, а затем решите, нужен ли долгосрочный узел. Такой порядок защищает вас и от недокупки ёмкости, и от многолетней оплаты простаивающих Runner.