Saltar a contenido

Operador de plataforma (logs y observabilidad)

Runbook del operador de la plataforma: cómo revisar los logs del cluster con Loki (vía Grafana o MCP), detectar problemas reales, y mantener el ruido a raya para que, el día que haya que diagnosticar un problema concreto, los logs solo contengan lo relevante.

Objetivo de higiene: si un día entras a mirar los logs por un problema, no quieres toparte con miles de warnings/errores de cosas que no tienen nada que ver. Este runbook describe cómo conseguirlo y mantenerlo.

1. Arquitectura de logs (resumen)

pods (stdout/stderr) ─┐
                       ├─▶ Alloy (DaemonSet, 3 nodos) ─▶ Loki (single-binary) ─▶ Grafana (datasource "Loki")
journald de nodos ─────┘        loki.source.kubernetes     monitoring ns :3100        Explore / MCP
  • Alloy (monitoring, DaemonSet) descubre pods vía API de k8s (loki.source.kubernetes) y lee el journald de cada nodo (loki.source.journal).
  • Loki (monitoring, single-binary) agrega y sirve los logs. Sin auth (auth_enabled: false), acceso intra-cluster.
  • Grafana (desplegada por kube-prometheus-stack) tiene el datasource Loki.

Acceso:

Cosa Valor
Grafana (UI) https://grafana.local.gorkarevilla.com
Datasource Loki uid 51e7573e-2948-4636-9a2b-014c6d87ce9c
Datasource Prometheus uid 04a2c368-db7a-47db-9927-39cbbd9d3c6b
Logs en vivo kubectl -n <ns> logs <pod> -c <container>

2. Esquema de labels

Los logs llegan con dos familias de labels según su origen.

Logs de pods (loki.source.kubernetes.pods)

Label Contenido Ejemplo
namespace namespace del pod argocd
pod nombre del pod argocd-repo-server-7c4c665579-rbgxx
node nodo donde corre kobe / kyrie / kawhi
instance namespace/pod:container — identificador del contenedor argocd/argocd-repo-server-...:repo-server
job siempre loki.source.kubernetes.pods —
service_name idem (label interno del componente) loki.source.kubernetes.pods
stream stdout / stderr —

⚠️ No existe label container (el relabel que lo mapea no llega a materializarse como label de stream). Para filtrar por contenedor usa instance=~"namespace/pod:container" o directamente pod.

Logs de journald (loki.source.journal.journal)

Label Contenido Ejemplo
node nodo del journal kobe
unit unidad systemd k3s.service, snapd.service, ssh.service
job loki.source.journal.journal —

Nivel de log (detected_level)

Loki infiere el nivel y lo expone como structured metadata (detected_level: debug/info/warn/error/critical). Se filtra en el pipeline (no en el selector):

{namespace="argocd"} | detected_level="error"
{namespace="argocd"} | detected_level=~"error|warn"

Los logs sin nivel reconocible (p. ej. muchas líneas de k8s client-go) directamente carecen de la metadata detected_level (no se les asigna info). Para pillarlos hay que filtrar por texto del mensaje (|= "Warning:", etc.), no por detected_level.

3. Consultas básicas (LogQL)

En Grafana Explore, datasource Loki, modo code. O vía MCP.

# Últimos logs de un namespace
{namespace="argocd"}

# Errores de un namespace
{namespace="argocd"} | detected_level="error"

# Errores y warnings de un servicio concreto (por pod)
{pod=~"argocd-repo-server-.*"} | detected_level=~"error|warn"

# Contar errores por namespace en 24h (instant)
sum by (namespace) (count_over_time({namespace=~".+"} | detected_level="error" [24h]))

# Contar volumen por pod en un namespace (encuentra el pod verboso)
sum by (pod) (count_over_time({namespace="monitoring"} [24h]))

# Logs de un contenedor concreto (via instance)
{instance=~"argocd/argocd-repo-server-.*:repo-server"}

# journald de un nodo/unidad (p. ej. k3s)
{unit="k3s.service"}
{node="kobe"} | detected_level="error"

4. Checklist de operación (paso a paso)

  1. Visión global (24h) — volumen y errores por namespace:

    sum by (namespace) (count_over_time({namespace=~".+"} [24h]))
    sum by (namespace) (count_over_time({namespace=~".+"} | detected_level="error" [24h]))
    
    Lo normal es que monitoring (el propio stack de observabilidad) domine el volumen; eso es ruido, no un problema.

  2. Localizar el pod verboso dentro del namespace que interese:

    sum by (pod) (count_over_time({namespace="<ns>"} [24h]))
    

  3. Muestrear mensajes distintos (no las últimas N líneas, que se repiten):

    {namespace="<ns>", pod="<pod>"} | detected_level=~"error|warn"
    
    o el tool query_loki_patterns para ver los patrones que más se repiten.

  4. Clasificar cada mensaje repetido: ¿error real, o ruido conocido? Consultar §6.

  5. Actuar:

  6. Error real → abrir issue / arreglar (y actualizar la página del servicio).
  7. Ruido conocido → ver si tiene fix (§7) o simplemente ignorarlo en el filtro de la consulta.

5. Cómo se identifica un problema real vs ruido

  • Ruido: se repite con cadencia fija, no coincide con ninguna caída, el servicio funciona bien (dashboards verdes, endpoints 200), y el mensaje es cosmético (context canceled, deprecation warnings, health checks, access logs). No correlaciona con detected_level que suba en el resto.
  • Problema real: aparece tras un cambio (deploy/config), se acompaña de errores en cascada en servicios dependientes, o correlaciona con métricas malas (crashloops, restarts, peticiones 5xx). Un pico de detected_level="error" nuevo en un servicio que antes estaba limpio es la señal más clara.

6. Catálogo de ruido conocido

Cosas que ya están catalogadas y se pueden ignorar. Si encuentras un mensaje que se repite y no está aquí, añádelo (es la forma de mantener la higiene).

Servicio Mensaje (fragmento) Nivel Por qué es ruido Acción
CoreDNS (kube-system) No files matching import glob pattern: /etc/coredns/custom/*.server / *.override warn k3s incluye import /etc/coredns/custom/* en el Corefile pero no crea el directorio Arreglado (ConfigMap coredns-custom)
Longhorn (longhorn-system) Warning: v1 Endpoints is deprecated in v1.33+ warn Deprecation warning de client-go en longhorn-manager Se corrige al actualizar Longhorn
Longhorn instance-manager Error reading from wire ... use of closed network connection error Cierre normal de conexiones gRPC de datos al attach/detach Ignorar
Loki (monitoring, loki-0) error processing requests from scheduler ... context canceled error Cancelación normal de queries (Grafana Live/Explore) Ignorar
Loki Error receiving response from grpc tail client ... context canceled + reached tail max duration limit error Tail de Grafana Live que se cancela Ignorar
Grafana (monitoring) Error writing to response ... does not allow body error Respuestas 204 sin body (avatar, etc.) Ignorar
Argo CD repo-server Error serving health check request ... context canceled error Health-check gRPC que cancela al estar el repo aún listándose Ignorar (conocido)
k3s (k3s.service / k3s-agent.service) Proxy error: write failed: io: read/write on closed pipe warn Proxy TLS del API server al cerrar una conexión Ignorar
k3s (kube-controller-manager) Failed to list /registry/events/ ... mvcc: required revision has been compacted error Reflector de events que quedó atrás tras compactar etcd Ignorar (conocido)
MetalLB speaker memberlist: Failed fallback TCP ping: timeout ... i/o timeout error memberlist sí se usa en L2 (lista de speakers activos → elección del anunciador y failover). El fallback TCP puntual con UDP OK es menor, pero si se repite conviene revisar la conectividad del puerto 7946 entre nodos Investigar si sube la cadencia
CNPG operator Error retrieving lease lock ... context canceled error Leader election que cancela al reconciliar Ignorar
Reloader KUBERNETES_NAMESPACE is unset, will detect changes in all namespaces warn Aviso de arranque; es el comportamiento deseado Ignorar (o setear namespace)
VKS HealthChecks.DefaultHealthCheckService[103] warn Health-check de .NET que falla de forma puntual Investigar si sube la cadencia
Traefik SafeNaming is not explicitly set ... warn Aviso de migración v3.6, una vez por restart Arreglado (safeNaming: true)
Traefik Traefik can reject some encoded characters in the request path ... warn Aviso incondicional de arranque (v3.7.12, cmd/traefik/traefik.go); no se silencia por config Ignorar (el hardening encodedCharacters: false ya está aplicado)

7. Cómo reducir / silenciar ruido

Orden de preferencia (de más limpio a más invasivo):

  1. Arreglar la causa (mejor): p. ej. crear el dir de coredns/custom, actualizar Longhorn, setear SafeNaming en Traefik.
  2. Bajar el nivel de log del servicio ruidoso (Loki, Alloy).
  3. Desactivar access logs (loki-gateway nginx).
  4. Ajustar frecuencia (loki-canary push interval).
  5. Drop en origen (Alloy): como último recurso, descartar un patrón en el pipeline de Alloy antes de enviarlo a Loki (solo si no se puede arreglar en el servicio).

Los cambios de config van en clusters/luka/apps/<svc>/application.yaml (Helm valuesObject) o en k3s/ansible/roles/<svc>/ (plataforma core instalada por Ansible), con su PR. Cada cambio de ruido debe reflejarse en el §6 y en la página del servicio (docs/services/<svc>.md → ## Monitorización o ## Notas / TODO).

Aplicado (estado actual)

Servicio Cambio Fichero
Loki log_level: warn (mata el log de cada query) clusters/luka/apps/loki/application.yaml
Loki gateway.verboseLogging: false (mata el access log nginx) ídem
Loki lokiCanary.extraArgs: ["-interval=1m"] (canary cada minuto) ídem
Alloy logging { level = "warn" } (mata el opened log stream) clusters/luka/apps/alloy/application.yaml
Kured sysctl fs.inotify.max_user_instances=512 + max_user_watches=524288 (fix too many open files) k3s/ansible/roles/longhorn-prereqs/tasks/main.yml
CoreDNS ConfigMap coredns-custom con placeholders .server/.override k3s/ansible/roles/coredns-custom/
Traefik providers.kubernetesCRD.safeNaming: true k3s/ansible/roles/traefik/files/values.yaml

Pendiente de aplicar (requieren decisión/manual)

Servicio Ruido Fix propuesto
Longhorn v1 Endpoints deprecation Sin fix aún: ya está en la última versión (1.12.1); lo arreglará una futura release de Longhorn con client-go actualizado

8. Incidencias detectadas (no son ruido)

Hallazgos que requieren atención, fuera del ruido rutinario:

  1. Kured: failed to create fsnotify watcher: too many open files — root cause confirmado: fs.inotify.max_user_instances (default 128) agotado (kawhi 143 en uso, kobe 134, kyrie 93). inotify_init() devuelve EMFILE y kured no puede vigilar el sentinel de reinicio. Resuelto: fs.inotify.max_user_instances=512
  2. max_user_watches=524288 vía /etc/sysctl.d/90-homelab-inotify.conf (role Ansible longhorn-prereqs), ya aplicado en los 3 nodos y verificado. Ver docs/services/kured.md.

  3. Corte transitorio ~11:10–11:16 (07/09) — fallos de DNS (lookup api.github.com ... connection refused en ARC), Vaultwarden Can't connect to database, CNPG context canceled y restarts de varios pods (Traefik, Reloader, cloudflared). Consistente con un reinicio de nodo/CoreDNS puntual. No persistente; si se repite, correlacionar con reinicios de kured o eventos de nodo.

  4. Incidentes del 24/09 — errores de I/O en los volúmenes iSCSI de Longhorn en kyrie (cascada de restarts), 502 en Heimdall por authentik-server evictado durante los drenajes fallidos de kured (kernel pendiente), y VKS en crash-loop. Documentados por separado en la sección Incidencias.

9. Análisis por servicio (referencia)

Cada servicio tiene su página en docs/services/. Aquí, el dato de log mínimo para consultarlo: qué namespace/pod y qué buscar.

Servicio Namespace Qué mirar
k3s kube-system + journald unit=k3s*.service errores de nodo, mvcc compacted, Proxy error
CoreDNS kube-system (pod coredns-*) warnings de import glob (ruido §6)
MetalLB metallb-system speaker/controller, memberlist (ruido §6)
Traefik traefik access/error logs del gateway, SafeNaming
Argo CD argocd application-controller (sync), repo-server (health-check §6), notifications-controller
cert-manager cert-manager renovación de certificados, errores de ACME
Longhorn longhorn-system longhorn-manager (deprecation §6), csi-plugin, instance-manager
CloudNativePG cnpg-system operador, backups (vaultwarden-db-daily-backup)
Reloader reloader reinicios por cambio de Secret/ConfigMap
Kured kured fsnotify (incidencia §8), reinicios de nodo
VKS vaultwarden-kubernetes-secrets sync colección Homelab → Secrets
cloudflared cloudflared estado del túnel
kube-prometheus-stack monitoring Prometheus, Grafana, node-exporter, kube-state-metrics
Loki monitoring (loki-0) errores de ingesta/consulta
Alloy monitoring (alloy-*) errores de write a Loki
grafana-operator monitoring reconciliación de CRs de Grafana
ARC arc-systems + arc-runners runners self-hosted, jobs de CI
pgAdmin pgadmin conexiones a Postgres
Vaultwarden vaultwarden errores de auth/DB
Heimdall heimdall poco tráfico

10. Notas de operación

  • Tus propias consultas generan logs: cada query a Loki produce varias líneas metrics.go en loki-0. No te asustes al ver picos de monitoring mientras exploras; es normal.
  • Retención: Loki 30 días, Prometheus 15 días. Los logs no se backupean.
  • Verbosidad esperada por diseño: monitoring, argocd y arc-runners son los namespaces más ruidosos; es inherente al stack. Antes de "optimizar" un servicio, mira si su ruido ya está catalogado en §6.
  • Tras cualquier cambio de config de logging, verificar el impacto repitiendo el checklist (§4) y actualizar este runbook.