Ir al contenido

Solución de problemas

no basic auth credentials o denied en docker pull

El registry es privado y todavía no te autenticaste, o la identidad que hace el pull no es la que autorizamos. Consulta Autenticarse antes del pull.

Si el pull falla dentro del orquestador pero funciona desde tu terminal, la identidad es otra: ahí quien hace el pull es la plataforma (task execution role, role del node group, service account), no tú. Dinos cuál es para que podamos autorizarla.

certificate has expired

El certificado de tu tenant venció. No se renueva solo: pide un par nuevo a WhyStack, reemplaza el contenido montado en /certs y recicla las réplicas.

Para que no te vuelva a pasar, el notAfter sale en el log en cada boot:

{"level":"info","message":"using provisioned cert","context":"runner","notAfter":"..."}

is not in the cert's altnames

Te conectaste por un hostname que no está en el SAN del certificado del túnel. Aunque resuelva a la IP correcta, TLS lo rechaza. En producción el hostname es tunnel.whystack.ai.

No pasa nada, y no hay error

Confirma que hay un túnel de control-plane escuchando en RUNNER_TUNNEL_URL. En desarrollo el valor por defecto es :8443 en el host; en Linux, Compose ya mapea host.docker.internal vía host-gateway.

Un ciclo 1006 → exited 0 → restart

El túnel del otro lado se está reiniciando. Estabilízalo y el runner deja de ciclar. El exited 0 se debe a que el timer de reconexión está marcado con unref; restart: unless-stopped lo trae de vuelta cuando el túnel vuelve.

El runner valida la configuración por schema y muere con el motivo. Los más comunes:

Mensaje Qué hacer
either provide RUNNER_CERT_PEM+RUNNER_KEY_PEM (provisioned cert) or RUNNER_ENROLL_URL Elige uno de los dos caminos de identidad.
RUNNER_ATTESTATION_MODE=bootstrap-token requires RUNNER_BOOTSTRAP_TOKEN Falta el token. El fail-fast es intencional: sin él, el síntoma se vuelve un loop de 401 que parece un problema de red.
RUNNER_ATTESTATION_MODE=aws-sts requires AWS_REGION La región no se adivina, a propósito.
RUNNER_SECRET_STORE_MODE=vault requires RUNNER_VAULT_ADDR Configuración de Vault incompleta.
attestation mode 'eks-oidc' not implemented eks-oidc no existe. En EKS, usa aws-sts.
op "…" claimed by both datadog and grafana Los dos providers de observabilidad están registrados. Elige uno en RUNNER_OBSERVABILITY.
Could not locate the bindings file La imagen se construyó con --ignore-scripts. better-sqlite3 es un addon nativo y necesita su postinstall.

Síntomas típicos: entries.map is not a function, u “op no implementada”.

Estás en synthetic, que es el valor por defecto y es solo un seed de 3 operaciones cuyas formas no coinciden con las de los connectors reales.

  • Para datos reales: RUNNER_CONNECTORS=real más las claves.
  • Para el catálogo entero sin credenciales: RUNNER_CONNECTORS=demo.

La señal está en el log de boot:

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

Tres causas, en orden de frecuencia:

  1. La clave nunca llegó al contenedor. El pass-through de Compose es por nombre y explícito: una variable ausente en el host es una clave omitida en el contenedor. Verifica que el nombre esté en la lista del docker-compose.yml.
  2. El nombre es distinto. El connector azion declara AZION_TOKEN; un vault que guarda AZION_API_TOKEN produce exactamente el síntoma de clave ausente. Verifica los nombres exactos en Credenciales por connector.
  3. El secreto no está en el store. En los backends remotos, “no encontrado” se resuelve como undefined en vez de una excepción, a propósito, para que el modelo lazy funcione.
skipped: no cluster configured (EKS_CLUSTER)

Falta EKS_CLUSTER. Vale la pena distinguirlo: el connector aparece como no configurado en la Flota, que es distinto de roto, e igual de ciego.

El health check de EKS dice EKS cluster não encontrado nesta conta

Sección titulada «El health check de EKS dice 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)

La credencial funciona: el Agent llegó a la API de AWS, y el clúster que nombra EKS_CLUSTER no existe en la cuenta y la región que lee. EKS_CLUSTER es por Agent, y cada Agent lee una sola cuenta. El caso típico es una flota con un Agent por cuenta y el mismo valor en todos, donde solo el Agent de la cuenta correcta encuentra el clúster.

Rotar el secreto no ayuda. Apunta cada Agent a un clúster de su propia cuenta (el mensaje lista los que existen ahí), o quita la variable del Agent cuya cuenta no tiene clúster, y el probe vuelve a skipped, que en ese caso es el estado verdadero. Sin el permiso eks:ListClusters, el mensaje sale sin la lista.

El interruptor maestro por sí solo no alcanza: el exporter por defecto es none. Consulta Telemetría.

Esta es la pregunta de soporte más frecuente, y la respuesta importa: null aquí significa “no sé”, nunca “es cero”. La distinción es deliberada, y salió de una clase de incidente en la que un 0 fabricado se leyó como salud.

Tres reglas que sigue el edge:

1. Antes de devolver null, intenta la otra fuente. Tener la tasa de error no termina la lectura de latencia: si los percentiles no llegan listos y hay spans crudos disponibles, se calculan a partir de ellos, y el resultado dice de dónde salió (percentileSource: 'spans').

2. Un null que sobrevive lleva el código del motivo. Campos como percentilesUnavailable e instancesUnavailable existen para que “no se emitió” y “no consulté” dejen de verse idénticos.

3. Un payload vacío prueba si la pregunta existía. Un nombre de servicio que no coincide con ningún servicio de APM vuelve con serviceResolved: false y una lista de candidatos, en vez del mismo null generalizado que produciría un servicio real y silencioso.

Y una métrica derivada de un denominador cero es null, nunca 0: una razón sin denominador es indefinida, y 0% es lo opuesto de “no sé”: lleva a la acción opuesta.

No es un bug. Datadog envía todo stderr a status:error, así que una búsqueda de errores puede devolver líneas cuyo texto dice otra cosa.

El borde expone la divergencia con textLevel y levelMismatch, en vez de reescribir level. La clasificación de la fuente es un hecho sobre la fuente, y corregirla en silencio borraría información.

Si el certificado pasó 6 minutos de su vencimiento (5 de gracia del control-plane, 1 de margen para skew), la identidad es irrecuperable: la atestación de renovación prueba la posesión con el certificado que murió.

El runner sale del proceso a propósito, para que el supervisor levante un reemplazo que hace el enrollment desde cero. Confirma que tu supervisor esté configurado para eso. Sin eso, el efecto es un runner que desaparece.

El mensaje del lado del control-plane:

presented cert expired beyond the renewal grace window

Del lado del Agent, la misma situación aparece así, y la segunda línea solo existe cuando el host dejó de contar el tiempo (suspensión, snapshot, un contenedor sin 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"}

Mientras tanto el Agent deja de reconectarse, en vez de repetir un handshake que ya sabe que será rechazado. Las caídas de ese período se etiquetan como identity_expired, no network_error: un certificado vencido cierra el socket sin aviso, lo que parece una caída de red y te manda a investigar el lado equivocado.

Es lo esperado en un reclaim duro (SIGKILL, OOM, spot sin drain). El flush periódico (RUNNER_LEARNED_FLUSH_INTERVAL_MS, por defecto 60 s) acota la pérdida; no la elimina.

Recuerda también que el learned-store local es una caché: la fuente de verdad es el control-plane. En Kubernetes, dimensiona terminationGracePeriodSeconds por encima de RUNNER_DRAIN_TIMEOUT_MS (por defecto 120 s) para que el drain pueda completarse.

Cómo observar un runner que no expone ningún puerto

Sección titulada «Cómo observar un runner que no expone ningún puerto»

No hay probe HTTP, porque el runner es un cliente de salida. Las señales disponibles son:

  • whystack-health, desde dentro del contenedor: live (el proceso está corriendo) y ready (enrollment hecho, túnel conectado, hello aceptado). Es lo que usan el HEALTHCHECK de la imagen y los probes exec de Kubernetes, y su mensaje de falla dice qué condición falta.
  • Logs de boot: boot: liveness, boot: ready y cualquier connector not served.
  • La vista Flota del control-plane, que ve el runner conectado y el estado por connector reportado en el handshake.
  • OTel, si está activado correctamente.