← Вернуться в дневник

Fastlane и два облачных Mac: 3 типичных сбоя после разделения build и upload

Runbook · 2026.06.04 · ~8 мин · в runbook дежурного

Два облачных Mac: archive в Гонконге, upload Fastlane в Канаде

Вы дежурите с двумя и более арендованными облачными Mac и Fastlane для iOS, и видите зелёные логи сборки, но нет нового билда в TestFlight (или загружен не тот пакет). Ещё выбираете облако или второй Mac? Сначала отдельный Mac под TestFlight или перенос сборок Xcode в облако — здесь уже есть разделение: одна машина собирает, другая загружает.

После прочтения вы должны понимать: ① по логам — сертификат, артефакт или сеть; ② правильно ли разделены роли двух Mac и нужно ли покупать железо.

Термины (ниже повторяются)
  • Машина сборки: только компилирует в Xcode и экспортирует пакет (.ipa)
  • Машина загрузки: подписывает релизным сертификатом и отправляет .ipa в TestFlight; исходящий IP в App Store Connect должен быть только у неё

Можно пропустить, если: у вас один Mac или TestFlight стабилен и вы ещё не разделили «сборка + загрузка». Если несколько облачных Mac и пайплайн ломается — читайте дальше.

TL;DR (30 секунд)
После dual cloud Mac узкое место редко CPU, а три пограничные ошибки Fastlane:
  1. Граница сертификатов — релизный сертификат синхронизировался на машину сборки, машина загрузки не может подписать (самое опасное)
  2. Граница артефакта — между ЦОД гоняют .xcarchive вместо проверяемой .ipa (самое незаметное)
  3. Дрейф сетевой идентичности — в ASC whitelist старый IPv4 upload-хоста (самое муторное в отладке)
Ниже — дерево triage за 30 с, три режима сбоя, таблица приёмки, четыре стадии CI и блок «итог», по которому можно решить без полного перечитывания. Таблицы и код — справка; логика решений — в абзацах основного текста.

Контекст (1 мин): после второго Mac ломаются границы, не CPU

Многие арендуют два облачных Mac: один ближе к Азии собирает приложение в пакет днём, другой в Канаде передаёт пакет Apple (TestFlight / App Store). Второй Mac — про скорость, а дежурство съедают обе машины, трогающие сертификаты и файлы: сборка зелёная, TestFlight без новой версии.

Рекомендуемое разделение: модель A (сборка — только пакет, загрузка — только публикация)

Коротко: машина сборки отдаёт только .ipa; машина загрузки владеет сертификатами и TestFlight. Не запускайте match на обеих.

Dual cloud Mac roles (Model A)
Role Машина сборки (Гонконг) Example: APAC daytime builds Машина загрузки (Канада) Fixed IP for Apple
Do
Do not
Hand offPackage file + build ID (RUN_ID)None (endpoint is App Store Connect)

Оба runner — выделенные Mac mini Hashvps ради фиксированного IP + изоляции ролей (IP upload в ASC, IP build — нет). Спеки Гонконга: аренда Mac mini в Гонконге. Сначала зафиксируйте upload-идентичность в ASC, потом масштабируйте build.

Build · без match Upload · единая подпись build → export .ipa no certs · no upload includes build ID certs + TestFlight upload fixed public IP registered in ASC .ipa → Минимальная единица между ЦОД: IPA с проверкой подписи, не xcarchive
Модель A: match и подпись только на upload; build отдаёт IPA

Оба runner — выделенные Mac mini Hashvps ради фиксированного IP + изоляции ролей (IP upload в ASC, IP build — нет). Спеки Гонконга: аренда Mac mini в Гонконге. Сначала зафиксируйте upload-идентичность в ASC, потом масштабируйте build.

Частая ошибка: модель B (обе «помогают»)

Модель B — роли не разделены, у нас так было в первую неделю. На бумаге две машины, по факту:

  • Обе запускают match → приватный ключ релиза на сборке → загрузка не подписывает → TestFlight падает
  • Build rsync .xcarchive → повторный export на upload, другой SHA → неверный артефакт, Invalid Binary
  • Двойной export_ipa → два плывущих ExportOptions.plistupload OK, билд не промоутится

Если узнаёте свой workflow — третий Mac не поможет, нужен role split build/upload (модель A), а не больше runner’ов.

Наблюдаемость: один и тот же билд на build и upload

Самый частый инцидент: «залили вчерашний IPA». Одни и те же поля в логах сборки и загрузки:

  • RUN_ID (напр. hk-build-88421)
  • GIT_SHA (короткий commit)
  • путь артефакта + SHA256 (build/manifest.json)
Build OK · upload OK (тот же RUN_ID)
# Гонконг · build · build
[15:11:42]: Successfully exported: ./build/MyApp-20260604-1502.ipa
[15:11:42]: RUN_ID=hk-build-88421  GIT_SHA=a3f91c2  SHA256=9f2a…

# Канада · upload · upload
[02:22:01]: RUN_ID=hk-build-88421  GIT_SHA=a3f91c2  ipa_sha256=9f2a…
[02:24:18]: Successfully uploaded package to App Store Connect

Правило дежурства: нет того же RUN_ID в upload-логе — сначала ошибка артефакта, не ASC.

Triage 30 с: нет нового билда в TestFlight?

Не читайте весь лог сразу. Одна ветка дерева открывает нужную главу (закладка в runbook). Сначала RUN_ID, потом match, потом ASC:

Дерево triage (в runbook)
Нет нового билда TestFlight?
 ├─ RUN_ID upload ≠ build (или нет manifest)
 │     → ловушка 2 · граница артефакта
 ├─ В логе: match / AppStoreDistribution / code signing identity
 │     → ловушка 1 · граница сертификатов
 └─ Upload «успешен», ASC пуст / 403 / Invalid Binary (build всё зелёное)
       → ловушка 3 · дрейф сетевой идентичности
Три сбоя · шпаргалка (дежурство)
Режим Ключевые слова в логе (grep) Влияние на release
Ловушка 1 сертификаты AppStoreDistribution, No matching provisioning, match failed Блокер: TestFlight / Store остановлены
Ловушка 2 артефакт RUN_ID не совпадает, Invalid Binary, конфликт export_method Высокий: неверный билд или нет promote
Ловушка 3 идентичность 403, upload OK без processing, сетевые лимиты ASC Прерывистый: «магия»

Три режима сбоя (подробно) — каждый с 30-секундным фильтром, grep-ключами и конкретным исправлением lane

Ловушка 1: неверная граница сертификатов

30 с: archive на build зелёный; upload красный на match / signing.

Лог: AppStoreDistribution, Could not find a matching code signing identity, No matching provisioning profiles.

Release: блокер — TestFlight и App Store, пока приватный ключ снова только на upload.

Симптом: archive на build OK, lane upload падает; TestFlight без нового билда.

Типичный лог
Could not find a matching code signing identity for type 'AppStoreDistribution'
❌  Lane upload failed

Причина: модель B — build ещё выполняет match(appstore), ключа Distribution нет на upload.

Исправление: убрать все match с build; upload эксклюзивно (rotate только там, отдельное окно). См. match readonly.

Ловушка 2: неверная граница артефакта

30 с: сравнить RUN_ID и ipa_sha256 build vs upload; или ASC Invalid Binary без ошибок signing на build.

Лог: разный RUN_ID, Invalid Binary, двойной export_ipa / ExportOptions.

Release: высокий — старый commit, неверный билд или зависший processing; pipeline может быть зелёной.

Причина: .xcarchive между ЦОД или двойной export. Минимальная единица между ЦОД: IPA с SHA, не папка archive.

Исправление: один export_ipa + manifest.json на build; upload только upload_to_testflight.

Ловушка 3: дрейф сетевой идентичности

30 с: Fastlane пишет upload успешен; signing везде зелёный; TestFlight пуст или ASC 403.

Лог: 403, Successfully uploaded без билда, отказ ASC по сети при зелёных логах build.

Release: прерывистый — то работает, то нет.

Причина: IPv4 upload ≠ whitelist ASC (третий тихий класс сбоев).

Исправление: ежедневно на upload: curl -4 ifconfig.me vs ASC; при дрейфе — whitelist + алерт. IP build в ASC не заносите — иначе маскируется реальный дрейф upload.

Как понять, что split действительно сделан

Чеклист вместо разрозненного FAQ — через неделю после rollout каждая строка без споров «да». Прогоните один зелёный pipeline end-to-end и приложите скриншоты логов с RUN_ID к тикету — так проще онбордить следующего дежурного:

Приёмка dual Mac (модель A)
Проверка Ожидание
RUN_ID совпадаетВ логах export build и upload upload одинаковы: RUN_ID, GIT_SHA, ipa_sha256
Build без DistributionНа build в Keychain/логах никогда AppStoreDistribution и step match
Upload только дистрибуцияLane upload: только match (или resign) + upload_to_testflight, без ARCHIVE SUCCEEDED
Форма артефактаМежду ЦОД только .ipa + manifest (< 200 MB); без rsync .xcarchive
Идентичность ASCТекущий IPv4 upload = whitelist ASC; IP build не в ASC
Изоляция runnerМетки role=ios-build-hk и role=ios-upload-ca, цепочка needs

Стадия эволюции: где вы сейчас?

Инцидент — «сегодня ночью»; стадия — «на следующей неделе менять архитектуру?». Четыре стадии — держать, исправить или внедрить role split. Многие прыгают со стадии 1 на два Mac без labels и попадают в стадию 2; этот текст ведёт к стадии 3 с измеримой приёмкой, а не «на глаз».

Стадия 1: один Mac, одна Fastlane

Признаки: один runner; match, build, upload в одном Fastfile. Сначала диск, Derived Data и биллинг GitHub Actions, потом второй build-узел.

Вывод: dual Mac не нужен. Только медленный APAC-build → добавить build-узел, не дробить upload первым.

Стадия 2: два Mac без role split (модель B)

Признаки: два+ runner; несколько гоняют match / export / upload; в логах три границы из этой статьи. Часто из желания «каждый Mac умеет всё понемногу» — отсюда дрейф; на стадии 3 нужны явные labels и цепочка needs.

Вывод:Третий Mac не решит проблему, только усилит дрейф сертификатов и артефактов. Следующий шаг — модель A, не больше runner’ов.

Стадия 3: role split (модель A · цель статьи)

Признаки: build/upload разделены; между ЦОД только IPA с RUN_ID; match только на upload; фиксированный IP upload. На этой стадии имеет смысл мониторить дрейф IPv4 upload и еженедельно прогонять чеклист приёмки — не только реагировать на красный TestFlight.

Вывод: ✔ чеклист стабильно зелёный → горизонтально build-runner’ы или укрепить upload. Фиксированные IP и узлы: тарифы Hashvps (сначала IP ASC для upload, потом расширять build).

Стадия 4: несколько регионов upload (продвинуто)

Признаки: upload US/EU/APAC; ключи ASC и IP по регионам; promotion и compliance отдельной линией.

Вывод: платформа дистрибуции, не «ещё один CI-Mac» — registry, policy, audit; вне этого runbook.

Стадия → действие (решение)
Стадия Что делать сейчас
Stage 1Один Mac; настроить окна build/upload
Stage 2Не покупать Mac; модель A + чеклист
Stage 3Масштабировать build; мониторинг upload
Stage 4Отдельный проект: multi-region, не патч Fastlane
Триггер апгрейда (проблема уже есть)
Если CI уже показывает «TestFlight иногда пуст + несколько Mac + match на нескольких узлах», вы в обязательном role split, не в фазе «купить ещё runner». Застряли на стадии 2: дерево + анти-модель B — не третий Mac до split. Зафиксируйте чеклист приёмки в тикете; лишь когда все шесть строк зелёные, имеет смысл добавлять build-runner в Гонконге.

Итог (достаточно только этого блока)

Если верны все три пункта — решайте здесь, без перечитывания:

  • Не менее 2 Mac runner (или эквивалент multi-node CI)
  • Цепочка Fastlane с нестабильным TestFlight (match failed, зависший processing, пропадающие билды)
  • match уже выполнялся на более чем одном хосте

👉 Не покупайте новые машины.

👉 Три шага (можно сегодня):

  1. Остановить match / sync_code_signing на всех узлах кроме upload
  2. Только .ipa + manifest.json между ЦОД (запрет rsync .xcarchive)
  3. Унифицировать RUN_ID + GIT_SHA + ipa_sha256 перед upload

Иначе: вы на стадии 2 (модель B). Больше железа = больше сбоя. Сначала Fastfile и метки workflow, зелёный чеклист, потом ёмкость build. Окно обслуживания для rotate match — только на upload, никогда параллельно на build.

Fastfile upload (модель A · фрагмент)
lane :upload_only do |options|
  verify_run_id!(options) # manifest.json
  match(type: "appstore", readonly: true)
  upload_to_testflight(ipa: options[:ipa_path], api_key_path: "asc_api_key.json")
end
Дисциплина изменений
Неделя split: без rotate match. Смена сертификатов — отдельное событие: остановить обе lane, rotate только на upload.

Короткие вопросы

Может ли build архивировать без Distribution? Зависит от проекта; мы экспортируем IPA для финальной App Store-подписи на upload (или build = compile check, upload resign). Правило: match на одном хосте. Подпись на двух Keychain без ролей — это модель B под видом «помощи».

match скоро истекает? Rotate только на upload в non-readonly окне; build на паузе до стабильных профилей. Параллельный readonly match на build во время rotate — частая причина повторного падения TestFlight на следующий день.

Отличие от travel-runbook? Travel — Wi‑Fi на месте; здесь — роли ЦОД и границы Fastlane; чеклист копируется 1:1.

Серия: ① Runbook: dual cloud Mac + Fastlane (пустой TestFlight / match failed / multi-Mac iOS CI). Далее: ② Build·Sign·Distribute, ③ анти-модель B; вход настройка TestFlight. Дерево triage на 30 с — в закладки дежурной смены.

Дальше

Роли и узлы: Гонконг и цены. Нужен ли upload-Mac: статья TestFlight.

Hashvps

Этап 2? Сначала role split

Фиксированный IPv4 upload для ASC; build масштабировать в APAC.

Тарифы
Спецпредложение