Mac 已经恢复 SSH,但 GitLab 页面仍显示 Runner 离线。

最快解法:先确认故障位于 Mac 主机、图形会话、用户级 LaunchAgent、Runner 注册状态还是网络层,不要直接重装;涉及代码签名或 Simulator 时,恢复正确的用户登录会话,而不是擅自改成系统级 LaunchDaemon。

这篇文章适合维护单台远程 Mac CI 节点、重启后需要手动恢复 Runner 的独立开发者,也适合负责多项目构建、代码签名和模拟器任务的 DevOps、发布工程师。研发平台团队还可以把文末清单改造成远程 Mac 节点的重启验收标准。

先判断:到底是哪一层离线

“GitLab Runner Mac 重启后离线”不一定代表 Runner 服务没有启动。远程排障时,至少要区分以下 4 种状态:

  • 主机不可达:SSH、VNC 或网页控制台都无法连接。先处理 Mac 启动、网络、电源或远程访问通道。
  • 主机已恢复,但图形用户未登录:SSH 可以登录,运行 Runner 的用户却没有进入桌面。普通命令行任务可能可以手动执行,但 Keychain、代码签名和 iOS Simulator 通常不能据此判定可用。
  • Runner 进程未运行:用户已经进入图形会话,但服务状态异常,或者 LaunchAgent 没有加载。
  • Runner 在线但任务排队:Runner 能连接 GitLab,却没有匹配任务标签、项目范围或执行条件。

GitLab 官方确认,macOS 上的 Runner 采用用户级 LaunchAgent 运行。它属于当前登录用户,不是系统级 LaunchDaemon。因此,重启后即使 Mac 已经联网,用户会话没有恢复,Runner 也可能不会自动上线。查看 GitLab 官方的 macOS 安装与服务模式说明

先记录失败现场

假设目标账户为 <runner-user>,主机名为 <mac-host>,项目为 <project>

  1. 你通过 SSH 登录 <mac-host>,系统命令和磁盘检查都正常。
  2. GitLab 项目页面仍显示 Runner 离线。
  3. 执行 gitlab-runner start,返回 Could not find domain for
  4. 你使用 sudo 重新安装服务,进程看似启动,但签名任务随后失败。

这个现场包含两个常见误判。SSH 可达不等于图形用户会话已经建立;使用 sudo 也可能把配置切换到 root 的路径,导致原有用户配置、Keychain 和开发工具环境不再一致。

先保存以下信息:

who
id
hostname
date
gitlab-runner status

如果 SSH 登录的是管理员账户,而 Runner 属于 <runner-user>,后续排查必须切换到正确用户。不要把管理员账户的 config.toml、环境变量和日志路径当成 Runner 的实际配置。查看 GitLab Runner 命令与配置路径

第一步:确认远程 Mac 和用户会话

先测试 SSH:

ssh <runner-user>@<mac-host>

如果 SSH 失败,不要立即判断 GitLab Runner 有问题。Mac 可能停在磁盘解锁界面、用户登录界面,或者网络服务尚未恢复。

如果 SSH 正常,继续打开 VNC 或网页控制台。你要确认的是 <runner-user> 已经进入桌面,而不是只看到了登录窗口。

macOS 的 LaunchAgent 运行在已登录用户的上下文中。它可以访问该用户的环境、权限和部分用户级服务;LaunchDaemon 则面向系统级后台任务,不等价于一个拥有桌面会话的构建账户。查看 Apple 对 Service Management 的说明

这一步会直接影响以下任务:

  • 读取登录用户 Keychain 的代码签名任务;
  • 使用 iOS Simulator 的测试任务;
  • 需要图形会话或开发者工具授权的 Xcode 流程;
  • 依赖用户级 Homebrew、Ruby、Node.js 或环境变量的脚本。

如果只是运行不需要签名和 Simulator 的命令行构建,缺少图形会话可能暂时不暴露。但这不能证明生产节点已经恢复。

第二步:在图形终端检查 LaunchAgent

如果日志出现 Could not find domain for,优先判断服务是否曾经通过纯 SSH 会话安装或启动。

通过 VNC 或网页控制台进入 <runner-user> 的桌面,打开终端后执行:

whoami
gitlab-runner status
ls -l ~/Library/LaunchAgents/gitlab-runner.plist

相关操作应在目标用户的图形会话中执行。纯 SSH 会话可能没有对应的 GUI bootstrap domain,因此 launchctl 找不到管理用户级 LaunchAgent 所需的域。

正确的处理顺序是:

  1. 进入目标用户的图形会话;
  2. 确认 whoami 返回 <runner-user>
  3. 检查 plist 是否存在;
  4. 查看 gitlab-runner status
  5. 只有在确认服务文件缺失时,才执行安装或重新加载。

不要直接把 Runner 改成 LaunchDaemon。即使进程能够以 root 启动,也可能失去用户 Keychain、桌面会话和 Simulator 访问能力。对于 Xcode 发布任务,这种“服务在线、任务失败”的状态比完全离线更难发现。

第三步:检查 plist、日志和账户权限

先备份现有配置,不要在没有证据时删除注册信息:

mkdir -p ~/.gitlab-runner-backup-<date>

cp ~/.gitlab-runner/config.toml \
   ~/.gitlab-runner-backup-<date>/config.toml

cp ~/Library/LaunchAgents/gitlab-runner.plist \
   ~/.gitlab-runner-backup-<date>/gitlab-runner.plist

再检查关键路径:

ls -l /usr/local/bin/gitlab-runner
ls -ld ~/.gitlab-runner
ls -l ~/.gitlab-runner/config.toml

grep -n "StandardOutPath\|StandardErrorPath\|ProgramArguments" \
   ~/Library/LaunchAgents/gitlab-runner.plist

不同错误对应不同方向:

  • killed: 9:检查 plist 中的日志目录是否存在,以及 Runner 用户是否有写入权限。
  • exit status 134:先确认是否已有 Runner 进程,避免重复启动;再检查服务安装状态和日志。
  • Load failed: 5: Input/output error:检查 plist、二进制路径、日志目录和账户权限。
  • Operation timed out:转向代理、防火墙、路由、证书和 GitLab 实例可达性,不要继续盲目重装。

如果日志路径指向不存在的目录,可以先创建目录:

mkdir -p ~/gitlab-runner-log
chmod u+rwx ~/gitlab-runner-log

然后确认 plist 中的输出路径确实位于该目录。路径中的用户名、目录和文件名都必须替换成实际值,不能直接照抄其他节点。

重装只适合作为末级动作。只有在确认 plist 损坏、二进制路径失效或安装过程未完成时,才考虑:

gitlab-runner uninstall
gitlab-runner install
gitlab-runner start

重装前必须保留 config.toml、plist 备份和注册信息。重装只能修复服务文件问题,不能自动修复 FileVault、代理、标签、证书或代码签名权限。

第四步:把 Runner 在线和任务可执行分开验证

先在正确用户下执行:

gitlab-runner status
gitlab-runner list
gitlab-runner verify

status 用于观察本地服务进程,verify 用于验证已注册 Runner 与平台的连接。两者都正常,也不代表当前任务一定能够被领取。

继续检查 GitLab 项目设置和流水线配置:

  • Runner 是否属于正确的项目或组;
  • Runner 是否被暂停;
  • Runner 标签是否包含任务要求的 macosios 或自定义标签;
  • 任务是否允许无标签 Runner;
  • 是否已有其他任务占用节点;
  • 受保护分支和受保护 Runner 是否匹配。

例如,任务声明:

build_ios:
  tags:
    - macos

那么 Runner 必须拥有对应的 macos 标签,否则任务可能持续排队。Runner 页面显示在线,只能说明它具备连接平台的能力,不能说明标签路由正确。查看 GitLab 官方的 Runner 标签匹配规则

如果 Runner 进程在线但连接异常,可以采集调试输出:

gitlab-runner --debug verify

同时检查代理和系统时间:

env | grep -i proxy
scutil --proxy
date

浏览器能够打开 GitLab,不代表 Runner 使用的进程环境也能正常连接。Runner 可能继承不同的代理变量、证书链或用户级配置。

配置文件修改后,通常不需要马上重新注册 Runner。GitLab Runner 会定期检查配置变化,文档给出的默认检查间隔是 3 秒。但如果修改了服务路径、监听设置或 LaunchAgent 内容,仍应根据具体变更决定是否重启。查看 GitLab Runner 高级配置说明

第五步:处理 FileVault 和自动登录限制

FileVault 开启后,重启流程可能包含两个不同阶段:

  1. 解锁启动磁盘;
  2. 登录运行 Runner 的用户。

只完成第一步,不代表 LaunchAgent 已经恢复。你还需要通过图形控制台进入 <runner-user>,再验证服务、Keychain 和开发工具状态。

如果组织允许自动登录,可以检查系统设置。但不要把关闭 FileVault 当成默认修复方案。Apple 说明,FileVault、设备管理策略和账户安全设置都可能限制自动登录能力。查看 Apple 关于 Mac 自动登录限制的说明

更稳妥的远程 Mac 交付方式是保留双通道:

  • SSH:处理日志、配置、网络和命令行任务;
  • VNC 或网页控制台:处理磁盘解锁、用户登录、Keychain、Simulator 和图形授权。

如果只有 SSH,没有图形控制台,节点即使能够偶尔完成命令行构建,也不适合承载依赖签名和模拟器的生产流水线。

FAQ:几个容易混淆的恢复场景

为什么 Mac 重启后 Runner 没有自动上线?

因为 macOS Runner 运行在当前用户的 LaunchAgent 中。Mac 重新通电或恢复网络,只能说明主机可能已经可达;如果运行 Runner 的账户还停留在登录界面,LaunchAgent 就没有进入目标用户上下文。涉及 Keychain、签名或 Simulator 的任务,还必须验证图形会话和用户权限。

SSH 启动 Runner 时为什么找不到 launchctl domain?

纯 SSH 会话可能没有对应用户的 GUI bootstrap domain。此时 launchctl 无法正确管理用户级 LaunchAgent。你应通过 VNC、网页控制台或本机图形终端进入运行 Runner 的账户,再执行状态检查和启动操作,不要使用 sudo 强行改成系统级服务。

Runner 显示在线,为什么 macOS CI 任务仍然排队?

Runner 在线只代表它能和 GitLab 通信。任务是否执行,还取决于标签、项目范围、分支保护、并发限制和无标签任务设置。先比较 .gitlab-ci.yml 中的 tags 与 Runner 实际标签,再检查 Runner 是否被暂停或已被其他任务占用。

开启 FileVault 后,远程 Mac Runner 怎样恢复?

保留图形控制台,先完成磁盘解锁,再登录运行 Runner 的用户。随后检查 LaunchAgent、Keychain、签名和 Simulator。不要为了追求无人值守而默认关闭 FileVault;如果组织策略不允许自动登录,就应把人工解锁步骤纳入节点交付和故障恢复标准。

用真实重启完成验收

修复后至少执行一次真实重启。只运行一次 gitlab-runner start,不能证明重启恢复链路有效。

  • [ ] 记录 <runner-user><mac-host>、项目名、Runner 标签和配置文件路径。
  • [ ] 先完成一个普通 CI 任务,确认日志、产物和缓存正常。
  • [ ] 通过 SSH 记录主机可达性、当前用户和系统时间。
  • [ ] 通过 VNC 或网页控制台确认磁盘状态和图形登录状态。
  • [ ] 在图形会话中执行 gitlab-runner status
  • [ ] 执行 gitlab-runner verify,确认注册信息仍能连接平台。
  • [ ] 检查 ~/Library/LaunchAgents/gitlab-runner.plist 是否存在。
  • [ ] 检查标准输出和错误日志目录是否存在且可写。
  • [ ] 重启 Mac,不使用手动启动作为唯一恢复动作。
  • [ ] 重启后重新确认 SSH、图形会话和 Runner 服务状态。
  • [ ] 提交一个不依赖图形能力的 Shell 构建任务。
  • [ ] 再提交一个依赖 Keychain、代码签名或 Simulator 的 Xcode 任务。
  • [ ] 记录任务开始时间、Runner 上线时间、日志片段和最终结果。
  • [ ] 如果必须人工登录后才能恢复,明确标记为“需人工恢复”节点。
  • [ ] 如果任务标签不匹配,修正 CI 配置或 Runner 标签后再次测试。

Shell executor 会直接以 Runner 用户权限执行脚本。GitLab 官方提醒,这种执行方式只适合可信项目;共享节点可能暴露其他项目的代码、凭据和任务令牌。查看 GitLab Shell executor 安全说明

因此,多项目节点不能只验收“能不能跑”。还要确认项目边界、工作目录清理、凭据隔离和访问权限。若多个互不信任的项目共用静态 Shell executor,安全风险可能比一次重启故障更严重。

什么时候该更换远程 Mac 交付方案

如果你现在使用办公室里的 Mac mini,重启后常见的实际缺点包括:没有稳定的远程图形控制台、FileVault 解锁必须等人在现场、家庭或办公室网络存在端口与路由问题,以及单台设备故障时没有快速替换节点。

如果改用普通 Linux 云主机,又会遇到 Xcode、代码签名、Keychain 和 iOS Simulator 无法完整复现的问题。虚拟 macOS 环境也可能在硬件能力、图形会话或开发工具兼容性上增加新的变量。

更稳妥的做法,是先用一台同时支持 SSH 与图形控制台的远程 Mac 复现完整重启流程。你可以通过 MACCOME 的远程 Mac 方案 验证用户登录、LaunchAgent、签名和 Simulator 的恢复链路,再决定是否迁移生产任务或增加备用节点。若你还在评估 Mac mini 类型的远程算力,可进一步查看 Mac mini 云算力方案

稳定的 GitLab Runner,不是页面显示在线就算完成,而是重启后能在预期用户会话中恢复,能领取正确标签的任务,并且签名与 Simulator 流水线也能通过复测。如果当前节点只能依赖现场登录、人工解锁或临时改权限,MACCOME 的远程 Mac 更适合作为短期测试环境、备用构建节点或迁移前的验证节点。