Self-Hosted Bare Metal Runners


Um Self-Hosted Runner é um Bare Metal Runner que você executa e gerencia por conta própria, em vez de usar um Runner On-Demand ou Dedicated gerenciado pela Bencher. Você aponta o binário runner para um servidor de API Bencher, e ele reivindica e executa Jobs no seu próprio hardware.

Auto-hospedar um Runner é uma boa escolha quando você quer:

  • Executar benchmarks em hardware específico que a Bencher Cloud não oferece
  • Manter as cargas de trabalho de benchmark dentro da sua própria rede ou em um ambiente isolado (air-gapped)
  • Executar o Bencher Self-Hosted de ponta a ponta

Este guia mostra como registrar um Runner, descrever seu hardware com uma Spec, vincular os dois e iniciar o binário runner. Para uma visão conceitual de como Runners, Specs, Sandboxes e Jobs se encaixam, consulte a Visão Geral de Bare Metal.

🐰 O sandboxing com Firecracker requer Linux 5.14 ou posterior com KVM habilitado e uma hierarquia cgroup v2 gravável em /sys/fs/cgroup, e o Runner precisa ser executado como root. Cada Job com sandbox roda no seu próprio cgroup sob /sys/fs/cgroup/bencher, seja qual for o número de núcleos de CPU, com os controladores cpuset, memory e pids habilitados. O Runner encerra os processos de um Job pelo cgroup.kill desse cgroup, que kernels mais antigos não têm: neles um Runner sai ao iniciar ou, iniciado com --danger-allow-no-sandbox, faz cada Job com sandbox falhar. O processo do Firecracker por trás de cada Job com sandbox fica confinado em um chroot como usuário sem privilégios, e construir esse confinamento exige permissões que habilitar o KVM sozinho não concede. Um Runner que atende apenas Specs sem sandbox pode rodar em qualquer host suportado.

Criar um Runner

Um Runner é o recurso que autentica o binário runner no seu servidor de API Bencher. Crie um com a CLI bencher, a Runners REST API, ou o Bencher Console.

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

A Bencher retorna o novo Runner junto com sua chave:

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

A chave é exibida apenas uma vez, então guarde-a com segurança. Você a passará ao binário runner quando iniciar o Runner. Se uma chave for perdida ou vazada, rotacione-a com a API POST /v0/runners/{runner}/key, que invalida imediatamente a chave anterior.

Criar uma Spec

Uma Spec descreve o hardware que um Runner fornece: sistema operacional, arquitetura de CPU, tipo de Sandbox, número de CPUs, memória, disco e acesso à rede. Um Job declara a Spec de que precisa, e um Runner só reivindica Jobs para Specs que ele suporta.

Crie uma Spec com a CLI bencher, a Specs REST API, ou o 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

Omita --sandbox para definir uma Spec sem sandbox, e passe --network se os Jobs nesta Spec tiverem permissão de acesso à rede. Memória e disco são especificados em bytes. Para mais sobre Specs e como um Testbed registra a Spec usada em cada Report, consulte Testbeds & Specs.

Atribuir uma Spec a um Runner

Uma Runner Spec vincula uma Spec a um Runner. Um Runner pode suportar múltiplas Specs, e uma Spec pode ser suportada por múltiplos Runners. Um Runner só reivindica Jobs cuja Spec lhe foi atribuída.

Adicione uma Spec a um Runner com a CLI bencher ou a Runner Specs REST API:

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

Remova o vínculo mais tarde com a API DELETE /v0/runners/{runner}/specs/{spec}.

Iniciar o Runner

Com um Runner criado e ao menos uma Spec atribuída, inicie o binário runner no seu hardware com runner up. Forneça o host do servidor de API, o UUID ou slug do Runner e a chave do Runner, seja como flags ou variáveis de ambiente:

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

O binário runner abre uma única conexão WebSocket ao servidor de API, faz polling por Jobs que correspondam às suas Specs, executa cada um e reporta os resultados de volta. A conexão permanece aberta entre os Jobs, e o binário se atualiza entre os Jobs quando uma nova versão está disponível. Por padrão, o Runner segue o canal de atualização stable e só atualiza para releases versionadas. O canal canary, por sua vez, acompanha o build canary contínuo implantado no Bencher Cloud.

Um Runner que executa Jobs com sandbox precisa rodar como root, então os comandos acima são mostrados como o usuário root os executaria, ou sob um gerenciador de serviços que inicie o Runner como root.

Por padrão, um Runner rejeita qualquer Job cuja Spec não tenha Sandbox. Para permitir que um Runner execute Jobs sem sandbox diretamente no host, inicie-o com a flag --danger-allow-no-sandbox.

  • runner up

    runner up

    Inicia o runner, fazendo polling e executando Jobs de benchmark. Este é o comando de longa duração usado para operar um Self-Hosted Runner. O runner abre uma única conexão WebSocket ao servidor de API, reivindica Jobs que correspondam às suas Specs, os executa e reporta os resultados de volta. O runner escreve seu log no stderr como JSON, um objeto por linha, e registra a saída de um Job apenas como contagens de bytes.

    runner up [OPTIONS]

    Opções

    --host <HOST>

    O servidor de API Bencher ao qual se conectar. Por padrão, https://api.bencher.dev é usado. Também pode ser definido com a variável de ambiente BENCHER_HOST.

    --runner <RUNNER>

    O UUID ou slug do Runner sob o qual operar. Também pode ser definido com a variável de ambiente BENCHER_RUNNER.

    --key <KEY>

    A chave de autenticação do Runner (bencher_runner_...) retornada quando o Runner foi criado. Também pode ser definida com a variável de ambiente BENCHER_RUNNER_KEY.

    --poll-timeout <POLL_TIMEOUT>

    O timeout de long-poll em segundos enquanto aguarda um Job, entre 1 e 900. Por padrão, 55 é usado.

    --state-dir <STATE_DIR>

    O diretório de estado persistente do Runner. Deve ser um caminho absoluto. A jail que confina o sandbox é criada sob este diretório, e as jails deixadas por um encerramento não limpo são removidas dele antes de cada Job com sandbox. Cada jail contém um sistema de arquivos raiz completo do convidado e uma cópia do binário da microVM, e um Runner executa os seus Jobs um de cada vez, portanto este diretório precisa de capacidade real para uma jail. Com --image-cache-size, ele também guarda o cache de Image, até esse tamanho. Um host executa um único Runner por vez, seja qual for o seu diretório de estado: um Runner iniciado como root mantém o bloqueio /run/bencher/runner.lock enquanto está em execução, e um segundo iniciado como root encerra imediatamente com um erro que nomeia esse bloqueio; um Runner que não é root não consegue abrir o bloqueio e é executado sem ele. Ao iniciar, e de novo antes de cada Job com sandbox, um Runner iniciado como root encerra qualquer processo ainda em execução em um cgroup sob /sys/fs/cgroup/bencher, como a sandbox de um Runner encerrado no meio de um Job, seja qual for o diretório de estado desse Runner. Um sistema de arquivos dedicado ou uma montagem tmpfs é suportado e recomendado: mantém essa carga de escrita fora do disco do sistema, e um sistema de arquivos recém-criado é aceito mesmo que venha com um diretório lost+found. O sistema de arquivos não pode estar montado com nodev ou noexec, como /tmp, /dev/shm e /run costumam estar, então monte para ele um tmpfs sem essas opções. O próprio diretório de estado pode ser um link simbólico criado pelo root para um sistema de arquivos assim, como /var/lib/bencher-runner apontando para um disco dedicado. O link só é seguido quando o diretório que o contém só pode ser escrito pelo root, ele aponta diretamente para o seu destino final, e esse destino já existe em um caminho gravável apenas pelo root; um link cujo destino ainda não existe é recusado, então crie o destino primeiro. Por padrão, /var/lib/bencher-runner é usado. Também pode ser definido com a variável de ambiente BENCHER_STATE_DIR.

    --jail-uid <JAIL_UID>

    O ID de usuário sem privilégios para o qual o processo do sandbox muda. Por padrão, 61016 é usado. Não pode ser 0. Um processo no host que seja dono desse ID de usuário pode enviar sinais ao sandbox, portanto defina um ID que o host não destine a mais nada. Também pode ser definido com a variável de ambiente BENCHER_JAIL_UID.

    --jail-gid <JAIL_GID>

    O ID de grupo sem privilégios para o qual o processo do sandbox muda. Por padrão, 61016 é usado. Não pode ser 0. Também pode ser definido com a variável de ambiente BENCHER_JAIL_GID.

    --danger-allow-no-sandbox

    Permite executar Jobs sem um Sandbox. Sem essa flag, um Job cuja Spec não tem Sandbox é rejeitado em tempo de execução. Jobs sem sandbox rodam diretamente no host, então habilite isso apenas para cargas de trabalho confiáveis. Também pode ser definida com a variável de ambiente BENCHER_DANGER_ALLOW_NO_SANDBOX.

    --sandbox-log-level <SANDBOX_LOG_LEVEL>

    O nível de log para o processo do sandbox. Por padrão, warning é usado.

    --no-raid-pause

    Continuar aceitando Jobs enquanto um array RAID md do host faz ressincronização, recuperação, verificação, reparo ou remodelação. Por padrão, o Runner verifica seus arrays md antes de pedir cada Job, e enquanto algum deles executa uma dessas operações ele pausa: não aceita nenhum Job novo até a sincronização terminar, e nunca interrompe um Job que já está em execução. Um Runner pausado continua conectado e continua se atualizando.

    --no-auto-update

    Desabilita as atualizações automáticas do servidor. Por padrão, o runner se atualiza entre os Jobs quando o servidor oferece uma nova versão. Também pode ser definida com a variável de ambiente BENCHER_NO_AUTO_UPDATE.

    --update-channel <UPDATE_CHANNEL>

    O canal de atualização para as atualizações automáticas, stable ou canary. Por padrão, stable é usado. O canal stable atualiza para releases versionadas. O canal canary acompanha o build canary contínuo, que é reconstruído sempre que o runner muda em um deploy do Bencher Cloud. Também pode ser definido com a variável de ambiente BENCHER_UPDATE_CHANNEL. Conflita com --no-auto-update.

    --max-download-size <MAX_DOWNLOAD_SIZE>

    O tamanho máximo de download em bytes para binários de auto-atualização. Por padrão, 500 MiB é usado. Conflita com --no-auto-update.

    --image-cache-size <IMAGE_CACHE_SIZE>

    O tamanho máximo em bytes dos blobs de Image que o Runner mantém entre Jobs. O Runner mantém os blobs de configuração e de camadas de cada Image que baixa em blobs, sob o seu --state-dir, de modo que um Job baixa apenas os blobs que o Runner ainda não tem, embora ainda busque o manifesto da sua Image no registro todas as vezes. Entre Jobs, o Runner remove os blobs usados há mais tempo até que o restante caiba neste tamanho. Ao iniciar, o Runner cria o armazenamento e o torna seu, e se não conseguir, encerra imediatamente com um erro que nomeia o diretório. Por padrão, ou com 0, o Runner não mantém blobs entre Jobs e remove os que tiver mantido antes. Também pode ser definido com a variável de ambiente BENCHER_IMAGE_CACHE_SIZE.

    --max-output-size <MAX_OUTPUT_SIZE>

    O tamanho máximo em bytes para stdout e stderr coletados. Por padrão, 25 MiB é usado.

    --max-file-count <MAX_FILE_COUNT>

    O número máximo de arquivos de saída a decodificar. Por padrão, 255 é usado.

    O número máximo de symlinks a seguir durante a resolução de caminho, correspondendo ao limite MAXSYMLINKS do kernel Linux. Por padrão, 40 é usado. Usado apenas no modo sem sandbox.

    Ajuste do Host

    Antes de cada benchmark, o runner aplica ajustes ao host para reduzir o ruído de medição. Por padrão, ele desabilita ASLR, o NMI watchdog, SMT / hyper-threading e o turbo boost; define o governor de escalonamento de CPU como performance, a swappiness como 10 e perf_event_paranoid como -1. Em um host que o runner não ajustou desde a última reinicialização, as flags abaixo mantêm otimizações individuais nos padrões do host.

    O ajuste do host persiste até que o host seja reiniciado: cada configuração é uma escrita apenas em tempo de execução que o runner não reverte ao sair, exceto a retenção dos C-states, que termina quando o runner sai. Após uma execução com ajuste, uma flag que pula qualquer outra configuração passa a valer a partir da próxima reinicialização: até lá, a configuração mantém o valor ajustado, e o runner registra esse valor ao iniciar. Reinicie um host compartilhado depois de remover o runner.

    --no-tuning

    Desabilita todas as otimizações de ajuste do host.

    --aslr

    Não desabilita o ASLR (padrão: desabilitado para benchmarks).

    --nmi-watchdog

    Não desabilita o NMI watchdog (padrão: desabilitado para benchmarks).

    --smt

    Não desabilita o SMT / hyper-threading (padrão: desabilitado para benchmarks).

    --turbo

    Não desabilita o turbo boost (padrão: desabilitado para benchmarks).

    --numa-balancing

    Não desabilita o balanceamento NUMA automático (padrão: desabilitado para benchmarks).

    --timer-migration

    Não desabilita a migração de timers (padrão: desabilitada para benchmarks).

    --soft-watchdog

    Não desabilita o soft lockup watchdog (padrão: desabilitado para benchmarks).

    --ksm

    Não desabilita o KSM, kernel samepage merging (padrão: desabilitado para benchmarks).

    --cstates

    Não desabilita os C-states profundos (padrão: desabilitados para benchmarks).

    --no-irq-steering

    Não direciona as IRQs de dispositivos e as workqueues do kernel para os núcleos de housekeeping.

    --no-cpuset-partition

    Não desacopla os núcleos de benchmark do escalonador por meio de uma partição cpuset isolada.

    --swappiness <SWAPPINESS>

    Define o valor de swappiness. Por padrão, 10 é usado.

    --governor <GOVERNOR>

    Define o governor de escalonamento de CPU. Por padrão, performance é usado.

    --perf-event-paranoid <PERF_EVENT_PARANOID>

    Define o valor de perf_event_paranoid. Por padrão, -1 é usado.

    --thp <THP>

    Define o modo de transparent hugepage do host: never, madvise, always ou leave. Use leave para manter o modo atual do host. Por padrão, never é usado.

    --help

    Imprime a ajuda.

Consulte a referência da CLI runner para todas as opções, e a referência do Runner Protocol para as mensagens trocadas com o servidor.

Manutenção do host

Um Runner e a manutenção do seu host, como atualizações de pacotes ou uma verificação de RAID, se revezam, de modo que a manutenção nunca roda durante um Job. Eles se revezam por meio de /run/bencher, um diretório que um Runner iniciado como root cria apenas para root:

  • O bloqueio de Job, /run/bencher/job.lock. O Runner o obtém antes de pedir um Job e o mantém até essa consulta terminar sem Job ou terminar o Job que ela trouxer. A manutenção espera pelo bloqueio, então só começa entre Jobs, e um Runner que encontra o bloqueio em uso não aceita nenhum Job até que ele fique livre.
  • Marcadores de manutenção, /run/bencher/maintenance ou /run/bencher/maintenance.<name>. A manutenção cria seu próprio marcador antes de esperar pelo bloqueio e o remove quando termina. Enquanto existir algum marcador, o Runner não pede nenhum Job e deixa o bloqueio livre, então a manutenção em espera começa assim que terminar a consulta atual do Runner, ou o Job que essa consulta trouxer.

Enquanto espera pela manutenção, um Runner fica pausado: continua conectado e continua se atualizando. runner run também espera pelo bloqueio de Job, e avisa, mas roda, enquanto existir um marcador ou um array md estiver sincronizando. Um Runner que não é root não consegue abrir o bloqueio de Job nem ver os marcadores, então aceita Jobs sem eles.

Com o systemd, um drop-in faz uma unidade de manutenção esperar sua vez. Por exemplo, /etc/systemd/system/fstrim.service.d/zz-bencher-quiet.conf envolve o próprio comando da unidade em flock -F. O ExecStart= vazio apaga esse comando, então a linha seguinte deve repeti-lo exatamente:

[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 ocupa o descritor de arquivo 3, então o bloqueio continua em uso mesmo que o comando reabra o descritor 3, como faz o apt.systemd.daily. Enquanto o host se prepara para desligar, ExecStopPost= mantém o marcador, então o Runner não aceita nenhum Job antes que a reinicialização esvazie /run.

Esvaziar um Runner

Para esvaziar um Runner, crie o marcador simples como root:

Terminal window
touch /run/bencher/maintenance

O Runner termina sua consulta atual, no máximo --poll-timeout segundos, e qualquer Job em execução ou trazido por essa consulta, e depois não aceita nenhum outro Job até o marcador sumir:

Terminal window
rm /run/bencher/maintenance

/run é esvaziado na inicialização, então uma reinicialização também remove o marcador.

Fluxo de Trabalho do Self-Hosted Runner

Uma vez que o Runner está conectado, você envia benchmarks da mesma forma que com qualquer Bare Metal Runner: construa e publique uma Image, depois execute bencher run --image com uma Spec correspondente. O diagrama abaixo mostra o caminho completo, desde o registro do Runner até a execução de um 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

Continue: Referência da CLI runner ➡

🤖 Este documento foi traduzido automaticamente por IA. Pode não ser preciso e pode conter erros. Se você encontrar algum erro, abra um problema no GitHub.


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