Saltar a contenido

Runbook — observability-stack

Deploy

Dónde corre: VM devops de la nube, Docker Compose, directorio /opt/monitoring (checkout de este repo en la rama main). Contenedores: prometheus, alertmanager, grafana, loki, blackbox_exporter.

Automático: cada push a main dispara el workflow deploy.yml, que entra por SSH (secrets SERVER_HOST, SERVER_USER, SSH_KEY) y ejecuta exactamente esto:

cd /opt/monitoring
git pull
# Hot-reload sin reiniciar contenedores
curl -s -X POST http://localhost:9090/-/reload   # prometheus
curl -s -X POST http://localhost:9093/-/reload   # alertmanager
# Si hay servicios nuevos en el compose:
docker compose up -d --remove-orphans

Manual: correr el mismo bloque por SSH en la VM. Si el cambio toca blackbox/blackbox.yml, loki/config.yml o el provisioning de Grafana, el hot-reload no alcanza: reiniciar ese contenedor.

docker compose restart blackbox_exporter   # o loki / grafana

Grafana relee los dashboards provisionados cada 10 s (updateIntervalSeconds).

Cómo verificar que el deploy salió bien:

curl -s localhost:9090/-/healthy && curl -s localhost:9093/-/healthy
curl -s localhost:3100/ready && curl -s localhost:3200/api/health
docker compose ps
# Targets y reglas cargadas
curl -s 'localhost:9090/api/v1/targets' | grep -o '"health":"[a-z]*"' | sort | uniq -c
curl -s 'localhost:9090/api/v1/rules' | grep -o '"name":"[A-Za-z]*"' | wc -l

Si -/reload devuelve error, Prometheus sigue con la config anterior: revisar docker compose logs prometheus --tail 50 y validar con promtool (ver AGENTS.md).

Probar una regla sin tocar producción

tests/alertas_test.yml tiene un caso por alerta nueva con series sintéticas. Antes de desplegar reglas:

docker run --rm -v "$PWD/rules:/rules:ro" -v "$PWD/tests:/tests:ro" \
  --entrypoint promtool prom/prometheus:latest test rules /tests/alertas_test.yml

Si falla, muestra las alertas esperadas y las obtenidas con sus labels.

Logs

Qué Dónde Cómo verlos
Cualquier contenedor stdout de Docker en la VM docker compose logs -f --tail 100 <servicio>
Errores de notificación (SMTP, Slack) Alertmanager docker compose logs alertmanager --tail 50 \| grep -iE "error\|email\|smtp"
Targets caídos y último error de scrape Prometheus http://<vm>:9090/targets o curl -s localhost:9090/api/v1/targets
Alertas activas y silencios Alertmanager http://<vm>:9093
Errores de provisioning de dashboards Grafana docker compose logs grafana \| grep -i provisioning
Datos de Prometheus (TSDB) /opt/monitoring/data (bind mount, ignorado por git) du -sh /opt/monitoring/data
Datos de Grafana y Loki volúmenes grafana_data, loki_data docker volume inspect grafana_data
Ejecución del deploy GitHub Actions del repo pestaña Actions, workflow deploy.yml

Rollback

Es un repo de configuración: volver atrás es volver al commit anterior. No hay migraciones ni estado que dependa de la versión de los archivos.

# Opción A (recomendada): revertir en git y dejar que el workflow despliegue
git revert <commit-malo>
git push origin main

# Opción B: en la VM, sin esperar a Actions
cd /opt/monitoring
git checkout <commit-bueno> -- .
curl -s -X POST http://localhost:9090/-/reload
curl -s -X POST http://localhost:9093/-/reload
docker compose up -d --remove-orphans
# Luego dejar el árbol limpio: git checkout . && git pull

Condición conocida: Prometheus, Alertmanager, Grafana y Blackbox usan el tag latest. Un docker compose pull trae la versión que haya ese día y no hay forma de volver a la imagen anterior desde el repo (ver ADR-0003). Loki sí está fijado en 3.0.0.

Fallas frecuentes

0. Cambié prometheus.yml y el reload dice OK pero no toma los cambios

prometheus.yml y alertmanager.yml están montados como bind mount de un solo archivo. git pull escribe un archivo nuevo (otro inode) y el contenedor sigue leyendo el viejo: curl -X POST :9090/-/reload devuelve 200 y prometheus_config_last_reload_successful = 1, pero /api/v1/status/config muestra la configuración anterior. Comprobar con docker exec prometheus grep -c "<algo nuevo>" /etc/prometheus/prometheus.yml. Solución: docker restart prometheus (o alertmanager). rules/ y grafana/provisioning/ son directorios y no tienen este problema.

El workflow deploy.yml hoy no llega a la VM: el puerto 22 de devops-vm solo admite el rango de IAP, así que GitHub Actions no puede conectar. Hasta que eso cambie el deploy es manual (opción B).

1. Alertmanager loguea Notify for alerts failed ... slack ... 404: no_team

Evidencia: log pegado en alertmanager/alertmanager.yml.save (2026-02-05); el receptor critico-slack sigue definido con un slack_api_url placeholder.

  • Diagnóstico: docker compose logs alertmanager --tail 50 | grep -i slack. Si aparece no_team, alguna ruta apunta a critico-slack y el webhook no es real.
  • Resolución: hoy todas las rutas van a equipo-infra (correo); mantenerlo así. Si se quiere Slack, poner un webhook válido vía slack_api_url_file (no en el YAML) y recién entonces enrutar severity: critical a ese receptor. Aplicar con curl -X POST localhost:9093/-/reload.

2. Un probe de Blackbox da probe_success = 0 con el sitio funcionando

Evidencia: commits del 2026-03-24 y 2026-03-25 (61773ca, faca326 headers de Cloudflare Access; 2a2f7f3 IP con dígito de más; d3da17a módulo http_2xx_or_404 para PC Manager).

  • Diagnóstico: probar el módulo a mano desde la VM con debug=true y leer el código HTTP y el log del probe:
    curl -s 'localhost:9115/probe?module=http_2xx&target=https://integrador.compulandia.com.py&debug=true' | head -60
    
    Causas vistas: (a) el sitio está detrás de Cloudflare Access y el service token del módulo http_2xx no está o venció → respuesta 302/403; (b) la raíz del sitio responde 404 por diseño → usar http_2xx_or_404; (c) target mal tipeado en prometheus.yml.
  • Resolución: corregir el módulo o el target en el repo, push a main y, si cambió blackbox.yml, docker compose restart blackbox_exporter.

3. Alertas ruidosas: correos repetidos por umbrales o endpoints

Evidencia: umbrales de CPU y memoria subidos de 85/95 a 95/99 (a363717), SlowProbe subido de 3 s a 5 s y luego 10 s (b0537bc, 2a2f7f3), EndpointDown con for de 2 a 4 min y finalmente todo el grupo blackbox comentado (4ab2a4d, 2026-04-06).

  • Diagnóstico: en http://<vm>:9093 ver qué alertname se repite; en Prometheus, graficar la expresión de la regla para ver cuánto tiempo está sobre el umbral.
  • Resolución: silenciar temporalmente desde Alertmanager (Silence) y ajustar for: o el umbral en rules/alerts_rules.yml explicando el motivo en el commit. Estado actual: las alertas de endpoints y certificados están desactivadas; reactivarlas requiere decidir umbrales que no generen ruido.

4. Alertas de PostgreSQL / PgBouncer (postgres-db-vm)

Las bases de Medusa, Payload, n8n, Outline, Planka, Traccar y OpenWebUI viven en postgres-db-vm (10.158.0.24) detrás de PgBouncer (:6432). Tablero: PostgreSQL — postgres-db-vm.

Alerta Qué mirar primero
PostgresDown / PgBouncerDown systemctl status postgresql@16-main pgbouncer en la VM. Si el servicio está bien y la alerta sigue, es el exporter (prometheus-postgres-exporter, pgbouncer_exporter).
PostgresConexionesAltas Panel Conexiones por base: qué base creció. Alguna app puede estar conectando directo al 5432 en vez de PgBouncer, o hay fuga de conexiones. max_connections es 100.
PostgresTransaccionLarga select pid, datname, now()-xact_start, state, left(query,80) from pg_stat_activity where xact_start is not null order by 3 desc limit 5; y decidir si se cancela (pg_cancel_backend).
PgBouncerClientesEsperando / PgBouncerEsperaMaxima Pool corto para esa base (pool_size en pgbouncer.ini) o queries lentas del lado de PostgreSQL.
PostgresCacheHitBajo Sostenido en una base concreta: queries pesadas nuevas (revisar la app) o shared_buffers corto para la carga.
PostgresDeadlocks Logs de PostgreSQL (/var/log/postgresql/) traen las dos consultas en conflicto.

5. Alertas del propio stack (devops-vm)

Alerta Qué mirar primero
ServidorCaido con job stack docker compose ps en ~/observability-stack; docker logs <servicio>.
PrometheusRecargaFallida promtool check config sobre el repo y docker logs prometheus. Hasta corregir sigue corriendo la config anterior.
AlertmanagerNotificacionesFallando docker logs alertmanager: credenciales SMTP o el webhook Slack sin ruta (falla 1). Mientras dure, no está llegando ningún correo.
PrometheusTSDBDiscoBajo Disco raíz de devops-vm compartido por TSDB (30 d), Loki y 43 contenedores de apps. docker system df y du -sh data/ loki/.
LokiErroresDeIngesta docker logs loki: límites de rate o de líneas, o disco.
RemoteWriteSinDatos El job indicado dejó de llegar: Alloy/Agent de esa red caído, o túnel/Access rechazando el remote_write (falla 2 pero del lado del agente).

6. Alertas por contenedor (cAdvisor)

Tablero Contenedores — cAdvisor, filtrable por VM y contenedor. cAdvisor corre en devops-vm (servicio del compose raíz), ecommerce-srv y redis-srv-vm (/opt/monitoring/cadvisor, ver cadvisor/README.md). Versión mínima 0.55.1 por Docker 29.

Alerta Qué mirar primero
ContenedorReiniciando docker logs --tail 100 <name> en la VM; casi siempre es la app que muere al arrancar (config, base inaccesible). docker inspect <name> --format '{{.State.ExitCode}}'.
ContenedorOOM El contenedor tiene límite de memoria corto o una fuga. Ver panel % del límite de memoria; subir el límite en el compose de la app o investigar la app.
ContenedorMemoriaCercaDelLimite Mismo panel. Preferible actuar antes del OOM.
ContenedorCPUAlto Panel CPU (núcleos). En ecommerce-srv, Kafka y medusa_backend son los candidatos; en devops-vm, Frappe y Chatwoot.
ContenedorCriticoAusente El contenedor no está corriendo: docker ps -a en la VM y docker compose up -d en su carpeta (/opt/medusa, /opt/storefront, /opt/payload-cms, /opt/kafka/deploy/produccion). Si además hay ServidorCaido del job cadvisor, lo que cayó es cAdvisor, no la app.

7. Probar en vivo que la cadena de alertas funciona

Hecho el 2026-09-04 con blackbox_exporter, el componente de menor impacto (los probes quedan sin datos 2 minutos; sin alertas asociadas todavía):

docker stop blackbox_exporter
# ServidorCaido{job="stack"} pasa a pending y a firing a los ~75 s;
# Alertmanager la recibe y manda el correo (también los 11 targets blackbox_* caen).
docker start blackbox_exporter
# se resuelve en ~40 s y llega el correo de resolved.

Ver en localhost:9093/api/v2/alerts y en el contador alertmanager_notifications_total{integration="email"}.

8. Alertas de probes (Blackbox)

Reactivadas el 2026-09-04 (rules/blackbox_alertas.yml) tras estar comentadas desde abril. La severity la trae cada target desde prometheus.yml (critical para ecommerce, integrador, SAP y hosts; warning para wp-server-1 y PC Manager). Tablero Blackbox — Monitoreo Completo de Endpoints.

Alerta Qué mirar primero
ProbeCaido probe_http_status_code y probe_failed_due_to_regex en el tablero. Código 0 = no conectó (túnel, DNS, contenedor caído). Si es un sitio detrás de Access y el resto de sitios sigue bien, revisar el service token (falla 2). Para medusa /health, docker ps en ecommerce-srv: el backend se reinicia solo y tarda 1-2 min.
ProbeHttpError5xx El edge y el túnel están bien; falla la app o su base. Contenedor de la app y postgres_alertas.
HostSinRespuestaICMP gcloud compute instances list: si la VM está RUNNING, es red o firewall; si además hay ServidorCaido del node_exporter, la VM está caída.
ProbeLatenciaAlta Sostenido en un solo sitio: la app. En varios a la vez: túnel o edge. www (wp-server-1) ya venía con p95 de 2,6 s.

Umbrales: 3 min para ProbeCaido porque medusa /health tuvo cortes de 1-2 min por reinicios del backend (81 min caído en 7 días al 2026-09-04); 10 min y 5 s para latencia. Sin alerta de certificados: Cloudflare los renueva.

9. Ecommerce: qué mirar cuando cae algo

Tablero Ecommerce — Medusa, storefront y Payload. Tres componentes en ecommerce-srv, base en postgres-db-vm, Redis local a Medusa.

Síntoma Lectura
Probe público caído y el interno OK Túnel de Cloudflare o edge. systemctl status cloudflared en devops-vm.
Público e interno caídos La app. docker ps en ecommerce-srv y docker logs --tail 100 <contenedor>.
ContenedorCriticoAusente El contenedor no corre: cd /opt/<medusa|storefront|payload-cms> && docker compose up -d.
medusa_backend reiniciando Hasta 7 reinicios acumulados al relevar. Ver logs; suele ser la conexión a Postgres o Redis al arrancar.
payload-cms unhealthy aunque responde El healthcheck del compose hace wget localhost:3000 y busybox resuelve localhost a ::1, donde Node no escucha. Corregir en el repo de payload-cms: usar 127.0.0.1. Mientras tanto, el probe HTTP es la verdad.
RedisDown de medusa_redis Medusa pierde cache, event bus, workflows y locks: los pedidos se traban. docker compose up -d redis en /opt/medusa.
Storefront devuelve 307 en bucle / y /py redirigen a /py sin fin para clientes sin cookie (curl, bots). Los probes usan /api/health por eso. Revisar en el repo del storefront.

10. Alertas de Redis

Alerta Qué mirar primero
RedisDown docker ps en la VM (redis en redis-srv-vm, medusa_redis en ecommerce-srv). Si el contenedor corre, revisar .env del exporter (REDIS_ADDR, REDIS_PASSWORD).
RedisMemoriaAlta Sin maxmemory, Redis crece hasta agotar la RAM de la VM (2 GB en redis-srv-vm). Ver claves por base y expiradas/evictadas en el tablero; definir maxmemory + política en el redis.conf.
RedisClientesRechazados maxclients (10.000 por defecto) alcanzado o fuga de conexiones en Chatwoot/Medusa.
RedisSinPersistenciaReciente redis_rdb_last_bgsave_status y disco de la VM.

El panel clientes bloqueados del tablero muestra siempre unos 12 en redis-srv-vm: son los hilos de Sidekiq esperando trabajo con BRPOP, no una falla. Por eso no hay alerta sobre ese contador.

11. Alertas de Kafka

Kafka 4.2 en KRaft con un solo broker en ecommerce-srv (/opt/kafka/deploy/produccion). Tablero Kafka — ecommerce-srv; kafka-ui en kafka-ui.compulandia.com.py para ver mensajes.

Alerta Qué mirar primero
KafkaDown docker ps y docker logs --tail 100 kafka en ecommerce-srv. Con un broker, cualquier reinicio corta el flujo SAP → Medusa/n8n/integrador; los productores reintentan, los consumidores esperan. Verificar disco: el broker se niega a arrancar con el disco lleno.
KafkaParticionSinLider Broker arrancando o con el log dañado. docker logs kafka y espacio en disco.
KafkaConsumerLagAlto / Critico Tablero, panel Lag por consumer group. Ubicar el consumidor: sap-enrichment-worker y sap-bridge en devops-vm (docker logs), integrador-sap-items es el servicio integrador-kafka-consumer en integrador (journalctl -u), n8n-* son workflows de n8n, kafka-jornadas-*, kafka-order-completed, algolia-stock-indexer, kafka-bp-enrich y kafka-trigger viven en devops-vm o en n8n. Lag que crece con miembros > 0: consumidor lento o con errores; con miembros = 0: consumidor caído.
KafkaGrupoSinMiembros El proceso consumidor se detuvo pero el grupo sigue registrado. Reiniciar el consumidor; el lag se recupera solo.
KafkaConsumidorCriticoAusente El grupo desapareció del broker (kafka-ui → Consumers). Si sap-enrichment-worker no aparece, los ítems de SAP dejan de enriquecerse y Medusa no recibe productos.
KafkaMensajesEnDLQ Abrir el topic DLQ en kafka-ui y leer los mensajes: traen el error del consumidor. Corregir la causa en la app y reprocesar. La retención del topic los borra después.

12. Alertas de n8n

n8n 2.x en modo queue en devops-vm (/opt/n8n): n8n-production-n8n-1 (main: editor, webhooks, triggers), n8n-production-n8n-worker-1 (ejecuta, concurrency 5) y n8n-production-redis-1 (cola Bull). Base n8n en postgres-db-vm. Consume Kafka en los grupos n8n-orders y n8n-service-call. Tablero n8n — devops-vm.

Alerta Qué mirar primero
ServidorCaido de cloud-n8n / ProbeCaido de n8n docker ps y docker logs --tail 100 n8n-production-n8n-1. Si el interno responde y el público no, es el túnel.
N8nSinLider Reiniciar main: cd /opt/n8n && docker compose restart n8n. Sin líder no corren triggers ni cron.
N8nWorkflowFallando Trae el nombre del workflow. En n8n, abrirlo → Executions filtrado por Error: el nodo que falla. Suele ser una credencial vencida o un servicio externo (SAP Service Layer, Medusa).
N8nEjecucionesFallando Respaldo global (más del 20 % de todas las ejecuciones). Panel Ejecuciones por workflow para ubicar el origen.
N8nEjecucionesCrashed El worker murió a mitad de ejecución: panel Memoria vs límite (2 GB) y Reinicios. Subir el límite o dividir el workflow.
N8nEventLoopLento Main saturado de CPU: workflows con Code nodes pesados corriendo en main en vez del worker, o demasiados webhooks.
N8nWorkerAusente cd /opt/n8n && docker compose up -d n8n-worker. Mientras falte, todo se encola.
N8nColaAtascada Worker caído o saturado, o Redis de n8n con problemas (RedisDown de n8n). Escalar: docker compose up -d --scale n8n-worker=2.

Métricas de cola y por workflow activas desde el 2026-09-08 con N8N_METRICS_INCLUDE_QUEUE_METRICS, ..._MESSAGE_EVENT_BUS_METRICS, ..._WORKFLOW_ID_LABEL y ..._WORKFLOW_NAME_LABEL. Las variables deben estar referenciadas en el compose (bloque compartido), no solo en el .env: Compose no inyecta el .env entero al contenedor.

Otros fixes registrados en el historial

  • Loki sin red monitoring → Grafana no llegaba a loki:3100 (976d4d5).
  • Clave volumes duplicada en el compose al agregar Loki (4430338).
  • TODO: no hay post-mortems ni registro de incidentes fuera de los commits; cuando ocurra uno, documentarlo en docs/incidentes/AAAA-MM-tema.md.