Troubleshooting
A imagem não baixa
Seção intitulada “A imagem não baixa”no basic auth credentials ou denied no docker pull
O registry é privado e você ainda não autenticou, ou a identidade que está puxando não é a que autorizamos. Veja Autenticar antes do pull.
Se o pull falha dentro do orquestrador mas funciona no seu terminal, a identidade é outra: quem puxa ali é a plataforma (task execution role, role do node group, service account), não você. Nos informe qual é para autorizarmos.
O runner não conecta
Seção intitulada “O runner não conecta”certificate has expired
O certificado do seu tenant venceu. Ele não se renova sozinho: solicite o par novo ao RootPilot,
substitua o conteúdo montado em /certs e recicle as réplicas.
Para não ser pego de novo, o notAfter sai no log a cada boot:
{"level":"info","message":"using provisioned cert","context":"runner","notAfter":"..."}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 (provisioned cert) 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 windowDo lado do Agent, a mesma situação aparece assim — e a segunda linha só existe quando o host ficou sem contar o tempo (suspensão, snapshot, container sem CPU):
{"level":"error","message":"identity expired beyond the renewal grace window — pausing reconnects"}{"level":"warn","message":"renewal deadline slipped — wall-clock advanced while this process was not running"}Enquanto isso o Agent para de reconectar, em vez de repetir o handshake que já se sabe recusado. As
quedas desse período saem rotuladas identity_expired, e não network_error: um certificado vencido
fecha o socket sem aviso, o que parece queda de rede e manda investigar o lado errado.
Perdi 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.