Self-Hosted Bare Metal Runner


Self-Hosted Runner는 Bencher가 관리하는 On-Demand 또는 Dedicated Runner를 사용하는 대신 직접 실행하고 관리하는 Bare Metal Runner입니다. runner 바이너리를 Bencher API 서버로 향하게 하면, 자체 하드웨어에서 Job을 청구(claim)하고 실행합니다.

Runner를 셀프 호스팅하는 것은 다음과 같은 경우에 적합합니다:

  • Bencher Cloud가 제공하지 않는 특정 하드웨어에서 벤치마크를 실행하려는 경우
  • 벤치마크 워크로드를 자체 네트워크나 에어 갭(air-gapped) 환경 안에 유지하려는 경우
  • Bencher Self-Hosted를 처음부터 끝까지 실행하려는 경우

이 가이드는 Runner를 등록하고, Spec으로 그 하드웨어를 설명하고, 둘을 함께 연결한 다음, runner 바이너리를 시작하는 과정을 안내합니다. Runner, Spec, Sandbox, Job이 어떻게 맞물리는지에 대한 개념적 개요는 Bare Metal 개요를 참고하세요.

🐰 Firecracker 샌드박싱에는 KVM이 활성화되고 /sys/fs/cgroup에 쓰기 가능한 cgroup v2 계층이 있는 Linux 5.14 이상이 필요하며, Runner는 root로 실행해야 합니다. 각 샌드박스 Job은 CPU 코어 수와 관계없이 /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는 runner 바이너리를 Bencher API 서버에 인증하는 리소스입니다. 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만 청구(claim)합니다.

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

샌드박스가 없는 Spec을 정의하려면 --sandbox를 생략하고, 이 Spec의 Job에 네트워크 액세스를 허용하려면 --network를 전달하세요. 메모리와 디스크는 바이트 단위로 지정합니다. Spec에 대한 자세한 내용과 Testbed가 각 Report에 사용된 Spec을 어떻게 기록하는지는 Testbed & Spec을 참고하세요.

Runner에 Spec 할당

Runner Spec은 Spec을 Runner에 연결합니다. 하나의 Runner는 여러 Spec을 지원할 수 있고, 하나의 Spec은 여러 Runner가 지원할 수 있습니다. Runner는 자신에게 할당된 Spec의 Job만 청구(claim)합니다.

bencher CLI 또는 Runner Specs REST API로 Runner에 Spec을 추가하세요:

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 또는 슬러그, 그리고 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에 걸쳐 열린 상태로 유지되며, 새 버전이 있을 때 바이너리는 Job 사이에 스스로 업데이트합니다. 기본적으로 Runner는 stable 업데이트 채널을 따르며, 버전이 지정된 릴리스로만 업데이트합니다. canary 채널은 Bencher Cloud에 배포되는 롤링 canary 빌드를 추적합니다.

샌드박스 Job을 실행하는 Runner는 root로 실행해야 하므로, 위 명령은 root 사용자가 실행하거나 Runner를 root로 시작하는 서비스 관리자 아래에서 실행하는 것을 전제로 합니다.

기본적으로 Runner는 Sandbox가 없는 Spec의 Job을 거부합니다. Runner가 샌드박스 없는 Job을 호스트에서 직접 실행하도록 하려면, --danger-allow-no-sandbox 플래그로 시작하세요.

  • runner up

    runner up

    Runner를 시작하여 벤치마크 Job을 폴링하고 실행합니다. Self-Hosted Runner를 운영하는 데 사용하는 장기 실행 명령입니다. Runner는 API 서버로 단일 WebSocket 연결을 열고, 자신의 Spec과 일치하는 Job을 청구(claim)하고, 실행한 후, 결과를 다시 보고합니다. Runner는 자신의 로그를 한 줄에 객체 하나씩 JSON으로 stderr에 기록하며, Job의 출력은 바이트 수로만 기록합니다.

    runner up [OPTIONS]

    옵션

    --host <HOST>

    연결할 Bencher API 서버입니다. 기본적으로 https://api.bencher.dev가 사용됩니다. BENCHER_HOST 환경 변수로도 설정할 수 있습니다.

    --runner <RUNNER>

    운영할 Runner의 UUID 또는 슬러그입니다. BENCHER_RUNNER 환경 변수로도 설정할 수 있습니다.

    --key <KEY>

    Runner를 생성할 때 반환된 Runner 인증 키(bencher_runner_...)입니다. BENCHER_RUNNER_KEY 환경 변수로도 설정할 수 있습니다.

    --poll-timeout <POLL_TIMEOUT>

    Job을 기다리는 동안의 롱 폴(long-poll) 타임아웃(초)으로, 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 전에, Job 도중 강제 종료된 Runner의 샌드박스처럼 /sys/fs/cgroup/bencher 아래의 cgroup에서 아직 실행 중인 프로세스를 그 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 실행을 허용합니다. 이 플래그가 없으면, Sandbox가 없는 Spec의 Job은 런타임에 거부됩니다. 샌드박스 없는 Job은 호스트에서 직접 실행되므로, 신뢰할 수 있는 워크로드에만 활성화하세요. BENCHER_DANGER_ALLOW_NO_SANDBOX 환경 변수로도 설정할 수 있습니다.

    --sandbox-log-level <SANDBOX_LOG_LEVEL>

    샌드박스 프로세스의 로그 레벨입니다. 기본적으로 warning이 사용됩니다.

    --no-raid-pause

    호스트의 md RAID 어레이가 재동기화, 복구, 검사, 수리 또는 재구성 중일 때도 계속 Job을 받습니다. 기본적으로 Runner는 각 Job을 요청하기 전에 md 어레이를 확인하고, 그중 하나라도 이러한 작업을 실행하는 동안에는 일시 중지합니다. 동기화가 끝날 때까지 새 Job을 받지 않으며, 이미 실행 중인 Job은 절대 중지하지 않습니다. 일시 중지된 Runner는 연결을 유지하며 계속 스스로 업데이트합니다.

    --no-auto-update

    서버로부터의 자동 업데이트를 비활성화합니다. 기본적으로, 서버가 새 버전을 제공하면 Runner는 Job 사이에 스스로 업데이트합니다. BENCHER_NO_AUTO_UPDATE 환경 변수로도 설정할 수 있습니다.

    --update-channel <UPDATE_CHANNEL>

    자동 업데이트에 사용할 업데이트 채널로, stable 또는 canary입니다. 기본적으로 stable이 사용됩니다. stable 채널은 버전이 지정된 릴리스로 업데이트합니다. canary 채널은 롤링 canary 빌드를 추적하며, 이 빌드는 Bencher Cloud 배포에서 Runner가 변경될 때마다 다시 빌드됩니다. 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는 풀(pull)한 각 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 / 하이퍼스레딩, 터보 부스트를 비활성화하고, CPU 스케일링 거버너를 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

    소프트 락업 워치독을 비활성화하지 않습니다(기본값: 벤치마크에서는 비활성화).

    --ksm

    KSM(커널 동일 페이지 병합)을 비활성화하지 않습니다(기본값: 벤치마크에서는 비활성화).

    --cstates

    깊은 C-state를 비활성화하지 않습니다(기본값: 벤치마크에서는 비활성화).

    --no-irq-steering

    디바이스 IRQ와 커널 워크큐를 하우스키핑 코어로 보내지 않습니다.

    --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 Protocol 참조를 확인하세요.

호스트 유지 관리

Runner와 호스트의 유지 관리(패키지 업그레이드나 RAID 스크럽 등)는 번갈아 실행되므로, 유지 관리가 Job 실행 중에 실행되는 일은 없습니다. 번갈아 실행하는 데는 /run/bencher를 사용합니다. 이 디렉터리는 root로 시작된 Runner가 root 전용으로 만듭니다.

  • Job 잠금, /run/bencher/job.lock. Runner는 Job을 요청하기 전에 이를 가져오고, 그 폴링이 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에서는 드롭인으로 유지 관리 유닛이 차례를 기다리게 합니다. 예를 들어 /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