팀이 GitLab CI와 GitHub Actions를 동시에 쓰면서 Apple Silicon 베어메탈 한 대에 GitLab Runner와 셀프 호스팅 Runner를 올릴 때, 실패는 대개 슬롯 과다 예약·태그 오배정·자격 증명 혼선·NVMe 쓰기 경합에서 납니다. 아래는 동시성·태그·비밀·캐시 파티션만 짚은 대조표입니다. 병렬 codesign·키체인 설계는 다중 Job codesign·Keychain 격리 FAQ를, CI와 다른 워크로드 분리 패턴은 OpenClaw 2026.3.x Docker·doctor·CI 공존 튜토리얼과 함께 보세요.
1. 동시성 할당: 두 스케줄러가 같은 CPU·RAM을 나눈다
GitLab Runner는 concurrent와 executor별 limit로 상한을 잡고, GitHub Actions는 조직·저장소 정책과 러너 그룹으로 동시 실행을 제한합니다. 한 장비에 둘 다 두면 양쪽 소프트 한도의 합이 코어 수·시뮬레이터용 메모리 여유보다 커지지 않게 먼저 고정하세요. 대기열이 길어지는 것은 정상이지만, 전체 벽시계가 들쭉날쭉하면 대개 과다 구독(over-subscribe)입니다.
2. 태그 라우팅: 같은 이름이 양쪽에서 충돌하지 않게
GitLab은 Job tags, GHA는 runs-on의 self-hosted 라벨 집합으로 라우팅합니다. mac처럼 넓은 태그 대신 gl-ios·gha-ios처럼 플랫폼 접두어를 붙이고 워크플로마다 전체 태그를 명시하면 오배정 로그가 줄어듭니다. 러너 표시 이름에도 접두어를 두면 대시보드에서 원인 추적이 쉬워집니다.
3. 비밀 격리: PAT·시스템 사용자·키체인을 쌍으로 쪼갠다
가장 안정적인 방법은 러너마다 별도 macOS 사용자 또는 최소한 분리된 HOME(예: ~/.gitlab-runner와 ~/actions-runner)을 쓰는 것입니다. login Keychain과 fastlane match 저장소도 분리하고, 환경 변수 이름은 CI_TOKEN 같은 포괄명 대신 GITLAB_… / GH_…처럼 감사 가능한 접두어를 쓰세요.
4. NVMe 캐시 파티션: DerivedData·_work·GitLab cache를 한 나무에 담지 않기
APFS 볼륨이나 최소한 루트가 다른 상위 디렉터리로 DerivedData, Actions의 _work, GitLab 원격/로컬 캐시를 나눕니다. 읽기 위주 의존성 캐시는 별도 접두 경로에 두고, Job TTL·정리 스크립트를 플랫폼별로 독립시키면 한쪽이 다른 쪽 캐시를 지우는 사고를 막을 수 있습니다. CPU는 여유인데 대기 시간이 길면 디스크 쓰기 병목을 의심하세요.
5. 적용 FAQ
- Xcode 한 벌을 공유해도 되나요? 가능합니다.
DEVELOPER_DIR와 설치 버전을 고정하고 업그레이드는 두 러너를 동시에 끊지 않는 창에서 진행하세요. - 디스크를 먼저 채우는 쪽은? 상한이 없는 캐시·아티팩트 쪽입니다. 볼륨별 임계 알람을 먼저 두고 확장합니다.
- 컴플라이언스로 풀을 나눠야 하나요? 태그·등록 단위로 완전 분리하거나 물리 분리가 가장 명확합니다. 한 대 혼합 시 데이터 상주 경계를 문서화하세요.
베어메탈 듀얼 러너는 Mac mini·macOS가 기준선을 단순화한다
두 스케줄러를 한 실리콘에 올리면 안정적인 디스크 I/O와 낮은 서명·키체인 드리프트가 생명줄입니다. Apple Silicon 통합 메모리는 병렬 컴파일과 시뮬레이터를 같은 칩에서 감당하고, macOS는 Gatekeeper·SIP·FileVault로 무인 운영에 익숙합니다. 유휴 전력이 낮은 Mac mini M4는 24시간 러너 베이스에 잘 맞습니다.
동시성 슬라이스와 NVMe 파티션을 먼저 단일 노드에서 검증한 뒤 풀을 넓히면 설득과 장애 대응이 쉬워집니다. 전용 노드가 필요하면 Macstripe 홈에서 리전과 모델을 비교해 보세요. 지금 Mac mini M4로 듀얼 러너 기준선을 고정하면 이후 확장 비용을 줄일 수 있습니다.