Self-Hosted Bare Metal Runner


Self-Hosted Runner 是一个由你自己运行和管理的 Bare Metal Runner, 而不是使用 Bencher 托管的 On-Demand 或 Dedicated Runner。 你将 runner 二进制文件指向某个 Bencher API 服务器, 它便会在你自己的硬件上认领并执行 Job。

在以下场景中自托管 Runner 是一个不错的选择:

  • 在 Bencher Cloud 未提供的特定硬件上运行基准测试
  • 将基准测试工作负载保留在你自己的网络或气隙(air-gapped)环境内
  • 端到端运行 Bencher Self-Hosted

本指南将逐步介绍如何注册 Runner、用 Spec 描述其硬件、 将两者关联起来,以及启动 runner 二进制文件。 关于 Runner、Spec、Sandbox 和 Job 如何协同工作的概念性概述, 请参阅 Bare Metal 概述。

🐰 Firecracker 沙箱需要 Linux 5.14 或更高版本,启用 KVM,并在 /sys/fs/cgroup 提供可写的 cgroup v2 层级, 并且 Runner 必须以 root 身份运行。 无论 CPU 核心数量多少,每个沙箱 Job 都在 /sys/fs/cgroup/bencher 下自己的 cgroup 中运行,并为其启用 cpuset、memory 和 pids 控制器。 Runner 通过该 cgroup 的 cgroup.kill 终止 Job 的进程,而较旧的内核没有它:在这些内核上,Runner 会在启动时退出;若以 --danger-allow-no-sandbox 启动,则每个沙箱 Job 都会失败。 运行每个沙箱 Job 的 Firecracker 进程都会以非特权用户的身份被限制在 chroot 中, 而构建这种限制所需的权限,仅仅启用 KVM 是无法获得的。 仅服务于非沙箱化 Spec 的 Runner 可以在任何受支持的主机上运行。

创建 Runner

Runner 是用于向你的 Bencher API 服务器认证 runner 二进制文件的资源。 可以使用 bencher CLI、 Runners REST API 或 Bencher Console 创建一个。

Terminal window
bencher runner create \
--host https://api.bencher.example.com \
--name "My Runner"

Bencher 会返回新的 Runner 及其密钥:

{
"uuid": "...",
"key": "bencher_runner_..."
}

该密钥只显示一次,因此请妥善保管。 你将在启动 Runner 时把它传给 runner 二进制文件。 如果密钥丢失或泄露,请使用 POST /v0/runners/{runner}/key API 轮换它, 这会立即使之前的密钥失效。

创建 Spec

Spec 描述 Runner 提供的硬件: 操作系统、CPU 架构、Sandbox 类型、CPU 数量、内存、磁盘和网络访问。 Job 声明它所需的 Spec,而 Runner 只认领其所支持的 Spec 对应的 Job。

可以使用 bencher CLI、 Specs REST API 或 Bencher Console 创建 Spec:

Terminal window
bencher spec create \
--host https://api.bencher.example.com \
--name "Intel v1" \
--os linux \
--architecture x86_64 \
--sandbox firecracker \
--cpu 4 \
--memory 51539607552 \
--disk 137438953472

省略 --sandbox 可定义一个非沙箱化的 Spec, 如果允许该 Spec 上的 Job 进行网络访问,则传入 --network。 内存和磁盘以字节为单位指定。 关于 Spec 以及 Testbed 如何记录每个 Report 所使用的 Spec 的更多信息, 请参阅 Testbed 与 Spec。

将 Spec 分配给 Runner

Runner Spec 将一个 Spec 关联到一个 Runner。 一个 Runner 可以支持多个 Spec,一个 Spec 也可以由多个 Runner 支持。 Runner 只认领其被分配的 Spec 对应的 Job。

可以使用 bencher CLI 或 Runner Specs REST API 将 Spec 添加到 Runner:

Terminal window
bencher runner spec add my-runner \
--host https://api.bencher.example.com \
--spec intel-v1

之后可使用 DELETE /v0/runners/{runner}/specs/{spec} API 移除该关联。

启动 Runner

在创建好 Runner 并至少分配一个 Spec 后, 使用 runner up 在你的硬件上启动 runner 二进制文件。 提供 API 服务器主机、Runner 的 UUID 或 slug,以及 Runner 密钥, 既可以作为标志,也可以作为环境变量:

Terminal window
runner up \
--host https://api.bencher.example.com \
--runner my-runner \
--key bencher_runner_...
Terminal window
export BENCHER_HOST=https://api.bencher.example.com
export BENCHER_RUNNER=my-runner
export BENCHER_RUNNER_KEY=bencher_runner_...
runner up

runner 二进制文件向 API 服务器打开单个 WebSocket 连接, 轮询与其 Spec 匹配的 Job,逐个执行,并将结果报告回去。 该连接在多个 Job 之间保持打开, 并且当有新版本可用时,二进制文件会在 Job 之间自我更新。 默认情况下,Runner 遵循 stable 更新通道, 仅更新到版本化的发布版本。 canary 通道则跟踪部署到 Bencher Cloud 的滚动 canary 构建。

执行沙箱 Job 的 Runner 必须以 root 身份运行, 因此上面的命令是按 root 用户执行的方式给出的, 或者在以 root 启动 Runner 的服务管理器下运行。

默认情况下,Runner 会拒绝任何其 Spec 没有 Sandbox 的 Job。 要让 Runner 直接在主机上执行非沙箱化的 Job, 请使用 --danger-allow-no-sandbox 标志 启动它。

  • runner up

    runner up

    启动 runner,轮询并执行基准测试 Job。 这是用于操作 Self-Hosted Runner 的长时间运行命令。 runner 向 API 服务器打开单个 WebSocket 连接, 认领与其 Spec 匹配的 Job,执行它们,并将结果报告回去。 runner 以 JSON 格式将日志写入 stderr,每行一个对象,并且只以字节数记录 Job 的输出。

    runner up [OPTIONS]

    选项

    --host <HOST>

    要连接的 Bencher API 服务器。 默认使用 https://api.bencher.dev。 也可以通过 BENCHER_HOST 环境变量设置。

    --runner <RUNNER>

    要作为其运行的 Runner 的 UUID 或 slug。 也可以通过 BENCHER_RUNNER 环境变量设置。

    --key <KEY>

    创建 Runner 时返回的 Runner 认证密钥(bencher_runner_...)。 也可以通过 BENCHER_RUNNER_KEY 环境变量设置。

    --poll-timeout <POLL_TIMEOUT>

    等待 Job 时的长轮询超时(秒),介于 1 和 900 之间。 默认使用 55。

    --state-dir <STATE_DIR>

    Runner 的持久状态目录。 必须是绝对路径。 限制沙箱的 jail 在该目录下创建, 非正常退出遗留的 jail 会在每个沙箱 Job 运行前从中清除。 每个 jail 都保存着完整的客户机根文件系统和一份 microVM 二进制文件副本, 而 Runner 一次只运行一个 Job, 因此该目录只需为一个 jail 准备真实的容量。 设置 --image-cache-size 后,它还会保存 Image 缓存,最多到该大小。 无论状态目录如何,一台主机同一时间只运行一个 Runner: 以 root 身份启动的 Runner 在运行期间一直持有锁 /run/bencher/runner.lock, 以 root 身份启动的第二个 Runner 会立即退出,并报出指明该锁的错误;不以 root 身份运行的 Runner 无法打开该锁,会在没有锁的情况下运行。 以 root 身份启动的 Runner 在启动时以及每个沙箱 Job 之前,都会终止仍在 /sys/fs/cgroup/bencher 下的 cgroup 中运行的任何进程,例如在 Job 中途被终止的 Runner 留下的沙箱,无论该 Runner 使用哪个状态目录。 支持并推荐使用专用文件系统或 tmpfs 挂载: 这能让写入负载远离系统磁盘, 并且刚创建的文件系统即使带有 lost+found 目录也会被接受。 该文件系统不能以 nodev 或 noexec 挂载,而 /tmp、/dev/shm 和 /run 常常如此挂载,因此请为它单独挂载一个不带这些选项的 tmpfs。 状态目录本身也可以是由 root 创建、指向这类文件系统的符号链接, 例如让 /var/lib/bencher-runner 指向一块专用磁盘。 只有当存放链接的目录仅 root 可写、 链接直接指向其最终目标、 且该目标已存在于仅 root 可写的路径上时,链接才会被跟随; 目标尚不存在的链接会被拒绝,因此请先创建目标。 默认使用 /var/lib/bencher-runner。 也可以通过 BENCHER_STATE_DIR 环境变量设置。

    --jail-uid <JAIL_UID>

    沙箱进程切换到的非特权用户 ID。 默认使用 61016。 不能为 0。 宿主机上拥有该用户 ID 的进程可以向沙箱发送信号, 因此请设置为宿主机未分配给其他用途的 ID。 也可以通过 BENCHER_JAIL_UID 环境变量设置。

    --jail-gid <JAIL_GID>

    沙箱进程切换到的非特权组 ID。 默认使用 61016。 不能为 0。 也可以通过 BENCHER_JAIL_GID 环境变量设置。

    --danger-allow-no-sandbox

    允许在没有 Sandbox 的情况下执行 Job。 如果没有此标志,其 Spec 没有 Sandbox 的 Job 会在运行时被拒绝。 非沙箱化的 Job 直接在主机上运行,因此仅对受信任的工作负载启用此项。 也可以通过 BENCHER_DANGER_ALLOW_NO_SANDBOX 环境变量设置。

    --sandbox-log-level <SANDBOX_LOG_LEVEL>

    沙箱进程的日志级别。 默认使用 warning。

    --no-raid-pause

    在主机上的 md RAID 阵列执行重新同步、恢复、检查、修复或重塑时,继续接受 Job。 默认情况下,Runner 在请求每个 Job 之前检查其 md 阵列, 只要其中任何一个正在执行这些操作,Runner 就会暂停:在同步完成之前不接受新的 Job, 并且从不停止已经在运行的 Job。 暂停的 Runner 保持连接,并继续自我更新。

    --no-auto-update

    禁用来自服务器的自动更新。 默认情况下,当服务器提供新版本时,runner 会在 Job 之间自我更新。 也可以通过 BENCHER_NO_AUTO_UPDATE 环境变量设置。

    --update-channel <UPDATE_CHANNEL>

    自动更新使用的更新通道,stable 或 canary。 默认使用 stable。 stable 通道更新到版本化的发布版本。 canary 通道跟踪滚动 canary 构建, 每当 runner 在 Bencher Cloud 部署中发生变化时,该构建都会重新构建。 也可以通过 BENCHER_UPDATE_CHANNEL 环境变量设置。 与 --no-auto-update 冲突。

    --max-download-size <MAX_DOWNLOAD_SIZE>

    自我更新二进制文件的最大下载大小(字节)。 默认使用 500 MiB。 与 --no-auto-update 冲突。

    --image-cache-size <IMAGE_CACHE_SIZE>

    Runner 在 Job 之间保留的 Image blob 的最大大小(字节)。 Runner 将其拉取的每个 Image 的 config 和 layer blob 保存在其 --state-dir 下的 blobs 中, 因此 Job 只下载 Runner 尚未持有的 blob, 但每次仍从注册表获取 Image 清单。 在 Job 之间,Runner 会先移除最久未使用的 blob,直到其余部分不超过此大小。 Runner 在启动时创建该存储并使其仅归自己所有,如果无法做到,则立即退出,并报错指明该目录。 默认情况下,或设置为 0 时,Runner 不在 Job 之间保留 blob,并会移除之前保留的 blob。 也可以通过 BENCHER_IMAGE_CACHE_SIZE 环境变量设置。

    --max-output-size <MAX_OUTPUT_SIZE>

    收集的 stdout 和 stderr 的最大大小(字节)。 默认使用 25 MiB。

    --max-file-count <MAX_FILE_COUNT>

    要解码的输出文件的最大数量。 默认使用 255。

    路径解析期间要跟随的符号链接的最大数量, 与 Linux 内核的 MAXSYMLINKS 限制相匹配。 默认使用 40。 仅在非沙箱化模式下使用。

    主机调优

    在每次基准测试之前,runner 会应用主机调优以减少测量噪声。 默认情况下,它会禁用 ASLR、NMI 看门狗、SMT / 超线程和睿频加速(turbo boost); 将 CPU 调频调速器(governor)设为 performance,swappiness 设为 10,perf_event_paranoid 设为 -1。 在自上次启动以来 runner 尚未调优过的主机上,下面的标志会改为将各项优化保持在其主机默认值。

    主机调优会一直保留到主机重启:每项设置都是仅在运行时生效的写入,runner 退出时不会将其还原;C-state 保持除外,它会在 runner 退出时结束。 在一次调优运行之后,跳过其他任何设置的标志从下一次重启起生效:在此之前,该设置保持其调优值,runner 会在启动时记录该值。 移除 runner 后,请重启共享主机。

    --no-tuning

    禁用所有主机调优优化。

    --aslr

    不禁用 ASLR(默认:基准测试时禁用)。

    --nmi-watchdog

    不禁用 NMI 看门狗(默认:基准测试时禁用)。

    --smt

    不禁用 SMT / 超线程(默认:基准测试时禁用)。

    --turbo

    不禁用睿频加速(默认:基准测试时禁用)。

    --numa-balancing

    不禁用自动 NUMA 平衡(默认:基准测试时禁用)。

    --timer-migration

    不禁用定时器迁移(默认:基准测试时禁用)。

    --soft-watchdog

    不禁用软锁死(soft lockup)看门狗(默认:基准测试时禁用)。

    --ksm

    不禁用内核同页合并 KSM(默认:基准测试时禁用)。

    --cstates

    不禁用深度 C-state(默认:基准测试时禁用)。

    --no-irq-steering

    不将设备 IRQ 和内核工作队列引导到内务(housekeeping)核心。

    --no-cpuset-partition

    不通过隔离的 cpuset 分区将基准测试核心从调度器中分离。

    --swappiness <SWAPPINESS>

    设置 swappiness 值。 默认使用 10。

    --governor <GOVERNOR>

    设置 CPU 调频调速器。 默认使用 performance。

    --perf-event-paranoid <PERF_EVENT_PARANOID>

    设置 perf_event_paranoid 值。 默认使用 -1。

    --thp <THP>

    设置主机的透明大页(transparent hugepage)模式:never、madvise、always 或 leave。 使用 leave 可保留主机当前的模式。 默认使用 never。

    --help

    打印帮助。

有关每个选项,请参阅 runner CLI 参考, 有关与服务器交换的消息,请参阅 Runner 协议 参考。

主机维护

Runner 与其主机的维护(例如软件包升级或 RAID 巡检)轮流进行,因此维护绝不会在 Job 运行期间进行。 轮流通过 /run/bencher 实现,这是一个以 root 启动的 Runner 只为 root 创建的目录:

  • Job 锁,/run/bencher/job.lock。 Runner 在请求 Job 之前获取它,并持有到该轮询无 Job 结束或其带来的 Job 结束。 维护会等待这个锁,因此只在 Job 之间开始, 而发现锁被占用的 Runner 在锁释放之前不接受任何 Job。
  • 维护标记,/run/bencher/maintenance 或 /run/bencher/maintenance.<name>。 维护在等待锁之前创建自己的标记,完成后将其删除。 只要存在任何标记,Runner 就不请求 Job 并让锁保持空闲, 因此等待中的维护会在 Runner 当前的轮询或该轮询带来的 Job 结束后立即开始。

在等待维护期间,Runner 处于暂停状态:它保持连接,并继续自我更新。 runner run 也会等待 Job 锁;在存在标记或 md 阵列正在同步时,它会发出警告但仍然运行。 不是 root 的 Runner 既无法打开 Job 锁,也看不到标记,因此在没有它们的情况下接受 Job。

在 systemd 中,可以用一个 drop-in 让维护单元等待轮到自己。 例如,/etc/systemd/system/fstrim.service.d/zz-bencher-quiet.conf 用 flock -F 包装该单元自身的命令。 空的 ExecStart= 会清除该命令,因此下一行必须原样重复它:

[Service]
ExecStartPre=+/usr/bin/install -d -m 0700 /run/bencher
ExecStartPre=+/usr/bin/touch /run/bencher/maintenance.%n
OpenFile=/dev/null
ExecStart=
ExecStart=/usr/bin/flock -F /run/bencher/job.lock /sbin/fstrim --listed-in /etc/fstab:/proc/self/mountinfo --verbose --quiet-unsupported
ExecStopPost=+/bin/sh -c '/usr/bin/busctl get-property org.freedesktop.login1 /org/freedesktop/login1 org.freedesktop.login1.Manager PreparingForShutdown | /usr/bin/grep -q true || /usr/bin/rm -f /run/bencher/maintenance.%n'
TimeoutStartSec=infinity

OpenFile=/dev/null 占用文件描述符 3,因此即使命令像 apt.systemd.daily 那样重新打开描述符 3,锁也仍然保持。 在主机准备关机期间,ExecStopPost= 会保留标记,因此在重启清空 /run 之前 Runner 不接受任何 Job。

排空 Runner

要排空一个 Runner,请以 root 身份创建普通标记:

Terminal window
touch /run/bencher/maintenance

Runner 会完成当前的轮询(最多 --poll-timeout 秒)以及正在运行或由该轮询带来的 Job,之后在标记消失之前不再接受其他 Job:

Terminal window
rm /run/bencher/maintenance

/run 在启动时会被清空,因此重启也会删除该标记。

Self-Hosted Runner 工作流

一旦 Runner 连接好,你提交基准测试的方式与任何 Bare Metal Runner 相同: 构建并推送一个 Image,然后使用匹配的 Spec 运行 bencher run --image。 下图展示了从注册 Runner 到执行 Job 的完整路径。

OCI Registryrunner binaryAPI ServerOCI Registryrunner binaryAPI ServerAdminbencher runner createRunner + keybencher spec createbencher runner spec addrunner up --host --runner --keyConnect (WebSocket) and poll for JobsAssign matching JobPull ImageImage layersRun benchmarkSubmit resultsAdmin

继续学习:runner CLI 参考 ➡

🤖 该文档由 AI 自动翻译。 它可能不准确并且可能包含错误。 如果您发现任何错误,请在 GitHub 上提出问题.


Published: Fri, June 19, 2026 at 8:00:00 AM UTC | Last Updated: Fri, October 9, 2026 at 3:39:11 PM UTC