Troubleshooting
O runner não conecta
Seção intitulada “O runner não conecta”certificate has expired ou divergência de hostname
Os certificados de desenvolvimento duram 30 dias. Regenere:
npm run dev:certsO SAN cobre host.docker.internal. Se ainda assim falhar, RUNNER_TLS_INSECURE_SKIP_VERIFY=true
destrava. O túnel continua validando o certificado do runner; só o runner para de checar o do
servidor. Escotilha de desenvolvimento, não leve para produção.
is not in the cert's altnames
Você conectou por um hostname que não está no SAN do certificado do túnel. Mesmo que ele resolva para
o IP certo, o TLS recusa. Em produção o hostname é tunnel.rootpilot.sh.
Nada acontece, e não há erro
Confirme que existe um túnel de control-plane escutando no RUNNER_TUNNEL_URL. Em desenvolvimento o
default é :8443 no host; no Linux, o Compose já mapeia host.docker.internal via host-gateway.
Ciclo 1006 → exited 0 → restart
O túnel do outro lado está reiniciando. Estabilize-o e o runner para de ciclar. O exited 0 é o timer
de reconexão ser unref’d; o restart: unless-stopped o traz de volta quando o túnel volta.
O boot falha imediatamente
Seção intitulada “O boot falha imediatamente”O runner valida a configuração por schema e morre com o motivo. Os mais comuns:
| Mensagem | O que fazer |
|---|---|
either provide RUNNER_CERT_PEM+RUNNER_KEY_PEM (dev shortcut) or RUNNER_ENROLL_URL |
Escolha um dos dois caminhos de identidade. |
RUNNER_ATTESTATION_MODE=bootstrap-token requires RUNNER_BOOTSTRAP_TOKEN |
Falta o token. O fail-fast é intencional: sem ele o sintoma vira 401 em loop, que parece problema de rede. |
RUNNER_ATTESTATION_MODE=aws-sts requires AWS_REGION |
A região não é adivinhada de propósito. |
RUNNER_SECRET_STORE_MODE=vault requires RUNNER_VAULT_ADDR |
Configuração de Vault incompleta. |
attestation mode 'eks-oidc' not implemented |
eks-oidc não existe. No EKS, use aws-sts. |
op "…" claimed by both datadog and grafana |
Os dois providers de observabilidade registrados. Escolha um em RUNNER_OBSERVABILITY. |
Could not locate the bindings file |
A imagem foi construída com --ignore-scripts. O better-sqlite3 é addon nativo e precisa do postinstall. |
Uma ferramenta responde formato errado
Seção intitulada “Uma ferramenta responde formato errado”Sintomas típicos: entries.map is not a function, ou “op não implementada”.
Você está em synthetic, que é o default e é apenas um seed de 3 operações com formatos que não
batem com os connectors reais.
- Para dados reais:
RUNNER_CONNECTORS=realmais as chaves. - Para o catálogo inteiro sem credencial:
RUNNER_CONNECTORS=demo.
Um connector sumiu
Seção intitulada “Um connector sumiu”O sinal está no log de boot:
connector not served {connectorId: "azion", missing: ["AZION_TOKEN"]}Três causas, em ordem de frequência:
- A chave não chegou ao container. O pass-through do Compose é por nome e explícito: variável
ausente no host é chave omitida no container. Confira se o nome está na lista do
docker-compose.yml. - O nome está diferente. O connector
aziondeclaraAZION_TOKEN; um cofre comAZION_API_TOKENproduz exatamente o mesmo sintoma de chave ausente. Confira os nomes exatos em Credenciais por connector. - O segredo não existe no store. Nos backends remotos, “não encontrado” resolve para
undefinedem vez de exceção, de propósito, para o modelo lazy funcionar.
O health check do EKS diz skipped
Seção intitulada “O health check do EKS diz skipped”skipped: no cluster configured (EKS_CLUSTER)Falta EKS_CLUSTER. Vale distinguir: o connector aparece como não-configurado no Fleet, que é
diferente de quebrado, e igualmente cego.
Ligei o OTel e não vejo span nenhum
Seção intitulada “Ligei o OTel e não vejo span nenhum”O master switch sozinho não basta: o exporter default é none. Veja
Telemetria.
Por que um campo vem null
Seção intitulada “Por que um campo vem null”Esta é a pergunta de suporte mais frequente, e a resposta importa: null aqui significa “não sei”,
e nunca “é zero”. A distinção é deliberada, e veio de uma classe de incidente em que um 0
fabricado foi lido como saúde.
Três regras que o edge segue:
1. Antes de devolver null, tenta a outra fonte. Ter taxa de erro não encerra a leitura de
latência: se os percentis não vierem prontos e houver spans crus disponíveis, eles são calculados a
partir dali, e o resultado diz de onde veio (percentileSource: 'spans').
2. O null que sobrevive carrega o código do motivo. Campos como percentilesUnavailable e
instancesUnavailable existem para que “não foi emitido” e “não consultei” parem de ter a mesma cara.
3. Payload vazio prova se a pergunta existia. Um nome de serviço que não bate com nenhum serviço de
APM volta com serviceResolved: false e a lista de candidatos, em vez do mesmo null geral que um
serviço real e silencioso produziria.
E métrica derivada de denominador zero é null, nunca 0: uma razão sem denominador é indefinida, e
0% é o oposto de “não sei”: leva à ação oposta.
Um log de erro contém [INFO]
Seção intitulada “Um log de erro contém [INFO]”Não é bug. O Datadog manda todo stderr para status:error, então uma busca por erro pode devolver
linhas cujo texto diz outra coisa.
A borda expõe a divergência com textLevel e levelMismatch, em vez de reescrever o level. A
classificação da fonte é um fato sobre a fonte, e corrigi-la em silêncio apagaria informação.
O runner morreu e não voltou
Seção intitulada “O runner morreu e não voltou”Se o certificado passou de 6 minutos além do vencimento (5 de graça do control-plane, 1 de margem para skew), a identidade é irrecuperável: a atestação de renovação prova posse com o certificado que morreu.
O runner sai do processo de propósito, para que o supervisor suba um substituto que enrolla do zero. Confirme que o seu supervisor está configurado para isso. Sem ele, o efeito é um runner que some.
Mensagem do lado do control-plane:
presented cert expired beyond the renewal grace windowPerdi estado aprendido depois de um kill
Seção intitulada “Perdi estado aprendido depois de um kill”Esperado num reclaim duro (SIGKILL, OOM, spot sem drain). O flush periódico
(RUNNER_LEARNED_FLUSH_INTERVAL_MS, default 60 s) limita a perda; não a elimina.
Lembre também que o learned-store local é cache: a fonte de verdade é o control-plane. Em
Kubernetes, dimensione terminationGracePeriodSeconds acima de RUNNER_DRAIN_TIMEOUT_MS (default
120 s) para que o drain complete.
Como observar um runner que não expõe porta
Seção intitulada “Como observar um runner que não expõe porta”Não existe probe HTTP, porque o runner é cliente de saída. Os sinais disponíveis são:
- Logs de boot:
boot: liveness,boot: ready, e osconnector not served. - O Fleet do control-plane, que enxerga o runner conectado e o status por connector reportado no handshake.
- OTel, se ligado corretamente.