Вы дежурите с двумя и более арендованными облачными Mac и Fastlane для iOS, и видите зелёные логи сборки, но нет нового билда в TestFlight (или загружен не тот пакет). Ещё выбираете облако или второй Mac? Сначала отдельный Mac под TestFlight или перенос сборок Xcode в облако — здесь уже есть разделение: одна машина собирает, другая загружает.
После прочтения вы должны понимать: ① по логам — сертификат, артефакт или сеть; ② правильно ли разделены роли двух Mac и нужно ли покупать железо.
- Машина сборки: только компилирует в Xcode и экспортирует пакет (
.ipa) - Машина загрузки: подписывает релизным сертификатом и отправляет
.ipaв TestFlight; исходящий IP в App Store Connect должен быть только у неё
Можно пропустить, если: у вас один Mac или TestFlight стабилен и вы ещё не разделили «сборка + загрузка». Если несколько облачных Mac и пайплайн ломается — читайте дальше.
- Граница сертификатов — релизный сертификат синхронизировался на машину сборки, машина загрузки не может подписать (самое опасное)
- Граница артефакта — между ЦОД гоняют
.xcarchiveвместо проверяемой.ipa(самое незаметное) - Дрейф сетевой идентичности — в ASC whitelist старый IPv4 upload-хоста (самое муторное в отладке)
Контекст (1 мин): после второго Mac ломаются границы, не CPU
Многие арендуют два облачных Mac: один ближе к Азии собирает приложение в пакет днём, другой в Канаде передаёт пакет Apple (TestFlight / App Store). Второй Mac — про скорость, а дежурство съедают обе машины, трогающие сертификаты и файлы: сборка зелёная, TestFlight без новой версии.
Рекомендуемое разделение: модель A (сборка — только пакет, загрузка — только публикация)
Коротко: машина сборки отдаёт только .ipa; машина загрузки владеет сертификатами и TestFlight. Не запускайте match на обеих.
| Role | Машина сборки (Гонконг) Example: APAC daytime builds | Машина загрузки (Канада) Fixed IP for Apple |
|---|---|---|
| Do | ||
| Do not | ||
| Hand off | Package 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.
Оба 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.plist→ upload 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 · 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:
Нет нового билда 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 к тикету — так проще онбордить следующего дежурного:
| Проверка | Ожидание |
|---|---|
| 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 |
Итог (достаточно только этого блока)
Если верны все три пункта — решайте здесь, без перечитывания:
- Не менее 2 Mac runner (или эквивалент multi-node CI)
- Цепочка Fastlane с нестабильным TestFlight (
match failed, зависший processing, пропадающие билды) matchуже выполнялся на более чем одном хосте
👉 Не покупайте новые машины.
👉 Три шага (можно сегодня):
- Остановить
match/sync_code_signingна всех узлах кроме upload - Только
.ipa+manifest.jsonмежду ЦОД (запрет rsync.xcarchive) - Унифицировать
RUN_ID+GIT_SHA+ipa_sha256перед upload
Иначе: вы на стадии 2 (модель B). Больше железа = больше сбоя. Сначала Fastfile и метки workflow, зелёный чеклист, потом ёмкость build. Окно обслуживания для rotate match — только на upload, никогда параллельно на build.
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
Короткие вопросы
Может ли 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 с — в закладки дежурной смены.