结论先行:普通项目应先验证托管 macOS Agent;只有当流水线确实需要持久缓存、固定 Xcode 环境、内网依赖或可控签名钥匙串时,才部署由独立低权限账户运行的远程 Mac 自托管 Agent。节点显示 Online 只代表注册成功,完成真实构建、重启恢复和凭据隔离后,才具备生产使用资格。

这篇指南适合 3 类人:
- 使用 Azure Pipelines 执行 iOS 或 macOS 构建、测试与发布的开发者。
- 需要让远程 Mac 长期在线并接入团队 Agent Pool 的 DevOps 工程师。
- 负责证书、描述文件、发布权限和构建节点隔离的移动研发平台维护者。

先判断托管 macOS Agent 是否已经够用

托管 macOS Agent 适合工具链变化不大、任务来源可信度不一、并且不希望自行维护系统的项目。它能减少硬件采购、系统更新、Agent 服务维护和故障恢复工作;对于外部贡献代码,隔离环境通常也比共享自托管节点更容易控制。Microsoft 托管 Agent 的隔离与运行说明

但托管节点并不适合所有 Apple 平台流水线。自托管 Agent 可以保留机器级缓存、安装固定版本的工具链,并接入团队内网;代价是工作区、缓存、钥匙串和网络权限都由团队负责。Microsoft 对 Agent 类型和适用边界的说明,也强调了自托管节点需要自行承担维护与安全责任。Azure Pipelines Agent 类型说明

部署远程 Mac 前,先确认是否至少满足以下条件:

  • ✅ 项目必须固定使用某个 Xcode 环境,不能接受托管镜像更新后工具链变化。
  • ✅ 构建依赖内网 Git、私有制品库、测试接口或受防火墙保护的服务。
  • ✅ 大量依赖下载、编译缓存或模拟器数据需要跨任务保留。
  • ✅ 团队需要独立控制钥匙串、签名证书安装位置和发布权限。
  • ❌ 如果只是偶发构建,或者任务包含来自外部贡献者的未知代码,自托管节点通常不是更安全的选择。

其中,固定工具链和持久缓存是最常见的进入条件;签名钥匙串则是风险最高的进入条件。不能因为本地构建偶尔失败,就直接把生产证书放到一台同时服务多个项目的共享 Mac 上。

建立独立账户与专用 Agent Pool

远程 Mac 不应使用开发者的日常登录账户运行 Agent。建议创建专用系统账户,例如 <CI_USER>,并让 Agent、工作目录和构建缓存都归属于该账户;如果任务不需要图形界面,也不要额外授予桌面控制或系统管理权限。

Azure DevOps 侧应建立专用 Agent Pool,例如 <APPLE_CI_POOL>,再按项目授予使用权限。这样可以把 Apple 构建节点与其他通用节点分开,也方便将生产签名任务限制到更小的范围。Microsoft 的流水线安全文档建议使用最低必要权限,并根据任务敏感程度拆分资源边界。Azure Pipelines 流水线安全建议

建议按以下顺序准备:

  1. 创建专用 Agent Pool,例如 <APPLE_CI_POOL>
  2. 只向需要提交 Apple 构建任务的项目授权。
  3. 在远程 Mac 上创建专用系统账户 <CI_USER>
  4. 准备工作目录,例如 /Users/<CI_USER>/azagent/Users/<CI_USER>/azwork
  5. 从 Agent Pool 控制台获取当前有效的下载、配置和认证指令。
  6. 使用控制台当时生成的组织地址、Pool 名称、Agent 名称和认证选项完成注册。
  7. 将注册结果、Agent 版本和 Capabilities 页面保存为初始验收记录。

注册命令不能把真实组织名、令牌、账户或长期路径写进公开文档。不同组织策略和 Agent 版本可能提供不同认证方式,因此命令必须以控制台当时生成的内容为准,不能复制博客中的临时令牌。当前认证选项和权限要求应以官方 Agent 认证说明为准。自托管 Agent 认证方式

如果团队使用 PAT 完成初始注册,应只授予 Agent Pool 所需范围,并将 PAT 视为初始化凭据,而不是流水线访问代码仓库、制品库或发布服务的通用密钥。注册后还应检查令牌有效期、保管位置和撤销流程。

初步注册只算通过以下检查时,才可以进入下一阶段:

  • [ ] Agent Pool 中出现预期的 Agent 名称。
  • [ ] 状态显示 Online。
  • [ ] Agent 版本已经记录。
  • [ ] Capabilities 中能看到系统、架构和基础软件能力。
  • [ ] 工作目录由专用账户拥有读写权限。
  • [ ] 远程 Mac 能访问 Azure Pipelines,以及项目所需的内网资源。

Online 只证明注册和基础通信成功,不代表 Xcode、Simulator、签名或重启恢复已经通过。

分开验证后台构建与图形会话

纯命令行构建、单元测试和归档任务,通常可以作为后台服务运行;但 Simulator、UI 测试或依赖桌面会话的任务,不能简单等同于 SSH 登录后执行的脚本。

macOS Agent 官方提供 svc.sh,可将 Agent 配置为 launchd LaunchAgent 服务。该方式适合需要长期运行的 Agent,但其环境与交互式 SSH shell 并不完全相同,PATH、语言环境、开发者目录和登录状态都需要单独验证。macOS Agent 服务配置说明

建议完成以下操作:

  1. 在专用账户下完成 Agent 配置,不要先用管理员账户注册,再临时切换运行用户。
  2. 执行 ./svc.sh install,生成对应的服务配置。
  3. 执行 ./svc.sh start,再使用 ./svc.sh status 查看状态。
  4. 检查 ~/Library/LaunchAgents/ 下是否生成对应的 plist 文件。
  5. 断开 SSH 连接后,提交一个最小命令行构建。
  6. 注销远程桌面会话,分别测试无图形任务和 Simulator 任务。
  7. 重启系统后再次检查服务状态,并运行一次真实流水线。

如果任务依赖 UI,不能把“SSH 断开后任务仍在运行”作为唯一证据。还需要验证专用账户是否拥有稳定的图形登录会话,以及锁屏、注销、远程控制方式变化后,Simulator 和 UI 测试是否仍能启动。

对齐 Agent 能力与 Xcode 工具链

Azure Pipelines 会根据 Agent 的 Capabilities 与流水线的 demands 选择节点。即使远程 Mac 已安装 Xcode,如果能力名称没有刷新,或者 YAML 中的 demands 与实际值不一致,任务仍可能排队或找不到合适的 Agent。Azure Pipelines 运行与 Agent 匹配机制

在远程 Mac 上先检查:

xcode-select --print-path
xcodebuild -version
xcrun simctl list devices
which xcodebuild

验收重点不是某一条命令能否返回结果,而是命令行环境与流水线环境是否一致:

  • xcode-select --print-path 指向预期的 Xcode。
  • xcodebuild -version 返回项目要求的版本。
  • Agent Capabilities 页面出现流水线需要的 Xcode 能力。
  • YAML 的 pool.name 指向专用 Agent Pool。
  • demands 使用的能力名称和值与控制台实际显示一致。
  • 新增或切换 Xcode 后,Agent 已重启并重新发现能力。

Apple 的命令行工具文档说明,xcodebuildsimctl 等工具依赖有效的 Xcode 开发者目录。多版本并存时,可以使用 xcode-select --switch 修改默认路径,也可以使用 DEVELOPER_DIR 只对当前命令指定版本。Apple Xcode 命令行工具参考

随后使用最小项目进行无签名构建:

xcodebuild \
  -workspace <WORKSPACE>.xcworkspace \
  -scheme <SHARED_SCHEME> \
  -configuration Debug \
  -sdk iphonesimulator \
  -derivedDataPath <DERIVED_DATA_PATH> \
  build

项目 Scheme 必须设置为 Shared,否则命令行环境可能找不到开发者在 Xcode 界面中看到的 Scheme。此阶段应同时检查测试结果目录、构建日志和产物路径,先证明 Xcode 构建链路正常,再处理证书问题。

用独立链路处理签名与发布

签名环境应作为单独的安全边界验收,而不是在 Agent 显示 Online 后立即导入证书。P12、provisioning profile、证书密码和发布权限不应提交到代码仓库,也不应写入普通变量或会被打印的脚本参数。

Azure Pipelines 的 Secure Files 可用于保存证书和描述文件,并限制具体流水线访问。官方文档还要求使用不低于 2.182.1 的 Agent 版本来调用 Secure Files;实际 Agent 版本仍应以当前控制台与官方文档为准。Secure Files 官方说明

推荐按照“先无签名、后受控签名”的顺序验收:

  1. 使用 Simulator 目标完成无签名构建。
  2. 将 P12 与 provisioning profile 放入 Secure Files。
  3. 只授权目标流水线访问这些文件,不启用不必要的全局访问。
  4. 使用 InstallAppleCertificate@2InstallAppleProvisioningProfile@1 在构建期间安装文件。
  5. 使用锁定变量保存证书密码,不把密码直接写进 YAML。
  6. 执行受控 Archive,检查签名身份、Bundle ID、描述文件和导出结果。
  7. 构建结束后检查临时证书、描述文件和导出目录是否清理。

Microsoft 的移动应用签名文档也建议根据安全需要在构建期间安装证书和描述文件,并在任务结束后删除临时文件。Apple 平台流水线签名流程

多项目共享远程 Mac 时,至少应拆成两类 Agent Pool:

  • 普通构建池:执行无签名编译、单元测试和依赖验证,不保存生产证书。
  • 敏感发布池:只允许受控分支和指定流水线使用,负责 Archive、签名和发布。

如果一台 Mac 同时接收外部贡献者代码、普通测试项目和生产签名任务,即使 Secure Files 配置正确,也很难证明缓存、工作区和钥匙串不会互相影响。

用故障恢复和工作区卫生决定是否上线

远程 Mac 作为 Azure DevOps macOS Agent 的长期节点,价值取决于失败后能否恢复,而不是第一次构建是否成功。自托管节点会复用工作区和缓存,因此必须明确哪些内容可以保留,哪些内容必须在每次任务前后清理。

上线前执行以下检查:

  • [ ] 连续执行无签名测试与受控签名归档。
  • [ ] 构建失败后重新运行,确认失败工作区不会污染第二次任务。
  • [ ] 断开 SSH 后执行命令行构建,排除临时终端依赖。
  • [ ] 系统重启后确认 Agent 自动恢复在线。
  • [ ] 重启后再次检查 Xcode capability 是否仍然正确。
  • [ ] Agent 升级后重新执行最小项目构建。
  • [ ] 检查 DerivedData、依赖缓存、日志和制品目录的增长。
  • [ ] 确认多个项目不会共享同一签名目录或可写凭据路径。
  • [ ] 为磁盘不足、证书过期、服务掉线和工具链切换准备退出方案。

托管 Agent 的官方文档列出单个任务可用存储限制为 10 GB。如果项目依赖、DerivedData 或构建制品容易超过该边界,应先评估缓存拆分与制品上传策略,再决定是否使用持久化远程 Mac。托管 Agent 存储限制说明

决策条件:继续使用托管节点还是部署远程 Mac

  • 若不需要固定 Xcode、内网访问或持久缓存,选择托管 macOS Agent。
  • 若只是偶发 Apple 平台构建,先使用托管节点,不要为单次任务维护远程 Mac。
  • 若需要固定工具链,但代码包含外部贡献,保留托管节点,并将自托管节点限制在可信分支。
  • 若同时需要内网依赖、长期缓存和专用构建账户,选择独立远程 Mac Agent Pool。
  • 若签名证书必须长期留在钥匙串中,只有在项目、账户和 Agent Pool 完成隔离后才考虑预安装。
  • 若重启恢复、失败重试或凭据清理任一项未通过,回退到托管方案,或把远程节点限定为非生产测试节点。

Azure DevOps macOS Agent 常见落地问题

Azure Pipelines 找不到 Xcode 能力时先查哪里

先查看 Agent Pool 的 Capabilities,再检查 xcode-select --print-pathxcodebuild -version 和 Agent 服务实际使用的账户。若刚安装或切换 Xcode,旧进程可能尚未重新发现能力;重启 Agent 后再观察能力列表,并确认 YAML 中的 demands 没有写错名称或值。

远程 Mac 是否适合直接运行 iOS 构建任务

适合,但必须先完成自托管 Agent 注册,并安装项目需要的 Xcode、依赖管理器和命令行组件。建议使用最小项目完成 Simulator 构建、测试结果和产物验收,再逐步加入签名、归档与发布,避免把工具链故障误判成证书故障。

macOS Agent 重启后怎样确认自动恢复

应同时验证 launchd 服务状态、Agent Pool 中的 Online 状态和真实流水线结果。只查看控制台状态不够,还要测试 SSH 断开、用户注销和系统重启,确认任务不依赖临时会话;涉及 UI 测试时,还要单独检查图形登录环境。

iOS 签名证书在流水线中怎样安全使用

将 P12 和 provisioning profile 放入 Secure Files,限制流水线授权范围,用锁定变量保存密码,并在构建结束后删除临时文件。生产签名任务最好使用独立 Agent Pool,避免外部代码和非敏感任务运行在同一台保留生产钥匙串的 Mac 上。

两种方案的适用边界

对比项 托管 macOS Agent 远程 Mac 自托管 Agent 判断标准
工具链控制 依赖托管镜像与任务配置 可固定 Xcode、依赖和系统设置 需要精确复现时考虑远程 Mac
缓存与工作区 环境更干净,持久化较少 可跨任务保留缓存 需要缓存时必须增加清理策略
外部代码安全 更适合不受信任代码 可能接触内网、缓存和凭据 外部 PR 优先使用托管节点
内网访问 需要额外网络配置 可位于团队可控网络边界 强依赖内网时考虑远程 Mac
签名方式 通常在构建时临时安装 可临时安装或受控使用钥匙串 生产签名必须单独分池
运维责任 平台维护负担较低 团队负责系统、Agent、磁盘和恢复 无专人维护时不要自托管
任务连续性 适合短任务和干净环境 适合长期在线与固定节点 先用真实流水线测试
上线阶段 必须看到的证据 未通过时的处理
注册 Agent Online、版本和基础能力可见 检查认证、网络和 Pool 权限
工具链 xcode-selectxcodebuild、Scheme 和测试结果正确 修复 Xcode 路径或重启 Agent
任务路由 demands 能把任务送到目标节点 调整 capability 名称和值
签名 无签名构建与受控 Archive 均成功 暂停生产签名,检查 Secure Files
恢复 断开 SSH、注销和重启后仍能执行任务 检查 launchd、登录会话和服务账户
卫生 失败重试无交叉污染,临时凭据已清理 清理工作区或拆分 Agent Pool
生产上线 连续真实流水线通过,升级后仍可复测 继续使用托管 Agent 或保持试运行

如果当前问题只是缺少一台稳定的 Apple Silicon 构建机,直接购买 Mac mini 并不一定是第一选择。采购硬件还会带来系统维护、远程接入、磁盘管理、闲置时段和故障替换责任;普通 Linux 云主机则无法替代 macOS 与 Xcode 工具链。

对于需要固定 Xcode、长期在线和独立权限账户,但暂时不想承担实机采购与维护工作的团队,可以先查看 远程 Mac 构建节点配置与租用周期,再决定是否建立专用 Agent Pool。若需要核对不同周期的方案,可参考 远程 Mac 方案与套餐说明

完成最小流水线、重启恢复和签名隔离验收后,RUVCLOUD 的远程 Mac 才适合作为 Azure Pipelines 的真实 macOS 构建节点。关键不是把 Agent 注册成 Online,而是证明它在正确的账户、工具链、权限和故障恢复条件下,能够稳定完成每一次 Xcode 构建。