Self-Hosted Bare Metal Runners


Ein Self-Hosted Runner ist ein Bare Metal Runner, den Sie selbst betreiben und verwalten, anstatt einen von Bencher verwalteten On-Demand- oder Dedicated Runner zu nutzen. Sie richten das runner-Binary auf einen Bencher API-Server aus, und es beansprucht und führt Jobs auf Ihrer eigenen Hardware aus.

Das Self-Hosting eines Runner eignet sich gut, wenn Sie Folgendes möchten:

  • Benchmarks auf spezifischer Hardware ausführen, die Bencher Cloud nicht anbietet
  • Benchmark-Workloads innerhalb Ihres eigenen Netzwerks oder in einer Air-Gapped-Umgebung halten
  • Bencher Self-Hosted durchgängig betreiben

Dieser Leitfaden führt durch das Registrieren eines Runner, das Beschreiben seiner Hardware mit einer Spec, das Verknüpfen beider und das Starten des runner-Binary. Einen konzeptionellen Überblick darüber, wie Runner, Specs, Sandboxes und Jobs zusammenpassen, finden Sie in der Bare Metal-Übersicht.

🐰 Firecracker-Sandboxing erfordert Linux 5.14 oder neuer mit aktiviertem KVM und einer beschreibbaren cgroup-v2-Hierarchie unter /sys/fs/cgroup, und der Runner muss als root laufen. Jeder Sandbox-Job läuft in einer eigenen Cgroup unter /sys/fs/cgroup/bencher, unabhängig von der Zahl der CPU-Kerne, mit aktivierten Controllern cpuset, memory und pids. Der Runner beendet die Prozesse eines Jobs über cgroup.kill dieser Cgroup, das ältere Kernel nicht haben: Dort beendet sich ein Runner beim Start, oder er lässt, mit --danger-allow-no-sandbox gestartet, jeden Sandbox-Job fehlschlagen. Der Firecracker-Prozess hinter jedem Sandbox-Job wird in einem Chroot als unprivilegierter Benutzer eingesperrt, und der Aufbau dieser Isolierung benötigt Rechte, die das Aktivieren von KVM allein nicht gewährt. Ein Runner, der nur Specs ohne Sandbox bedient, kann auf jedem unterstützten Host laufen.

Einen Runner erstellen

Ein Runner ist die Ressource, die das runner-Binary gegenüber Ihrem Bencher API-Server authentifiziert. Erstellen Sie einen mit der bencher CLI, der Runners REST-API oder der Bencher Console.

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

Bencher gibt den neuen Runner zusammen mit seinem Schlüssel zurück:

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

Der Schlüssel wird nur einmal angezeigt, bewahren Sie ihn also sicher auf. Sie übergeben ihn dem runner-Binary, wenn Sie den Runner starten. Falls ein Schlüssel jemals verloren geht oder durchsickert, rotieren Sie ihn mit der POST /v0/runners/{runner}/key-API, was den vorherigen Schlüssel sofort ungültig macht.

Eine Spec erstellen

Eine Spec beschreibt die Hardware, die ein Runner bereitstellt: Betriebssystem, CPU-Architektur, Sandbox-Typ, CPU-Anzahl, Arbeitsspeicher, Festplatte und Netzwerkzugriff. Ein Job deklariert die Spec, die er benötigt, und ein Runner beansprucht nur Jobs für Specs, die er unterstützt.

Erstellen Sie eine Spec mit der bencher CLI, der Specs REST-API oder der Bencher Console:

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

Lassen Sie --sandbox weg, um eine Spec ohne Sandbox zu definieren, und übergeben Sie --network, wenn Jobs auf dieser Spec Netzwerkzugriff erhalten dürfen. Arbeitsspeicher und Festplatte werden in Bytes angegeben. Mehr zu Specs und wie ein Testbed die für jeden Report verwendete Spec aufzeichnet, finden Sie unter Testbeds & Specs.

Eine Spec einem Runner zuweisen

Eine Runner Spec verknüpft eine Spec mit einem Runner. Ein Runner kann mehrere Specs unterstützen, und eine Spec kann von mehreren Runnern unterstützt werden. Ein Runner beansprucht nur Jobs, deren Spec ihm zugewiesen wurde.

Fügen Sie eine Spec zu einem Runner mit der bencher CLI oder der Runner Specs REST-API hinzu:

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

Entfernen Sie die Verknüpfung später mit der DELETE /v0/runners/{runner}/specs/{spec}-API.

Den Runner starten

Sobald ein Runner erstellt und mindestens eine Spec zugewiesen ist, starten Sie das runner-Binary auf Ihrer Hardware mit runner up. Geben Sie den API-Server-Host, die Runner-UUID oder den Slug sowie den Runner-Schlüssel an, entweder als Flags oder als Umgebungsvariablen:

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

Das runner-Binary öffnet eine einzige WebSocket-Verbindung zum API-Server, fragt Jobs ab, die zu seinen Specs passen, führt jeden aus und meldet die Ergebnisse zurück. Die Verbindung bleibt über Jobs hinweg offen, und das Binary aktualisiert sich selbst zwischen Jobs, wenn eine neue Version verfügbar ist. Standardmäßig folgt der Runner dem stable Update-Kanal und aktualisiert nur auf versionierte Releases. Der canary-Kanal folgt stattdessen dem rollierenden Canary-Build von Bencher Cloud.

Ein Runner, der Sandbox-Jobs ausführt, muss als root laufen, die obigen Befehle werden also so gezeigt, wie der Benutzer root sie ausführen würde, oder unter einem Dienstmanager, der den Runner als root startet.

Standardmäßig lehnt ein Runner jeden Job ab, dessen Spec keine Sandbox hat. Um einem Runner zu erlauben, Jobs ohne Sandbox direkt auf dem Host auszuführen, starten Sie ihn mit dem --danger-allow-no-sandbox-Flag.

  • runner up

    runner up

    Startet den Runner und fragt Benchmark-Jobs ab und führt sie aus. Dies ist der langlaufende Befehl zum Betrieb eines Self-Hosted Runner. Der Runner öffnet eine einzige WebSocket-Verbindung zum API-Server, beansprucht Jobs, die zu seinen Specs passen, führt sie aus und meldet die Ergebnisse zurück. Der Runner schreibt sein Log als JSON nach stderr, ein Objekt pro Zeile, und protokolliert die Ausgabe eines Jobs nur als Byte-Anzahl.

    runner up [OPTIONS]

    Optionen

    --host <HOST>

    Der Bencher API-Server, zu dem eine Verbindung hergestellt werden soll. Standardmäßig wird https://api.bencher.dev verwendet. Kann auch mit der Umgebungsvariable BENCHER_HOST gesetzt werden.

    --runner <RUNNER>

    Die UUID oder der Slug des Runner, als der agiert werden soll. Kann auch mit der Umgebungsvariable BENCHER_RUNNER gesetzt werden.

    --key <KEY>

    Der Runner-Authentifizierungsschlüssel (bencher_runner_...), der zurückgegeben wurde, als der Runner erstellt wurde. Kann auch mit der Umgebungsvariable BENCHER_RUNNER_KEY gesetzt werden.

    --poll-timeout <POLL_TIMEOUT>

    Das Long-Poll-Timeout in Sekunden beim Warten auf einen Job, zwischen 1 und 900. Standardmäßig wird 55 verwendet.

    --state-dir <STATE_DIR>

    Das persistente Zustandsverzeichnis für den Runner. Es muss ein absoluter Pfad sein. Das Jail, das die Sandbox einschließt, wird unter diesem Verzeichnis erstellt, und Jails, die durch eine unsaubere Beendigung zurückgeblieben sind, werden vor jedem Sandbox-Job daraus entfernt. Jedes Jail enthält ein vollständiges Gast-Root-Dateisystem und eine Kopie der microVM-Binärdatei, und ein Runner führt seine Jobs nacheinander aus, daher braucht dieses Verzeichnis echte Kapazität für ein Jail. Mit --image-cache-size enthält es zusätzlich den Image-Cache, bis zu dieser Größe. Auf einem Host läuft immer nur ein Runner, unabhängig von seinem Zustandsverzeichnis: Ein als root gestarteter Runner hält die Sperre /run/bencher/runner.lock, solange er läuft, und ein zweiter, als root gestarteter beendet sich sofort mit einem Fehler, der diese Sperre nennt; ein Runner, der nicht als root läuft, kann die Sperre nicht öffnen und läuft ohne sie. Beim Start und erneut vor jedem Sandbox-Job beendet ein als root gestarteter Runner jeden Prozess, der noch in einer Cgroup unter /sys/fs/cgroup/bencher läuft, etwa die Sandbox eines Runners, der mitten in einem Job beendet wurde, unabhängig vom Zustandsverzeichnis dieses Runners. Ein eigenes Dateisystem oder ein tmpfs-Mount wird unterstützt und empfohlen: es hält diese Schreiblast von der Systemplatte fern, und ein neu erstelltes Dateisystem wird akzeptiert, auch wenn es ein lost+found-Verzeichnis mitbringt. Das Dateisystem darf nicht mit nodev oder noexec eingehängt sein, wie es /tmp, /dev/shm und /run oft sind; hängen Sie dafür also ein tmpfs ohne diese Optionen ein. Das Zustandsverzeichnis selbst darf ein von root angelegter symbolischer Link auf ein solches Dateisystem sein, etwa /var/lib/bencher-runner auf eine eigene Platte. Dem Link wird nur gefolgt, wenn das Verzeichnis, das ihn enthält, nur von root beschreibbar ist, er direkt auf sein endgültiges Ziel zeigt und dieses Ziel bereits auf einem nur von root beschreibbaren Pfad existiert; ein Link, dessen Ziel noch nicht existiert, wird abgelehnt, also zuerst das Ziel anlegen. Standardmäßig wird /var/lib/bencher-runner verwendet. Kann auch mit der Umgebungsvariable BENCHER_STATE_DIR gesetzt werden.

    --jail-uid <JAIL_UID>

    Die unprivilegierte Benutzer-ID, auf die der Sandbox-Prozess wechselt. Standardmäßig wird 61016 verwendet. Sie darf nicht 0 sein. Ein Prozess auf dem Host, dem diese Benutzer-ID gehört, kann Signale an die Sandbox senden, setzen Sie sie also auf eine ID, die der Host nichts anderem zuweist. Kann auch mit der Umgebungsvariable BENCHER_JAIL_UID gesetzt werden.

    --jail-gid <JAIL_GID>

    Die unprivilegierte Gruppen-ID, auf die der Sandbox-Prozess wechselt. Standardmäßig wird 61016 verwendet. Sie darf nicht 0 sein. Kann auch mit der Umgebungsvariable BENCHER_JAIL_GID gesetzt werden.

    --danger-allow-no-sandbox

    Erlaubt das Ausführen von Jobs ohne Sandbox. Ohne dieses Flag wird ein Job, dessen Spec keine Sandbox hat, zur Laufzeit abgelehnt. Jobs ohne Sandbox laufen direkt auf dem Host, aktivieren Sie dies also nur für vertrauenswürdige Workloads. Kann auch mit der Umgebungsvariable BENCHER_DANGER_ALLOW_NO_SANDBOX gesetzt werden.

    --sandbox-log-level <SANDBOX_LOG_LEVEL>

    Das Log-Level für den Sandbox-Prozess. Standardmäßig wird warning verwendet.

    --no-raid-pause

    Weiterhin Jobs annehmen, während ein md-RAID-Array auf dem Host resynchronisiert, wiederherstellt, prüft, repariert oder umformt. Standardmäßig prüft der Runner seine md-Arrays, bevor er nach jedem Job fragt, und solange eines davon einen dieser Vorgänge ausführt, pausiert er: Er nimmt keinen neuen Job an, bis die Synchronisierung abgeschlossen ist, und er stoppt nie einen Job, der bereits läuft. Ein pausierter Runner bleibt verbunden und aktualisiert sich weiterhin selbst.

    --no-auto-update

    Deaktiviert automatische Updates vom Server. Standardmäßig aktualisiert sich der Runner zwischen Jobs selbst, wenn der Server eine neue Version anbietet. Kann auch mit der Umgebungsvariable BENCHER_NO_AUTO_UPDATE gesetzt werden.

    --update-channel <UPDATE_CHANNEL>

    Der Update-Kanal für automatische Updates, entweder stable oder canary. Standardmäßig wird stable verwendet. Der stable-Kanal aktualisiert auf versionierte Releases. Der canary-Kanal folgt dem rollierenden Canary-Build, der neu gebaut wird, wann immer sich der Runner bei einem Bencher Cloud-Deployment ändert. Kann auch mit der Umgebungsvariable BENCHER_UPDATE_CHANNEL gesetzt werden. Steht im Konflikt mit --no-auto-update.

    --max-download-size <MAX_DOWNLOAD_SIZE>

    Die maximale Download-Größe in Bytes für Self-Update-Binaries. Standardmäßig werden 500 MiB verwendet. Steht im Konflikt mit --no-auto-update.

    --image-cache-size <IMAGE_CACHE_SIZE>

    Die maximale Größe in Bytes der Image-Blobs, die der Runner über Jobs hinweg behält. Der Runner behält die Config- und Layer-Blobs jedes Image, das er herunterlädt, in blobs unter seinem --state-dir, sodass ein Job nur die Blobs herunterlädt, die der Runner noch nicht hat, das Manifest seines Image aber trotzdem jedes Mal aus der Registry abruft. Zwischen Jobs entfernt der Runner die am längsten nicht verwendeten Blobs, bis der Rest in diese Größe passt. Beim Start legt der Runner den Speicher an und macht ihn zu seinem eigenen; kann er das nicht, beendet er sich sofort mit einem Fehler, der das Verzeichnis nennt. Standardmäßig, oder mit 0, behält der Runner keine Blobs über Jobs hinweg und entfernt alle, die er zuvor behalten hat. Kann auch mit der Umgebungsvariable BENCHER_IMAGE_CACHE_SIZE gesetzt werden.

    --max-output-size <MAX_OUTPUT_SIZE>

    Die maximale Größe in Bytes für erfasstes stdout und stderr. Standardmäßig werden 25 MiB verwendet.

    --max-file-count <MAX_FILE_COUNT>

    Die maximale Anzahl an Ausgabedateien, die dekodiert werden. Standardmäßig wird 255 verwendet.

    Die maximale Anzahl an Symlinks, denen bei der Pfadauflösung gefolgt wird, entsprechend dem MAXSYMLINKS-Limit des Linux-Kernels. Standardmäßig wird 40 verwendet. Wird nur im Modus ohne Sandbox verwendet.

    Host-Tuning

    Vor jedem Benchmark wendet der runner Host-Tuning an, um Messrauschen zu reduzieren. Standardmäßig deaktiviert er ASLR, den NMI-Watchdog, SMT / Hyper-Threading und Turbo Boost; setzt den CPU-Scaling-Governor auf performance, Swappiness auf 10 und perf_event_paranoid auf -1. Auf einem Host, auf den der runner seit dem letzten Neustart kein Host-Tuning angewendet hat, belassen die folgenden Flags einzelne Optimierungen stattdessen bei ihren Host-Standardwerten.

    Das Host-Tuning bleibt bis zum Neustart des Hosts bestehen: Jede Einstellung ist ein reiner Laufzeit-Schreibvorgang, den der runner beim Beenden nicht zurücksetzt, mit Ausnahme der C-State-Sperre, die endet, wenn sich der runner beendet. Nach einem Lauf mit Host-Tuning wirkt ein Flag, das eine andere Einstellung überspringt, erst ab dem nächsten Neustart: Bis dahin behält die Einstellung ihren getunten Wert, und der runner protokolliert diesen Wert beim Start. Starten Sie einen gemeinsam genutzten Host neu, nachdem Sie den runner entfernt haben.

    --no-tuning

    Deaktiviert alle Host-Tuning-Optimierungen.

    --aslr

    Deaktiviert ASLR nicht (Standard: für Benchmarks deaktiviert).

    --nmi-watchdog

    Deaktiviert den NMI-Watchdog nicht (Standard: für Benchmarks deaktiviert).

    --smt

    Deaktiviert SMT / Hyper-Threading nicht (Standard: für Benchmarks deaktiviert).

    --turbo

    Deaktiviert Turbo Boost nicht (Standard: für Benchmarks deaktiviert).

    --numa-balancing

    Deaktiviert das automatische NUMA-Balancing nicht (Standard: für Benchmarks deaktiviert).

    --timer-migration

    Deaktiviert die Timer-Migration nicht (Standard: für Benchmarks deaktiviert).

    --soft-watchdog

    Deaktiviert den Soft-Lockup-Watchdog nicht (Standard: für Benchmarks deaktiviert).

    --ksm

    Deaktiviert KSM, das Kernel Samepage Merging, nicht (Standard: für Benchmarks deaktiviert).

    --cstates

    Deaktiviert tiefe C-States nicht (Standard: für Benchmarks deaktiviert).

    --no-irq-steering

    Lenkt Geräte-IRQs und Kernel-Workqueues nicht auf Housekeeping-Kerne.

    --no-cpuset-partition

    Löst Benchmark-Kerne nicht über eine isolierte cpuset-Partition vom Scheduler.

    --swappiness <SWAPPINESS>

    Setzt den Swappiness-Wert. Standardmäßig wird 10 verwendet.

    --governor <GOVERNOR>

    Setzt den CPU-Scaling-Governor. Standardmäßig wird performance verwendet.

    --perf-event-paranoid <PERF_EVENT_PARANOID>

    Setzt den perf_event_paranoid-Wert. Standardmäßig wird -1 verwendet.

    --thp <THP>

    Setzt den Transparent-Hugepage-Modus des Hosts: never, madvise, always oder leave. Mit leave bleibt der aktuelle Modus des Hosts erhalten. Standardmäßig wird never verwendet.

    --help

    Hilfe ausgeben.

Die runner CLI-Referenz beschreibt jede Option, und die Runner Protocol-Referenz beschreibt die mit dem Server ausgetauschten Nachrichten.

Host-Wartung

Ein Runner und die Wartung seines Hosts, etwa Paket-Upgrades oder ein RAID-Scrub, wechseln sich ab, sodass Wartung nie während eines Jobs läuft. Sie wechseln sich über /run/bencher ab, ein Verzeichnis, das ein als root gestarteter Runner nur für root anlegt:

  • Die Job-Sperre, /run/bencher/job.lock. Der Runner nimmt sie, bevor er nach einem Job fragt, und hält sie, bis diese Abfrage ohne Job endet oder der Job, den sie bringt, endet. Wartung wartet auf die Sperre und beginnt daher nur zwischen Jobs, und ein Runner, der die Sperre belegt vorfindet, nimmt keinen Job an, bis sie frei ist.
  • Wartungsmarker, /run/bencher/maintenance oder /run/bencher/maintenance.<name>. Wartung legt ihren eigenen Marker an, bevor sie auf die Sperre wartet, und entfernt ihn, wenn sie fertig ist. Solange ein Marker existiert, fragt der Runner nach keinem Job und lässt die Sperre frei, sodass wartende Wartung beginnt, sobald die laufende Abfrage des Runners oder der Job, den diese bringt, endet.

Während er auf Wartung wartet, ist ein Runner pausiert: Er bleibt verbunden und aktualisiert sich weiterhin selbst. runner run wartet ebenfalls auf die Job-Sperre und warnt, läuft aber, solange ein Marker existiert oder ein md-Array synchronisiert. Ein Runner, der nicht root ist, kann weder die Job-Sperre öffnen noch die Marker sehen und nimmt Jobs daher ohne sie an.

Mit systemd sorgt ein Drop-in dafür, dass eine Wartungs-Unit sich einreiht. Zum Beispiel umschließt /etc/systemd/system/fstrim.service.d/zz-bencher-quiet.conf den eigenen Befehl der Unit mit flock -F. Das leere ExecStart= löscht diesen Befehl, daher muss die nächste Zeile ihn exakt wiederholen:

[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 belegt den Dateideskriptor 3, sodass die Sperre gehalten bleibt, auch wenn der Befehl den Deskriptor 3 neu öffnet, wie es apt.systemd.daily tut. Während sich der Host auf das Herunterfahren vorbereitet, lässt ExecStopPost= den Marker stehen, sodass der Runner keinen Job annimmt, bevor der Neustart /run leert.

Einen Runner leeren

Um einen Runner zu leeren, legen Sie als root den einfachen Marker an:

Terminal window
touch /run/bencher/maintenance

Der Runner beendet seine laufende Abfrage, höchstens --poll-timeout Sekunden, und jeden Job, der läuft oder den diese Abfrage bringt, und nimmt danach keinen weiteren Job an, bis der Marker entfernt ist:

Terminal window
rm /run/bencher/maintenance

/run wird beim Booten geleert, daher entfernt auch ein Neustart den Marker.

Self-Hosted Runner-Workflow

Sobald der Runner verbunden ist, reichen Sie Benchmarks auf dieselbe Weise ein wie bei jedem Bare Metal Runner: Erstellen und pushen Sie ein Image und führen Sie dann bencher run --image mit einer passenden Spec aus. Das folgende Diagramm zeigt den vollständigen Weg, vom Registrieren des Runner bis zum Ausführen eines Jobs.

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

Weiter geht’s: runner CLI-Referenz ➡

🤖 Dieses Dokument wurde automatisch von KI übersetzt. Es ist möglicherweise nicht korrekt und kann Fehler enthalten. Wenn Sie Fehler finden, öffnen Sie bitte ein Problem auf GitHub.


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