팀은 이미 GitLab(SaaS 또는 셀프 매니지드)을 쓰지만 iOS/macOS 빌드는 호스티드 macOS 러너 대기열, 노트북 겸용, Jenkins 구축 사이에 갇혀 있습니다. VPS 운영 사고방식의 플랫폼 엔지니어에게 이 가이드는 「노드처럼 Mac을 빌리는」 경로를 엽니다. 전용 원격 Mac에서 GitLab Runner로 실제 마찰을 드러내는 일곱 가지 체크리스트, Jenkins SSH 에이전트 및 GitHub Actions 셀프 호스티드 러너와의 삼자 비교표, 이어서 등록·태그·캐시 디렉터리·동시 실행과 .gitlab-ci.yml 스케치를 담은 여섯 단계 인수인계 런북, 그리고 러너·Jenkins·SwiftPM/Pods 디스크 거버넌스 글과의 상호 링크입니다.
GitLab 문서의 gitlab-runner register는 매끈해 보이지만, 운영에서는 시간이 보통 Apple 툴체인 상태 머신과 동시 디스크 밟기에서 탑니다. 아래 일곱 항목으로 「러너 하나 달자」를 서명 가능한 리스크 표로 바꿉니다.
원격 Mac을 Linux 러너처럼 취급: TCC, 키체인, 가끔 필요한 GUI를 무시하면 첫 서명이 터집니다. SSH 대 VNC 체크리스트와 함께 검토하세요.
개인 macOS 계정으로 등록: 절전, 업데이트 알림, 데스크톱 세션이 진짜 무인 흐름을 깨뜨립니다. 재현 가능 빌드에 맞춘 전용 CI 사용자를 쓰세요.
동시 실행을 CPU 코어만으로 정함: Xcode RAM 스파이크와 NVMe 쓰기 증폭이 먼저 물어뜯습니다. DerivedData를 버킷하지 않으면 두 잡이 교착됩니다. SwiftPM/Pods 디스크 거버넌스와 같은 계약입니다.
러너 토큰과 등록 위생 무시: config.toml 백업을 평문처럼 흩뿌리면 로테이션 날 큐가 붉어질 때 거짓 안심만 남습니다.
캐시 정책 복붙: 나쁜 cache: 키는 브랜치를 오염시키거나 항상 미스합니다. 브랜치와 락파일 차원으로 키를 설계하세요.
아티팩트를 러너 디스크에만 둠: GitLab 아티팩트나 객체 스토리지 없이는 디스크와 컴플라이언스가 함께 아픕니다. 보유 기간을 보안 검토에 묶으세요.
「첫 사람 창」 없음: 초기 서명 프로파일은 헤드리스로 돌아가기 전 일회성 VNC나 데스크톱 확인이 필요할 수 있습니다. Fastlane + CI를 보세요.
공통 근원은 「원격 Mac」을 Xcode 지문과 키체인 경계를 가진 호스트가 아니라 순수 연산으로 취급하는 것입니다. 이미지 지문, 툴체인 버전, 정리 워터마크, 러너 태그 계약을 DB 레플리카처럼 유지하세요. 엔터프라이즈 빌드 풀과 짝을 이룹니다. 여러 프로젝트가 한 호스트를 쓰면 GitLab tags는 「아무 Mac」보다 세밀해야 하며 그렇지 않으면 resource_group이 진짜 격리를 표현하지 못합니다.
GitHub Actions와의 차이는 「컴파일되나」가 아니라 파이프라인 정의와 이벤트 소스입니다. .gitlab-ci.yml은 MR 수명주기에 네이티브로 묶이고, Actions는 PR에 묶이지만 GitHub 이탈 비용이 큽니다. GitLab 올인이면 macOS를 셸 러너로 두는 편이 iOS용 둘째 Jenkins 방언을 유지하는 것보다 보통 일관됩니다. 그래도 온프레 오케스트레이션 리뷰에서는 Jenkins가 이기기도 합니다. 다음 표가 트레이드오프를 고정합니다.
등록 전에 러너 가이드의 캐시·라벨 절을 읽으세요. 대부분의 디렉터리 계약은 GitLab cache와 CI_PROJECT_DIR로 그대로 옮겨집니다. 바뀌는 것은 워크플로에서 파이프라인으로의 트리거뿐입니다.
만능 해결책은 없습니다. 오케스트레이션 멘탈 모델과 자격 증명 경계를 고릅니다. 리뷰에 세 가지 SLA를 적습니다. 큐 지연, 설명 가능한 실패, 키 로테이션 비용입니다.
| 차원 | GitLab Runner(macOS 셸) | GitHub Actions 셀프 호스티드 | Jenkins SSH 에이전트 |
|---|---|---|---|
| 파이프라인 정의 | .gitlab-ci.yml이 프로젝트/MR에 네이티브. 템플릿·include가 성숙 | 저장소 YAML이 PR/Issue 이벤트에 강하게 결합 | Job DSL / Pipeline Groovy — 크로스 저장소 오케스트레이션은 유연하지만 스타일 드리프트가 큼 |
| 등록 모델 | 프로젝트/그룹 토큰. config.toml이 실행기와 태그를 한곳에 모음 | 조직/저장소 러너 토큰과 비교적 표준화된 설정 | 컨트롤러가 SSH 자격 증명을 쥠 — 컨트롤 평면을 강화 |
| 동시 실행·스로틀 | resource_group, parallel, 러너 동시 실행 한도 | YAML의 매트릭스와 concurrency | 라벨 + 스로틀 플러그인 — 유연하지만 설정 부담 큼 |
| 캐시·아티팩트 | 네이티브 cache/artifacts. 나쁜 키가 캐시를 오염 | 풍부한 actions/cache와 아티팩트 생태계 | 객체 스토어로의 DIY 접착이 흔함 |
| 최적 적합 | MR 파이프라인과 통합 러너 풀이 필요한 GitLab 중심 조직 | GitHub 중심, PR 주도 전달 | 다중 제품 라인, 온프레 아티팩트 스토어, 승인, 혼합 Git 호스트 |
GitLab 관점에서 Mac을 「VPS처럼」 빌린다는 것은 등록 가능한 러너 프로파일을 사는 것입니다. 고정 SSH, 예측 가능한 디스크 티어, Xcode 지문을 태그에 찍는 능력입니다.
GitLab Runner가 이기면 tags를 일급으로 취급합니다. Xcode 마이너, 무거운 pod install 허용 여부, UI 테스트 실행 여부를 모두 명시합니다. 스냅샷 대 장수 노드와 짝을 이룹니다. 장수 러너는 증분 정리에 기대고, 골든 이미지는 예열 이미지와 롤백 스모크에 기댑니다.
여러 CI를 운영하면 DerivedData 버킷팅을 통일해 같은 원격 Mac에서 GitLab 잡이 Jenkins나 Actions 잡을 밟지 않게 하세요. Unix 사용자나 루트를 나누지, 「스케줄만 어긋나길」 바라지 마세요.
순서가 중요합니다. 먼저 신원과 디렉터리, 다음 등록과 태그, 마지막 동시 실행입니다. 재현 가능 빌드와 지문 스크립트를 맞춰 GitLab이 「git clone만 됨」이 아니라 안정 서명을 검증하게 하세요.
전용 사용자와 작업 루트 생성: 예: /Users/ci/gitlab-runner. 개인 ~/Desktop과 섞지 않음. SSH는 키만.
gitlab-runner 설치: 공식 macOS 패키지 또는 Homebrew. PATH의 바이너리가 서비스 계정(launchd)에서 실행 가능해야 함.
register 실행: shell 실행기 선택, GitLab URL과 등록 토큰 제공. tag_list(예: ios,shell,m4)를 대화형 또는 비대화형 플래그로 고정.
첫 헬스 체크 잡: xcode-select -p, xcodebuild -version, swift --version, 디스크 스냅샷 출력. 로그를 러너 인수 증거로 보관.
.gitlab-ci.yml에서 DerivedData 명시: SwiftPM/Pods 디스크 거버넌스와 같은 계약 — 프로젝트별 버킷, 기본 공유 경로 회피.
타임아웃, 아티팩트, 정리 정의: timeout, 실패 보존, 디스크 부족 시 중단선(모니터링 + 러너 일시 중지 API).
stages: [build]
variables:
DERIVED_DATA: "$CI_PROJECT_DIR/.derivedData/$CI_PIPELINE_ID"
build_ios:
stage: build
tags: [ios, shell, m4]
timeout: 45m
script:
- xcode-select -p
- xcodebuild -version
- df -h
- xcodebuild -scheme "App" -configuration Release -destination "generic/platform=iOS" -derivedDataPath "$DERIVED_DATA" build
artifacts:
when: on_failure
paths:
- "**/*.xcresult"
expire_in: 3 days
팁: 파이프라인이 스토어 배포도 한다면 Fastlane + CI를 읽고 빌드 사용자, 키체인 파티션, App Store Connect API 키를 GitLab CI/CD 변수(마스킹 + 시크릿 보호)와 맞추세요.
GitLab 또는 러너 업그레이드 시 동일 커밋에서 전후 카나리아 iOS 잡을 돌려 지문 출력과 빌드 시간 분포를 비교하세요. 러너 캐싱 대비: 너무 느슨한 GitLab 캐시 키는 브랜치 A가 B의 Pods 캐시를 오염시키고, 너무 엄격한 키는 끝없는 콜드 스타트를 뜻합니다. 플랫폼과 제품이 보존 티어에 합의해야 합니다.
공급자가 고정 SSH 포트와 non-root 사용자를 주면 연결 매개변수는 흩어진 변수 설명이 아니라 내부 런북에 모으고, 로테이션은 한곳에서. Jenkins + 원격 Mac과 함께 SSH 기준선(키, 방화벽, 감사)은 CI 스택 전반에서 단일 출처로, 세 방언으로 나누지 마세요.
resource_group, 무거운 의존성 설치 스로틀흔한 실수는 「xcodebuild 프로세스가 몇 개 들어가나」만으로 동시 실행을 정하는 것입니다. pod install / SPM 해석과 컴파일 스파이크는 종종 다른 단계에 있습니다. .gitlab-ci.yml에서 resource_group으로 뮤텍스 자원을 표현하거나 잡을 쪼개세요. SwiftPM/Pods 거버넌스와 함께 무거운 해석 잡을 따로 스로틀해 자주 녹는 빌드의 슬롯을 빼앗지 마세요.
테스트는 또 다른 숨은 차원입니다. 시뮬레이터 UI 테스트는 컴파일 전용 파이프라인과 다른 동시 실행 모델이 필요합니다. XCTest와 시뮬레이터 샤딩을 읽고 GitLab에서 전용 태그나 러너 풀로 격리하세요.
경고: 디스크가 안전 워터마크 아래일 때 큐를 계속 채우지 마세요. 스케줄을 멈추고 먼저 정리하지 않으면 짧은 대기보다 비싼 반쯤 쓴 Xcode/git 상태를 초래합니다.
러너가 여러 리전에 있으면 리전을 이름과 태그에 넣고 아티팩트 경로에 라벨을 달아 대규모 리전 간 전송을 빌드 실패로 오독하지 마세요. 구매 대 임대 TCO와 함께 지연과 이그레스는 처음부터 비용 모델에 넣으세요.
아래 항목은 내부 정렬용입니다. 저장소 크기와 병렬도에 맞게 임계값을 조정하세요.
gitlab-runner --version, xcodebuild -version, CocoaPods 시 Ruby/Bundler, 디스크 모델 기록. 변경 후 카나리아 파이프라인 트리거.사무실 전용 Mac은 절전, 네트워크 지터, 툴체인 드리프트에 시달립니다. Linux는 Apple 공식 iOS 툴체인을 호스트할 수 없습니다. 익숙한 웹/MR 워크플로에서 GitLab을 유지하면서 macOS 실행을 전용·상시 가동·SSH 도달 가능한 원격 노드로 옮기면 「파이프라인 진실의 단일 소스」가 슬로건이 아니라 계약이 됩니다. 애드혹 자사 하드웨어나 연약한 가상화 Xcode 스택과 비교할 때 SSH 엔드포인트, 디스크 티어, 재현 가능한 러너 프로파일이 더 분명한 NodeMini Mac Mini 클라우드 임대가 보통 더 강한 플랫폼 수입니다. 사양과 요금은 대여 가격에서 비교하고 온보딩은 헬프 센터로 마무리하세요.
이 런북을 내부 툴체인 변경 등급에 묶으세요. Xcode 패치/마이너/메이저 업그레이드는 승인, 카나리아 범위, 캐시 무효화 정책이 다릅니다.
대부분의 네이티브 iOS/macOS 팀은 Xcode, 키체인, 시뮬레이터와의 마찰이 가장 적은 셸 실행기로 시작합니다. Docker는 컨테이너화 스택이나 더 강한 격리에 맞지만 Apple 툴체인에서는 비용이 큽니다. 먼저 하드웨어 티어를 대여 가격에서 비교하세요.
코어만으로 정하지 마세요. 단일 잡의 피크 RAM과 NVMe 쓰기 증폭을 재고 동시 실행을 늘리며 P95를 봅니다. DerivedData와 의존성 캐시를 버킷하고 무거운 pod install 잡은 스로틀합니다. 온보딩 질문은 헬프 센터를 보세요.
GitLab은 프로젝트/그룹 러너 토큰을 .gitlab-ci.yml과 묶고, Jenkins는 엔터프라이즈 오케스트레이션과 플러그인에 기대며, Actions는 PR 이벤트에 강하게 묶입니다. 이벤트 소스, 시크릿 경계, 큐 SLA를 문서화하세요. 로고가 아닙니다. 이어서 Jenkins + 원격 Mac과 GitHub Actions 러너를 보세요.