VPSSpark Блог
← Вернуться к дневнику

Хватит ли двух Cloud Mac для CI Flutter-команды?

Заметки с сервера · Cloud Mac CI #9 · 2026.06.08 · ~12 мин

Часто ищут: Flutter CI macOS · сборка iOS Flutter · self-hosted runner Cloud Mac

Разработчик Flutter отлаживает приложение на Mac, контекст CI
У dual-platform Flutter CI узкое место — iOS/macOS; два Cloud Mac делят fast и archive.

VPSSpark Cloud Mac CI, выпуск #9. Вопрос, который часто задают Flutter-команды в России и СНГ: хватит ли двух Cloud Mac для стабильной CI при 8–15 разработчиках? Да — если разделить Android и iOS: сборки Android остаются на хостинге GitHub (ubuntu-latest), на macOS — два self-hosted runner в пулах macos-fast и macos-archive, и железное правило: никакого archive на PR. Ниже — модель нагрузки, уровни job, фрагмент workflow и чеклист внедрения. Диагностика очередей и подключение runner — в серии #2 и в кейсе три Cloud Mac при высоком объёме iOS CI.

1 · Неочевидный вывод: узкое место Flutter CI — не Dart

Большинство красных прогонов Flutter CI падают не из‑за медленного flutter test, а потому что iOS-джобы занимают слоты macOS runner — часто из‑за flutter build ipa на PR.

Кроссплатформенность создаёт иллюзию «один workflow на всё». На практике flutter analyze и unit-тесты идут и на Linux; macOS нужен для CocoaPods, сборки Xcode, подписи и экспорта IPA. Если archive попадает в путь PR, один Cloud Mac блокируется на 25–40 минут — остальные PR встают в Queued. То же, что у native iOS-команд; формула диагностики: wait time >> run time (см. диагностику очереди macOS runner).

Два Cloud Mac — не «две универсальные машины», а осознанная двухпуловая топология: быстрый feedback и тяжёлые release-задачи. APK/AAB остаются на Linux — не сжигать macOS-слоты на Gradle.

2 · Модель нагрузки: сколько параллелизма macOS нужно команде 8–15 человек?

База — умеренно активная Flutter-команда (1–2 PR на человека в день, nightly на main, еженедельные релизы):

12
Типичный размер команды
~18
macOS job/рабочий день
2
Слота Cloud Mac

В fast-пуле (macos-fast): analyze, тесты, сборка под iOS-симулятор — цель 8–14 минут wall time с кэшем pub/DerivedData. В archive-пуле (macos-archive): release IPA + нотаризация — 20–35 минут, но редко (main, теги, nightly). Смешанные пулы: archive душит fast; при разделении P95 ожидания PR обычно укладывается в 10 минут — достаточно для ритма code review в распределённых командах СНГ.

При 15+ людях или множестве flavor в matrix monorepo имеет смысл третья archive-машина — планирование как в сценарии 500 сборок/день, не тема этого старта с двух машин.

Платформа Runner Типичный job На PR?
Android ubuntu-latest (хостинг) flutter build apk/appbundle, Android-тесты Да
iOS быстрый feedback Cloud Mac #1 · macos-fast analyze, тесты, build ios --simulator Да
iOS release Cloud Mac #2 · macos-archive build ipa, нотаризация, TestFlight Нет

3 · Три уровня Flutter job: CI Hard Rules

Сопоставьте pipeline Flutter с L0/L1/L2 — в духе on-call руководства:

Уровень Содержимое Flutter Пул Триггер на PR?
L0 dart format --set-exit-if-changed, flutter analyze, лёгкие тесты Linux или macos-fast Да
L1 Интеграционные тесты, flutter build ios --simulator, widget-тесты macos-fast Да · без archive
L2 flutter build ipa, App Store Connect, нотаризация macos-archive Нет · только main/tag/schedule

Три правила как у native iOS: Rule 1 — без L2 на PR; Rule 2 — L2 только в изолированном пуле; Rule 3 — fast-пул не блокируется archive. Особенность Flutter: многое из L0 идёт на Linux — привязка всего к macos-fast тратит macOS-конкурентность впустую. На PR: Linux и macOS job параллельно; macOS только для «нельзя на Linux».

4 · Топология из двух машин: роли Cloud Mac #1 и #2

Рис. 1 · Типичная топология: два Cloud Mac для CI Flutter-команды

GitHub Actions · разделение PR / mainAndroid → ubuntu-latest
Cloud Mac #1 · macos-fastL0/L1 · 8–14 мин/job
Cloud Mac #2 · macos-archiveL2 · только main/tag

Cloud Mac #1 (fast-пул): labels [self-hosted, macOS, macos-fast, flutter]. Flutter SDK зафиксирован (fvm или pin в образе), Xcode, CocoaPods. Runner как постоянный сервис с автозапуском; держите pub cache и DerivedData тёплыми — короткие job, высокий throughput.

Cloud Mac #2 (archive-пул): labels [self-hosted, macOS, macos-archive, flutter-release]. Физическое разделение — никогда два runner разных пулов на одном Mac (иначе Rule 2 бессмысленна). Distribution-сертификаты и App Store Connect API Key в Keychain; шаги подключения — как в практическом onboarding runner. Перед релизом: flutter doctor -v и dry-run archive — не заражать fast-пул.

Один регион, одна мажорная версия Xcode — меньше «fast зелёный, archive красный». Cloud Mac: фиксированный образ, стабильный канал к GitHub, без офисного UPS — удобно для CI 7×24. Про границы безопасности self-hosted runner: archive-машину ограничьте одним repo или org.

5 · Пример workflow: разделение PR и main

Ядро маршрутизации (полный pipeline добавит cache, secrets, artifacts). Документация CD Flutter по подписи iOS; сертификаты уже в Keychain archive.

flutter-ci.yml (ядро)
jobs:
                  android-pr:
                    if: github.event_name == 'pull_request'
                    runs-on: ubuntu-latest
                    steps:
                      - run: flutter analyze && flutter test
                      - run: flutter build appbundle --release

                  ios-pr-fast:
                    if: github.event_name == 'pull_request'
                    runs-on: [self-hosted, macOS, macos-fast, flutter]
                    steps:
                      - run: flutter analyze
                      - run: flutter test
                      - run: flutter build ios --simulator --no-codesign

                  ios-release:
                    if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')
                    runs-on: [self-hosted, macOS, macos-archive, flutter-release]
                    steps:
                      - run: flutter build ipa --export-options-plist=ExportOptions.plist
                      # TestFlight / нотаризация — только L2

В ios-pr-fast намеренно нет build ipa. Сборка под устройство на PR — только через workflow_dispatch, всё равно archive-пул, не стандартное PR-событие. С concurrency в синтаксисе workflow отменяйте старые PR-run — часто ~20 % меньше бессмысленного ожидания.

6 · Чеклист кэша: четыре каталога для Flutter-команд

Два Cloud Mac держатся на хорошем кэше лучше, чем на новейшем CPU. Персистентно в home runner (или nightly snapshot):

  • PUB_CACHE / ~/.pub-cache — зависимости Dart; инкремент при смене pubspec.lock
  • Каталог Flutter SDKfvm фиксирует версию; без flutter upgrade в CI
  • ios/Pods + кэш CocoaPodspod install часто длинный хвост iOS
  • Xcode DerivedData — заметный выигрыш при том же ios/Podfile.lock

Fast и archive не делят один DerivedData — смешение Debug/Simulator и Release/Archive даёт призрачные ошибки линковки. Gradle-кэш Android на ubuntu-latest через Actions Cache; берегите диск Cloud Mac.

Базовый образ
Раз в месяц «чистый» образ: minor Flutter + patch Xcode + pod repo update — сначала на archive, потом синхронизация fast-пула. Ежедневные PR только переиспользуют кэш; не ставьте flutter pub upgrade дефолтом в CI.

7 · Чеклист внедрения: от нуля к двум подключённым машинам

За 1–2 рабочих дня до PoC:

  • 2 Cloud Mac одинаковой spec (M4 + 16 ГБ; пики iOS-сборки часто 12 ГБ+)
  • #1: Flutter/Xcode/CocoaPods, runner macos-fast; #2: материалы подписи, runner macos-archive
  • Разделить workflow: Android ubuntu-latest; PR только L1; main/tag — L2
  • Персистентность pub/DerivedData/Pods; 3 дня смотреть P95 wait < 10 мин
  • Если wait >> run сохраняется: сначала L2 в PR, потом добавление машин

PoC с одной Cloud Mac на fast-пул; archive временно на хостинговом macos-latest (принять очередь) — проверить логику, затем вторая машина для SLA релизов. Не противоречит «двум машинам»: вторая страхует releases, не PR-feedback.

8 · FAQ

Все Flutter-тесты на Linux — хватит одного Cloud Mac?

Без macOS-сборки на PR теоретически да — большинство команд гоняют build ios --simulator для native-плагинов. Один Mac на L1 возможен; archive должен быть изолирован от быстрого feedback, иначе релизы блокируют все PR. Две машины — надёжный выбор.

Два Cloud Mac vs два Mac mini в офисе?

Топология та же; разница в эксплуатации: Cloud Mac без закупки железа, снимаемые образы, часто стабильнее канал к GitHub. Офисный Mac при 7×24 и серверной; распределённые команды СНГ чаще берут Cloud Mac — см. также масштабирование при высоком объёме job.

Оставлять Codemagic или хостинговый GitHub macOS?

Как резерв archive или overflow PR. После основного self-hosting хостинговый macOS уместен при < 4 релизах/месяц; активные Flutter-команды долгосрочно часто экономят на своих runner.

Monorepo с несколькими app — как считать job?

Matrix × flavors перемножаются. Больше четырёх macOS job на PR — расширять fast-пул или сжимать matrix, а не впихивать archive в PR.

Навигация по серии (#9 Flutter, две машины)
#1 ёмкость · #2 очередь · #3 стоимость self-hosted · #8 ускорение сборки · #9 эта статья

Два Cloud Mac — CI Flutter для Android и iOS без конфликтов

Android остаётся на Linux; сборка iOS, подпись и экспорт IPA требуют macOS. Два Cloud Mac mini M4 с macos-fast и macos-archive разводят PR-feedback и release-job — выгоднее, чем archive в каждом PR и Queued для всех. Apple Silicon ускоряет flutter build ios и линковку Xcode; ~4 В в простое подходят для CI 7×24.

Против офисной «мак-стойки»: Cloud Mac по тарифу, клонируемые образы, стабильный канал к GitHub — удобно распределённым Flutter-командам в РФ и СНГ. Нативный Unix для Flutter, CocoaPods, Fastlane без лишней виртуализации.

При миграции iOS CI вашей Flutter-команды: старт с двух VPSSpark Cloud Mac mini M4 как минимальная топологиясмотреть тарифы, PoC fast-пула, затем archive-пул для релизов.

Акция

Flutter CI упирается в iOS? Два Cloud Mac — два пула

macos-fast + macos-archive · PR и релиз раздельно

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