핵심 요약
- 먼저 실패를 분류하라: 워크플로 YAML을 수정하기 전에 로그를 Context(캐시/상태), Execution(CPU/메모리/IO), Permission(서명/시크릿)에 매핑한다.
- 클라우드 VM 패턴: 무상태 콜드 스타트, 다중 테넌트 IO 경합, 세션 종료 시 디렉터리 삭제——이 셋이 겹치면 「초록이 될 때까지 재실행」이 기본값이 된다.
- 비대칭 결론: CI 안정성의 분수령은 YAML 영리함이 아니라 실행 컨텍스트가 Job 간에 살아남을 수 있는가이다.
- 판단 신호: 같은 커밋이 연속 두 번 실패하거나 warm 빌드도 타임아웃되면 전용 셀프호스트 Mac을 검토할 시점이다.
- 실행 경로: 아래 7단계 Runbook으로 로그 분류에서 환경 인수까지 진행해 「캐시 키 추가 후 재실행」 루프를 끊는다.
결론 먼저
GitHub Actions 실패율의 근본 원인은 보통 「셸 명령 한 줄이 틀렸다」가 아니라, 클라우드 VM이 재사용 가능한 빌드 컨텍스트를 제공하지 못하기 때문이다.
kvmboot 지원 티켓에서 자주 보는 전형: 팀이 GitHub 호스트 macos-latest에서 PoC를 초록으로 만든 뒤 비용 절감을 위해 서드파티 「Mac 클라우드 호스트」나 공유 VPS로 이전한다——그리고 「느림」에서 「느리고 불안정」으로 옮겨간다. pod install이 무작위로 타임아웃되고, xcodebuild가 가끔 OOM되며, codesign이 errSecInternalComponent를 던지고, 두 번 재실행하면 Job이 다시 초록이 된다. 팀은 retry, sleep, 더 큰 timeout-minutes를 추가하지만, CI/CD 병목 트러블슈팅의 진짜 출발점은 더 단순하다: Runner 실행 환경이 어떤 클래스인가? Job 간에 상태를 유지할 수 있는가?
공식 참고: Understanding GitHub Actions, GitHub-hosted runners, Self-hosted runners.
1. 클라우드 VM이 GitHub Actions를 반복 실패시키는 이유
GitHub Actions는 두 층으로 나뉜다: 컨트롤 플레인(GitHub가 워크플로를 스케줄하고 코드를 가져오며 아티팩트를 저장)과 실행 플레인(실제로 xcodebuild를 돌리는 머신). 클라우드 VM에서 Job이 돌면 실패는 거의 실행 플레인에 착지한다——실행 플레인 문제는 호스트가 공유인지, 상태를 영속할 수 있는지와 강하게 상관된다.
1.1 무상태 Runner: 매 Job이 콜드 스타트
GitHub 호스트 Runner는 쓰고 버리기 철학을 따른다: Job이 끝나면 디스크 스냅샷이 회수되고 DerivedData, CocoaPods 인덱스, 글로벌 npm 캐시가 사라진다. 많은 서드파티 공유 클라우드 VM도 같은 패턴을 복사한다——세션 종료나 야간 유지보수 스크립트가 ~/Library, /tmp, 또는 home 전체를 지운다. actions/cache를 설정했다고 생각하지만 경로 이동, Podfile.lock 해시 변경, restore 타임아웃으로 캐시 키가 드리프트한다. 두 번째 빌드도 전체 콜드 경로를 걷고, 벽시계 시간이 늘어 timeout-minutes가 발동하며 로그에는 「느림」이 아니라 「GitHub Actions failed」가 찍힌다.
1.2 다중 테넌트 경합: 예측 불가한 Execution
공유 클라우드 VM에서는 CPU 할당량, 디스크 IOPS, 송신 대역폭이 불투명한 경우가 많다. 이웃 테넌트가 대형 Flutter 프로젝트나 DB 백업을 동시에 돌리면 swiftc 링크 단계가 느려지고 메모리 피크가 쌓여 OOM killer가 개입한다——macOS에서는 xcodebuild가 조용히 종료되거나 Signal 9로 죽는다. 이런 실패는 코드와 무관하다. 이웃이 우연히 유휴일 때 재실행하면 Job이 초록이 된다. 팀은 「네트워크 지터」로 라벨링한다.
1.3 서명과 Keychain: 매 Job마다 Permission 재구축
iOS / macOS CI는 codesign, notarytool, 전용 CI Keychain에 의존한다. 공유 클라우드 VM은 GUI 세션 제한, 사용자 정의 보안 정책 금지, Keychain 장기 잠금 해제 금지가 흔하다. 매 Job이 security create-keychain → 인증서 import → 잠금 해제 → 서명 → 삭제을 반복한다. 어느 단계든 타임아웃이나 권한 거부에 걸리면 파이프라인 전체가 빨개진다. 거부 코드 표는 Apple Silicon 클라우드 Mac에서 iOS CI codesign과 공증을 참고하라.
1.4 「돌아간다」≠ 「안정적으로 돌아간다」
많은 팀은 PoC에서 「한 번 초록 빌드」만 검증하고 분산(편차)을 무시한다. CI 신뢰성은 다음으로 측정해야 한다: 동일 커밋 10회 성공률, P95 소요 시간, 실패가 같은 단계에 몰리는지. 클라우드 VM은 전용 베어 메탈보다 세 지표 모두에서 열세다——원격 iOS 빌드에 베어 메탈 Mac 서버를 선택해야 하는 이유의 핵심 논점 중 하나다.
2. 4가지 실패 모드: 트러블슈팅 전 분류
CI/CD 병목 트러블슈팅에서 마지막 에러 줄부터 역추적하지 말라. 먼저 묻는다: 이 실패는 어느 카테고리인가?
2.1 타임아웃
로그 시그니처: ##[error]The job running on runner … has exceeded the maximum time, 또는 6시간 상한 전 단계 종료. 흔한 원인: 느린 pod install / flutter pub get, 콜드 DerivedData 컴파일, 너무 큰 actions/cache 업로드/다운로드. 클라우드 VM에서 특히 흔하다——디스크 쓰기가 느리면 「캐시 restore」 자체가 병목이 된다.
2.2 OOM / 디스크 / Signal 9
로그 시그니처: xcodebuild가 명확한 에러 없이 종료, Killed, No space left on device, inode 고갈. 16GB 공유 VM에서 시뮬레이터와 전체 archive를 병렬 실행하면 쉽게 발생한다. 튜닝 가이드는 Apple Silicon 클라우드 Mac에서 Runner 메모리와 swap 거버넌스를 참고하라.
2.3 Codesign / Keychain / Provisioning
로그 시그니처: errSecInternalComponent, Provisioning profile doesn't match, resource busy. 다중 Team ID나 외주 병행에서는 공유 환경이 Keychain을 격리하지 못해 실패가 간헐적으로 나타난다. codesign과 공증 트러블슈팅 표와 대조하라.
2.4 환경 드리프트(캐시 miss / 툴체인 불일치)
로그 시그니처: 같은 커밋이 때로는 통과 때로는 실패; Xcode version mismatch; CI에서만 Module not found. 원인은 Runner 이미지 불일치나 잘못 설계된 캐시 키——클라우드 VM에서는 「야간 Xcode 업그레이드」 호스트 유지보수도 더해진다.
3. 핵심 비교: 호스트 Runner vs 클라우드 VM vs 전용 Mac
아래 표는 7열 헤더를 통일해 아키텍처 리뷰와 조달 문서가 같은 시각을 공유하게 한다.
| 옵션 | Entry | Execution | Context | Cost | Permission 경계 | 최적 용도 |
|---|---|---|---|---|---|---|
| GitHub 호스트 Runner | YAML 편집 후 시작 | 표준 macOS 이미지; 커스텀 커널 불가 | 무상태; actions/cache 의존 |
분 단위; 대형 리포는 비쌈 | 샌드박스; 시크릿은 GitHub Secrets | 하루 3회 미만 빌드, PoC 팀 |
| 공유 클라우드 VM(Mac VPS) | SSH + 수동 Runner 설치 | 저렴해 보임; IO/메모리 예측 불가 | 운영이 디렉터리 삭제; 캐시가 잘 안 남음 | 낮은 월 요금; 재실행 세금 높음 | 다중 테넌트; Keychain 격리 어려움 | 가벼운 검증만——주 릴리스에는 부적합 |
| 전용 베어 메탈 Cloud Mac mini | 셀프호스트 Runner + 라벨 라우팅 | Apple Silicon 베어 메탈; Xcode 버전 고정 | DerivedData/Pods가 Job 간 영속 | 일/주 임대; 릴리스 주 ROI | 전용 Keychain; 감사 가능 | iOS/Flutter 릴리스, 컴플라이언스 팀 |
YAML 트릭은 단계 순서를 최적화할 수 있지만, 공유 클라우드 VM을 재사용 가능한 빌드 컨텍스트로 바꿀 수는 없다——그것은 아키텍처 결정이다.
4. 시나리오 매트릭스: 팀이 향해야 할 곳
| 시나리오 | 일일 빌드 횟수 | 권장 | 클라우드 VM을 고집한다면 |
|---|---|---|---|
| 개인 사이드 프로젝트 | <1 | GitHub 호스트 Runner | 가끔 실패는 허용 |
| 소규모 Flutter 팀 MVP | 1~3 | 호스트 Runner + 경량 캐시 | Podfile.lock 고정; 병렬 Job 비활성화 |
| 릴리스 주 고밀도 빌드 | 5~15 | 전용 Mac 셀프호스트 Runner | 실패율이 종종 30% 초과——비권장 |
| 다중 Team ID / 외주 병행 | 무관 | 베어 메탈 Mac + 격리 ci 사용자 |
서명 실패는 거의 불가피 |
| Windows 호스트 + 원격 iOS 빌드 | 3~10 | Cloud Mac 실행 플레인 + 로컬 컨트롤 플레인 | 공유 VPS는 점프 호스트만 |
「릴리스 주 고밀도 빌드」나 「다중 Team ID」에 해당하면 클라우드 VM 워크플로 미세 조정에 더 쓰는 시간의 ROI는 낮다——DerivedData를 warm으로 유지할 전용 Mac 인수를 우선하라. 빌드 시간이 어디로 가는지는 Flutter CI: GitHub Actions에서 시간은 어디로 가나를 읽어라.
5. 권장 스택 A / B / C
팀 성숙도별 세 가지 조합 가능 스택:
【스택 A — 호스트 Runner 분류】(하루 3회 미만)
GitHub 호스트 macos-14/15
→ actions/cache (Pods + DerivedData 키 분리)
→ Job 단계별 timeout-minutes 분할
→ 동일 브랜치 concurrency 제한
【스택 B — 클라우드 VM + 셀프호스트 Runner】(전환기——주의해서 사용)
공유 Mac VPS에 Runner 설치
→ 영속 볼륨에 derivedDataPath 고정
→ launchd 관리 Runner (Mac mini Runner 가이드 참고)
→ 주간 디스크/inode 점검
⚠ 이웃 IO 스파이크에 여전히 취약
【스택 C — 전용 Cloud Mac 프로덕션】(릴리스 팀 권장)
전용 M4 Mac mini + 셀프호스트 Runner
→ ci 사용자 + 라벨 라우팅 (ios / flutter)
→ Golden Image로 Xcode + CocoaPods 고정
→ 장기 CI Keychain + match 또는 수동 인증서
→ 컨트롤 플레인은 GitHub Actions 유지
스택 B는 티켓에서 가장 흔한 함정이다: Runner를 설치했다고 「프로덕션 준비」로 느끼지만 공유 VM 실행 플레인은 그렇지 않다. 스택 C는 독점 실행에 달려 있다——Mac mini GitHub Actions 셀프호스트 Runner 설정 가이드와 Flutter + Mac mini 셀프호스트 아키텍처 개요를 참고하라.
6. 흔한 오해
- 오해 1: 모든 실패에
retry추가——환경 불안정을 가리고 Runner 분을 태우며 더러운 상태를 릴리스에 밀어 넣을 수 있다. - 오해 2: 거대한 단일 캐시 키——Pod 버전이 올라가면 blob 전체 무효;
pods-cache와deriveddata-cache를 분리하라. - 오해 3: 공유 클라우드 VM에서 프로덕션 서명——Keychain을 격리할 수 없다;
errSecInternalComponent는 정기적으로 돌아온다. - 오해 4: 월 임대료만 비교——엔지니어 분류 시간, 재실행 비용, 릴리스 지연을 무시; 공유 VPS TCO는 종종 더 높다.
- 오해 5: 「로컬에서 빌드된다」= CI 준비 완료——로컬에는 warm DerivedData와 잠금 해제된 Keychain이 있다; 비교는 불공정하다.
- 오해 6: 16GB VM에서 병렬 Job——OOM 확실;
concurrency: group: ios-build, cancel-in-progress: true를 써라.
7. 7단계 트러블슈팅 Runbook
- 현장 동결: 실패 Job 전체 로그 다운로드; 커밋 SHA, Runner 이름,
runs-on라벨, 총 소요 시간, 단계별 시간 기록. - 단계 귀속: 상위 3개 느린 단계 표시(흔함:
pod install,xcodebuild,cache restore)하고 각각 Context / Execution / Permission에 매핑. - 리소스 확인: 실패 시점에 디스크(
df -h), 메모리(vm_stat), swap 사용 점검; 클라우드 VM에서는 병렬 Job 없는지 확인. - 캐시 검증: 동일 커밋 두 번 실행; 캐시 히트율과
pod install이 여전히 대량Installing을 출력하는지 비교. - 서명 검증:
codesign -vvv만 도는 워크플로로 분리해 컴파일 노이즈 제거; 거부 코드에 맞춰 Keychain 수정. - 환경 패리티 검증:
xcodebuild -version과pod --version을 로컬과 맞춤; Runner 이미지 또는 Golden Image 고정. - 이전 결정: 같은 단계가 연속 두 번 실패하고 재실행이 불안정하면 장기 셀프호스팅 전에 전용 Mac 일 단위 PoC(콜드 vs warm 비교)를 시작하라.
분류 중 GitHub debug logging과 workflow commands로 더 촘촘한 타임스탬프를 확보하라.
8. FAQ
클라우드 VM에서 GitHub Actions가 실패하는 가장 흔한 이유는?
가장 흔한 패턴은 세 층이 겹치는 것이다: 무상태 Runner로 인한 캐시 miss(Context), 공유 VM 리소스 경합으로 인한 OOM·타임아웃(Execution), 매 Job마다 Keychain/서명 재구축(Permission). 한 층만 고쳐도 잘 안 붙는다——본문의 3차원 프레임워크가 필요하다.
재실행하면 가끔 통과하는 것은 환경 문제인가?
예. 간헐적 성공은 불안정한 리소스나 상태를 뜻하며 깨진 코드 로직이 아니다. 「초록이 될 때까지 재실행」을 기술 부채로 취급하고 실패율을 추적하라; 10% 초과는 프로덕션 준비가 아니다.
클라우드 VM 스펙을 올리면 영구적으로 고쳐지나?
OOM과 일부 타임아웃은 완화되지만 고칠 수 없는 것은 공유 테넌트 IO 경합, 세션 정리로 인한 캐시 무효화, Keychain을 잠금 해제한 채 둘 수 없음이다. 24GB 공유 VPS도 이웃이 디스크를 두드리면 무작위로 실패한다.
호스트 Runner에서 셀프호스트로 옮겨야 할 때는?
같은 워크플로가 주 2회 이상 실패하고 로그가 pod install / xcodebuild / codesign에 몰리거나, DerivedData 캐시 히트율이 장기 50% 미만이면——전용 베어 메탈 Mac 셀프호스트 Runner를 평가하라. 48시간 일 단위 임대로 콜드/warm 인수는 충분하다.
Linux 클라우드 VM으로 iOS CI가 가능한가?
아니다——네이티브 iOS 빌드 체인 전체는 불가하다. xcodebuild, codesign, 시뮬레이터는 macOS가 필요하다. Linux VM은 Flutter Android 타깃이나 일반 백엔드 CI에는 적합; iOS 실행 플레인은 macOS여야 하며 전용이 공유 클라우드 VM보다 낫다.
9. 요약
CI/CD 병목 트러블슈팅의 첫 질문은 「어느 YAML 줄이 틀렸나」가 아니라 「어떤 클래스의 실행 환경이며 컨텍스트가 Job 간에 살아남을 수 있나」이다. GitHub 호스트 Runner는 저빈도 PoC에 맞고; 공유 클라우드 VM CI는 저렴해 보이지만 Context, Execution, Permission에 동시에 지뢰를 심어 GitHub Actions 실패가 간헐적이고 재현 어렵고 치료하기 힘들어진다.
iOS나 Flutter 릴리스를 맡는다면 경로는: 7단계 Runbook 실행 → 전용 Cloud Mac 일 단위 인수 → launchd 셀프호스트 Runner 가동 → 실패율과 P95로 ROI 측정. 안정성 분수령은 실행 컨텍스트이지 캐시 키 하나 더가 아니다.
전용 Cloud Mac으로 간헐적 GitHub Actions 실패를 끝내기
kvmboot Cloud Mac mini M4는 전용 Apple Silicon 베어 메탈을 제공한다: DerivedData와 Pods를 Job 간에 유지할 수 있고, CI Keychain은 안정적이며, 이웃 IO 경합이 없다. GitHub Actions 셀프호스트 Runner 실행 플레인으로——일 단위 임대 후 콜드/warm 두 번 돌려 현재 클라우드 VM CI와 실패율·P95를 비교한 뒤 월 임대를 결정하라.