Mac이 없을 때 iOS 파이프라인은 어디서 끊기는가
많은 팀이 먼저 Linux에서 단위 테스트와 정적 분석을 돌리며 "CI가 이미 갖춰졌다"고 생각합니다——TestFlight 패키지가 필요해질 때까지요. xcodebuild, codesign, notarytool, App Store Connect API는 모두 Apple이 서명한 macOS에서 실행되어야 합니다. Docker 이미지로 우회할 수 있는 문제가 아닙니다: Archive 인증서 체인, Apple Silicon에 최적화된 Swift 컴파일러 경로 모두 실제 Mac 위에서 동작한다고 가정합니다.
따라서 iOS 제품을 가진 모든 팀은 결국 "누가 이 Mac을 장기적으로 제공할 것인가"에 직면합니다. 일반적인 선택지는 세 가지입니다: GitHub 관리형 macOS Runner, 사무실 자체 구매 Mac mini, 클라우드 전용 물리 머신. 절대적인 우열은 없으며, 대기 시간 허용 범위, 월간 빌드 빈도, 시스템과 인증서 전담 인력 유무에 따라 달라집니다. 이 글은 세 번째 경로의 기술 구현에 초점을 맞추지만, 나머지 두 경로의 적합 범위도 먼저 판단하는 데 도움을 드립니다.
하드웨어: Mac mini M4 · 10코어 CPU · 16 GB 통합 메모리 · 256 GB NVMe · 1 Gbps 전용 대역폭(PixVPS 일본 노드).
시스템: macOS 15 Sequoia, Xcode 16.4. 샘플 프로젝트: SwiftUI 중형 앱(약 11.8만 줄, Extension target 3개 포함).
CI: GitHub Actions self-hosted runner 2.323.0; Jenkins 2.479 LTS + macOS agent.
서명: Apple Distribution 인증서 + App Store Connect API Key(Issuer ID + Key ID + .p8).
관리형 Runner, 자체 데이터센터, 클라우드 전용: 어떻게 선택할까
완전한 iOS 빌드를 머신 대기, 컴파일·링크, 서명·업로드 세 단계로 나누면 각 방안의 병목이 완전히 다르다는 것을 알 수 있습니다. 동일 commit에서 GitHub 관리형 macos-14 Runner와 PixVPS M4 전용 노드를 비교했습니다: 관리형 Runner는 UTC 13:00–17:00 피크 시간대에 평균 22분 대기 후 job이 시작되었고, 실제 xcodebuild archive는 5분 38초가 걸렸습니다. 전용 M4는 대기 없이 Archive 전체 빌드 3분 52초, clean build 시 16 GB 통합 메모리에서 swap이 발생하지 않았습니다.
| 빌드 경로 | 일반적인 월 비용 | 대기 / 동시 실행 | 더 적합한 시나리오 |
|---|---|---|---|
| GitHub 관리형 macOS Runner | 분 단위 과금(약 $0.08/min부터) | 공유 풀, 피크 시간대 대기 심함 | 월 빌드 < 500분, 대기 허용 가능 |
| 자체 구매 Mac mini 데이터센터 설치 | 하드웨어 일회성 + 전기·운영 비용 | 전용, OS 업그레이드 직접 관리 필요 | 고정 사무실, 연중 고빈도 빌드 |
| 클라우드 전용 Mac(일 단위 임대) | PixVPS $21.1/일부터, 계약 없음 | 물리 머신 전용, 결제 후 즉시 사용 가능 | 중소 규모 팀, 릴리스 주 집중 빌드, 원격 협업 |
push 후 30분 뒤에야 컴파일 성공 여부를 알 수 있다면 병목은 Xcode 자체보다 대기열에 있을 가능성이 큽니다. Runner를 항상 온라인인 전용 Mac에 고정하는 것이 피드백 루프를 줄이는 가장 직접적인 방법입니다.
원격 Mac 노드 환경 초기화 4단계 체크리스트
PixVPS가 제공하는 Mac mini에는 완전한 macOS가 사전 설치되어 있고 관리자 권한이 부여됩니다. 개설 후 SSH 또는 브라우저 VNC로 로그인하고, 인증서와 프로비저닝 프로파일을 고정 디렉터리 규칙에 따라 보관해 서로 다른 Runner 스크립트가 각자 경로를 찾는 일을 피하세요. 아래 네 단계는 새 노드를 초기화할 때마다 따르는 표준 체크리스트입니다.
- 01 Xcode 설치 및 라이선스 동의
App Store에서 Xcode 16.x를 설치하고
sudo xcodebuild -license accept와xcodebuild -runFirstLaunch를 실행합니다. 확인:xcodebuild -version이 예상 버전을 출력하는지 확인하세요. - 02 Distribution 인증서 및 프로비저닝 프로파일 가져오기
.p12 파일을
~/certs/에 두고security import로 전용 키체인~/Library/Keychains/ci.keychain-db에 저장합니다. .mobileprovision은~/Library/MobileDevice/Provisioning Profiles/에 넣습니다. - 03 App Store Connect API Key 설정
Apple Developer에서 API Key를 생성하고
AuthKey_XXXXXX.p8를~/private_keys/에 저장합니다. TestFlight 업로드 시altool또는 Fastlanepilot upload를 사용해 대화형 2단계 인증을 우회합니다. - 04 첫 전체 Archive 실행 및 DerivedData 보존
저장소를 클론한 뒤 로컬에서 Release Archive를 한 번 성공시켜 서명 체인에 오류가 없는지 확인합니다. DerivedData를 보존하면 이후 증분 빌드 시간을 약 30–45% 절약할 수 있습니다.
~/.zprofile 또는 Runner 시작 스크립트에서 KEYCHAIN_PATH, P8_KEY_PATH, DEVELOPER_DIR를 통일해내면 GitHub Actions와 Jenkins가 동일한 참조를 사용해 "로컬에서는 되는데 CI에서 인증서를 못 찾는" 문제를 줄일 수 있습니다.
GitHub Actions 셀프 호스팅 Runner: 등록부터 workflow까지
셀프 호스팅 Runner 등록이 완료되면 workflow가 label을 통해 job을 이 클라우드 Mac에 정확히 배정합니다. 경로: 저장소 Settings → Actions → Runners → New self-hosted runner, macOS ARM64를 선택한 뒤 페이지 안내에 따라 actions-runner 패키지를 다운로드하고 실행합니다:
./config.sh --url https://github.com/YOUR_ORG/YOUR_REPO --token RUNNER_TOKEN --labels macos-m4,pixvps,ios-build --unattended
등록 후 시스템 서비스로 설치: sudo ./svc.sh install → sudo ./svc.sh start. workflow YAML에서 runs-on: [self-hosted, macos-m4]를 지정합니다. 일반적인 iOS job 단계: 코드 체크아웃 → CI 키체인 잠금 해제 → xcodebuild archive → xcodebuild -exportArchive → Fastlane upload_to_testflight. M4 노드에서 push 트리거부터 TestFlight 처리 완료까지 평균 약 10분, 실제 빌드와 업로드는 5–6분만 소요됩니다.
Runner는 저장소 소스와 서명 키에 접근할 수 있으므로 collaborator 권한을 제한하고 registration token을 정기적으로 교체하세요. fork된 PR에서 키가 포함된 workflow가 자동 실행되지 않도록 합니다. 여러 프로젝트가 한 노드를 공유할 때는 저장소마다 다른 Runner를 등록하거나 OpenClaw 샌드박스로 Agent의 파일 시스템 접근 범위를 제한할 수 있습니다.
Jenkins macOS Agent 탄력적 연결
팀에 Jenkins 컨트롤러(Linux에서 실행 가능)가 이미 있다면 macOS 빌드 역량은 Agent 노드로 연결합니다. 클라우드 Mac에 JDK 17을 설치하고 agent.jar를 다운로드한 뒤 LaunchDaemon으로 상시 실행하고, 컨트롤러가 SSH 또는 JNLP로 빌드 작업을 전달합니다.
Jenkins의 강점은 시각적 파이프라인과 플러그인 생태계입니다: Credentials Binding으로 키체인 비밀번호 주입, AnsiColor 로그 색상, 빌드 산출물을 Artifactory에 보관 등. 일반적인 Pipeline은 stage('Archive')에서 sh 'xcodebuild ...'를, stage('Upload')에서 Fastlane을 호출합니다. GitHub Actions와 비교하면 Jenkins는 다중 브랜치, 다중 환경, 수동 승인 게이트가 필요한 기업 내부 프로세스에 더 적합합니다.
클라우드 Mac을 일 단위로 임대하면 "탄력적 Agent"로 활용할 수 있습니다: 릴리스 주에 노드를 개설해 Jenkins에 연결하고 비수기에는 해제하면 365일 내내 데이터센터 Mac을 운영할 필요가 없습니다. DEVELOPER_DIR을 고정해 여러 Xcode 버전 전환 혼란을 막는 것은 Jenkins 환경에서 가장 간과되기 쉬운 안정성 요소입니다.
Archive, Export, TestFlight 전체 명령줄 파이프라인
GitHub Actions든 Jenkins든 최종 산출물 경로는 동일합니다: Archive로 .xcarchive 생성 → Export로 .ipa 생성 → App Store Connect 업로드. 명령줄 방식이 CI의 표준이며 Xcode GUI에 의존하지 않습니다.
Archive 예시(Release, scheme 지정):
xcodebuild archive -workspace MyApp.xcworkspace -scheme MyApp -configuration Release -archivePath build/MyApp.xcarchive CODE_SIGN_STYLE=Manual PROVISIONING_PROFILE_SPECIFIER="MyApp AppStore"
Export에는 ExportOptions.plist가 필요합니다(method는 app-store로 설정):
xcodebuild -exportArchive -archivePath build/MyApp.xcarchive -exportPath build/export -exportOptionsPlist ExportOptions.plist
TestFlight 업로드(API Key 방식, 무인 운영에 적합):
xcrun altool --upload-app -f build/export/MyApp.ipa -t ios --apiKey KEY_ID --apiIssuer ISSUER_ID
M4의 10코어 CPU는 Swift 동시 컴파일을 구형 Intel CI 머신보다 눈에 띄게 빠르게 합니다. 프로젝트에 Swift Package 의존성이 많다면 workflow에서 ~/Library/Developer/Xcode/DerivedData와 SourcePackages 디렉터리를 캐시하면 두 번째 빌드부터 소요 시간을 약 1/3 더 줄일 수 있습니다.
무인 서명: 키체인 문제 해결 매뉴얼
SSH 또는 헤드리스 Runner에서 codesign이 실패하는 경우 대부분 인증서 만료가 아니라 키체인 문제입니다. 빌드 스크립트 시작 부분에 잠금 해제와 권한 부여를 고정해 실행하세요:
security unlock-keychain -p "$KEYCHAIN_PASSWORD" ~/Library/Keychains/ci.keychain-db
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" ~/Library/Keychains/ci.keychain-db
비밀번호는 GitHub Secrets 또는 Jenkins Credentials로 주입하고, 저장소에 평문으로 기록하지 마세요. errSecInternalComponent 오류가 나면 키체인이 기본값인지, codesign이 신뢰 목록에 있는지 확인하세요. altool 업로드가 인증에서 멈추면 API Key의 Issuer ID와 .p8 파일명이 일치하는지 확인하세요.
또 흔한 문제는 Provisioning Profile과 Bundle ID 불일치입니다——Archive 단계에서는 오류가 없고 Export 단계에서 실패합니다. CI에 security cms -D -i profile.mobileprovision로 UUID를 출력하는 단계를 추가해 프로젝트의 PROVISIONING_PROFILE_SPECIFIER와 대조하세요.
릴리스 주기에 맞춰 클라우드 빌드 머신 임대
독립 개발자와 소규모 팀은 종종 딜레마에 빠집니다: macOS 빌드가 필요한데 CI 전용 머신을 위해 사무실을 임대하고, 전용선을 깔고, 정전과 OS 업그레이드를 처리하고 싶지 않습니다. 공용 클라우드 Linux VM은 충족할 수 없습니다(완전한 macOS와 Apple 서명 체인 없음). 집에 Mac mini를 두면 업로드 대역폭 불안정, IP 변경, 실수로 전원 끄기 등의 위험이 있습니다.
PixVPS는 전용 물리 Mac mini M4를 제공합니다: 가상화 없음, 초과 판매 없음, 각 머신 16 GB 메모리와 1 Gbps 전용 대역폭, 결제 후 1–5분 내 자동 개설. 싱가포르, 일본, 한국, 홍콩, 미국 동부 다섯 지역 노드 중 사용자 분포에 맞게 선택할 수 있습니다. 일본 시장용 앱은 도쿄 노드를 선택해 TestFlight 업로드 시 국경 간 지연을 줄일 수 있습니다.
일 $21.1, 주 $57.1, 월 $105.7로 과금되며 장기 계약이 없습니다. 릴리스가 집중된 한 주에 노드를 개설해 Runner를 연결하고 일상 유지 기간에는 해제하면 연중 자체 구매 머신과 전기료보다 경제적인 경우가 많습니다. 여러 대에서 Archive를 병렬 실행하려면 Thunderbolt 5 병렬 서비스로 80 Gbps 클러스터를 구성할 수 있으며, 대형 monorepo나 다중 앱 매트릭스에 적합합니다.
- 01 PixVPS에서 노드 선택 및 개설
콘솔에 로그인해 지역과 임대 기간을 선택하면 결제 후 SSH 자격 증명과 VNC 접속 정보가 자동으로 발급됩니다. 접속 방법은 도움말 센터를 참고하세요.
- 02 3절에 따라 Xcode 및 서명 기준선 완료
키체인 경로와 API Key 경로를 환경 변수에 기록해 GitHub Actions / Jenkins가 동일하게 참조하도록 합니다.
- 03 Runner 등록 및 첫 pipeline 성공
먼저 Debug 빌드로 컴파일을 검증한 뒤 Release Archive + TestFlight로 전환하고 Fastlane lane을 저장소에 고정합니다.
| 팀 형태 | 권장 방식 | 클라우드 Mac 역할 |
|---|---|---|
| 독립 개발자, 월 1–2회 릴리스 | 릴리스일 일 단위 임대 + 수동 Archive | 임시 빌드 머신, 사용 후 즉시 해제 |
| 5–15명, 하루 여러 번 push | 상시 self-hosted Runner | 월 단위 임대, 전용·대기 없음 |
| Jenkins 보유, macOS Agent 부족 | 클라우드 Mac을 탄력적 Agent로 | 피크 시 확장, 신규 하드웨어 구매 회피 |
| 다중 앱 매트릭스 + 야간 일괄 빌드 | TB5 클러스터 병렬 | 여러 대 M4 병렬 Archive |
iOS CI/CD의 문턱은 Xcode 메뉴바가 아니라 안정적이고 예측 가능한 macOS 연산력 + 단일 신뢰할 수 있는 서명 환경에 있습니다. 공용 Runner는 저빈도 빌드에 적합하고, 자체 데이터센터는 운영 역량이 있는 성숙한 팀에 적합합니다. 클라우드 전용 Mac은 "대기는 싫지만 머신은 사고 싶지 않은" 중간 지대를 채웁니다. 컴파일을 클라우드로 옮기면 로컬 MacBook은 밤늦게 CI 작업에 팬과 메모리를 빼앗기지 않고 코드 작성에 집중할 수 있습니다.
iOS 파이프라인에 대기 없는 macOS 빌드 머신을
PixVPS Mac mini M4 전용 노드: 완전한 macOS와 Xcode, 16 GB 통합 메모리, SSH / VNC 접속, GitHub Actions와 Jenkins Runner 연결 가능, 일 $21.1부터.