Pular para o conteúdo

Troubleshooting

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.

certificate has expired

O certificado do seu tenant venceu. Ele não se renova sozinho: solicite o par novo ao WhyStack, 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.whystack.ai.

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

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 na Frota, que é diferente de quebrado, e igualmente cego.

O health check do EKS diz EKS cluster não encontrado nesta conta

Seção intitulada “O health check do EKS diz EKS cluster não encontrado nesta conta”
EKS cluster não encontrado nesta conta: orders-cluster (região us-east-1; clusters ali: data-cluster)

A credencial funciona: o Agent chegou à API da AWS, e o cluster que o EKS_CLUSTER nomeia não existe na conta e na região que ele lê. O EKS_CLUSTER é por Agent, e cada Agent lê uma conta só. O caso típico é uma frota com um Agent por conta e o mesmo valor em todos, em que só o Agent da conta certa acha o cluster.

Rotacionar o segredo não resolve. Aponte cada Agent para um cluster da própria conta (a mensagem lista os que existem ali) ou tire a variável do Agent cuja conta não tem cluster, e o probe volta a skipped, que nesse caso é o estado verdadeiro. Sem a permissão eks:ListClusters, a mensagem sai sem a lista.

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

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

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:

  • O whystack-health, de dentro do contêiner: live (o processo anda) e ready (enrollado, túnel conectado, hello aceito). É o que o HEALTHCHECK da imagem e os probes exec do Kubernetes usam, e a mensagem de falha diz qual das condições faltou.
  • Logs de boot: boot: liveness, boot: ready, e os connector not served.
  • A Frota do control-plane, que enxerga o runner conectado e o status por connector reportado no handshake.
  • OTel, se ligado corretamente.