Руководство - CI/CD

GitLab CI/CD Runner на Mac Mini M4: полное руководство

Установите и настройте GitLab CI/CD runner на выделенном Mac Mini M4. Собирайте iOS-приложения нативно на Apple Silicon, запускайте тесты на реальных симуляторах и деплойте в TestFlight — всё из вашего пайплайна GitLab.

30 мин чтения Обновлено в январе 2025

1. Зачем использовать собственные GitLab Runner на Mac?

GitLab предлагает общие runner на Linux, но для сборки iOS-приложений требуется macOS, работающая на оборудовании Apple. Собственные macOS runner от GitLab ограничены и дороги. Собственный GitLab Runner на Mac Mini M4 даёт вам:

Неограниченные минуты CI/CD

Бесплатный тариф GitLab включает 400 минут CI/CD на общих runner. На собственном runner ограничений нет.

Нативный Apple Silicon

Собирайте на том же чипе M4, на котором работают устройства ваших пользователей. Никаких накладных расходов на трансляцию через Rosetta.

Полный контроль окружения

Устанавливайте любую версию Xcode, симуляторы, инструменты и зависимости, которые вам нужны.

Постоянные кэши

DerivedData, пакеты SPM и кэши CocoaPods сохраняются между запусками пайплайна.

2. Установка GitLab Runner

Подключитесь к вашему Mac Mini M4 по SSH и установите GitLab Runner с помощью Homebrew:

# Connect to your Mac Mini M4
ssh admin@your-server-ip

# Install Homebrew (if not already installed)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"

# Install GitLab Runner
brew install gitlab-runner

# Verify installation
gitlab-runner --version
# Version:      17.7.0
# Git revision:  ...
# Git branch:    17-7-stable
# GO version:    go1.22.10
# Built:         ...
# OS/Arch:       darwin/arm64

Установка как службы macOS

# Install the runner as a launchd service
# This ensures it starts automatically on boot
brew services start gitlab-runner

# Verify the service is running
brew services list | grep gitlab-runner
# gitlab-runner started admin ~/Library/LaunchAgents/homebrew.mxcl.gitlab-runner.plist

# Check runner status
gitlab-runner status
# gitlab-runner: Service is running

3. Регистрация Runner

Перейдите в ваш проект (или группу) в GitLab и откройте Settings > CI/CD > Runners > New project runner. Скопируйте токен регистрации.

Регистрация через новый процесс регистрации Runner (GitLab 16+)

# Register the runner using the authentication token from GitLab UI
# (GitLab 16+ uses authentication tokens instead of registration tokens)
gitlab-runner register \
  --non-interactive \
  --url "https://gitlab.com/" \
  --token "YOUR_RUNNER_AUTHENTICATION_TOKEN" \
  --executor "shell" \
  --description "mac-mini-m4-runner" \
  --tag-list "macos,apple-silicon,m4,ios,xcode"

Регистрация через устаревший токен регистрации (GitLab 15 и ранее)

# For older GitLab instances using registration tokens
gitlab-runner register \
  --non-interactive \
  --url "https://gitlab.com/" \
  --registration-token "YOUR_REGISTRATION_TOKEN" \
  --executor "shell" \
  --description "mac-mini-m4-runner" \
  --tag-list "macos,apple-silicon,m4,ios,xcode" \
  --run-untagged="false"

Проверка конфигурации Runner

# View the runner config file
cat ~/.gitlab-runner/config.toml

# Expected output:
# concurrent = 2
# check_interval = 0
#
# [session_server]
#   session_timeout = 1800
#
# [[runners]]
#   name = "mac-mini-m4-runner"
#   url = "https://gitlab.com/"
#   token = "..."
#   executor = "shell"
#   [runners.cache]
#     MaxUploadedArchiveSize = 0

# Adjust concurrency based on your hardware:
# Mac Mini M4 (16GB): concurrent = 2
# Mac Mini M4 Pro (24GB): concurrent = 3
# Mac Mini M4 Pro (48GB): concurrent = 4

Отредактируйте ~/.gitlab-runner/config.toml, чтобы настроить параллелизм:

# Edit the config
nano ~/.gitlab-runner/config.toml

# Set concurrent to match your hardware capacity
concurrent = 2

# Restart the runner to apply changes
gitlab-runner restart

Runner должен теперь отображаться как Online в вашем проекте GitLab в разделе Settings > CI/CD > Runners.

4. Создание .gitlab-ci.yml для iOS

Создайте файл .gitlab-ci.yml в корне вашего репозитория. Этот полный пайплайн собирает, тестирует и деплоит ваше iOS-приложение:

# .gitlab-ci.yml

stages:
  - setup
  - build
  - test
  - deploy

variables:
  SCHEME: "MyApp"
  WORKSPACE: "MyApp.xcworkspace"
  DESTINATION: "platform=iOS Simulator,name=iPhone 16 Pro,OS=18.2"
  DERIVED_DATA: "${CI_PROJECT_DIR}/DerivedData"

# Only run on our Mac runner
default:
  tags:
    - macos
    - m4

# ---- SETUP ----

setup:
  stage: setup
  script:
    - sudo xcode-select -s /Applications/Xcode-16.2.app/Contents/Developer
    - xcodebuild -version
    - swift --version
    # Install CocoaPods if using Podfile
    - |
      if [ -f "Podfile" ]; then
        pod install --repo-update
      fi
  cache:
    key: pods-${CI_COMMIT_REF_SLUG}
    paths:
      - Pods/
      - .spm-cache/

# ---- BUILD ----

build:
  stage: build
  needs: ["setup"]
  script:
    - |
      xcodebuild build \
        -workspace "${WORKSPACE}" \
        -scheme "${SCHEME}" \
        -destination "${DESTINATION}" \
        -derivedDataPath "${DERIVED_DATA}" \
        -clonedSourcePackagesDirPath ".spm-cache" \
        CODE_SIGNING_ALLOWED=NO \
        | xcbeautify
  cache:
    key: derived-data-${CI_COMMIT_REF_SLUG}
    paths:
      - DerivedData/
      - .spm-cache/
  artifacts:
    paths:
      - DerivedData/
    expire_in: 1 hour

# ---- TEST ----

unit_tests:
  stage: test
  needs: ["build"]
  script:
    - |
      xcodebuild test \
        -workspace "${WORKSPACE}" \
        -scheme "${SCHEME}" \
        -destination "${DESTINATION}" \
        -derivedDataPath "${DERIVED_DATA}" \
        -resultBundlePath "TestResults.xcresult" \
        -parallel-testing-enabled YES \
        | xcbeautify
  artifacts:
    when: always
    paths:
      - TestResults.xcresult/
    reports:
      junit: TestResults.xcresult/report.junit
    expire_in: 7 days
  after_script:
    - xcrun simctl shutdown all 2>/dev/null || true

# ---- DEPLOY ----

deploy_testflight:
  stage: deploy
  needs: ["unit_tests"]
  only:
    - main
  script:
    - |
      # Install or update Fastlane
      which fastlane || brew install fastlane

      # Run Fastlane beta lane
      fastlane beta
  environment:
    name: testflight
  variables:
    MATCH_PASSWORD: ${MATCH_PASSWORD}
    APP_STORE_CONNECT_API_KEY_ID: ${APP_STORE_KEY_ID}
    APP_STORE_CONNECT_API_ISSUER_ID: ${APP_STORE_ISSUER_ID}
    APP_STORE_CONNECT_API_KEY_CONTENT: ${APP_STORE_KEY_CONTENT}

Добавление переменных CI/CD в GitLab

Перейдите в Settings > CI/CD > Variables в вашем проекте GitLab и добавьте эти переменные как «Masked» и «Protected»:

  • MATCH_PASSWORD — пароль для шифрования Fastlane Match
  • APP_STORE_KEY_ID — ID ключа App Store Connect API
  • APP_STORE_ISSUER_ID — Issuer ID App Store Connect
  • APP_STORE_KEY_CONTENT — содержимое файла ключа .p8

5. Оптимизация производительности

Конфигурация кэша GitLab

GitLab Runner поддерживает локальное кэширование для shell executor. Поскольку ваш runner постоянный, локальные кэши чрезвычайно эффективны:

# In .gitlab-ci.yml, configure cache per branch:
cache:
  key: "${CI_COMMIT_REF_SLUG}"
  paths:
    - DerivedData/
    - .spm-cache/
    - Pods/
  policy: pull-push

# For test jobs that don't modify cache, use pull-only:
unit_tests:
  cache:
    key: "${CI_COMMIT_REF_SLUG}"
    paths:
      - DerivedData/
    policy: pull

Использование артефактов для передачи данных между этапами

# Pass build artifacts between stages efficiently
build:
  artifacts:
    paths:
      - DerivedData/Build/Products/
    expire_in: 2 hours

# The test stage receives the built products without rebuilding
test:
  needs: ["build"]  # only download artifacts from the build job
  script:
    - xcodebuild test-without-building \
        -scheme "${SCHEME}" \
        -destination "${DESTINATION}" \
        -derivedDataPath "${DERIVED_DATA}"

Параллельное выполнение тестов

# Split tests across parallel jobs using GitLab's parallel keyword
unit_tests:
  stage: test
  parallel: 2
  script:
    - |
      # Use test plan partitioning or custom splitting
      xcodebuild test \
        -workspace "${WORKSPACE}" \
        -scheme "${SCHEME}" \
        -destination "${DESTINATION}" \
        -derivedDataPath "${DERIVED_DATA}" \
        -parallel-testing-enabled YES \
        -maximum-parallel-testing-workers 4

Плановая очистка кэша

# On the Mac Mini, set up a weekly cleanup cron job
crontab -e

# Add these lines:
# Clean DerivedData older than 7 days every Sunday at 3 AM
0 3 * * 0 find ~/builds/*/DerivedData -maxdepth 0 -mtime +7 -exec rm -rf {} + 2>/dev/null

# Clean old GitLab Runner builds older than 14 days
0 4 * * 0 find ~/builds -maxdepth 2 -mtime +14 -type d -exec rm -rf {} + 2>/dev/null

# Clean Homebrew cache monthly
0 5 1 * * /opt/homebrew/bin/brew cleanup --prune=30 2>/dev/null

6. Устранение неполадок

Runner отображается как «offline» в GitLab

Проверьте статус службы runner и логи:

# Check service status
brew services list | grep gitlab-runner

# View logs
cat /usr/local/var/log/gitlab-runner.log

# Restart the service
brew services restart gitlab-runner

# Verify connectivity to GitLab
gitlab-runner verify

Ошибки отказа в доступе при сборке

Runner может нуждаться в доступе к директории разработчика Xcode:

# Ensure the runner user has Xcode access
sudo xcode-select -s /Applications/Xcode-16.2.app/Contents/Developer
sudo xcodebuild -license accept

# If using simulators, ensure the user can access them
xcrun simctl list devices

Кэш не восстанавливается

Убедитесь, что ключи кэша согласованы и пути существуют:

# Check cache directory permissions
ls -la ~/builds/

# The shell executor stores caches locally by default
# Verify the cache directory in config.toml:
cat ~/.gitlab-runner/config.toml

# Ensure [runners.cache] section has the right settings
# For local caching (most efficient for persistent runners):
# [runners.cache]
#   Type = ""  # empty = local cache

Сборка Xcode зависает или превышает тайм-аут

Это часто вызвано запросами доступа к связке ключей или проблемами с симулятором:

# Unlock the keychain before builds
security unlock-keychain -p "YOUR_PASSWORD" ~/Library/Keychains/login.keychain-db

# Kill stuck simulators
xcrun simctl shutdown all
pkill -f "Simulator.app" 2>/dev/null || true

# Set a build timeout in .gitlab-ci.yml
build:
  timeout: 30 minutes

7. FAQ

Можно ли использовать Docker executor на macOS?

Docker на macOS запускает контейнеры Linux в виртуальной машине, которая не имеет доступа к API macOS, Xcode или симуляторам iOS. Для сборки iOS необходимо использовать shell executor. Docker подходит для серверного Swift или других задач на базе Linux, работающих параллельно с вашим Mac runner.

Как зарегистрировать runner для группы GitLab?

Перейдите в настройки вашей группы GitLab Settings > CI/CD > Runners > New group runner. Используйте токен группового runner вместо токена проекта. Это сделает runner доступным для всех проектов в группе.

Что лучше использовать: shell executor или SSH executor?

Используйте shell executor. Он выполняет команды непосредственно на Mac, что обеспечивает полный доступ к Xcode, симуляторам и связке ключей. SSH executor предназначен для удалённых машин, что не нужно, когда runner уже находится на Mac.

Можно ли запускать runner GitLab и GitHub Actions на одном Mac?

Да. Оба runner легковесны и могут работать одновременно на одном Mac Mini M4. Просто учитывайте общее потребление ресурсов при настройке уровня параллелизма для каждого runner.

Как обновить GitLab Runner?

# Update via Homebrew
brew upgrade gitlab-runner

# Restart the service
brew services restart gitlab-runner

# Verify the new version
gitlab-runner --version

Связанные руководства

Готовы ускорить ваши пайплайны GitLab?

Получите выделенный Mac Mini M4 для вашего GitLab CI/CD runner. Неограниченные сборки от $75/мес. с 7-дневным бесплатным пробным периодом.