Pular para o conteúdo

Troubleshooting

certificate has expired ou divergência de hostname

Os certificados de desenvolvimento duram 30 dias. Regenere:

Terminal window
npm run dev:certs

O 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 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.

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=real mais as chaves.
  • Para o catálogo inteiro sem credencial: RUNNER_CONNECTORS=demo.

O sinal está no log de boot:

connector not served {connectorId: "azion", missing: ["AZION_TOKEN"]}

Três causas, em ordem de frequência:

  1. 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.
  2. O nome está diferente. O connector azion declara AZION_TOKEN; um cofre com AZION_API_TOKEN produz exatamente o mesmo sintoma de chave ausente. Confira os nomes exatos em Credenciais por connector.
  3. O segredo não existe no store. Nos backends remotos, “não encontrado” resolve para undefined em vez de exceção, de propósito, para o modelo lazy funcionar.
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.

O master switch sozinho não basta: o exporter default é none. Veja Telemetria.

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.

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.

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 window

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.

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 os connector not served.
  • O Fleet do control-plane, que enxerga o runner conectado e o status por connector reportado no handshake.
  • OTel, se ligado corretamente.