GitLab 官方文档明确说明,macOS 上的 GitLab Runner 使用用户态 LaunchAgent,不是系统级 LaunchDaemon;它跟随已登录用户启动和退出,并依赖用户会话访问钥匙串与图形界面。GitLab macOS 安装文档

这直接决定了本周的部署建议:先准备专用普通账户,再安装 Shell executor,随后验证用户会话、Xcode 工具链、签名隔离和整机重启恢复。 Runner 页面显示在线,只能证明它能向 GitLab 报到,不能证明它已经具备生产构建能力。

如果你正在把 iOS 项目从手工打包迁移到 GitLab CI,应重点关注第一条可复现流水线。
如果你负责跨平台 CI 基础设施,应重点关注常驻方式、权限边界和恢复机制。
如果团队没有长期在线的真实 Mac,这篇文章也能帮助你判断远程 Mac 节点的配置与交付条件。

01

先判断:这个任务是否真的需要远程 Mac

macOS GitLab Runner 部署不适合所有构建任务。普通的后端编译、容器构建和多数脚本任务,可以继续放在 Linux 节点上。只有任务明确依赖以下能力时,Mac 节点才有必要:

  • 需要 Xcode、Apple SDK 或 xcodebuild
  • 需要 iOS 模拟器、macOS 图形会话或用户钥匙串。
  • 需要证书、描述文件和 Apple 平台签名。
  • 需要在 Apple Silicon 环境中验证架构、依赖或运行行为。

实际维护中,最容易被低估的是下面三个限制。

第一,Shell executor 会直接在 Runner 主机上执行项目脚本。GitLab 官方明确提示,Shell executor 对不可信代码存在较高风险,任务可能读取同一用户权限范围内的文件或执行任意命令。Shell executor 安全说明

第二,macOS 的服务模式和 Linux 不同。你不能简单把 Runner 改成 root 守护进程,然后期待它同时访问钥匙串、模拟器和图形会话。GitLab 当前文档把用户态 LaunchAgent 作为 Mac 上支持的服务模式。

第三,Xcode 构建成功不等于发布成功。无签名测试、归档、导出和上传使用的凭据不同。若一开始就把发布证书放进所有分支可触达的 Runner,故障排查和密钥泄露的成本都会上升。

02

第一步:划清 Runner 范围和可信代码边界

先决定 Runner 属于项目、群组,还是多个项目共享。

项目级 Runner 适合单一应用,权限和依赖最容易控制。群组级 Runner 适合多个受控仓库共用同一套 Xcode 工具链。共享节点则应非常谨慎,尤其不能把持有发布证书的 Mac Runner 暴露给不熟悉的仓库。

建议在注册前写下四项条件:

  1. 哪些项目可以调用这个 Runner。
  2. 哪些分支可以执行签名和发布任务。
  3. 哪些任务只允许无签名构建。
  4. Runner 账户可以读取哪些目录和凭据。

标签不要只写 macos。更好的做法是区分任务能力,例如:

build_ios:
  tags:
    - macos
    - xcode
    - ios-build

发布任务则使用单独标签,例如 ios-release,并配合受保护分支。这样做的目的不是让配置看起来复杂,而是避免测试任务和发布任务无意中落到同一执行路径。

03

第二步:用专用账户完成安装和注册

创建一个只承载 CI 作业的普通 macOS 用户。不要直接使用个人开发账户,也不要把 Runner 运行在管理员账户下。这个账户需要能够访问 Xcode、项目依赖、构建目录和专用钥匙串,但不应拥有不必要的系统管理权限。

GitLab 官方安装页提供 Apple Silicon 与 Intel 架构的 Runner 二进制下载方式。安装时必须根据实际处理器选择对应文件,不能把另一种架构的安装命令直接复制过来。GitLab Runner macOS 安装步骤

示例中的路径和令牌只使用占位符:

sudo curl --output /usr/local/bin/gitlab-runner \
  "<按官方页面选择的 macOS Runner 下载地址>"

sudo chmod +x /usr/local/bin/gitlab-runner

gitlab-runner register \
  --url "<GitLab 实例地址>" \
  --token "<RUNNER_AUTH_TOKEN>" \
  --executor "shell" \
  --description "macos-ios-build" \
  --tag-list "macos,xcode,ios-build"

令牌应从 GitLab 控制台当前生成的 Runner 注册流程获取。不要把真实令牌写进仓库、工单、截图或流水线日志。注册范围确定后,再把 Runner 设置为项目或群组专用,而不是为了方便直接扩大可见范围。

Shell executor 是 iOS 和 macOS 本机构建常用的选择,因为它能直接调用主机上的 Xcode、模拟器和钥匙串。但它的隔离能力有限,只适合可信代码、可信仓库和你能控制的主机环境。

04

第三步:处理用户会话常驻,别把 SSH 当成桌面登录

这是 macOS GitLab Runner 部署最容易失败的阶段。

GitLab 文档说明,Mac Runner 的 LaunchAgent 属于当前登录用户。用户登录时启动,用户退出时停止;它还需要用户会话来访问钥匙串和 iOS Simulator。

因此,下面这种现象并不矛盾:

  • 你通过 SSH 执行安装命令,注册成功。
  • 控制台短时间显示 Runner 在线。
  • SSH 断开或机器重启后,Runner 变成离线。
  • 重新登录图形桌面后,Runner 又恢复。

安装和启动应在 CI 专用账户的 macOS 图形终端中完成。官方命令形式如下:

cd ~
gitlab-runner install
gitlab-runner start
gitlab-runner status

安装过程会创建用户目录下的 LaunchAgent 配置,并交给 launchctl 管理。GitLab 还特别提醒,LaunchAgent 需要图形登录会话才能正常加载。GitLab Runner 服务配置说明

重启前,按这个顺序验证:

  • 正常退出 CI 账户,再重新登录。
  • 断开 SSH,确认 Runner 是否仍在线。
  • 临时中断网络,再观察恢复后的连接状态。
  • 执行整机重启,等待网络和用户会话恢复。
  • 查看 Runner 状态、服务日志和最近一次任务时间。

自动登录可以改善重启后的可用性,但会降低物理设备被接触时的保护能力。是否采用,应结合数据中心访问控制、磁盘加密、远程恢复方式和项目凭据等级决定。不要把自动登录当成无条件的安全建议。

05

第四步:先跑无签名构建,再接入 Xcode 归档

GitLab CI 调用 Xcode 时,第一条流水线不要直接执行发布。先证明 Runner 能正确拉取仓库、解析依赖、调用目标 Xcode,并在无签名条件下完成构建与测试。

Apple 文档指出,Xcode 的构建设置会影响编译、链接、调试信息生成和产品打包;命令行传入的设置还拥有较高优先级。Xcode 构建设置说明

可采用这样的最小结构:

stages:
  - verify
  - test
  - archive

variables:
  LANG: "en_US.UTF-8"

verify_xcode:
  stage: verify
  tags:
    - macos
    - xcode
  script:
    - whoami
    - xcode-select -p
    - xcodebuild -version
    - git --version

test_ios:
  stage: test
  tags:
    - macos
    - xcode
  script:
    - bundle exec pod install
    - xcodebuild test \
        -workspace "<WORKSPACE>.xcworkspace" \
        -scheme "<SCHEME>" \
        -destination "platform=iOS Simulator,name=<SIMULATOR_NAME>"

archive_ios:
  stage: archive
  tags:
    - macos
    - xcode
    - ios-release
  script:
    - xcodebuild archive \
        -workspace "<WORKSPACE>.xcworkspace" \
        -scheme "<SCHEME>" \
        -archivePath "build/<APP>.xcarchive"
  artifacts:
    when: always
    paths:
      - build/

项目使用 Swift Package Manager、CocoaPods 或其他依赖管理方式时,依赖解析也要作为验收的一部分。不要只在个人 Mac 上预热缓存,然后把缓存命中误认为 Runner 配置正确。

首次流水线至少保留:

  • xcode-select -pxcodebuild -version 输出。
  • 依赖解析日志。
  • 测试退出码。
  • xcresult 或等效测试结果。
  • .xcarchive 和必要的符号文件。
  • 失败任务结束后的工作目录状态。

Apple 的构建设置参考也明确列出了 CODE_SIGN_IDENTITYCODE_SIGN_STYLEDEVELOPMENT_TEAM 等签名相关设置。它们应在项目配置、受保护变量和专用钥匙串之间形成清晰边界,而不是散落在脚本中。Apple Xcode 构建设置参考

06

第五步:把签名任务从普通测试任务中隔离

无签名测试可以覆盖更广的分支。发布归档则应只允许可信分支、受保护变量和专用 Runner。

签名资产通常包括:

  • 开发或分发证书。
  • 私钥。
  • Provisioning Profile。
  • App Store Connect 相关凭据。
  • 专用钥匙串及其解锁权限。

Apple 说明,证书可以通过开发者账户或 Xcode 创建与撤销;开发证书和分发证书的用途、权限范围并不相同。Apple 证书总览

在 CI 中,推荐按以下方式处理:

✅ 为 CI 账户创建专用钥匙串。
✅ 让证书和描述文件只在需要发布的任务中导入。
✅ 使用 GitLab 的受保护变量保存敏感内容。
✅ 限制发布任务只能由受保护分支触发。
✅ 任务结束后清理临时证书、描述文件和导出目录。
❌ 不把 .p12、私钥或明文密码提交到仓库。
❌ 不让分叉仓库或外部贡献者触达发布 Runner。
❌ 不在日志中打印变量内容或完整签名命令。

Apple 的证书文档还说明,证书文件安装后会出现在 Mac 的钥匙串中。因此,“文件已经复制到磁盘”不能证明 Xcode 能够签名;你必须用实际归档任务验证 Runner 账户是否能访问正确钥匙串。Apple 证书安装与分发说明

07

上线前的三张决策表

下面的表格用于决定 Runner 是否适合生产,而不是用于比较控制台上的“在线”状态。

决策维度 继续使用当前 Mac Runner 暂缓上线或更换方案
构建能力 真实项目能完成构建与测试 只能运行空项目或示例命令
Xcode 环境 xcode-select、依赖和 Scheme 均可复现 依赖个人账户或人工点击
用户会话 重启后能恢复登录和 Runner 服务 重启后必须人工登录才能上线
签名边界 发布任务使用专用账户、钥匙串和受保护变量 测试与发布共用个人凭据
安全范围 Runner 只承接可信项目 外部代码可以修改脚本并触达主机
失败恢复 能清理目录、停止残留进程并重新执行 失败后必须人工删除文件或重启

再看执行器选择。对 iOS 构建而言,Shell executor 通常最直接,但它并不等于强隔离。

执行方式 适合场景 主要优点 主要风险
Shell executor 可信 iOS / macOS 项目 直接访问 Xcode、模拟器和钥匙串 作业与主机权限边界较弱
容器类执行器 可容器化的普通构建 环境更容易标准化 不适合直接替代完整 macOS 图形与签名环境
共享 Runner 无敏感凭据的公共任务 配置成本较低 节点归属、工具链和数据边界不稳定
专用 Mac Runner 受控项目与发布任务 工具链、权限和凭据可单独管理 需要承担持续维护和恢复责任

最后用任务阶段检查交付条件:

阶段 必须留下的证据 未通过时的处理
注册 Runner 标签、范围和认证配置 删除错误注册,重新限定项目或群组
常驻 退出 SSH、重新登录、整机重启后的状态 检查 LaunchAgent 和图形登录会话
构建 真实仓库的编译、测试日志和退出码 先修复工具链,不接入签名
归档 .xcarchive、测试结果和构建产物 检查 Scheme、签名设置和路径
安全 凭据不进仓库、不出日志,外部代码不可触达 拆分测试 Runner 与发布 Runner
运维 失败清理、磁盘回收、证书轮换和故障记录 暂缓扩大任务范围
08

上线首周观察真实任务,而不是跑分

上线后的第一周,应使用真实项目观察四类问题。

一是排队。若所有任务都使用同一个标签,测试、归档和发布可能互相等待。二是工作目录增长。失败任务、DerivedData、归档和依赖缓存都可能持续占用磁盘。三是并发冲突。多个任务共享模拟器、钥匙串或固定输出目录时,失败原因往往不是代码本身。四是重启恢复。节点能否在维护窗口后自动回到可用状态,比空项目跑通更重要。

维护责任也要提前划分:

  • 项目团队负责 .gitlab-ci.yml、Scheme、依赖和产物规则。
  • DevOps 负责 Runner 版本复核、标签、日志、磁盘和恢复流程。
  • 发布负责人负责证书、描述文件和 App Store Connect 凭据轮换。
  • 节点提供方负责主机可用性、远程访问、系统维护窗口和交付边界。

如果你需要远程 Mac 作为长期构建节点,可以先查看 远程 Mac CI 构建节点配置选择,再根据项目是否需要固定环境、长期在线和独立账户,比较 Mac mini M4 租赁方案 与本地购置方案。涉及地区和链路时,再参考 Mac mini M4 租赁价格

09

远程 Mac 与本地 Mac mini:如何选择 CI 节点

如果你已经有一台可长期在线的 Mac,且团队能够管理账户、证书、磁盘和系统更新,本地设备可以继续承担 CI。它的缺点是采购成本一次性发生,硬件故障、网络中断和远程恢复也由你自己负责。

远程 Mac 的价值不只是“少买一台电脑”。它更适合短期迁移、临时扩展构建容量、验证 Apple Silicon 工具链,或为没有 Mac 实机的团队提供可持续运行节点。它仍然需要你维护 GitLab Runner、Xcode、签名资产和流水线规则,但不需要你先承担硬件闲置、机房网络和物理故障处理。

如果当前方案是 Linux 云主机或虚拟化 macOS,常见问题是无法直接提供完整的 Xcode 工具链、模拟器、钥匙串和真实 Apple Silicon 行为;如果当前方案是个人 Mac,则常见问题是睡眠、用户退出、磁盘被开发任务占满,以及发布凭据和个人账户混在一起。对需要真实 macOS 构建的团队来说,租赁一台可长期在线、允许独立账户和完整工具链配置的 Mac,通常比先购买一台可能长期闲置的硬件更容易完成验证。

当你的项目负载长期稳定、需要物理接口,或必须完全掌控硬件生命周期时,自购 Mac 仍然合理。若需求集中在迁移期、发布周期或临时 CI 扩容,VpsMesh 的远程 Mac 租赁则更适合先按实际任务验证,再决定是否扩大长期投入。