VPSSpark Cloud Mac CI 시리즈 #9. Flutter 팀에서 자주 받는 질문에 답합니다: 8~15명 규모 팀이 Cloud Mac 2대로 CI를 안정적으로 돌릴 수 있을까? 결론은 가능합니다——전제는 Android와 iOS를 분리 설계하는 것입니다. Android 빌드는 GitHub 호스트 ubuntu-latest에 두고, macOS 쪽은 셀프호스트 Runner 2대로 macos-fast와 macos-archive 두 풀을 나누며, 「PR에서는 Archive 금지」를 지킵니다. 아래에 부하 모델, Job 계층, workflow 조각, 도입 체크리스트를 정리했습니다. 대기열 진단과 Runner 등록 세부는 시리즈 #2와 병망 FAQ를 참고하세요.
1 · 직관과 다른 결론: Flutter CI 병목은 Dart가 아니다
대부분 Flutter 팀의 CI 적등은 flutter test가 느려서가 아니라, iOS 쪽이 macOS Runner 슬롯을 점유하기 때문입니다——PR에서 실수로 flutter build ipa를 돌리는 경우가 전형적입니다.
Flutter의 크로스플랫폼 장점 때문에 「workflow 하나로 전부」라고 착각하기 쉽습니다. 실제로 flutter analyze와 단위 테스트는 Linux에서도 돌아갑니다. macOS가 필수인 건 CocoaPods 해석, Xcode 컴파일, 서명, IPA 내보내기입니다. PR 경로에 Archive Job이 섞이면 Cloud Mac 한 대가 25~40분짜리 iOS 패키징에 묶이고, 나머지 PR은 전부 Queued——네이티브 iOS 팀과 같은 함정이며, 진단식은 여전히 wait time >> run time입니다(macOS runner queued 진단 참고).
그래서 「Cloud Mac 2대」는 만능기 2대가 아니라 의도적으로 설계한 듀얼 풀 토폴로지입니다. 한 대는 빠른 피드백, 한 대는 릴리스 무거운 작업. Android APK/AAB는 호스트 Linux에 두고, 귀한 macOS 슬롯을 Gradle에 쓰지 않습니다.
2 · 부하 모델: 8~15명 팀에 필요한 macOS 동시성
중간 활동량 Flutter 팀을 기준으로 산출합니다(1인 1일 1~2 PR, main nightly + 주간 릴리스):
Fast 풀(macos-fast) 1 Job 목표: analyze + 단위 테스트 + iOS Simulator 빌드, wall time 8~14분(pub/DerivedData 캐시). Archive 풀(macos-archive) Release IPA + 공증은 1회 20~35분이지만 빈도는 낮고, main 머지·tag·nightly만. 두 대를 섞으면 Archive가 fast를 막습니다. 분리 후 PR P95 대기는 보통 10분 이내로, Flutter 팀 코드 리뷰 리듬에 충분합니다.
15명 초과이거나 monorepo에서 matrix가 flavor별로 늘면, Archive용 3번째 대나 탄력 확장을 검토합니다——대규모 iOS CI 용량 설계 영역이며, 본문 「2대 스타트」 범위 밖입니다.
| 플랫폼 | Runner | 전형 Job | PR 실행 |
|---|---|---|---|
| Android | ubuntu-latest(호스트) |
flutter build apk/appbundle, Android 단위 테스트 |
예 |
| iOS 빠른 피드백 | Cloud Mac #1 · macos-fast |
analyze, 단위 테스트, build ios --simulator |
예 |
| iOS 릴리스 | Cloud Mac #2 · macos-archive |
build ipa, 공증, TestFlight 업로드 |
아니오 |
3 · Flutter Job 3계층: CI Hard Rules 정렬
Flutter 파이프라인을 시리즈 공통 L0/L1/L2에 매핑해 온콜 매뉴얼과 맞춥니다:
| 계층 | Flutter 내용 | 풀 | PR 트리거 |
|---|---|---|---|
| L0 | dart format --set-exit-if-changed, flutter analyze, 경량 단위 테스트 |
Linux 또는 macos-fast |
예 |
| L1 | 통합 테스트, flutter build ios --simulator, Widget 테스트 |
macos-fast |
예 · Archive 금지 |
| L2 | flutter build ipa, App Store Connect 업로드, 공증 |
macos-archive |
금지 · main/tag/schedule만 |
세 가지 철칙은 네이티브 iOS와 같습니다: Rule 1 PR에서 L2 금지; Rule 2 L2는 격리 풀; Rule 3 fast 풀을 Archive가 막지 않음. Flutter 특유: L0 대부분은 Linux로 충분——macos-fast에 올리면 macOS 동시성을 낭비합니다. PR에서 Linux Job과 macOS Job을 병렬로, macOS는 「mac 필수」 단계만 돌리세요.
4 · 듀얼 머신 토폴로지: Cloud Mac #1과 #2 역할
그림 1 · Cloud Mac 2대로 Flutter 팀 CI를 지탱하는 전형 토폴로지
macos-fastL0/L1 · 8~14 min/Jobmacos-archiveL2 · main/tag 전용Cloud Mac #1(Fast 풀): 라벨 [self-hosted, macOS, macos-fast, flutter]. Flutter SDK(fvm 또는 이미지 pin), Xcode, CocoaPods 고정 버전 사전 설치. Runner는 상시 서비스로 부팅 자동 시작. Job이 짧고 회전이 빨라 pub cache·DerivedData를 적극 보존할 수 있습니다.
Cloud Mac #2(Archive 풀): 라벨 [self-hosted, macOS, macos-archive, flutter-release]. Fast 풀과 물리 격리——한 Mac에 두 풀 Runner를 달지 마세요(Rule 2가 무의미해집니다). Distribution 인증서와 App Store Connect API Key를 Keychain + 최소 권한 토큰으로 보관(등록 절차는 30~60분 병망 체크리스트). 릴리스 전 flutter doctor -v와 dry-run Archive를 여기서 돌려 Fast 환경을 오염시키지 않습니다.
두 대는 동일 리전·동일 Xcode 메이저를 권장해 「Fast는 녹색, Archive는 적색」 버전 드리프트를 막습니다. Cloud Mac은 사무실 Mac 대비 이미지 고정, GitHub 안정 회선, UPS/물리 보안 불필요로 7×24 CI 노드에 적합합니다. 셀프호스트 Runner 보안 경계에서 Archive 머신 권한은 단일 repo 또는 단일 org로 좁히세요.
5 · Workflow 예시: PR과 main 분기
아래는 핵심 분기 로직 조각입니다(완전 pipeline에는 캐시, 시크릿, artifact 단계가 추가로 필요). Flutter 공식지속 배포에 iOS 서명 상세가 있습니다. 여기서는 인증서가 Archive 머신 Keychain에 import됐다고 가정합니다.
jobs: android-pr: if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - run: flutter analyze && flutter test - run: flutter build appbundle --release ios-pr-fast: if: github.event_name == 'pull_request' runs-on: [self-hosted, macOS, macos-fast, flutter] steps: - run: flutter analyze - run: flutter test - run: flutter build ios --simulator --no-codesign ios-release: if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') runs-on: [self-hosted, macOS, macos-archive, flutter-release] steps: - run: flutter build ipa --export-options-plist=ExportOptions.plist # 上传 TestFlight / 公证 — L2 only
ios-pr-fast에는 build ipa가 없습니다. PR에서 실기기 패키지 검증이 필요하면 workflow_dispatch 수동 트리거로, 그래도 Archive 풀 경유——PR 이벤트 기본값으로 두지 마세요. GitHub Actions workflow 문법의 concurrency 그룹으로 같은 PR의 구 run을 취소하면 불필요한 대기를 약 20% 더 줄일 수 있습니다.
6 · 캐시 체크리스트: Flutter 팀이 persist할 4 디렉터리
Cloud Mac 2대를 안정시키려면 CPU 모델보다 캐시가 중요합니다. Runner 사용자 디렉터리에 영속화(또는 nightly 스냅샷) 권장:
PUB_CACHE/~/.pub-cache— Dart 의존성.pubspec.lock변경 시 증분 갱신- Flutter SDK 디렉터리 —
fvm으로 버전 고정.flutter upgrade드리프트 방지 ios/Pods+ CocoaPods 다운로드 캐시 —pod install이 iOS 쪽 긴 꼬리- Xcode DerivedData — 같은
ios/Podfile.lock이면 히트율 높을 때 효과 큼
Fast와 Archive 풀은 DerivedData 디렉터리를 공유하지 마세요——Debug/Simulator와 Release/Archive 산출물 혼재는 기묘한 링크 오류를 냅니다. Android Gradle cache는 ubuntu-latest에서 Actions Cache면 충분, Cloud Mac 디스크를 쓰지 않습니다.
pod repo update를 Archive 머신에서 먼저 검증 후 Fast 풀 동기화. 일상 PR은 캐시 재사용만. flutter pub upgrade를 CI 기본 단계에 넣지 마세요.
7 · 도입 체크리스트: 0에서 듀얼 머신 병망까지
순서대로 실행하면 1~2 영업일 내 PoC 완료 가능:
- 동일 스펙 Cloud Mac 2대 개통(M4 + 16GB 이상 권장. iOS 컴파일 메모리 피크 12GB+ 흔함)
- #1에 Flutter/Xcode/CocoaPods 설치 후
macos-fastRunner 등록. #2에 서명 자료 추가,macos-archive등록 - workflow 분리: Android는
ubuntu-latest. PR macOS는 L1만. main/tag만 L2 - pub/DerivedData/Pods 영속화. 3일간 P95 wait time 10분 미만 관측
- wait >> run이 계속되면, 먼저 L2가 PR에 들어갔는지 확인 후 증설
PoC 단계에서는 Cloud Mac 1대로 Fast 풀만, Archive는 GitHub 호스트 macos-latest(대기 허용)도 가능——분풀 로직 검증 후 2번째 대 추가. 「2대 스타트」와 모순 없습니다. 2번째 대는 릴리스 SLA 보험이며 PR 피드백 필수는 아닙니다.
8 · FAQ
Flutter 단위 테스트를 전부 Linux에 두고 Cloud Mac 1대면 될까?
PR에서 macOS 컴파일 검증이 불필요하면 이론상 가능——다만 대부분 팀은 PR에서 build ios --simulator로 네이티브 플러그인 이슈를 잡습니다. 1대로 L1은 가능하지만 Archive는 빠른 피드백과 격리 필수, 릴리스 때 PR 전선 Queued. 안전하게는 2대.
Cloud Mac 2대 vs 사무실 Mac mini 2대 차이?
토폴로지는 같음. 차이는 운영: Cloud Mac은 하드 조달 불필요, 이미지 스냅샷, GitHub 회선 안정. 사무실 Mac은 7×24·전산실 조건 갖춘 장기 운영. 단기·분산 팀은 Cloud Mac을 더 자주 선택. 대규모 CI 노드 설계 참고.
Codemagic / GitHub 호스트 macOS는 남길까?
Archive 풀 백업이나 PR overflow로 유효. 셀프호스트를 주 경로로 한 뒤, 호스트 macOS는 「월 릴리스 4회 미만」 소규모 팀에 맞음. 활발한 Flutter 팀은 장기 비용에서 셀프호스트가 유리한 경우가 많습니다.
monorepo 다중 app Job 수는?
flavor/matrix로 곱셈. PR matrix가 macOS Job 4개 초과면 Fast 풀 2대 부족——matrix 축소 또는 3번째 대 추가, Archive를 PR에 넣지 마세요.
Cloud Mac 2대로 Flutter Android·iOS CI 역할 분담
Flutter 팀 듀얼 파이프라인에서 Android는 Linux에 둘 수 있지만, iOS 컴파일·서명·IPA 내보내기는 macOS 없이 불가합니다. Cloud Mac mini M4 2대를 macos-fast와 macos-archive로 나누면 PR 피드백과 릴리스 무거운 작업이 슬롯을 서로 빼앗지 않습니다——Archive를 PR workflow에 넣고 전원 Queued 대기보다 훨씬 낫습니다. Apple Silicon 통합 메모리는 flutter build ios와 Xcode 링크 단계를 매끄럽게 하고, 대기 약 4W 소비전력은 7×24 CI 노드 상시 가동에 적합합니다.
사무실 Mac 전산실 자구축보다 Cloud Mac은 요금제 개통·이미지 복제·GitHub 안정 회선으로 분산 Flutter 팀이 듀얼 풀 토폴로지를 빠르게 깔 수 있습니다. macOS 네이티브 Unix 환경은 Flutter, CocoaPods, Fastlane을 추가 가상화 없이 바로 씁니다.
Flutter 팀 iOS CI 이전을 계획 중이라면 VPSSpark Cloud Mac mini M4 2대로 시작하는 게 최소可行 토폴로지입니다——요금제 알아보기. 먼저 Fast 풀 PoC를 통과한 뒤 Archive 풀로 릴리스를 맡기세요.