PAYATHON 2026

GitHub Actions 如何在主 self-hosted runner 失败后串行切换到备用 runner?

数据小许

可以将主、备用执行分别配置为两个 job:主 job 指定 runner-01,备用 job 指定 runner-02。通过 needs 建立依赖,再根据主 job 的结果决定是否执行备用 job。

备用 job 必须等主 job 结束后才能运行。主 runner 离线后,GitHub Actions 可能仍让 job 排队或等待故障确认,因此这个方案不能保证立即切换。备用 runner 也不会自动接管主 runner 的进程或内存状态,任务需要重新执行,或读取任务自行保存的检查点。

为什么 runs-on 不能表达主备顺序?

runs-on: [self-hosted, runner-01, runner-02] 要求同一台 runner 同时具备这三个标签,数组顺序不表示优先级。应给两台机器分别配置唯一的自定义标签 runner-01 和 runner-02;机器显示名称不会自动成为同名标签。标签匹配方式见 GitHub 的 self-hosted runner 使用说明。

如何配置串行重试?

下面假设两台 runner 都能运行 Bash,仓库中已有 scripts/long-task.sh,并且已经安装任务所需的依赖。120 分钟只是示例,需要按任务正常耗时调整。

name: Sequential runner failover

on:
  workflow_dispatch:

permissions:
  contents: read

jobs:
  primary:
    runs-on: [self-hosted, runner-01]
    timeout-minutes: 120
    steps:
      - name: Check out repository
        uses: actions/checkout@v4

      - name: Run task on primary runner
        shell: bash
        run: bash ./scripts/long-task.sh

  fallback:
    needs: primary
    if: ${{ !cancelled() && (needs.primary.result == 'failure' || needs.primary.result == 'cancelled') }}
    runs-on: [self-hosted, runner-02]
    timeout-minutes: 120
    steps:
      - name: Check out repository
        uses: actions/checkout@v4

      - name: Retry task on fallback runner
        shell: bash
        run: bash ./scripts/long-task.sh

这段配置中:

  • needs: primary 让备用 job 等待主 job 完成,建立两次尝试的先后关系。
  • 条件显式检查主 job 的 failure 或 cancelled 结果;主 job 成功或被跳过时,不执行备用 job。
  • !cancelled() 是状态检查函数,可以覆盖默认的成功条件,并避免在整个 workflow 已被取消后继续发起备用执行。

只写 needs 时,上游失败通常会导致下游跳过;只写普通结果比较而不使用状态检查函数,也可能受到默认 success() 条件的影响。相关行为见 job 依赖规则、状态检查函数和 needs 上下文。

任务脚本必须在失败时返回非零退出码。不要通过 || true 或 continue-on-error 隐藏需要触发重试的错误,否则备用 job 可能无法按预期启动。

主 runner 离线时,为什么没有立即切换?

需要区分两种情况:

  1. job 尚未开始。没有在线且空闲的 runner 匹配 runner-01 时,job 会排队。GitHub 文档说明,self-hosted job 排队超过 24 小时会失败。在此之前,依赖它的备用 job 仍需等待。参见 self-hosted runner 路由规则。
  2. job 已经开始。执行中断线不一定会立刻变成失败;备用 job 要等 GitHub 将主 job 判定为结束后,才会评估执行条件。

timeout-minutes 用于限制 job 的执行时间,不能当作自定义排队超时,也不能保证断线后在指定时间内完成切换。其定义见 job 超时配置。

如果必须在较短时间内处理离线 runner,需要额外的调度或监控机制。例如,在独立执行环境中监测排队时长和任务心跳,达到阈值后终止原尝试,再启动明确指定 runner-02 的新尝试。仅在执行前检查 runner 是否在线仍有竞态:检查通过后,runner 依然可能断线。

长任务切换时需要注意什么?

needs 保证的是 GitHub Actions job 的依赖顺序。网络中断时,原机器上的任务进程可能仍在运行;如果两次尝试绝对不能同时修改业务数据,就需要任务层面的租约或 fencing token,让旧执行者在失去执行资格后无法继续写入。

重试还应考虑以下问题:

  • 重复操作:让任务具备幂等性,或使用稳定的业务任务 ID 去重。
  • 中间进度:将检查点保存到备用 runner 可访问的持久存储。主机突然故障时,不能依赖最后一步上传本地文件。
  • 执行环境:两台 runner 应具备相同的代码、依赖、配置和必要凭据,不能假设它们共享工作目录。
  • 最终状态:示例保留了主 job 的失败结果,即使备用 job 成功,整个 workflow 仍可能显示失败。如果业务要求“任一次成功即整体成功”,需要额外设计结果汇总逻辑。

备注:内容仅供参考。