170 KB。 Jenkins 官方把 Agent 描述为一个独立的 Java 客户端进程,而不是“节点在线”页面上的一个绿色图标。(Jenkins 节点与 Agent 管理文档)

所以,出现 Jenkins macOS Agent 掉线 时,本周建议不要先重装节点:先按“调度 → 连接 → Java 进程 → macOS 会话 → Xcode 工具链”的顺序取证。偶发进程故障可以原地恢复;如果掉线反复出现、环境不断漂移或签名状态经常丢失,就应隔离节点后重建,或者切换到独立的远程 Mac。

这篇文章适合 3 类人:

  • 负责 Jenkins 与远程 Mac 日常运维,需要缩短 Agent 离线恢复时间的 DevOps 工程师。
  • 维护 Xcode 自动构建、测试和签名任务,需要判断“节点在线”是否等于“流水线可用”的 Apple 平台开发者。
  • 准备增加长期在线 macOS CI 节点,希望建立上线验收与故障取证标准的平台负责人。
01

先划清故障边界

Jenkins 页面显示的“离线”,不一定代表远程 Mac 断网。你需要先把问题分成 3 类:

  1. 节点真的没有连接:Agent 进程退出、网络链路中断、Secret 失效或启动方式失败。
  2. 节点在线但任务无法调度:标签不匹配、执行器被占满、节点被手动标记离线,或者任务限制了可运行节点。
  3. 节点在线且任务启动,但构建环境失效:工作目录不可写、磁盘空间不足、Xcode 路径错误、签名资源不属于 Agent 用户。

Jenkins 的节点、Agent 和执行器并不是同一个概念。节点代表承载任务的机器,Agent 是连接控制器并执行任务的进程,执行器则决定同一节点能同时运行多少任务。官方 Agent 文档也建议根据机器资源和任务类型设置执行器,而不是盲目提高并发。

排查开始前,先保存以下证据:

  • 控制器日志中首次出现异常的时间点。
  • Jenkins 节点页面显示的离线原因、最近一次连接时间和最近一次成功任务。
  • 远程 Mac 上 Agent 进程的 PID、退出码、标准错误和启动命令。
  • 最近一次成功构建与第一次失败构建使用的 Xcode、Java、标签和工作目录。
  • 是否发生过控制器重启、远程 Mac 重启、网络切换、凭据变更或系统更新。

这样做的价值在于:你能判断是“主动离线”“连接失败”,还是“任务根本没有被调度”,避免把排队问题误判成 Agent 掉线。

02

控制器调度与连接链路

节点页面与队列状态

先打开节点详情页,再查看构建队列中的等待原因。若队列提示没有匹配标签,问题不在网络;若节点被设置为暂时离线,恢复连接也不会自动让任务执行。

Jenkins 官方建议通过标签描述节点能力,例如 Arm64、代码签名或特定操作系统。执行器数量也会直接影响任务是否排队;官方文档指出,单个构建节点使用 1 个执行器通常是更安全的起点,多执行器配置必须结合 CPU、内存、磁盘 I/O 和网络活动观察。(Jenkins Agent 使用文档)

SSH、TCP Agent Listener 与 WebSocket

“SSH 能连接”与“Jenkins Agent 能连接”是两条不同的链路。

macOS 的 Remote Login 是系统级 SSH 服务。Apple 官方给出的基本连接形式是 ssh username@hostname,它只能验证远程 Mac 的登录服务、用户名和网络可达性。(Apple Remote Login 官方说明)

Jenkins 的 SSH Launch Method 还要经过 Jenkins 插件、主机密钥校验、Java 启动和 Agent 传输。入站 Agent 则由远程 Mac 主动连接控制器。若使用 TCP Agent Listener,需要检查控制器是否启用对应端口;若使用 WebSocket,则通常不需要额外开放 Agent TCP 端口。(Jenkins 服务与端口文档)

检查对象 可观察现象 重点取证 修复方向
系统 SSH 终端可登录,但 Jenkins 仍离线 ssh -vvv 输出、Remote Login 设置 修复系统用户、主机密钥或防火墙
SSH Launch Method Jenkins 日志显示启动失败 节点日志、Java 路径、凭据与主机名 校验 SSH 凭据、端口与启动命令
TCP Agent Listener 入站 Agent 无法握手 控制器监听设置、防火墙、代理规则 核对固定或随机端口策略
WebSocket HTTP 页面可访问但 Agent 不在线 控制器日志、反向代理和 WebSocket 转发 检查代理升级请求与控制器地址
调度层 Agent 在线但任务排队 标签、执行器、Usage 和队列原因 修正标签表达式或执行器配置

不要把 Jenkins 控制器的 SSH Server 与远程 Mac 的系统 SSH 混为一谈。Jenkins SSH Server 主要用于 Jenkins CLI,并不等于远程 Mac 的操作系统 Shell。

03

Java Agent 进程与凭据

进程是否真的存在

在远程 Mac 上,先确认 Agent 进程,而不是只执行一次手动启动命令:

ps aux | grep -i '[a]gent.jar'
pgrep -af java

如果进程不存在,再查看启动日志和退出码。常见边界包括:

  • agent.jar 与控制器当前 Remoting 要求不匹配。
  • Java 路径在交互式 Shell 中存在,但自动启动上下文找不到。
  • 启动参数中的控制器地址已经变化。
  • 节点 Secret 被重新生成,旧进程仍使用旧值。
  • 进程被系统终止,或者工作目录、日志目录不可写。
  • 同一台远程 Mac 上存在多个 Agent 进程,互相争抢工作目录。

Jenkins 当前 Java 支持范围必须以写作时的官方页面为准。官方 Java Support Policy 明确说明,Java 要求适用于控制器、所有类型的 Agent、CLI 客户端及其他组件;不要因为本机可以运行某个 Java 版本,就默认它适合当前 Jenkins。(Jenkins Java 支持策略)

在排查时记录:

java -version
which java
echo "$JAVA_HOME"
pwd
id

这组信息能发现一个高频问题:你通过 SSH 手动启动时使用的是用户 Shell 环境,而自动启动机制使用的是另一套 PATH、用户和工作目录。

Secret 与 SSH 凭据

如果使用入站连接,控制器页面显示的 Agent 命令中会包含节点名称、控制器地址和 Secret。本文不提供真实 Secret、主机名、用户名或密钥;这些内容只能从你当前节点页面复制,并且不应提交到代码仓库或公开日志。

如果使用 SSH Launch Method,要检查 Jenkins 中保存的 SSH 凭据、用户名和主机密钥验证策略。凭据应使用专用账户和受限作用域,避免把私钥硬编码进流水线脚本。

04

macOS 会话与常驻机制

“手动 SSH 启动成功”不等于“重启后能自动上线”。因为这两个动作可能使用不同的用户、环境变量、文件权限和启动上下文。

重点检查 4 个对象:

  1. 所属用户:Agent 用户是否与 Xcode、钥匙串和签名资源属于同一用户上下文。
  2. 工作目录:Remote Root Directory 是否存在,磁盘是否可写,路径是否在重启后仍挂载。
  3. 自动启动机制:根据你的实际部署方式核对 launchd 配置、服务状态和系统日志,不要直接套用未经验证的通用 plist。
  4. 会话依赖:Agent 是否错误依赖 VNC 窗口、交互式终端或登录后才生成的环境变量。

你可以按下面 5 步复测:

  1. 记录当前节点在线状态、Agent 用户和 Remote Root Directory。
  2. 断开 VNC 或 SSH,不关闭远程 Mac,观察 Agent 是否仍保持连接。
  3. 重启远程 Mac,等待系统完成启动后检查 Agent 进程。
  4. 重启 Jenkins 控制器,确认节点是否自动重连。
  5. 在无人值守状态下触发一次最小构建,记录恢复时长与是否需要人工启动。

如果只有“登录桌面后”节点才上线,说明常驻机制仍依赖用户会话。此时不要继续增加重试脚本,应先修复启动上下文。

05

在线状态与 Xcode 工具链

工作目录、标签和执行器

Agent 在线后,先运行一个只输出环境的最小作业:

echo "node=$NODE_NAME"
id
pwd
df -h
which java
xcode-select --print-path
xcodebuild -version

其中任何一项失败,都不能把节点判定为生产可用。

Apple 文档说明,xcode-select --print-path 用于查看当前生效的开发者目录;如果没有有效目录,命令会直接报错。多版本 Xcode 共存时,可以使用 sudo xcode-select --switch <path> 切换默认工具链,也可以用 DEVELOPER_DIR 只对当前命令临时指定版本。(Apple Xcode 命令行工具设置文档)

还要注意一个容易误判的边界:Apple 提供的 Command Line Tools 包含部分编译工具,但 xcodebuildxctrace 只随完整 Xcode 提供。(Apple Command Line Tools 安装文档)

三类最小验收作业

不要用一条 echo ok 就宣布 Xcode CI 节点恢复。至少准备 3 类验收:

  • 普通编译:验证仓库拉取、依赖解析、Xcode 路径和派生数据目录可用。
  • 测试任务:使用 xcodebuild test,检查测试结果包和日志是否能生成。Apple 文档指出,命令行测试会产生 .xcresults 结果包,其中包含测试结果、覆盖率和日志等信息。(Apple Xcode 测试结果文档)
  • 签名任务:验证证书、Provisioning Profile、钥匙串访问权限和导出配置是否属于运行 Agent 的同一用户。

Apple 官方支持使用 xcodebuild archivexcodebuild -exportArchive 完成归档与分发签名流程。(Apple Xcode 签名归档文档)

06

故障恢复决策与验收清单

先完成以下清单,再决定是否重建:

  • [ ] 已保存控制器日志、节点日志和远程 Mac 进程证据。
  • [ ] 已确认问题属于调度、连接、Java、会话或工具链中的具体一层。
  • [ ] 已验证 Jenkins 节点标签、Usage、执行器和 Remote Root Directory。
  • [ ] 已确认 Agent 运行用户、Java 路径和工作目录稳定。
  • [ ] 已检查 Secret、SSH 凭据和主机密钥验证策略。
  • [ ] 已完成远程 Mac 重启后的自动上线测试。
  • [ ] 已完成 Jenkins 控制器重启后的自动重连测试。
  • [ ] 已完成短暂断网后的恢复测试。
  • [ ] 已运行普通编译、测试和签名 3 类最小验收作业。
  • [ ] 已记录恢复是否需要人工介入,以及环境是否再次漂移。

你可以按以下条件处理:

  • 原地修复:仅一次进程退出,重启 Agent 后稳定,环境和签名资源没有变化。
  • 隔离后重建:反复掉线、工作目录权限持续异常、Java 或 Xcode 环境不可重复。
  • 增加独立远程 Mac 节点:发布任务与日常构建互相抢占,或者单节点重启会阻塞整个 Xcode CI 流程。
  • 暂缓接入生产任务:节点只能在 VNC 登录后运行,或签名任务需要人工解锁钥匙串。

如果你需要先准备一台可独立重启、长期在线并拥有完整权限的 Mac,可以查看 VpsMesh 的 Mac 租赁方案;若你的团队倾向于按月使用,也可以对照 Mac mini M4 租赁与订购说明,再决定是修复旧节点还是直接迁移构建环境。

07

常见问题

为什么 SSH 正常但 Jenkins macOS Agent 仍然离线?

SSH 证明的是 macOS Remote Login 可用,Jenkins Agent 还需要 Java 进程、控制器地址、Secret、传输通道和工作目录同时正常。请分别查看系统 SSH 输出、Jenkins 节点日志和远程 Mac 上的 Agent 进程,不要用 SSH 成功替代 Jenkins 握手验证。

重启后 Agent 不自动上线,问题通常在哪里?

优先检查自动启动机制使用的用户、环境变量、Java 路径和工作目录。交互式 SSH 会话加载的配置不一定会被 launchd 或其他常驻方式加载;如果必须登录桌面后手动启动,说明节点还没有达到无人值守 CI 的验收标准。

Agent 在线但任务排队,是否需要重启节点?

通常不需要。先看队列等待原因,再检查标签表达式、节点 Usage、执行器数量和任务是否要求特定能力。只有在确认调度配置正确、节点确实没有可用执行器,且进程或工具链也出现异常时,才考虑重启。

远程 Mac 应该使用 SSH Launch Method 还是入站连接?

取决于网络方向。控制器可以稳定访问远程 Mac 时,SSH 方式便于集中启动和查看启动错误;远程 Mac 无法被控制器主动访问时,可考虑入站连接或 WebSocket。无论采用哪种方式,都必须记录控制器日志、握手结果和控制器重启后的自动恢复表现。

什么结果才算节点真正恢复?

节点页面在线只是第一关。你还需要完成环境信息作业、普通 Xcode 编译、测试任务和签名任务,并覆盖远程 Mac 重启、控制器重启与短暂网络中断。若恢复过程仍依赖人工启动、手动解锁或重新选择 Xcode,就不能算生产恢复。

如果你当前的方案依赖个人电脑、临时虚拟机或单台无法独立重启的 Mac,真实缺点通常是:网络与电源不可控、重启需要人工介入、Xcode 和签名环境容易漂移,节点故障还会直接阻塞发布。对于需要长期在线、完整权限和可重复验收的任务,远程 Mac 往往比继续堆叠临时脚本更容易维护;你可以先按本文清单验证现有节点,再判断是否租赁一台新的 Mac 作为隔离的 Jenkins 构建节点。