TeamCity 2026.1 已经要求服务器和 Agent 使用 Java 21 才能启动,但 Agent 显示“已连接”并不代表它能稳定执行生产 Xcode 流水线。(TeamCity 官方快速设置指南)
症状: Agent 在线,却注册未授权、任务长期排队、Xcode 找不到,或重启后不再接单。
最快解法: 先锁定 Java 21 与非 root 运行账号,再验证 serverUrl、授权令牌、Xcode 路由、工作区和签名隔离,最后用真实归档与签名任务完成生产准入。
这篇文章适合 3 类人:正在为 TeamCity 增加 iOS 或 macOS 构建能力的平台工程负责人;准备把本地 Mac mini 改造成共享构建节点的企业 IT 负责人;以及需要采购或租用多台远程 Mac,并要求签名隔离、容量证据和故障恢复能力的技术决策者。
注意: 本文讨论的是 TeamCity On-Premises 2026.1。TeamCity Cloud 2026.2 的 Agent 安装和
launchd文档只能用来理解自托管 Agent 的启动机制,不能直接推导 On-Premises 的功能、授权或版本行为。(TeamCity Cloud 官方集成文档)
运行时基线:先解决 Agent 为什么启动不了
TeamCity 2026.1 的首个判断边界是 Java,而不是 Xcode。官方文档明确说明,从 2026.1 起,TeamCity 服务器和 Agent 不能再使用低于 Java 21 的版本启动;这只约束 Agent 自身运行时,不限制项目使用其它 JDK 编译、测试或部署。
因此,你需要把两个概念分开:
- Agent 运行 JDK: 用于启动 TeamCity Agent 进程,生产基线应明确为 Java 21。
- 项目编译 JDK: 由 Maven、Gradle、脚本或项目工具链决定,可以通过
JAVA_HOME、Agent 参数或构建参数选择。 - Xcode 工具链: 由 macOS、Xcode.app、
xcode-select和构建步骤共同决定,不等同于 Java 环境。
在 Apple Silicon 主机上,建议把以下资产记录放进节点登记表:
- macOS 版本与补丁状态;
teamcity.agent.jvm.os.arch返回值;- Xcode 完整安装路径;
- 当前命令行开发者目录;
- Agent 运行账号;
- Agent Home、Work、Temp 和 System 目录;
JAVA_HOME实际指向的位置;- 是否安装多个 JDK、多个 Xcode 或额外模拟器运行时。
最小核验命令可以保留为:
java -version
echo "$JAVA_HOME"
uname -m
xcode-select -p
xcodebuild -version
如果 java -version 不是 Java 21,或者 JAVA_HOME 在交互式终端中正确、在 launchd 环境中为空,先不要注册生产 Agent。macOS 的服务启动环境通常不会完整继承你的终端配置,很多“手工启动正常、重启后失效”的问题都从这里开始。
TeamCity 官方系统要求还要求 Agent 进程能够读写 Agent Home、Work、Temp 和 System 目录,并能向 serverUrl 发起出站 HTTP 连接。Agent 本身大约还需要 500 MB 内存,真正的 CPU、磁盘和内存压力则主要来自构建过程。(TeamCity 官方系统要求)
非 root 账号与管理员动作边界
生产 Agent 不应以 root 身份运行。更合理的做法是创建一个专用的构建账号,例如 teamcity-agent,并让它拥有:
- Agent 安装目录;
workDir、tempDir和systemDir;- 构建缓存中确实需要访问的目录;
- 临时 Keychain;
- 需要由流水线生成的归档和测试结果目录。
管理员只在初始化阶段执行系统级动作,例如安装 Xcode、安装 Java、创建账号、配置目录权限、加载 launchd,以及必要的网络或证书策略。之后的 Agent 进程、自动升级和构建脚本都交给专用账号完成。
这样做不是为了形式上的“最小权限”,而是为了避免构建脚本继承过大的文件访问能力。特别是当同一台 Mac 还接收外部贡献者的 PR 或第三方依赖构建时,root Agent 会把工作区污染、凭证读取和系统文件修改的影响面放大。
连接授权:在线状态为什么不能当作上线证据
Agent 配置核心在 buildAgent.properties。至少要核对以下字段:
serverUrl=https://ci.example.invalid/
name=ios-release-arm64-01
authorizationToken=REDACTED
workDir=../work
tempDir=../temp
systemDir=../system
serverUrl 必须是 Agent 能够访问的 TeamCity 地址,并包含协议。官方文档建议使用 HTTPS。配置文件还需要能被 Agent 进程写入,因为首次连接后,服务器端生成的授权令牌需要保存回配置文件。(TeamCity 官方 Agent 配置文档)
你需要同时检查 3 份证据:
- 主机证据:
buildAgent.properties、文件所有权、DNS、代理和防火墙记录。 - Agent 证据:
teamcity-agent.log中的连接、授权、重连和异常信息。 - 服务器证据: Agent 是否已授权、是否位于正确的 Agent Pool、是否报告了预期参数。
只看 TeamCity 控制台的绿色在线标识是不够的。连接成功但没有授权、进入错误 Pool、代理缓存了异常响应,或者配置文件无法写入,都可能造成“看起来在线、实际不能接单”的假在线状态。
如果企业网络通过反向代理连接 TeamCity,要特别确认:
- Agent 到
serverUrl的出站访问不被限制; - 代理不会缓存 TeamCity 服务端响应;
- HTTPS 证书链在 macOS 上可验证;
- 代理认证不会依赖某个用户的交互式登录;
- 代码仓库、制品存储和 TeamCity 服务器的访问路径分别可达。
Agent Pool 与固定名称
一个 Agent 只能属于一个 Agent Pool,但一个项目可以使用多个 Pool。新授权 Agent 默认会进入 Default Pool,因此不要把生产签名节点直接留在默认池中。(TeamCity 官方 Agent Pool 文档)
建议至少分出以下逻辑:
ios-sandbox:非可信 PR、依赖验证和失败频率较高的任务;ios-test:普通单元测试、模拟器测试和开发分支构建;ios-release:归档、签名和发布任务;ios-fallback:主节点故障时接管的备用节点。
固定 Agent 名称便于审计和回退,但不要只用名称做全部路由。名称解决“是哪台机器”,自定义 Agent 参数才解决“它具备什么能力”。
例如,你可以在 Agent 配置文件中加入:
mac.role=release
mac.arch=arm64
xcode.16.path=/Applications/Xcode.app
signing.scope=ios-release
然后在 TeamCity 的 Agent Requirements 中按参数筛选。官方文档说明,Agent Requirements 只能使用 Agent 在构建开始前能够报告的参数;多个条件默认按 AND 逻辑组合。(TeamCity 官方 Agent Requirements 文档)
Xcode 路由:工具链识别与任务分配
完整 Xcode 与 Command Line Tools 的区别
只安装 Command Line Tools 的 Mac,不能自动视为可执行完整 iOS 构建的节点。Apple 文档指出,xcodebuild、simctl 和 devicectl 等工具属于 Xcode 工具链,并且需要安装 Xcode.app、将其设置为活动开发者目录后才能正常调用。(Apple 官方 Xcode 命令行工具文档)
因此,验收时不要只执行:
xcode-select -p
还要确认路径确实指向完整的 Xcode.app,并检查:
xcodebuild -version
xcrun --find xcodebuild
xcrun simctl list devices
如果 Agent 报告在线,但 xcodebuild -version 失败,或者路径指向 /Library/Developer/CommandLineTools,它不应进入 iOS 发布 Pool。
单版本与多版本 Xcode
只有一个 Xcode 版本时,TeamCity 的 Xcode 构建步骤可以使用该节点默认工具链。安装或升级 Xcode 后需要重启 Agent,确保新的工具和参数被重新采集。
多个 Xcode 版本共存时,不要只依赖全局 xcode-select。更稳妥的路由方式是:
- 为每个 Xcode 版本登记一个明确的 Agent 参数;
- 在构建步骤中使用 Path to Xcode 指定路径;
- 用 Agent Requirements 限制任务可运行的节点;
- 在流水线开头打印
xcodebuild -version和xcode-select -p; - 将任务要求、节点参数和实际工具链输出进行三方比对。
这 3 个层面分别解决不同问题:
- Path to Xcode: 解决本次构建使用哪个 Xcode;
xcode-select: 解决主机默认命令行工具链;- Agent Requirements: 解决任务被分配到哪些节点。
TeamCity 的 Xcode Runner 会根据 Agent 上报告的工具链参数形成相关要求,也支持在不同 Xcode 配置之间进行选择。(TeamCity 官方 Xcode 构建文档)
| 检查层面 | 应记录的证据 | 失败时的典型后果 | 处理动作 |
|---|---|---|---|
| 任务要求 | Xcode、SDK、平台和架构要求 | 任务进入错误节点或长期排队 | 补充 Agent Requirements |
| 节点参数 | Xcode 路径、架构、角色参数 | 控制台显示兼容,实际工具链不匹配 | 修正 buildAgent.properties |
| 实际工具链 | xcodebuild -version、xcode-select -p、模拟器列表 |
编译、测试或归档失败 | 修复 Xcode 路径并重启 Agent |
| 服务器分组 | Agent Pool、授权状态、Agent 名称 | 发布任务落入测试节点 | 调整 Pool 绑定和路由条件 |
TeamCity 支持按 teamcity.agent.jvm.os.arch 区分 Apple Silicon 等架构。企业环境中可以把 Apple Silicon 节点和 Intel 节点分别放进不同 Pool,避免依赖、模拟器或预编译二进制因架构差异产生隐蔽失败。
工作区与签名隔离:共享 Mac 的真正风险
同一 Agent 上的构建并不会天然形成安全沙箱。工作目录、缓存、派生数据、临时文件和 Keychain 都可能跨任务留下痕迹。对于正式签名节点,真正需要审计的不是“有没有清理脚本”,而是清理后是否留下可验证的证据。
重点检查以下位置:
system.teamcity.build.checkoutDir对应的 Checkout Directory;system.teamcity.build.workingDir对应的工作目录;- 构建临时目录;
- DerivedData;
- CocoaPods、Swift Package Manager 和其它依赖缓存;
- 临时 Keychain;
- Provisioning Profile、证书和导出的签名文件;
- 构建失败后仍然存在的中间文件。
TeamCity 提供了 Checkout Directory、Working Directory 和 Temp Directory 等构建参数;其中构建临时目录会在构建后清理,但企业仍应通过目录差异和构建日志验证实际结果,而不能把默认行为当作完整隔离。(TeamCity 官方预定义构建参数文档)
哪些任务不能共用发布节点
以下任务不建议与正式签名任务共用同一台生产 Mac:
- 来源不受完全控制的外部 PR;
- 会执行第三方脚本或动态依赖安装的任务;
- 需要修改系统级配置的测试;
- 会导出开发者证书、Provisioning Profile 或签名密钥的任务;
- 允许开发者任意修改构建脚本的实验性流水线。
更安全的分法是:普通测试使用测试 Pool,非可信 PR 使用隔离节点,归档与签名使用专用 Mac。你还需要限制构建账号对签名材料的可见范围,并在构建结束后删除临时 Keychain、导出包和相关日志中的敏感字段。
如果团队正在设计完整的签名基线,可以把 企业远程 Mac 签名凭证与工作区隔离方案 作为后续设计入口;如果重点是验收证据,则应配合“Mac 构建节点上线验收与故障恢复清单”逐项留档。
重启恢复:开机启动不等于无人值守接单
TeamCity 官方 macOS 流程使用 launchd 启动 Agent,并要求相关文件由构建用户拥有。官方文档还描述了一个重要边界:macOS 的 LaunchAgent 流程通常依赖构建用户登录,必要时还要配置自动登录;重启后再检查 Agent 是否重新连接。(TeamCity 官方 Agent 启动文档)
这和企业要求的“无人值守生产节点”不是同一件事。你需要在准入前明确:
- 节点是否允许自动登录;
- FileVault 或系统安全策略是否会阻止无人值守启动;
launchd配置属于哪个用户会话;- Agent 目录是否由构建用户拥有;
- 自动升级后 plist 是否仍然有效;
- Xcode 首次调用是否弹出许可或图形界面提示;
- Keychain 是否能在无人工操作时解锁;
- 失败后谁接收告警,备用节点如何接管。
最小检查可以使用:
launchctl list | grep BuildAgent
tail -f ~/buildAgent/logs/teamcity-agent.log
不要把“重启后进程存在”当作成功。完整恢复测试至少应包含:
- 记录重启前的 Agent 名称、时间戳和当前任务;
- 远程重启 Mac;
- 确认构建用户和
launchd服务恢复; - 检查 Agent 重新连接并保持授权;
- 提交一次真实 Xcode 编译;
- 再执行模拟器测试或归档;
- 验证临时 Keychain 是否可用;
- 人为制造一次失败,确认告警和替代节点接管。
如果其中任何一步需要人工点击 Xcode 许可、手动解锁 Keychain 或重新加载 Agent,就不要把该节点直接放进发布 Pool。可以先放到试运行 Pool,直到恢复链路闭环。
生产准入:用真实流水线决定节点去留
空项目只能证明 Java、Xcode 和 TeamCity 基本能启动,不能证明节点有生产价值。企业 IT 应使用一条代表性流水线完成验收,至少覆盖:
- 普通 PR 编译;
- 模拟器测试;
- 依赖缓存命中与清理;
- Archive;
- 受控签名;
- 制品上传;
- 构建失败后的目录清理;
- 重启后的自动恢复;
- 主节点不可用时的备用节点接管。
建议把结果记录成以下准入表:
| 验收域 | 通过证据 | 暂缓原因 | 回退动作 |
|---|---|---|---|
| Java 运行时 | java -version、JAVA_HOME、Agent 日志 |
版本不符或服务环境为空 | 修正 JDK 与启动环境 |
| 注册授权 | Agent 日志、服务器授权状态、固定名称 | Token 无法写入或进入错误 Pool | 重建配置并重新授权 |
| Xcode 路由 | 任务要求、Agent 参数、实际 xcodebuild 输出一致 |
版本或架构不匹配 | 调整 Path、参数和 Requirements |
| 工作区清理 | 构建前后目录差异、缓存边界记录 | 凭证或中间文件残留 | 改为专用节点或强制清理 |
| 重启恢复 | 时间戳、launchd 状态、重新接单日志 |
需要人工登录或解锁 | 暂不进入发布 Pool |
| 真实流水线 | PR、测试、归档、签名均有记录 | 仅空项目成功 | 延长试点并补充容量证据 |
| 故障接管 | 备用 Agent 成功接单 | 无可用替代节点 | 增加冗余或降低发布并发 |
容量规划也要依据真实数据,而不是只看 CPU 核心数。你应记录排队时间、构建成功率、节点空闲窗口、归档任务占用、模拟器任务并发以及备用节点接管时间。需要扩容时,再按队列和冗余证据计算单节点、主备节点或弹性远程 Mac 池的组合。
决策条件列表
- 若 Agent 能使用 Java 21 启动,配置文件可写,且服务器端已授权,则进入连接与路由验收;否则先修复运行时或文件权限。
- 若任务要求、Agent 参数和
xcodebuild -version三者一致,则允许进入测试 Pool;否则禁止依赖“Connected”状态放行。 - 若正式签名任务与非可信 PR 使用不同 Agent Pool 或专用节点,则继续做凭证清理测试;否则回退到隔离节点方案。
- 若重启后无需人工登录、点击许可或解锁凭证即可重新接单,则进入真实流水线验收;否则不得进入发布 Pool。
- 若代表性归档、签名、清理和备用接管均有日志证据,则可以按队列和冗余规划容量;否则只保留为试点节点。
- 若你没有固定 Mac 容量,但需要尽快验证 TeamCity 路由和 Xcode 工具链,则先建立一台隔离的远程 Mac 试点节点;否则再评估自购 Mac mini 或长期固定节点。
当前方案与远程 Mac:什么时候值得先租一台试点节点
如果你现在把 Mac mini 放在办公室里作为共享打包机,常见缺点是硬件位置固定、远程故障恢复依赖现场人员,而且一台机器往往同时承担测试、签名和发布,隔离边界不够清晰。自购多台 Mac 也会带来采购周期、设备折旧、备机闲置和扩容前置投入。
把 Mac 直接放进通用云主机同样不能解决 Apple Silicon、完整 Xcode 和签名环境的问题;虚拟化或临时环境还可能增加工具链兼容性和恢复验证成本。对正在上线 TeamCity 2026.1 的团队,更稳妥的路径通常不是立刻承诺长期采购,而是先用一台独立远程 Mac 验证真实项目、Xcode 路由、签名隔离和重启恢复。
如果试点通过,再根据队列、发布窗口和故障接管记录选择按周、按月或更长期的容量。KVMNODE 的远程 Mac 更适合这种需要临时算力、独立测试环境或快速建立第二构建节点的场景;但如果你的团队长期持续高负载、必须接入特定物理设备,仍应评估自购 Mac 或专用机房部署。你可以先查看 KVMNODE 的远程 Mac 方案,再用生产准入表决定是否扩大节点数量。