Метки маршрутизации настроены правильно, но второй проект всё равно прочитал остатки исходников и кэша первого проекта.

Самое быстрое решение — оставить обычную сборку и тесты в управляемом общем пуле, а production-подпись, непроверенные merge request и проекты разных доменов доверия перевести на отдельные Mac Runner. Такой общий Mac Runner для нескольких проектов в GitLab CI должен работать в двух слоях: общий пул сборки и выделенный пул выпуска. Метки, Protected Runner и раздельные каталоги управляют маршрутом, но не превращают один macOS-хост в полноценную изолированную песочницу. Это прямо следует из ограничений Shell executor в официальной документации GitLab.

Эта инструкция предназначена для платформенных инженеров, которые предоставляют macOS-сборки нескольким репозиториям. Она также пригодится руководителям безопасности и release-команд, контролирующим сертификаты, приватные ключи и доступ к внутренним зависимостям.

Если вы принимаете решение о количестве Mac-узлов, используйте материал как план внедрения: от границы доверия и первого проекта до перекрёстной проверки, production-подписи и расширения пула.

Архитектура общего и выделенного пулов

До регистрации первого GitLab Runner разделите задачи не по удобству команды, а по уровню доверия.

Базовая схема выглядит так:

  • Общий пул сборки — доверенные проекты одной организации, обычная компиляция, unit-тесты и подготовка артефактов.
  • Выделенный пул выпуска — production-архивирование, подпись, загрузка релиза и операции с чувствительными ключами.
  • Отдельный узел или отдельная учётная запись Runner — проекты с другим владельцем, доступом к закрытой сети или непроверенным кодом.

Общий Mac Runner может обслуживать несколько проектов, если эти проекты находятся в сопоставимом домене доверия, а задания не получают production-секреты. Но общий физический хост не означает общую безопасность. У Shell executor задание выполняется через оболочку операционной системы; GitLab предупреждает, что такой режим подходит для доверенного кода и может допустить доступ к данным других проектов на том же хосте. Подробная граница описана в документации GitLab по безопасности Shell executor.

Перед подключением составьте три списка:

  • Можно размещать в общем пуле: доверенный код, одинаковые правила доступа, отсутствие production-ключей и отсутствие необходимости обращаться к закрытой инфраструктуре.
  • Нужна отдельная учётная запись Runner: проекты одной компании, которым требуется различный пользовательский контекст, разные локальные настройки или ограниченный доступ к каталогам.
  • Нужен отдельный Mac Runner: непроверенные merge request, проекты разных организаций, production-подпись, приватная сеть с повышенными требованиями или секреты, которые нельзя доверять другим заданиям.

Важно. Protected Runner ограничивает, какие защищённые ветки и теги могут использовать узел. Он не блокирует вредоносный или ошибочный скрипт после запуска на разрешённом узле.

На этом этапе отдельно зафиксируйте пять сущностей: область GitLab Runner, его служебную учётную запись, локальную учётную запись macOS, рабочий каталог и Keychain. Смешивать их в одной схеме нельзя. Область определяет, какие проекты видят Runner; локальная учётная запись определяет права процесса; Keychain хранит и защищает отдельные секреты.

Первый час: область Runner и маршрут заданий

Сначала выберите область регистрации. GitLab различает project runner, group runner и instance runner. В документации о масштабах Runner указано, что project runner связан с одним проектом, group runner может использоваться проектами группы, а instance runner доступен шире и управляется на уровне экземпляра GitLab.

Для многопроектного корпоративного пула начните с контролируемого group runner. Это безопасная исходная точка: список проектов можно ограничить одной группой, не открывая общий узел всему экземпляру GitLab. Instance runner имеет смысл только тогда, когда вы уже определили доверенные группы и правила допуска на уровне всей организации.

В конфигурации закрепите следующие условия:

  • общий узел принимает только задания с явной меткой, например macos-shared;
  • release-узел принимает только отдельную метку, например macos-release;
  • production-задачи запускаются исключительно на Protected Runner;
  • release-метка доступна только защищённым веткам или тегам;
  • проект не получает доступ к release-узлу только потому, что он входит в ту же группу.

Синтаксис маршрутизации проверяйте по официальному справочнику GitLab CI/CD YAML. Минимальная логика может выглядеть так:

build:
  tags:
    - macos-shared
  script:
    - ./ci/build.sh

release:
  tags:
    - macos-release
  rules:
    - if: '$CI_COMMIT_TAG'
  script:
    - ./ci/sign-and-upload.sh

Этот фрагмент задаёт маршрут, но не создаёт изоляцию файловой системы. В руководстве по настройке Runner отдельно описаны сопоставление тегов, защищённые Runner и правила выбора задания. Поэтому первый тест должен проверять не только успешную сборку, но и отказ неправильного маршрута.

Как выбрать group runner вместо project runner? Если несколько доверенных проектов должны получать одинаковый тип Mac-ресурса, начинайте с group runner и явного списка разрешённых проектов. Если проекту нужны собственные сертификаты, отдельный пользователь macOS или независимое расписание обслуживания, используйте project runner либо отдельный хост. Instance runner не следует выбирать только ради удобства регистрации.

Зафиксируйте доказательства первой контрольной точки:

  • область регистрации Runner;
  • список проектов, которым разрешён доступ;
  • назначенные метки;
  • состояние Protected Runner;
  • успешный запуск обычной сборки;
  • отклонённое задание с неподходящей меткой;
  • отказ release-задачи из незащищённой ветки.

Без этих записей статус «online» ничего не доказывает.

Первый запуск: учётная запись и каталоги

После маршрутизации запускайте первую задачу в реальном контексте службы GitLab Runner. Администраторская сессия macOS не подходит для приёмки: она может видеть файлы, переменные и Keychain, недоступные служебному пользователю.

Проверьте последовательно:

  1. Какой локальный пользователь запускает процесс Runner.
  2. Какая домашняя директория используется заданием.
  3. Где находятся исходники проекта.
  4. Где создаются временные файлы.
  5. Где лежит кэш зависимостей.
  6. Куда попадают архивы и результаты сборки.
  7. Какие пользовательские настройки Xcode и инструменты доступны процессу.

Не храните всё в одном каталоге. Разделите как минимум:

  • рабочее дерево текущего проекта;
  • временные файлы задания;
  • повторно создаваемый кэш;
  • артефакты, предназначенные для передачи;
  • долгосрочные секреты, которые вообще не должны находиться в рабочем каталоге.

GitLab описывает кэш как механизм повторного использования файлов между заданиями, а не как защищённое хранилище секретов. Это различие отражено в документации о CI/CD-кэше. В кэш нельзя помещать signing key, профиль подписи, токены доступа или конфигурацию, содержимое которой не должен читать другой проект.

Как изолировать проекты в macOS Shell executor? Каталоги и имена кэша снижают вероятность случайного использования чужих данных, но не защищают от задания, которое выполняется с теми же правами и намеренно читает доступные пути. Настоящее усиление достигается сменой доверительной границы: отдельной учётной записью Runner или отдельным Mac Runner.

Для первой проверки создайте тестовый проект и выполните действия в CI-контексте:

  1. Выведите владельца процесса и рабочий каталог.
  2. Создайте уникальный маркер в каталоге задания.
  3. Завершите задачу штатно.
  4. Запустите вторую задачу под тем же Runner.
  5. Убедитесь, что старый рабочий каталог и временные файлы удалены.
  6. Проверьте, что кэш восстанавливается только по ожидаемому ключу.
  7. Попробуйте обратиться к тестовому пути другого проекта и зафиксируйте результат.

Последний пункт важнее успешной сборки. Если второй проект может прочитать маркер первого, каталог нельзя считать границей безопасности. Очистка уменьшает остаточные данные, но не меняет полномочия процесса.

Второй проект: перекрёстная проверка

Не подключайте сразу все репозитории. Второй проект должен быть контрольным тестом, который специально ищет загрязнение среды.

Проверьте четыре класса остаточных ресурсов:

  • Файлы: исходники, архивы, временные конфигурации и логи.
  • Процессы: фоновые сервисы, зависшие скрипты, симуляторы и локальные серверы.
  • Инструменты: состояние Homebrew, выбранная версия Xcode, DerivedData и пользовательские настройки.
  • Переменные: экспортированные значения, файлы конфигурации и токены, оставшиеся после предыдущего задания.

Особое внимание уделите DerivedData и симуляторам. Если проекты используют один пользовательский каталог и одинаковые пути, результатом может стать не только утечка данных, но и недетерминированная сборка: второй проект получит объектные файлы или настройки первого. Это уже проблема воспроизводимости, даже если проекты принадлежат одной команде.

Выполните тесты в таком порядке:

  1. Проект A создаёт уникальный файл и временный процесс.
  2. Задание A завершается штатно и аварийно.
  3. Проект B запускается на том же общем Mac Runner.
  4. Проект B ищет файл, процесс, переменную и артефакт проекта A.
  5. После этого проверяется чистая сборка B.
  6. Состояние узла фиксируется до и после очистки.

Если проект B видит остатки A, остановите расширение общего пула. Не пытайтесь решить проблему только дополнительной меткой: метка выбирает Runner, но не удаляет доступ внутри уже запущенной оболочки.

Можно ли одному GitLab Runner одновременно обслуживать несколько проектов? Да, если проекты доверенные, задачи правильно маршрутизируются, секреты не смешиваются, а результаты перекрёстной проверки приемлемы. Нет, если проекты имеют разные домены доверия или используют один процесс с доступом к несовместимым секретам. В последнем случае нужен отдельный Runner или Mac-узел.

Если команды принадлежат разным подразделениям, заранее учитывайте не только технические права. Различия в владельце репозитория, правилах ревью, доступе к внутренней сети и процессе отзыва ключей уже являются достаточным основанием для разделения.

Production-подпись: отдельный контур

Обычная Xcode-сборка и production-подпись не должны автоматически попадать на один Mac Runner. Подпись обращается к сертификатам, приватным ключам, Provisioning Profile и, возможно, учётным данным App Store Connect. Эти активы имеют разные сроки действия, правила ротации и последствия компрометации.

Apple описывает модель Keychain и контроль доступа в документации Keychain Services. Дополнительные ограничения доступа к элементам рассматриваются в документации Apple о контроле доступа к Keychain и ограничении доступности элементов Keychain.

Практическая граница должна выглядеть так:

  • общий пул получает только то, что нужно для сборки и тестов;
  • release-пул принимает задания с защищённой ветки или тега;
  • production-сертификаты и приватные ключи доступны только release-процессу;
  • обычный проект не может выбрать release-метку;
  • токены загрузки не хранятся в кэше;
  • после перезапуска узла доступ к секретам проверяется повторно;
  • процедура ротации описывает отзыв старого ключа, выпуск нового и проверку отказа старого.

Нужен ли отдельный GitLab Runner для официальной iOS-подписи? Для production-подписи — обычно да, если общий пул обслуживает несколько команд, принимает код с разным уровнем доверия или имеет доступ к непроверенным изменениям. Выделенный узел не отменяет необходимость защищать Keychain, но сокращает число заданий и пользователей, которым доверен этот контур.

Минимальная приёмка release-узла включает:

  • тестовую подпись разрешённым проектом;
  • отказ неподходящего проекта;
  • проверку защищённого тега;
  • запись владельца процесса и используемой учётной записи;
  • проверку отсутствия ключей в артефактах и кэше;
  • проверку поведения после перезапуска;
  • документированную процедуру ротации.

Не называйте Keychain самостоятельной виртуальной машиной. Контроль доступа к элементу помогает ограничить использование секрета, но не делает произвольный скрипт безопасным, если он уже выполняется в доверенном контексте Runner.

Первая неделя: решение по ёмкости

После подключения второго проекта не расширяйте пул по ощущениям. В течение первой недели собирайте записи по каждому заданию:

  • время ожидания в очереди;
  • причина занятости Runner;
  • результат очистки;
  • конфликты DerivedData и симуляторов;
  • успешность восстановления после перезапуска;
  • количество production-задач;
  • случаи ручного вмешательства;
  • отказ неправильного маршрута.

Не подменяйте эти данные универсальными нормативами. Время сборки, допустимая очередь, число параллельных заданий и необходимая ёмкость зависят от проектов, версии Xcode, размера зависимостей и политики очистки. Для решения используйте фактические журналы GitLab и записи узла.

Контрольный список выбора схемы

Перед тем как оставить второй проект в общем пуле, поставьте отметки напротив всех подходящих условий. Один отрицательный ответ в блоке безопасности означает, что общий хост нельзя считать достаточным.

Оставляйте общий Mac Runner для обычных сборок, если:

  • [ ] все проекты принадлежат сопоставимому домену доверия;
  • [ ] общий Runner зарегистрирован в ограниченной области, предпочтительно как group runner;
  • [ ] каждая задача использует явную метку;
  • [ ] release-задачи отделены Protected Runner и защищёнными ветками или тегами;
  • [ ] второй проект не прочитал файлы, кэш, переменные и процессы первого;
  • [ ] общий пул не получает production-приватные ключи;
  • [ ] очистка рабочего каталога подтверждена журналами;
  • [ ] очередь и конфликты ресурсов соответствуют вашему внутреннему SLA.

Если все пункты отмечены, общий пул подходит для компиляции, тестов и подготовки артефактов. Production-подпись всё равно оставляйте в отдельном контуре.

Переводите проект на отдельную учётную запись Runner, если:

  • [ ] проекту нужны собственные локальные настройки macOS;
  • [ ] требуется отдельный набор инструментов или независимое расписание обслуживания;
  • [ ] команды используют разные правила доступа, но остаются в одной организации;
  • [ ] каталогами можно управлять раздельно, однако отдельный физический хост пока не нужен.

Если выполнены эти условия, но есть сомнения в доступе к файлам или секретам, сначала выделите отдельную учётную запись Runner и повторите перекрёстный тест. Это промежуточная мера, а не гарантия полноценной изоляции Shell executor.

Выбирайте отдельный Mac Runner, если:

  • [ ] проект принимает непроверенные merge request;
  • [ ] проекты принадлежат разным организациям или доменам доверия;
  • [ ] узлу нужен доступ к закрытой сети;
  • [ ] выполняется production-подпись;
  • [ ] второй проект прочитал остаточные данные первого;
  • [ ] общий Runner должен одновременно обслуживать код с несовместимыми уровнями доверия;
  • [ ] требования безопасности не допускают общий системный контекст.

Если отмечен хотя бы один из этих пунктов, не исправляйте проблему дополнительной меткой. Выносите задачу на отдельный Mac Runner или на отдельный хост.

Выбирайте гибридную схему, если:

  • [ ] обычные сборки имеют общий доверенный контур;
  • [ ] production-подпись требует постоянного выделенного узла;
  • [ ] нагрузка растёт неравномерно;
  • [ ] нужно проверить архитектуру до расширения собственного парка;
  • [ ] часть задач можно временно отправлять на удалённый Mac без передачи ему лишних секретов.

При таком результате оставляйте постоянный общий пул для доверенных задач, отдельный узел для выпуска и при необходимости добавляйте временный удалённый Mac для пиковых или изолированных сборок.

Этот список не заменяет журналов. Перед расширением составьте акт сквозной приёмки: получение кода, выбор правильного Runner, сборка в Xcode, создание артефакта, production-подпись, загрузка результата и восстановление после отказа. Каждый этап должен иметь ссылку на job log или запись узла.

Управление изменениями и аренда узлов

У общего Mac Runner есть несколько скрытых затрат, которые не видны в файле .gitlab-ci.yml:

  • обслуживание macOS и Xcode;
  • контроль локальных пользователей;
  • очистка рабочих каталогов;
  • ротация сертификатов;
  • расследование остаточных процессов;
  • восстановление после сбоя;
  • резервирование узла под release-задачи;
  • документирование допуска новых проектов.

Поэтому сравнивайте не цену Mac, а операционную модель. Собственный Mac оправдан при стабильной круглосуточной нагрузке, требованиях к физическому доступу и наличии команды, которая обслуживает узел. Удалённый Mac уместен для пилота, временного роста, распределённой команды и сценария, где нужно быстро добавить отдельный контур без закупки оборудования.

Если вы проверяете гибридную схему, начните с малого пилота: один общий Mac Runner для доверенных сборок и один отдельный узел для release-задач. В VMSPIN можно рассмотреть удалённую аренду Mac как способ проверить такую архитектуру до расширения парка. Для оценки бюджета используйте страницу тарифов VMSPIN, а для запуска изолированного пилота — страницу заказа удалённого Mac. Итоговое решение принимайте после теста маршрутизации и очистки, а не только по стоимости аренды.

Не стоит переносить на удалённый узел весь enterprise-контур без проверки договорных условий, резервного доступа, политики хранения данных и процесса отзыва секретов. Временная аренда не заменяет архитектуру доверия.

Итоговая граница общего Mac Runner

Если сейчас одна физическая машина одновременно принимает обычные сборки, непроверенные ветки и production-подпись, её нельзя считать изолированной CI-инфраструктурой. Такая схема имеет как минимум три недостатка: Shell executor выполняет задания в одном системном контексте, остаточные файлы и процессы могут перейти следующему проекту, а компрометация одного задания потенциально затрагивает ключи и внутренние зависимости.

Для большинства корпоративных команд рациональнее начать не с массовой закупки, а с двух узлов: общий пул для доверенной компиляции и отдельный Mac Runner для выпуска. Аренда удалённых Mac через VMSPIN в этом сценарии удобнее для пилота и переменной нагрузки: вы можете раздельно проверить маршрутизацию, очистку и подпись, не связывая решение с преждевременным расширением собственного парка. При стабильной тяжёлой нагрузке или необходимости физического доступа собственные Mac по-прежнему могут быть более подходящим вариантом.

Начните с одного контрольного проекта, затем подключите второй для перекрёстной проверки и только после этого открывайте общий пул остальным репозиториям. Если граница доверия не подтверждается журналами, возвращайтесь к выделенному Mac Runner, а не пытайтесь исправить проблему дополнительной меткой.