团队已在 GitLab(SaaS 或自托管)上跑流水线,却把 iOS/macOS 构建卡在「托管 macOS Runner 排队 / 本机兼职 / 另起 Jenkins」之间。本文给熟悉 VPS 运维 的平台工程一条「像买节点一样租 Mac」路径:先用七条清单拆 GitLab Runner + 独占远程 Mac 的真实摩擦点,再用一张三方案对照表收敛 Jenkins SSH Agent 与 GitHub Actions 自托管 的取舍,最后给出 六步可交接 Runbook(注册、tags、缓存目录、并发与 .gitlab-ci.yml 片段),并链到站内 Runner、Jenkins、SwiftPM/Pods 磁盘治理 等文章分工阅读。
GitLab 的文档把 gitlab-runner register 写得很顺,但生产里真正消耗时间的,往往是Apple 工具链状态机与共享磁盘上的并发踩踏。下面七条用于把争论从「我们上 Runner 吧」收敛成「可签字的风险表」。
把远程 Mac 当成 Linux Runner:忽略 TCC、钥匙串与偶发 GUI 依赖时,会在签名与首次授权阶段集体爆炸;应与 SSH 与 VNC 清单 的边界一起评审。
用个人账号注册 Runner:睡眠策略、系统更新弹窗与桌面会话纠缠会让「无人值守」变成伪命题;应使用专用 CI 用户并与 可复现构建 的 Keychain 合同对齐。
并发只按 CPU 核数估:Xcode 峰值内存与 NVMe 写放大往往先触顶;未给 DerivedData 分桶时,两个 Job 就能互相拖死——与 SwiftPM/Pods 磁盘治理 篇同一口径。
忽略 Runner token 与注册面:把 config.toml 当普通文本散落备份;轮换日会出现「队列全红但 UI 仍绿」的假安全感。
缓存策略复制粘贴:cache: 键设计不当会让多分支互相污染或永远 miss;应与分支/锁文件维度一起设计。
制品只留在 Runner 本地:未定义上传到 GitLab artifacts 或对象存储时,磁盘与合规都会吃紧;保留策略要与安全评审一起写清。
没有「首跑人工窗口」:第一次接入签名材料或安装描述文件时,仍可能需要一次性 VNC/桌面确认,再切回无头;可参考 Fastlane 与 CI 衔接。
这些假设的共同根因,是把「远程 Mac」理解成纯算力,而不是带 Xcode 指纹与钥匙串边界的节点。平台工程需要像管数据库副本一样,给每台构建机维护镜像指纹、工具链版本、清理水位与 Runner tags 合同。与 企业构建资源池 篇联动:当多项目共用节点时,GitLab 的 tags 要比「全员 mac」更细,否则 resource_group 与限流策略无法表达真实隔离需求。
与 GitHub Actions 对照时,关键差异不在「能不能编」,而在流水线定义与事件源:.gitlab-ci.yml 与 MR 生命周期原生绑定;Actions 与 PR 事件强耦合但跨 Git 宿主迁移成本高。若你的组织已 All-in GitLab,把 macOS 作为 Shell Runner 接入通常比「为了 iOS 再维护一套 Jenkins 方言」更一致。但若流水线大量依赖内网审批与多宿主编排,Jenkins 仍有生态位——下一节用三列表把取舍钉死。
在动手注册 Runner 前,建议先读完 Runner 篇 的缓存与标签章节:其中许多磁盘分桶原则可直接迁移到 GitLab cache 与 CI_PROJECT_DIR 设计,只是触发器从 workflow 换成 pipeline。
没有银弹:你要选的是编排心智模型与凭证边界。评审时把三条 SLA 写清:排队时延、失败可解释性、密钥轮换成本。
| 维度 | GitLab Runner(macOS Shell) | GitHub Actions 自托管 | Jenkins SSH Agent |
|---|---|---|---|
| 流水线定义 | .gitlab-ci.yml 与项目/MR 原生一体,模板与 include 成熟 | 仓库内 YAML,与 PR/Issue 事件强耦合 | Job DSL / Pipeline Groovy,跨仓库编排灵活但风格分裂风险高 |
| Runner 注册模型 | 项目/群组级 token,config.toml 集中描述 executor 与标签 | 组织/仓库级 runner token,配置相对标准化 | 控制器集中持有 SSH 凭据;需严控控制器攻击面 |
| 并发与限流 | resource_group、parallel、runner 并发上限组合 | 矩阵与 concurrency 在 YAML 层表达 | label + throttle 插件组合,灵活但配置复杂 |
| 缓存/制品 | 原生 cache/artifacts,键设计不当易污染 | actions/cache 与 artifacts 生态丰富 | 自建归档与对象存储胶水常见 |
| 典型适用 | GitLab 为中心的研发组织、需要 MR 流水线与 Runner 池统一治理 | GitHub 为中心、PR 驱动交付 | 多制品线、内网制品库、强审批与混合 Git 宿主 |
「像买 VPS 一样租 Mac」在 GitLab 语境里,意味着你买的是可注册的 Runner 画像:固定 SSH、可预期的磁盘档位、以及能把 Xcode 指纹写进 tags 的能力。
若你最终选择 GitLab Runner,请把 tags 当作一等公民:Xcode 次版本、是否允许重 pod install、是否允许 UI 测试,都应显式表达。与 快照与长期节点 篇联动:长期在线节点更依赖渐进式清理;黄金镜像更依赖镜像预热与回滚验收。
若你同时维护多套 CI,建议统一磁盘分桶与 DerivedData 合同,避免 GitLab Job 与 Jenkins/Runner Job 在同一台远程 Mac 上互相踩目录:用不同 Unix 用户或不同根路径隔离,而不是仅靠「错峰运行」。
下列顺序强调「先身份与目录,再注册与 tags,最后才放开并发」:与 可复现构建 的指纹脚本对齐,避免 GitLab 只验证「能 clone」却不验证「能稳定签」。
创建专用用户与工作根:例如 /Users/ci/gitlab-runner,禁止与个人 ~/Desktop 混用;仅接受密钥登录。
安装 gitlab-runner:使用 GitLab 官方 macOS 安装包或 Homebrew;确认二进制在 PATH 中且能以服务账户运行(launchd)。
执行 register:选择 shell executor,填入 GitLab URL 与注册 token;在交互或非交互参数里固定 tag_list(如 ios,shell,m4)。
首跑健康检查 job:打印 xcode-select -p、xcodebuild -version、swift --version 与磁盘快照,并把输出当作 Runner 验收记录。
在 .gitlab-ci.yml 显式传入 DerivedData:与 SwiftPM/Pods 磁盘治理 篇一致,按项目桶化路径,避免默认目录互踩。
定义超时、artifacts 与清理:timeout、失败保留策略、以及低水位磁盘时的停调度策略(可用外部监控 + API 停 Runner)。
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 Key」写成与 GitLab CI/CD Variables 一致的合同(敏感变量务必 masked + protected)。
在 GitLab 大版本或 Runner 小版本升级日,建议先对「样板 iOS job」做金丝雀:同一 commit 在升级前后各跑一次,比较指纹输出与构建耗时分布。与 Runner 篇 的缓存挂载策略对照:GitLab 的 cache key 如果过于宽松,会导致「分支 A 的 Pods 缓存污染分支 B」;如果过于严格,则会永远冷启动——需要平台与业务共同定义保留等级。
若你的远程 Mac 由供应商提供固定 SSH 端口与非 root 用户,请把连接参数写进内部 Runbook 而不是散落在多个变量的描述里:轮换时只改一处。与 Jenkins 篇 联动:SSH 基线(密钥、防火墙、审计)在三套 CI 上应同源,而不是每套各写一套方言。
resource_group 与「重依赖安装」限流:把容量写成可读模型平台工程里最常见的误判,是把「能同时开几个 xcodebuild」当成并发上限。实际上,pod install / SPM resolve 与 编译峰值往往出现在不同阶段,需要在 .gitlab-ci.yml 层用 resource_group 或拆 job 表达「互斥资源」。与 SwiftPM/Pods 治理 篇联动:重解析 job 应该单独限流,避免与高频 green build 抢同槽。
另一个隐蔽点是测试阶段:若流水线包含 Simulator UI 测试,并发模型与纯编译不同,需阅读 XCTest 与 Simulator 的分片策略,并在 GitLab 侧用独立 tags 或独立 Runner 池隔离。
注意:不要在磁盘低于安全水位时继续硬塞队列:应触发停调度与清理,否则 Xcode 与 git 可能出现半写入状态,修复成本远高于短暂排队。
若你在多地区有 Runner,建议把「区域」写进 Runner name 与 tags,并在 artifacts 路径上显式标注区域,避免跨区域大文件传输被误当成构建失败。与 买还是租 TCO 篇联动:跨区域时延与出口带宽应进入成本模型,而不是事后由业务感知。
下列条目用于内部对齐;具体阈值以你们仓库体量与并行度为准。
gitlab-runner --version、xcodebuild -version、Ruby/Bundler(若用 CocoaPods)与磁盘型号;变更后触发金丝雀 pipeline。纯办公室单机构建常受睡眠、网络抖动与工具链漂移影响;纯 Linux 又无法承载官方 iOS 工具链。把 GitLab 留在团队熟悉的 Web 与 MR 工作流里,而把 macOS 执行扇区放到独占、长期在线、SSH 可达的远程节点,才能把「单一流水线真相」从口号变成合同。相比自建零散机器或在不稳定的虚拟化环境里硬跑 Xcode,NodeMini 的 Mac Mini 云端租赁在固定 SSH、清晰磁盘档位与可复制的 Runner 画像上更利于平台治理;需要对比规格与价格时,可先阅读 租赁价格说明,再结合 帮助中心 完成接入。
落地时建议把本 Runbook 与内部「工具链变更等级」绑定:Xcode 小版本、次要与大版本升级对应不同审批、金丝雀范围与 cache 失效策略。
多数 iOS/macOS 原生构建团队从 Shell executor 起步:与 Xcode、钥匙串与 Simulator 的摩擦最小;Docker executor 适合已容器化工具链或希望强隔离的场景,但在 Apple 工具链上维护成本更高。需要节点规格与价格口径可先对照 租赁价格说明。
不要只看 CPU 核数:以单 job 压测得到峰值内存与磁盘写放大,再线性增加并发观察 P95;为 DerivedData 与依赖缓存分桶,并对 pod install 类重 job 单独限流。更多接入问题也可查看 帮助中心。
GitLab 侧是「项目/群组级 Runner + token 注册模型」与 .gitlab-ci.yml 原生绑定;Jenkins 更偏企业内网编排与插件生态;GitHub Actions 与 PR 事件耦合最强。选型应写清事件源、密钥边界与队列 SLA。可继续对照站内 Jenkins + 远程 Mac 与 GitHub Actions Runner 两篇。