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 apareceno_team, alguna ruta apunta acritico-slacky 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íaslack_api_url_file(no en el YAML) y recién entonces enrutarseverity: criticala ese receptor. Aplicar concurl -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=truey leer el código HTTP y el log del probe:Causas vistas: (a) el sitio está detrás de Cloudflare Access y el service token del módulocurl -s 'localhost:9115/probe?module=http_2xx&target=https://integrador.compulandia.com.py&debug=true' | head -60http_2xxno está o venció → respuesta 302/403; (b) la raíz del sitio responde 404 por diseño → usarhttp_2xx_or_404; (c) target mal tipeado enprometheus.yml. - Resolución: corregir el módulo o el target en el repo, push a
mainy, 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>:9093ver quéalertnamese 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 enrules/alerts_rules.ymlexplicando 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 aloki:3100(976d4d5). - Clave
volumesduplicada 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.