온콜 중이고 임대 클라우드 Mac 두 대 이상에서 Fastlane으로 iOS를 배포하는데, 「빌드 로그는 녹색인데 TestFlight에 새 빌드가 없다」 또는 「잘못된 패키지가 올라갔다」면 이 글이 대상입니다. 클라우드 도입·두 번째 Mac 여부는 TestFlight 전용 Mac 또는 Xcode 빌드 클라우드 이전을 먼저 보세요 — 여기서는 이미 한 대는 빌드, 한 대는 업로드로 나눴다고 가정합니다.
이 글을 읽고 나면 두 가지를 알 수 있어야 합니다. ① 로그로 인증서·아티팩트·네트워크 중 어디 문제인지. ② 두 Mac의 역할이 맞는지, 기계를 더 사야 하는지.
- 빌드 머신: Xcode로 프로젝트를 컴파일하고 설치 패키지(
.ipa)만보냄 - 업로드 머신: 배포 인증서로 서명해
.ipa를 TestFlight에 올림. App Store Connect에 등록하는 출구 IP는 이 호스트만 - Archive / 빌드: Xcode 배포용 출력. 호스트 간에는 완성된
.ipa만 전달 - match: Fastlane 배포 인증서 도구 — 한 호스트에서만
건너뛰어도 되는 경우: Mac이 한 대뿐이거나 TestFlight 업로드가 안정적이고 아직 「빌드 + 업로드」로 나누지 않았다면. 여러 클라우드 Mac으로 배포 중이고 파이프라인에 문제가 있으면 계속 읽으세요.
- 인증서 경계 — 배포 인증서가 빌드 머신에 동기화되어 업로드 머신이 서명 못 함 (가장 위험)
- 산출물 경계 — 검증 가능한
.ipa가 아니라.xcarchive를 DC 간 전송(가장 찾기 어려움) - 네트워크 ID 드리프트 — ASC IP 허용 목록이 오래된 IPv4(조사가 가장 까다로움)
배경(1분): 2대로 나눈 뒤 터지는 건 경계이지 CPU가 아님
많은 팀이 클라우드 Mac 두 대를 씁니다. 아시아에 가까운 한 대가 낮에 App을 설치 패키지로 컴파일하고, 캐나다 한 대가 Apple에 패키지를 전달합니다(TestFlight / App Store). 두 번째 Mac은 속도 때문인 경우가 많지만, 온콜 시간은 둘 다 인증서·파일 전송에 손대는 데 갑니다 — 빌드는 성공인데 TestFlight에 새 버전이 없는 패턴입니다.
먼저 흔한 잘못된 분리(모델 B), 그다음 우리가 쓰는 분리(모델 A)와 검증 방법을 설명합니다.
권장: 모델 A (빌드는 패키지만, 업로드는 배포만)
한 줄로: 빌드 머신은 .ipa만 만들고, 업로드 머신만 인증서와 TestFlight를 담당합니다. 양쪽에서 match를 돌리지 마세요 — 개인키가 잘못된 호스트에 남습니다.
| 역할 | 빌드 머신 (홍콩) Example: APAC daytime builds | 업로드 머신 (캐나다) Fixed IP for Apple |
|---|---|---|
| 할 일 | 코드 pull → Xcode 빌드 → .ipa보내기 | .ipa 수신 → 배포 서명 → TestFlight 업로드 |
| 하지 말 것 | match 금지, TestFlight 업로드 금지 | 전체 프로젝트 재컴파일 금지 |
| 다음 호스트로 | 설치 패키지 + RUN_ID | 없음 (종점: App Store Connect) |
당사 2대는 Hashvps 독립 Mac mini에서 동작합니다. 선택 이유는 고정 IP + 역할 분리(업로드 IP만 ASC, 빌드 IP 불필요)입니다. 홍콩 노드 사양은 홍콩 Mac mini 임대를 참고하세요.
당사 2대는 Hashvps 독립 Mac mini에서 동작합니다. 선택 이유는 고정 IP + 역할 분리(업로드 IP만 ASC, 빌드 IP 불필요)입니다. 홍콩 노드 사양은 홍콩 Mac mini 임대를 참고하세요.
흔한 실수: 모델 B (둘 다 「도와주는」 상태)
모델 B는 역할이 갈라지지 않은 상태입니다. 1주차에 우리도 이렇게 했습니다. 겉으로는 두 대 분담이지만 실제로는:
- 둘 다
match→ 배포 개인키가 빌드 머신에 남음 → 업로드 머신 서명 실패 → TestFlight 계속 실패 - 빌드에서
.xcarchiversync → 업로드에서 재 export, 빌드와 동일 SHA 검증 불가 → 산출물 혼선·Invalid Binary - 양쪽에서 각각
export_ipa→ExportOptions.plist2벌 드리프트 → 업로드는 성공하지만 빌드 승격 불가
기존 파이프라인에 하나라도 해당하면 해결책은 「Mac 한 대 더」가 아니라 build / upload role split(모델 A)가 필수입니다.
관측 가능성: build와 upload가 같은 빌드인지
가장 흔한 운영 사고는 「어제 IPA를 오늘 올렸다」입니다. 빌드·업로드 양쪽 로그에 같은 필드를 강제로 출력합니다:
RUN_ID(파이프라인 1회 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가 없으면 산출물 불일치로 처리. ASC부터 의심하지 마세요.
30초 트리아지: TestFlight에 새 build 없음?
전체 로그부터 열지 마세요. 아래 트리를 한 단계만 따라가면 해당 절로 갑니다(Runbook 북마크 권장).
TestFlight에 새 build 없음?
├─ 업로드 RUN_ID ≠ 빌드 RUN_ID (또는 manifest 없음)
│ → 함정 2 · 아티팩트 경계
├─ 로그에 match / AppStoreDistribution / code signing identity
│ → 함정 1 · 인증서 경계
└─ upload 성공인데 ASC 비어 있음 / 403 / Invalid Binary (빌드 전부 녹색)
→ 함정 3 · 네트워크 신원 드리프트
| 모드 | 로그 키워드(grep) | Release 영향 |
|---|---|---|
| 함정 1 인증서 경계 | AppStoreDistribution, No matching provisioning, match failed |
차단: TestFlight / 스토어 제출 전면 중단 |
| 함정 2 산출물 경계 | RUN_ID 불일치, Invalid Binary, export_method 충돌 |
높음: 잘못된 패키지·승격 불가 가능 |
| 함정 3 ID 드리프트 | 403, upload OK인데 processing 없음, ASC 네트워크 제한 |
간헐 차단: 「미스터리 누락」처럼 보임 |
세 가지 실패 모드(상세)
함정 1: 인증서 경계 오류
30초 판별: 빌드 archive 초록, 업로드 빨강, match / signing 단계에서 실패.
로그 키워드: AppStoreDistribution, Could not find a matching code signing identity, No matching provisioning profiles.
Release 영향: 차단급 — 개인키가 업로드 머신으로 돌아올 때까지 TestFlight / App Store 업로드 불가.
현상: 빌드 archive 초록, 업로드 upload 빨강. TestFlight 새 build 없음.
Could not find a matching code signing identity for type 'AppStoreDistribution' ❌ Lane upload failed
근본 원인: 모델 B — 빌드가 여전히 match(appstore) 실행, Distribution 개인키가 업로드에 없음.
수정: 빌드에서 모든 match 제거. 업로드만 match(rotate는 업로드만·독립 유지보수 창). match readonly 참고.
함정 2: 산출물 경계 오류
30초 판별: 빌드/업로드 로그의 RUN_ID, ipa_sha256 대조. 또는 ASC Invalid Binary인데 빌드에 signing 오류 없음.
로그 키워드: RUN_ID 불일치, Invalid Binary, 양쪽에서 export_ipa / ExportOptions 두 번.
Release 영향: 높음 — 옛 패키지·잘못된 commit 업로드, processing 정지. 파이프라인이 즉시 빨갛지 않을 수 있음.
근본 원인: DC 간 .xcarchive 전송 또는 양쪽 export. DC 간 최소 단위는 SHA 대조 가능한 IPA이지 archive 디렉터리가 아님.
수정: 빌드에서 export_ipa 1회 + manifest.json. 업로드는 upload만, 재 export 금지.
함정 3: 네트워크 ID 드리프트
30초 판별: Fastlane upload 성공. 빌드·업로드 signing 모두 초록. TestFlight 패키지 없음 또는 ASC 403.
로그 키워드: 403, Successfully uploaded인데 build 없음, ASC 네트워크 거부(빌드 로그 정상).
Release 영향: 간헐 차단 — 될 때도 있고 안 될 때도 있어 온콜 인내를 가장 소모.
근본 원인: 업로드 IPv4와 ASC IP 허용 목록 불일치(제3류 잠재 장애).
수정: 업로드에서 매일 curl -4 ifconfig.me로 ASC 대조. 드리프트 시 허용 목록 갱신 + 알림.
제대로 나눴는지 판단
게이트, FAQ, 온콜 항목을 아래 검수표로 통합했습니다. 분리 1주 후 각 행이 자동으로 「예」가 되어야 합니다.
| 검수 항목 | 기대 결과 |
|---|---|
| RUN_ID 정합 | 빌드 export와 업로드 upload 로그에서 RUN_ID, GIT_SHA, ipa_sha256 일치 |
| 빌드에 Distribution 없음 | 빌드 키체인/로그에 AppStoreDistribution, match 단계 전혀 없음 |
| 업로드는 upload만 | 업로드 lane 로그에 match(또는 resign)+ upload_to_testflight만. ARCHIVE SUCCEEDED 없음 |
| 산출물 형태 | DC 간 파일은 200MB 미만 .ipa + manifest. .xcarchive rsync 없음 |
| ASC 신원 | 업로드 현재 IPv4 = ASC 허용 목록. 빌드 IP는 ASC 미등록 |
| Runner 분리 | role=ios-build-hk와 role=ios-upload-ca 라벨 분리. workflow needs 직렬 |
성숙도: 지금 어느 단계?
장애 대응은 「오늘 밤 어떻게 끄나」, 단계 판단은 「다음 주 아키텍처를 바꿀까」를 결정합니다. 아래 4단계로 유지·수정·role split을 고르세요.
Stage 1: 단일 Mac Fastlane
특징: Mac runner 1대. match + build + upload가 동일 Fastfile·동일 키체인. 먼저 디스크, Derived Data, GitHub Actions 과금 창을 최적화한 뒤 build 노드 추가 검토.
결론: 듀얼 Mac 불필요. APAC 빌드만 느리면 먼저 build 노드 추가. upload 분리는 나중.
Stage 2: 2대 있으나 role split 미적용(모델 B)
특징: Mac 2대 이상. 여러 대에서 match / export / upload 실행. 본문 세 경계 오류가 로그에 자주 등장.
결론: ❌ 3대째를 추가해도 해결 안 됨. 인증서 드리프트와 산출물 불일치만 증가. 다음은 runner 수가 아니라 모델 A로 수렴.
Stage 3: Role split(모델 A · 본문 목표)
특징: build / upload 책임 분리. DC 간은 RUN_ID 붙은 IPA만. match는 upload 노드만. 업로드 egress IP 고정.
결론: ✔ 검수표가 안정적으로 채워짐 → build runner 가로 확장(APAC 병렬 빌드) 또는 upload 전용기 강화 가능. 고정 IP·노드별 계약은 Hashvps 요금제 참고(먼저 upload ASC IP 고정, 이후 build 확장).
Stage 4: Multi upload node(고급)
특징: upload 리전 복수(US / EU / APAC 등). ASC API Key·IP 전략 리전별. 산출물 승격·컴플라이언스 감사 독립 라인.
결론: 배포 시스템 영역. 「CI용 Mac 한 대 더」가 아님 — 산출물 저장소·정책·감사 필요, 본 Runbook 범위 밖.
| 단계 | 지금 해야 할 일 |
|---|---|
| Stage 1 | 단일 Mac 유지. 빌드·업로드 시간대 최적화 |
| Stage 2 | 증설 중단. 모델 A + 검수표 완주 |
| Stage 3 | build 가로 확장. upload 전용기·모니터링 강화 |
| Stage 4 | 별도 프로젝트: 다리전 배포·컴플라이언스(Fastlane 패치 아님) |
최종 결론(이 절만으로 실행 가능)
아래 세 조건을 동시에 만족하면 이 절로 결정하고 전문 재독은 불필요합니다:
- 이미 Mac runner 2대 이상(또는 동등 멀티노드 CI)
- Fastlane에서 TestFlight 불안정 / 업로드 실패(
match failed, processing 정지, 가끔 패키지 없음) match가 여러 머신에서 실행됐을 가능성
👉 더 이상 머신을 추가하지 마세요.
👉 오늘부터 가능한 세 가지:
- 중단: upload 외 노드의
match/sync_code_signing(서명 권한은 upload만) - 강제: DC 간은
.ipa+manifest.json만(.xcarchiversync 금지) - 통일:
RUN_ID+GIT_SHA+ipa_sha256. 업로드 전 반드시 검증
위 세 가지를 못 하면: 여전히 Stage 2(모델 B). 증설은 장애 증폭이지 수리가 아님. Fastfile / workflow 라벨을 먼저 고치고 검수표 통과 후 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
자주 묻는 질문
빌드에서 서명 없이 archive 가능? 프로젝트 설정에 따름. 당사는 빌드 export로 업로드가 최종 app-store 서명하는 IPA를 넘김(또는 빌드는 컴파일 검증만, 업로드에서 resign). 핵심은 match가 한 대뿐이라는 점.
match 만료가 가까울 때? 업로드만, non-readonly 유지보수 창에서 rotate. profile 안정까지 빌드 중지.
출장 Runbook과 차이? 출장용은 현지 Wi‑Fi·사람 조작. 본문은 DC 역할과 Fastlane 경계. 검수표는 그대로 재사용.
시리즈: 본문은 ① Runbook: 듀얼 클라우드 Mac + Fastlane 장애 대응(TestFlight 미업로드 / match failed / 멀티 Mac iOS CI). 후속: ② Build·Sign·Distribute 설계, ③ 모델 B 안티패턴. 선정은 TestFlight 전용 Mac.