← К техблогу

Резкий рост затрат на Xcode 26 Beta?
Изоляция runners как решение

Два self-hosted runner для Xcode 26 beta CI на удалённом Mac mini
Xcode 16 на prod, beta на выделенном runner—после WWDC iOS CI = разделить диски.

Вывод: Чтобы тестировать Xcode 26 Beta в CI после WWDC 26, не стоит переводить main на macos-latest и надеяться на лучшее — правильный путь: два независимых Runner на удалённом Mac: продакшен остаётся на Xcode 16, beta идёт через отдельный label и изолированный каталог DerivedData.

Три жёстких правила: ① prod job'ы не вызывают sudo xcode-select; ② у beta и prod разные -derivedDataPath; ③ runtime Simulator iOS 27 предустанавливается на beta-Runner один раз, а не скачивается в каждом job.

Границы статьи: почему CI замедляется после WWDC — в WWDC26 iOS CI медленнее; здесь — runbook на 48 часов по изоляции runners.

Через неделю после 10 июня в Slack уже не спрашивают «почему всё тормозит», а конкретно: «на каком Mac ставить beta?», «два Xcode на одной машине?», «кто виноват, если main красный?» Ответ прост и практичен: удалённый Mac mini как self-hosted runner и полное разделение beta-валидации и App Store релизов. Если своих runners ещё нет — начните с руководства по ускорению iOS CI; сравнение узлов и TCO — TCO runners в шести регионах.

WWDC 26 принесла SDK iOS 27, фреймворки Siri AI и более строгие проверки Swift 6 — всё на той же M4, что крутила CI ещё вчера. Команды, смешивающие beta и продакшен, платят дважды: длинные сборки, нестабильные pipeline и main, заблокированный экспериментальными toolchain. Изоляция runners — не enterprise-роскошь, а минимально жизнеспособный ops для тех, кто держит ритм TestFlight и параллельно исследует iOS 27.

Hosted-минуты macos-latest в beta-сезон особенно дороги: каждый job стартует с нуля, заново качает runtime Simulator и обнуляет ключи actions/cache после смены Xcode. Self-hosted на удалённом Mac mini переворачивает логику — диск остаётся тёплым, bootstrap сжимается, вы платите месячную аренду вместо поминутного счёта за каждый неудачный beta-эксперимент. Сравните hosted × ожидаемые beta-прогоны с TCO self-hosted; часто уже от ~15 beta-сборок в неделю отдельный beta-Runner дешевле GitHub Actions. Если команда только начинает iOS CI, параллельно прочитайте почему WWDC замедляет pipeline — там контекст SDK и очередей; здесь — практика разделения машин.

1) Три заблуждения: одна машина «для всего» не экономит

Самое частое решение после WWDC: поставить Xcode 16 и 26 beta на единственный CI-Mac и переключать через xcode-select -s в workflow. На бумаге — минус одна аренда; на практике — дороже второго runner:

  • Гонки: два параллельных job — поздний xcode-select перезаписывает глобальный путь. Prod Archive может незаметно собраться beta-компилятором.
  • Смешение кэша: общий ~/Library/Developer/Xcode/DerivedData смешивает Swift-модули разных версий. Ошибки-призраки: локально зелёно, CI случайно красно.
  • Диск и RAM: два Xcode + два runtime Simulator ≈ 80 ГБ+. На 16 ГБ beta full build уходит в swap и тормозит все job'ы, включая те, что должны оставаться на Xcode 16.

Ещё одна иллюзия: «beta только ночью, днём машина свободна для prod». Nightly-cron и push в main в 17:58 всё равно пересекаются. Расписание CI недетерминировано — labels детерминированы. Командам Flutter: flutter build ipa на prod-runner, пробы iOS 27 на beta — см. Flutter iOS CI для раздельных cache-path по lane.

Решение в одном предложении: команда шипит daily на main и держит TestFlight → prod и beta runners обязаны быть изолированы. Только маленькие команды с месячными релизами и чистой nightly-beta могут делить одну машину — всё равно с разными DerivedData.

2) Архитектура dual-runner: пулы labels и разделение workflow

Минимально жизнеспособная схема: два удалённых Mac mini M4 — или месячный prod + beta-машина посуточно на время адаптации iOS 27:

RunnerLabel GitHubВерсия XcodeJob'ы
Prod Aself-hosted, macos, ios-prodXcode 16.4 (закреплён)archive main, TestFlight, release-теги
Beta Bself-hosted, macos, xcode26-betaXcode 26 betaветки ios-27-*, nightly-адаптер, пробы API

Разделение в workflow через runs-on — beta-ветки никогда не должны триггерить label ios-prod:

.github/workflows/ios-prod.yml — prod lane
name: iOS Production CI
on:
  push:
    branches: [main, release/*]
jobs:
  archive:
    runs-on: [self-hosted, macos, ios-prod]
    env:
      DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer
      DERIVED_DATA: /var/ci/deriveddata/prod
    steps:
      - uses: actions/checkout@v4
      - name: Build & Archive
        run: |
          xcodebuild -scheme MyApp -configuration Release \
            -derivedDataPath "$DERIVED_DATA" \
            -archivePath build/MyApp.xcarchive archive
.github/workflows/ios-beta.yml — beta lane
name: iOS 27 Beta Adapter
on:
  push:
    branches: [ios-27-*, feature/siri-ai-*]
  schedule:
    - cron: '0 2 * * *'
jobs:
  beta-build:
    runs-on: [self-hosted, macos, xcode26-beta]
    continue-on-error: true
    env:
      DEVELOPER_DIR: /Applications/Xcode_26_beta.app/Contents/Developer
      DERIVED_DATA: /var/ci/deriveddata/beta
    steps:
      - uses: actions/checkout@v4
      - run: xcodebuild -scheme MyApp -sdk iphonesimulator build \
          -derivedDataPath "$DERIVED_DATA"

Labels — ops-контракт. Регистрация: документация GitHub self-hosted. Webhook: FAQ OpenClaw CI Runner.

Ограничивайте concurrency на уровне runner: GitHub Actions позволяет несколько параллельных job на один label — на 16 ГБ это ведёт к OOM при смешении beta и prod. Задайте concurrency: group: ios-prod и ios-beta в workflow, чтобы на lane выполнялся максимум один тяжёлый archive. Это важнее очередного «оптимизационного» флага в YAML.

3) Установка Xcode 26 beta на удалённом Mac и сосуществование версий

На bare-metal удалённом Mac два Xcode рядом в /Applications — явные имена папок, чтобы beta не перезаписала stable:

  1. Скачать Xcode 26 beta (.xip) с Apple Developer Downloads, распаковать по SSH в /Applications/Xcode_26_beta.app.
  2. Prod-runner: только абсолютный DEVELOPER_DIR в workflow — без sudo xcode-select -switch в job'ах.
  3. xcodebuild -runFirstLaunch и принятие лицензии один раз вручную — не в каждый CI job.
  4. Runtime Simulator iOS 27 предустановить на beta-машине; убрать -downloadPlatform из workflow.

Локальным разработчикам с двумя версиями — медленный Xcode? Cloud Mac mini: писать локально, тяжёлые сборки на M4 в облаке. Почему Simulator/Instruments только на macOS: toolchain Xcode только macOS. После первой установки beta проверьте, что prod-runner не видит /Applications/Xcode_26_beta.app в PATH job'ов — только явный DEVELOPER_DIR в prod workflow.

4) DerivedData / Pods / SPM — тройная изоляция кэша

Инвалидация после WWDC — от смены формата компилятора/модулей, не от «сломанного кэша». Стратегия:

Таблица: prod vs beta — постоянные пути между job'ами
Тип кэшаПуть prodПуть betaПримечание
DerivedData/var/ci/deriveddata/prod/var/ci/deriveddata/betaфиксированный -derivedDataPath
CocoaPods/var/ci/cocoapods/prod/var/ci/cocoapods/betaCP_HOME_DIR
SPM/var/ci/spm/prod/var/ci/spm/betaclonedSourcePackagesDirPath
ModuleCacheпод деревом prodотдельное дерево betaне делить ~/Library

actions/cache в WWDC-сезон редко хватает: ключи обнуляются после major Xcode; гигабайты DerivedData по сети часто медленнее локального диска. Ценность self-hosted — персистентный диск: вторая beta-сборка падает с 20+ до ~10 мин. Тройной кэш Flutter: Flutter iOS CI; подпись TestFlight и Match: руководство self-hosted (Fastlane описан там же — отдельной статьи Fastlane в русской версии блога пока нет, но workflow подписи полностью покрыт).

Init runner (один раз)
sudo mkdir -p /var/ci/{deriveddata,cocoapods,spm}/{prod,beta}
sudo chown -R $(whoami) /var/ci
du -sh /var/ci/deriveddata/*

5) Типичные ловушки: падения beta, раздувание SDK, путаница keychain

  • Beta красная блокирует merge: continue-on-error: true; beta никогда не required check.
  • Prod использует beta SDK: проверять DTXcode и DEVELOPER_DIR в логах archive.
  • Keychain / Match: отдельный login keychain на runner; git_url Match можно общий, пароль keychain — per machine. Матрица поддержки Xcode Apple.
  • Диск полон: SSD 512 ГБ на beta; weekly cron удаляет beta-подпапки старше 14 дней.
  • Параллельные archive OOM: M4 16 ГБ — concurrency prod = 1.
  • Слишком строгая branch protection: required checks без разделения beta/prod — отдельные статусы по label.
  • Flutter + native: flutter build ipa и xcodebuild не делят DerivedData вслепую — см. Flutter iOS CI.

Держите одностраничный runbook: какая машина несёт какой label, кто обновляет beta-Xcode, rollback если beta-сборка задела prod-keychain. Дешевле, чем weekend «CI зелёный локально, красный удалённо». Раз в неделю сверяйте версию Xcode на prod-runner с тем, что указано в App Store Connect для текущих submission — расхождение между «мы думаем, что на 16.4» и «на диске случайно beta» — частая причина отклонённых билдов, которую изоляция runners снимает сразу после внедрения.

6) Пример: P50 до и после split runner (не SLA)

МетрикаБез split (main на beta)Изоляция dual-runner
P50 archive main~42 мин~11 мин
P50 beta-адаптер(конкуренция с prod)~22 мин нед.1, ~12 мин нед.2
Сбои main из-за betaвысокие~0
Число Mac12× M4 или prod + посуточная beta

48-часовой A/B на своём репозитории. Ключевая метрика: main больше не платит за beta. Смотрите и дисперсию P50–P95 на main — меньше алертов «вчера 11, сегодня 38». Если после split prod всё ещё медленный, проблема не в beta — вернитесь к WWDC-статье и проверьте кэш CocoaPods/SPM на prod-lane отдельно от beta.

7) Чеклист внедрения за 48 часов

  1. День 0 утро: зафиксировать Xcode prod, DEVELOPER_DIR в prod workflow.
  2. День 0 день: удалённый Mac mini (аренда 48 ч) зарегистрировать как xcode26-beta.
  3. День 1: создать /var/ci/...; первый full build beta; prod без изменений зелёный.
  4. День 2: beta на nightly + ветки ios-27-*; убрать beta check из branch protection.
  5. Приёмка: три archive main < 15 мин; beta красная без Slack @channel.

Вторая машина на месяц? Сначала beta-spike посуточно — TCO: MacBook Pro vs облачный Mac и TCO шесть регионов. Зафиксируйте шаги в team wiki: кто трогает prod-workflow, кто обновляет beta-Xcode, какие Slack-алерты при красной beta должны молчать — иначе схема развалится, когда инженер, всё настроивший, уйдёт в отпуск.

8) Частые вопросы (15 пунктов)

1. Два Xcode на удалённом Mac? Да — разные папки .app; CI через DEVELOPER_DIR.

2. Beta-runner нужно 24 ГБ RAM? 16 ГБ M4 часто хватает; параллельные multi-target archive → 24 ГБ.

3. Prod иногда гоняет beta job'ы? Не рекомендуется — минимум разный DerivedData; лучше разные машины.

4. macos-latest заменит beta-runner? Нет — эфемерный диск, bootstrap 15+ мин в WWDC-сезон.

5. actions/cache хватит? Ключи обнуляются после major Xcode; сеть медленнее локального диска.

6. Beta нестабильна, CI весь красный? Ожидаемо — beta lane опциональна.

7. Как проверить, что prod без beta-компилятора? DTXcode или assertion DEVELOPER_DIR.

8. Runtime Simulator каждый job? Нет — один раз на beta-машине.

9. Runners в разных регионах? Да — prod ближе к команде.

10. OpenClaw для beta? FAQ OpenClaw CI.

11. Посуточной аренды хватит? Для spike адаптации да; долгий nightly → месяц.

12. Когда чистить DerivedData? Lane > 40 ГБ — сначала beta.

13. Swift 6 strict? Только beta lane; prod — постепенная миграция.

14. Связь со статьёй WWDC? Та — «почему медленно»; эта — «как разделить runners».

15. Две машины за 48 ч обязательны? Нет — без beta на main хватит prod; beta lane когда готова посуточная машина. Отдельной статьи Fastlane в ru-блоге нет — подпись, Match и загрузка TestFlight описаны в self-hosted-гайде; на beta-машину prod-сертификаты не импортируйте без нужды.

Beta может экспериментировать — prod обязан быть стабильным

После WWDC 26 часто выгоднее всего месячный prod-runner + beta-машина по запросу: Mac mini M4 7×24 для TestFlight, beta посуточно для адаптации iOS 27. Nuvcloud bare metal — DerivedData переживает job'ы; M4 16 ГБ хватает большинству iOS archive, 24 ГБ для параллельных schemes. Тихий, экономичный, CI без присмотра.

Планируете Xcode 26 Beta CI без превращения main в полигон? Nuvcloud Mac mini M4 в облаке — самая дешёвая точка старта для split смотреть цены , разделить beta и prod за 48 часов.

LIMITED Тарифы