VPSSpark Блог
← К дневнику разработки

3 Cloud Mac выдержали 500 iOS CI-сборок в день

Заметки с сервера · 2026.06.02 · ~14 мин

Очередь iOS CI и self-hosted Mac Runner на Cloud Mac

Вывод сразу: при ~500 iOS CI-сборках в сутки большинству команд не нужны восемь Mac mini — хватит трёх Cloud Mac на Apple Silicon, если разделить job’ы в GitHub Actions по уровням и изолировать быструю и release-очереди через Self-hosted Mac Runner, а не гонять три полных Archive параллельно на каждой машине.

Три месяца назад мы переняли Xcode CI у iOS-команды. Исходные цифры:

  • 18 разработчиков
  • 2 приложения в общем monorepo
  • GitHub Actions — единственная CI
  • Около 500 macOS job’ов в день (PR, unit-тесты, ночные релизы)

В закупке числилось: восемь Mac mini в серверной под self-hosted runner. Две недели логов workflow по типам job показали разрыв с интуицией: ~70 % — PR и интеграционные сборки, 20 % — Simulator unit-тесты, ~10 % — archive + upload (upload в TestFlight можно учитывать отдельно, см. рис. 1).

Итог внедрения: 2 × Fast Mac Runner (PR / тесты) + 1 × Release Mac Runner (archive, нотаризация, TestFlight) — три Cloud Mac стабильно держат весь iOS CI/CD. Ниже — расчёт ёмкости, почему не Xcode Cloud и не полностью управляемый Bitrise, а Self-hosted Runner на выделенных узлах.

Рис. 1 · Состав ~500 iOS CI-сборок в сутки (две недели GitHub Actions, замеры команды)

PR Build
65%
Unit Test
20%
Archive
10%
Upload
5%
3
Cloud Mac (хватает на практике)
8→3
Сокращение закупки
63
машино-ч/сутки (с кешем)

Расчёт ёмкости: с 91 до 63 машино-часов

500 job’ов/сутки за 24 часа — в среднем ~21 job/ч, но решают пики: утренние merge в EU/US, шторм PR перед релизом. Пики часто в 3–5 раз выше среднего. Ёмкость считайте под очередь в пик, а не под полночное среднее.

Вторая переменная — длительность job: Simulator-тесты 4–8 мин; PR-сборка с deps 12–20 мин; release archive + нотаризация + upload 25–45 мин. P50 за две недели: лёгкие ~6 мин, тяжёлые ~35 мин. Если ошибочно заложить 30 % archive, получится «нужно восемь Mac» — после разбивки тяжёлых job на 500 runs их заметно меньше.

Без кеша грубая оценка 325 лёгких + 175 тяжёлых ≈ 91 машино-ч/сутки — больше физического потолка трёх Mac (72 ч). С кешем DerivedData/SPM, сериализацией L2 и метками очередей лёгкие ~4 мин, тяжёлые ~22 мин на warm path: потребность ≈ 63 машино-ч. При коэффициенте пика ~1,3 это приемлемо — математика «хватит трёх».

Рис. 3 · Суточная потребность в машино-часах macOS (модель 500 job/сутки)

Без кеша
91 ч
С кешем
63 ч

Слои Xcode CI: не смешивайте archive и PR в одной runner-очереди

Пайплайны на 500 runs/сутки почти всегда делят Xcode CI на три уровня:

  • L0: SwiftLint, сборка модуля — метка macos-fast.
  • L1: PR-сборка без archive — Fast Mac Runner, до 2 лёгких job параллельно на узел.
  • L2: archive, нотаризация, TestFlight — метка macos-archive, не более 1 job одновременно на машину.

GitLab CI, Buildkite, Jenkins — та же логика меток, не общая «свалка». Burst с Buildkite: Mac Agent Buildkite на Cloud Mac. Mac VPS vs пул runner: гид Mac VPS / macOS в облаке.

Три Cloud Mac: роли и топология

Развёрнутая статическая топология из трёх узлов (имена гибкие, роли лучше сохранить):

Узел Метка runner Роль Параллелизм
Mac-A macos-fast PR Build, Unit Test 2 × L0/L1
Mac-B macos-fast Симметричен Mac-A, fast-очередь 2 × L0/L1
Mac-C macos-archive Archive, нотаризация, Upload 1 × L2

Рис. 2 · Топология трёх узлов (GitHub Actions Self-hosted Runner)

Mac-AFast Mac Runner
Mac-BFast Mac Runner
Fast QueuePR Build · Unit Test · L0/L1
Mac-CRelease Mac Runner
Archive QueueArchive · Нотаризация · TestFlight Upload
В нагрузочных тестах и проде используем VPSSpark Cloud Mac mini M4 и M4 Pro (два fast + один release). Цифры ёмкости, P95 очереди и hit rate кеша — с Self-hosted Runner на Apple Silicon Cloud Mac, не Hackintosh и не «серые» VM.

Два fast-узла дают пропускную способность, один release — предсказуемую поставку. Если L2-очередь в день релиза выше SLO, Mac-A временно может получить macos-archive — но отключите параллель L0, иначе Keychain и блокировки DerivedData дают случайные сбои подписи. Minor Xcode на всех трёх должен совпадать с заметками к релизам Xcode.

Параллелизм: M4 16 ГБ и GitHub Actions Mac Runner
Лёгкий Xcode CI: 2 job на машину; полный archive: 1 job. Два параллельных archive на M4 16 ГБ — частая причина «ложной нехватки» при 500 runs/сутки.

Почему GitHub Actions Mac Runner быстро встают в очередь

Многие начинают с macOS runner от GitHub и видят очередь при потоке PR. Редко «GitHub медленный», чаще: (1) org-wide лимит concurrency macOS; (2) workflow с полным archive на каждый PR; (3) нет разделения по типу job на Self-hosted Mac Runner — быстрые job за медленными. До перехода на три выделенных Cloud Mac queue_wait P95 превышал 40 мин; с dual fast/archive — L1 P95 < 8 мин.

Типичная схема: три Cloud Mac как baseline self-hosted, hosted runner только на open-source-недели или экстремальные пики L0 — затраты под контролем, секреты без еженедельной миграции.

Self-hosted Runner vs Xcode Cloud

Xcode Cloud — для команд в экосистеме Apple с нулевым ops agent. Self-hosted Mac Runner — когда нужны свои очереди, ключи кеша и смешение с внутренним Jenkins/Buildkite. Сравнение:

Измерение Xcode Cloud Cloud Mac + Self-hosted Runner
Биллинг Пакет минут + cap concurrency Подписка/день Cloud Mac, предсказуемые машино-часы
Очередь Общая платформа Свои метки fast/archive
Секреты Удобная интеграция ASC Match / Keychain — свой runbook
Кому подходит Редкие релизы, мало кастома 500+ runs/сутки iOS CI/CD

Когда переключаться после исчерпания пакета минут: FAQ лимиты Xcode Cloud vs Cloud Mac для archive.

Почему команда отказалась от Xcode Cloud

Xcode Cloud оценивали; выбрали GitHub Actions + Self-hosted Runner, потому что: (1) backend и Android уже на GitHub — вторая CI не нужна; (2) нужны свои ключи кеша и matrix monorepo; (3) в релизные недели объём job выходит за «комфорт» пакета минут, очередь не управляется. Xcode Cloud не плох — он плохо совпадает с «500 runs/сутки, очередь под контролем».

Bitrise vs Self-hosted Mac Runner — интуиция по стоимости

Bitrise и аналоги экономят ops agent, но тарифицируют concurrency. Для 18 человек, двух app, ~500 job/сутки годовая сумма часто выше подписки на три Cloud Mac, а параллель archive всё равно упирается в tier. Bitrise — стартапам без желания трогать runner; две недели на Self-hosted Mac Runner — окупаемость узлов за 3–6 месяцев. Уже на Bitrise? переносите L2 на release-узел поэтапно.

Mac Agent Buildkite: плюсы и минусы

Buildkite держит очередь в облаке, agent на вашем железе — хороший burst, понятные artifact. Минус — лишний слой оркестрации; для трёх Mac иногда «из пушки по воробьям». У клиента PoC Buildkite отличный по burst, но остались на native GitHub Actions — YAML знают только там. С существующим Buildkite — та же стратегия меток fast/archive, см. статью Buildkite + Cloud Mac.

Cloud Mac vs локальный Mac mini CI

Восемь Mac mini в своей стойке: CapEx, амортизация, отключение питания, аудит ЦОД. Три Cloud Mac: предсказуемый OpEx, PoC на сутки, регион ближе к Git. Локальный CI оправдан при >14 ч compile/сутки на машину годами без изменений. Cloud Mac CI — для плавающих пиков, недель подрядчиков, сезона review и нотаризации. Команда оставила два офисных Mac под разработку; тяжёлый Xcode CI в облаке — вместо восьми mini простаивающими большую часть суток.

SLO очереди: четвёртая машина только по данным

Метрики: queue_wait_seconds (P95), run_duration по L0/L1/L2, cache_hit_ratio, l2_concurrent (редко > 3). Пример порогов: L1 P95 < 8 мин; L2 P95 < 25 мин. Четвёртый release-узел — если L2 три дня подряд выше порога и cache hit > 60 %.

Кеш: рычаг для 63 машино-часов

DerivedData: ключ branch + версия Xcode; смена lock SPM/CocoaPods bust. Fast-узлы делят read-only кеш, release хранит L2 DerivedData локально на NVMe. Материалы подписи через vault — не в bundle кеша. Ключи с minor macOS/Xcode, иначе «hit, но link fail» после апгрейда.

Минимальная настройка GitHub Actions Self-hosted Runner

Три регистрации: macos-fast ×2, macos-archive ×1. Для L2 нужен concurrency, чтобы release job не отменяли друг друга:

GitHub Actions · Release Mac Runner
concurrency:
                  group: ios-archive-${{ github.ref }}
                  cancel-in-progress: false
                jobs:
                  archive:
                    runs-on: [self-hosted, macos-archive]
                    steps:
                      - uses: actions/checkout@v4
                      - run: xcodebuild archive -scheme App -archivePath build/App.xcarchive

Release-узел: выделенный пользователь macOS + Match; после reboot — unlock-скрипт перед ночной очередью. См. документацию xcodebuild и Fastlane.

День релиза: 500 runs становятся 650

Порядок: (1) остановить некритичный L0; (2) hosted runner только для L1; (3) четвёртый Cloud Mac на 48 ч посуточно. Держать L2 на 2 parallel/машину постоянно — случайные сбои нотаризации.

Когда четвёртая машина обязательна

  • L2 queue P95 > 40 мин неделю, кеш уже оптимизирован.
  • Monorepo >5 app на одном release, ночное окно не хватает.
  • Полный archive на каждый PR — сначала pipeline, не железо.

Анти-паттерны

Полный archive на PR; runner без меток; два archive на M4 16 ГБ; ключи кеша без ветки; смотреть только success rate без queue_wait — всё заставляет три машины казаться «мало» и толкает к заказу восьми mini.

FAQ: короткие ответы для поиска

Сколько Mac на 500 iOS-сборок в сутки?

При слоях job (PR / тесты / archive) и кеше DerivedData большинству команд хватит трёх Cloud Mac Apple Silicon: 2 Fast Mac Runner + 1 Release Mac Runner. Если >50 % runs — полный archive, сначала pipeline или четвёртый release-узел.

Сколько job параллельно на GitHub Actions Mac Runner?

На M4 16 ГБ: 2 лёгких job (PR, unit test) или 1 archive job. Не держите «2× archive» постоянно.

Почему archive нельзя сильно параллелить?

Давление на память → swap, очередь диска, блокировки Keychain и конфликты codesign — sporadic timeout, не воспроизводимые ошибки компиляции.

Cloud Mac дешевле Xcode Cloud?

При частом iOS CI/CD, постоянном Self-hosted Runner и resident secrets три Cloud Mac обычно предсказуемее поминутной оплаты. Редкие релизы, нулевой ops — сначала Xcode Cloud.

Bitrise или три Cloud Mac?

Bitrise экономит ops, быстрый старт. ~500 job/сутки с контролем очереди и кеша чаще выигрывает Self-hosted Mac Runner + Cloud Mac — ниже годовая сумма, L2 concurrency по вашим правилам.

VPSSpark: baseline Cloud Mac 2 Fast + 1 Release

Считаете «500 runs/сутки — сколько Mac?»: две недели логов как на рис. 1, затем проверьте, держится ли модель 63 машино-ч. VPSSpark Cloud Mac mini M4 / M4 Pro — PoC на сутки или подписка — для GitHub Actions Self-hosted Mac Runner, PR и archive в разных очередях.

См. тарифы Mac в облаке или главную VPSSpark — выберите регион, прогоните реальный workflow по bucket’ам, затем решите, хватит ли трёх — вместо предзаказа восьми mini.

Акция

500 CI-сборок в день — хватит трёх Cloud Mac

GitHub Actions Mac Runner · self-hosted · fast/archive

На главную
Акция Смотреть тарифы