首页 / 博客 / Buildkite Agent
ENGINEERING_BLOG · 2026.08.31

Buildkite Agent 远程 Mac 怎么部署?2026 Xcode 27 CI 指南

远程 Mac 上的 Agent 已经显示在线,但第一条 Xcode 构建仍然失败,或者重启后节点不再接任务。

最快的处理方式是:可以部署,但必须使用满足 Xcode 27 官方要求的 Apple Silicon Mac,并把 Beta 节点与稳定生产环境隔离;先验收命令行构建,再接入 Simulator、签名和重启恢复。

最后更新于 2026 年 8 月 31 日,兼容性与工具链状态核实自 Apple 的 Xcode 27 Beta Release NotesXcode 系统与 SDK 要求页面 以及 Buildkite 官方 Agent 文档。

这篇文章适合三类人:

  • 需要把 iOS 或 macOS 构建从本地电脑迁入 Buildkite 的移动开发者;
  • 需要维护可远程访问、长期在线构建节点的 DevOps 工程师;
  • 正在隔离验证 Xcode 27 Beta,同时必须保留稳定生产流水线的研发平台负责人。

SECTION 01 先确定节点边界:不要把 Beta Mac 直接放进生产队列

Xcode 27 目前仍应按测试版工具链管理。Apple 官方说明显示,Xcode 27 Beta 只能安装并运行在 Apple Silicon Mac 上,并要求 macOS Tahoe 26.4 或更高版本;系统要求页面当前列出的 Xcode 27 beta 6 也对应这一兼容边界。(developer.apple.com)

因此,Intel Mac、系统版本不满足要求的 Mac,或者与稳定版 Xcode 共用同一工作区的节点,都不应直接进入生产队列。不满足芯片或系统硬门槛时,停止安装,不要试图通过 Rosetta、软链接或修改环境变量绕过检查。

Buildkite 的职责是提供控制平面、流水线编排、队列和任务分发;远程 Mac 的职责是运行 Agent、执行 Shell 命令、调用 Xcode 工具链并保存构建产物。自托管 Agent 通过出站连接注册并领取任务,通常不需要为了接收任务而给远程 Mac 随意开放入站端口;真正需要开放的远程管理入口,应由你的 SSH、VNC 或网页控制台方案单独控制。可先核对 Buildkite 自托管 Agent 的官方工作方式

按工作负载拆分验收范围

工作负载 必要组件 首轮验收目标 不应直接推导出的结论
纯命令行构建 Apple Silicon、Xcode 27、依赖工具、代码访问 xcodebuild 成功并生成结果文件 不能证明 Simulator 可用
Simulator 测试 命令行环境、Simulator Runtime、用户会话 模拟器启动、测试执行、结果收集、清理完成 不能证明真机测试通过
签名与发布 证书、私钥、描述文件、受控钥匙串 非交互签名成功,产物可验证 不能证明普通 PR 可以访问发布资产

本篇的判断顺序很简单:先把节点当作命令行构建机,再把它升级为图形测试机,最后才允许它接触签名和发布凭据。这样做不是增加流程,而是把失败范围限制在可回滚的阶段。

远程 Mac 能否直接承载 Buildkite Agent?
可以。Buildkite 官方支持在 macOS 11 Big Sur 或更高版本安装 Agent,并说明 Agent 会以启动 launchd 服务的用户身份运行。也就是说,远程 Mac 本身不是问题,真正的边界在于系统版本、Agent 账户、Xcode 工具链以及任务所需的用户会话是否一致。(buildkite.com)

SECTION 02 第一个里程碑:用专用账户完成注册和队列路由

不要用管理员账户或日常登录账户运行 Buildkite self-hosted agent。建议创建一个只服务于 CI 的 macOS 用户,例如:

CI_USER="<CI专用账户>"
CI_HOME="/Users/<CI专用账户>"

该账户需要拥有构建目录、依赖缓存和必要开发工具的使用权限,但不应默认拥有生产签名资产、个人 SSH 密钥或其他用户目录的读取权限。Buildkite 文档明确指出,macOS Agent 的实际运行用户就是启动 launchd 服务的用户,因此账户选择会直接影响 HOME、钥匙串、SSH 配置和图形会话。(buildkite.com)

第一小时执行顺序

  1. 在远程 Mac 上确认芯片和系统:

bash uname -m sw_vers

预期分别看到 Apple Silicon 对应架构,以及满足 Xcode 27 要求的系统版本。命令输出应保存到部署记录中。

  1. 使用专用账户安装 Agent。若通过 Homebrew 安装,安装命令可写成:

bash brew install buildkite/buildkite/buildkite-agent

不要把真实 Token 直接写进公开脚本、Shell 历史或流水线日志。Buildkite 官方建议为不同环境创建专用 Agent Token,并通过安全存储或环境变量注入。

  1. 在目标 Cluster 中创建专用 Queue,例如:

text <XCODE27_QUEUE>

一个 Beta 节点只监听一个明确队列,不要让它落入默认队列。Buildkite 的 Queue 文档说明,Agent 可以通过 queue 标签路由到指定自托管队列,而流水线步骤也可以用 agents 属性选择目标队列。(buildkite.com)

  1. 使用占位符启动注册:

```bash export BUILDKITE_AGENT_TOKEN="" export BUILDKITE_AGENT_TAGS="queue=,os=macos,arch=arm64,xcode=27"

buildkite-agent start ```

  1. 提交最小诊断任务,只输出系统、架构和开发工具路径:

```bash set -euo pipefail

uname -a sw_vers uname -m xcode-select -p xcodebuild -version ```

  1. 在 Buildkite 页面检查该任务实际落到目标 Queue,而不是仅检查某个 Agent 是否显示在线。

怎样让任务只落到 Xcode 27 节点?
不要只在脚本里执行一次 xcode-select,而应同时使用专用 Queue 或 Agent 标签进行路由,再在任务开始时显式选择 Xcode 27 路径。这样可以避免任务被分配到稳定版节点,也能让 Xcode 版本成为可审计的节点属性。

例如,Pipeline 可以采用占位符:

steps:
  - label: "Xcode 27 command-line build"
    agents:
      queue: "<XCODE27_QUEUE>"
    command:
      - 'sudo xcode-select --switch "/Applications/<XCODE27_APP>.app/Contents/Developer"'
      - 'xcodebuild -version'
      - './ci/build.sh'

实际部署时不应允许普通开发者通过任意参数覆盖 Queue、证书路径或签名账户。Queue 的作用是隔离工作负载,不能代替权限控制;Token、代码访问和秘密数据仍需分别管理。

配置对象 建议做法 失败时先查什么
Cluster 为 Beta 工具链建立独立 Cluster,或至少使用独立 Queue Token 是否属于正确 Cluster
Queue 使用 <XCODE27_QUEUE> 等明确名称 Pipeline 的 agents.queue 是否拼写一致
Agent Token 每个环境使用专用 Token,并支持轮换 Token 是否失效、是否绑定错误 Cluster
Agent 用户 使用专用低权限账户 HOME、SSH、钥匙串是否属于该账户
工作目录 为 Xcode 27 节点设置独立构建路径 DerivedData 是否与其他任务冲突

SECTION 03 第二个里程碑:先跑通真实项目的命令行闭环

Agent 在线不等于节点可用。真正的通过证据应该来自一个真实项目:代码能被取到,依赖能解析,Xcode 能编译,单元测试能执行,结果文件能保存,失败时退出码能被 Buildkite 正确识别。

代码访问采用最小权限

如果仓库是私有的,优先使用可轮换的机器身份或 Buildkite Secrets,而不是把个人账户的长期私钥复制到远程 Mac。Buildkite 官方将 SSH 私钥存入 Secrets、并在具体步骤中通过 checkout.ssh_secret 引用列为推荐方式;该设置是步骤级配置,不能假设流水线级配置会自动继承。(buildkite.com)

示例只保留占位符:

steps:
  - label: "Checkout and build"
    agents:
      queue: "<XCODE27_QUEUE>"
    checkout:
      ssh_secret: "<CI_REPOSITORY_KEY>"
    command: "./ci/build-xcode27.sh"

在专用账户下先验证:

git ls-remote "<仓库地址占位符>"

如果代码访问失败,不要立即修改整个远程 Mac 的 SSH 配置。先确认任务到底使用了哪个账户、哪个 HOME 和哪一个秘密名称,再决定是修正步骤级 Secret、机器级密钥,还是仓库权限。

显式固定 Xcode 路径与输出位置

项目脚本不应依赖交互式 Shell 中的隐式变量,例如你在 SSH 会话里设置过 DEVELOPER_DIR,但 launchd 启动的 Agent 并不会自动继承它。

建议在构建脚本开头明确指定:

#!/bin/zsh
set -euo pipefail

export DEVELOPER_DIR="/Applications/<XCODE27_APP>.app/Contents/Developer"
WORKSPACE="<WORKSPACE_PATH>"
SCHEME="<SCHEME_NAME>"
DESTINATION="generic/platform=iOS"
RESULT_PATH="$PWD/artifacts/<RESULT_BUNDLE>.xcresult"

xcodebuild \
  -workspace "$WORKSPACE" \
  -scheme "$SCHEME" \
  -destination "$DESTINATION" \
  -resultBundlePath "$RESULT_PATH" \
  clean build test

首次执行时,建议拆成四次可观察步骤,而不是把所有动作压成一条长命令:

  1. 依赖解析;
  2. 编译;
  3. 单元测试;
  4. 结果文件与产物归档。

每一步都记录退出码、日志路径和产物路径。xcresult 是否生成、归档是否能被后续任务读取,比终端里出现“Build Succeeded”更适合作为验收证据。

你还应检查以下隐性成本:

  • 缓存污染:不同 Xcode 版本共用 DerivedData,可能产生难以复现的编译结果;
  • 环境漂移:Homebrew、Ruby、Node.js 或脚本依赖被自动升级后,原本成功的流水线可能改变行为;
  • 磁盘增长:Simulator Runtime、归档文件、依赖缓存和失败工作区会持续占用空间;
  • 权限不一致:手动 SSH 执行成功,不代表 launchd 下的 Agent 用户也能访问同样目录;
  • 并发争用:多个任务共用 DerivedData、Simulator 或钥匙串时,失败可能表现为随机超时,而不是明确的配置错误。

SECTION 04 第三个里程碑:只有需要时才接入 Simulator

纯命令行构建节点不应无条件安装并启用图形会话。Simulator、UI 测试和部分开发工具需要用户会话、图形服务、已安装的 Runtime 以及可见的模拟设备;这些条件会让无人值守重启、远程桌面和故障恢复变得更复杂。

Xcode 27 的官方发布说明列出了 Simulator 设备显示、并行输出延迟等已知问题,因此 Beta 节点必须把“测试任务成功”与“生产稳定性”分开记录,不能因为一次 UI 测试通过就把整条流水线切换过去。

最小图形任务的验收顺序

  1. 确认当前 Agent 运行账户与预期的图形会话账户一致:

bash id echo "$HOME" launchctl print "user/$(id -u)"

  1. 检查 Xcode 可见的模拟器设备:

bash xcrun simctl list devices available

  1. 在流水线中指定一个明确的 Simulator 目标,不要使用“任意可用设备”。

  2. 执行一个最小单元测试或 UI 测试,记录模拟器启动、测试执行和结果收集三个阶段。

  3. 测试结束后清理或关闭模拟器,并确认下一次任务不会继承异常状态。

  4. 连续执行两次相同任务,比较结果文件、退出码和日志结构。

远程自托管 Mac 是否适合执行 iOS Simulator 测试?
可以,但是否可用取决于远程 Mac 的 Xcode Runtime、用户会话、模拟器状态和项目测试类型。Buildkite 只负责把任务交给 Agent;Simulator 能否启动、是否能被当前用户看见,以及 UI 测试是否在无人值守会话中稳定运行,都必须在你的真实节点上验证。

不要把 Simulator 的通过结果扩大解释为真机测试或发布验收。真机连接、设备配对、推送权限、通知行为和 App Store 发布流程,仍然需要单独的测试边界。

SECTION 05 第四个里程碑:把签名发布从普通构建中隔离

签名环境是远程 Mac 部署中最容易被低估的风险点。普通 Pull Request 需要编译和测试,但通常不应接触生产证书、私钥或发布凭据;如果同一个账户、同一个 Queue 和同一个钥匙串同时服务普通构建与发布任务,仓库脚本或恶意依赖就可能获得超出任务需要的权限。

Apple 关于 CI 环境代码签名的说明特别强调:SSH 会话不会自动解锁登录钥匙串,代码签名任务应显式处理钥匙串解锁、私钥访问和非交互授权,并且不建议使用 root 进行签名。(developer.apple.com)

建议采用以下隔离方式:

  • 普通构建使用 <BUILD_QUEUE>,发布任务使用 <RELEASE_QUEUE>
  • 普通构建账户与发布账户分离,至少让发布步骤拥有更窄的访问范围;
  • 证书名称、Team ID、密码、描述文件和仓库信息全部使用占位符;
  • 发布任务只在受保护分支、受控审批或专用步骤中运行;
  • 钥匙串只在任务需要时解锁,任务结束后锁定或销毁临时工作区;
  • 先在测试证书和测试应用上完成非交互验证,再接入生产签名资产。

示例:

#!/bin/zsh
set -euo pipefail

KEYCHAIN_PATH="$HOME/Library/Keychains/<CI_KEYCHAIN>.keychain-db"
KEYCHAIN_PASSWORD="<KEYCHAIN_PASSWORD>"
IDENTITY="<SIGNING_IDENTITY>"
TEAM_ID="<TEAM_ID>"

security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"

xcodebuild \
  -workspace "<WORKSPACE_PATH>" \
  -scheme "<RELEASE_SCHEME>" \
  -destination "generic/platform=iOS" \
  DEVELOPMENT_TEAM="$TEAM_ID" \
  CODE_SIGN_IDENTITY="$IDENTITY" \
  archive

这里的命令只是结构示例,不应把密码写死在仓库。若你的安全策略无法证明普通任务不会读取发布钥匙串,先不要开启签名队列。

并发边界必须用真实任务确认

在单台 Mac 上增加第二个 Agent 进程,并不会自动带来可用的并行能力。你需要验证:

  • 两个任务是否写入同一 DerivedData;
  • 两个任务是否同时启动或重置同一个 Simulator;
  • 两个任务是否争用同一钥匙串;
  • 归档文件和临时目录是否互相覆盖;
  • CPU、内存、磁盘和网络是否出现可观察的排队或失败。

没有隔离证据时,优先让 Xcode 构建、Simulator 和签名任务串行执行。关于自托管节点的并发与扩展,可进一步参考自托管 Mac 构建节点的并发判断思路;不要只根据 Agent 数量决定扩容。

SECTION 06 第五个里程碑:用 launchd 验收重启后的自动上线

重启远程 Mac 后,怎样让 Agent 自动恢复接单?
关键不是手动执行一次 buildkite-agent start,而是让 Agent 由 macOS 的 launchd 按正确用户和会话启动,并在重启后验证它确实重新注册到目标 Queue。Buildkite 官方 macOS 安装文档提供了通过 Homebrew 配置登录启动的方式;Apple 的系统服务文档则说明,用户级 Launch Agent 运行在当前登录用户上下文中,系统级 Launch Daemon 与用户图形会话并不等价。(developer.apple.com)

验收时不要只看进程是否存在,按下面顺序执行一次受控重启:

  1. 重启前记录当前 Agent 名称、Queue、工作目录和 Xcode 路径。
  2. 确认没有正在写入的签名任务或未归档的构建任务。
  3. 执行重启,并从 SSH、VNC 或网页控制台验证远程访问恢复。
  4. 确认 CI 专用用户会话状态符合 Simulator 或签名任务需要。
  5. 检查 launchd 是否加载了 Agent 服务。
  6. 在 Buildkite 页面确认 Agent 重新注册到正确 Queue。
  7. 提交一条真实命令行构建任务。
  8. 如果该节点承担图形任务,再执行一次最小 Simulator 测试。
  9. 最后才执行签名或发布类验收。

可以使用以下检查命令:

launchctl list | grep -i "<AGENT_LABEL>"
pgrep -af buildkite-agent
xcode-select -p
xcodebuild -version
df -h

如果远程访问恢复,但 Agent 没有上线,优先检查用户会话、Token、Queue 和日志路径;如果 Agent 在线但 Simulator 失败,则检查图形会话和 Runtime;如果构建成功但签名失败,则回到钥匙串、私钥授权和账户隔离,不要反复重装 Agent。

SECTION 07 上线首周:把节点当成需要维护的硬件资产

远程 Mac 不是一次性安装的软件,而是一台持续消耗磁盘、缓存和维护时间的构建设备。上线第一周至少观察以下项目:

  • Agent 日志是否持续增长,是否有轮转策略;
  • 构建工作区是否在任务后清理;
  • DerivedData、归档文件和 Simulator 数据是否有保留上限;
  • Xcode 27 Beta 是否固定版本,谁负责升级与回滚;
  • Agent Token 是否有轮换计划;
  • 失败任务能否保留足够日志而不泄露 Token、证书或私钥;
  • 生产 Queue 是否仍然只接收稳定工具链;
  • Beta 节点是否有明确的回滚入口。

Buildkite 文档说明,自托管 Agent 的基础设施、扩展和更新由你负责;Token 也需要通过撤销或设置过期时间进行生命周期管理。

上线前可勾选验收清单

  • [ ] 远程 Mac 已确认是 Apple Silicon,系统版本满足 Xcode 27 官方要求。
  • [ ] Xcode 27 Beta 与稳定生产版工具链使用不同节点或不同 Queue。
  • [ ] Agent 使用专用低权限 macOS 账户运行。
  • [ ] Agent Token 未出现在仓库、命令历史或构建日志中。
  • [ ] Agent 已注册到明确的 Cluster 与 <XCODE27_QUEUE>
  • [ ] 最小任务能输出系统、架构、Xcode 路径和版本。
  • [ ] 真实项目已完成依赖解析、编译、单元测试和结果文件生成。
  • [ ] 项目脚本没有依赖交互式 Shell 的隐式环境变量。
  • [ ] Simulator 任务已单独验证启动、执行、结果收集和清理。
  • [ ] 普通构建任务无法读取生产签名私钥。
  • [ ] 签名任务已验证钥匙串解锁、私钥授权和非交互执行。
  • [ ] 单节点并发是否安全已有真实任务证据;没有证据时保持串行。
  • [ ] 已执行一次受控重启,并验证远程访问、用户会话、launchd、Queue 和真实构建。
  • [ ] 已记录 Xcode、Agent、依赖和系统版本,保留稳定回滚节点。

如果这份清单中有任何一项只能回答“理论上应该可以”,节点就不应直接进入生产发布队列。

SECTION 08 远程 Mac 与自建 Mac mini 方案,应该怎么选

如果你已有长期稳定、具备物理设备连接需求的 Apple Silicon Mac,自建节点通常更适合持续重负载和固定网络环境;你需要自行承担硬件采购、磁盘更换、系统升级、远程访问、断电恢复和签名资产维护。

如果当前方案是 Windows 或 Linux 主机再叠加虚拟化、远程桌面或临时转发,它的真实缺点通常是:不能完整替代 Xcode 所需的 macOS 工具链,图形会话和 Simulator 更难稳定恢复,Apple Silicon 与 Xcode 27 的兼容边界也更难按官方要求固定;如果直接购买 Mac mini,则会增加一次性硬件成本,并且临时 Beta 验证结束后可能出现设备闲置。

因此,当你的目标是短期验证 Xcode 27、隔离一条 macOS CI 流水线,或暂时缺少可长期在线的 Apple Silicon Mac 时,VPSNIX 的远程 Mac 租赁更适合作为先验收、后决定的方案。你可以先按本文的 Queue、签名隔离、Simulator 和重启标准验证节点,再根据任务周期查看 VPSNIX 的远程 Mac 方案;如果你还需要处理远程访问恢复,可结合远程 Mac 重启后的连接恢复方法设计自己的运维入口。

当团队已经确认需要全年稳定重载、专用物理接口或固定硬件资产时,自购 Mac 并自行维护仍然合理;但如果需求是临时算力、Beta 工具链验证或隔离测试环境,先租用一台真实 Apple Silicon Mac,往往比把不稳定的虚拟化方案直接推进生产更容易控制风险。