Signal Path · SRE y observabilidad

Signal Path: De la métrica al post-mortem

Un recorrido práctico de SRE y observabilidad: instrumentas un servicio real, mides confiabilidad con SLO y error budget, correlacionas métricas, trazas y logs, y cierras el ciclo con alertas, runbook, incidente y post-mortem. Todo sobre Docker Compose, para correr en tu propio VPS (Hetzner, Oracle, Hostinger) con Coolify o Portainer.

módulos
10
fases
5
proyecto final
1

Prometheus Grafana Loki Tempo OpenTelemetry Alertmanager Docker Compose

Compartir WhatsAppLinkedInX

Módulo 1 · Fase 1 · Fundamentos

Fundamentos de SRE y observabilidad

Antes de instalar nada: qué problema estamos resolviendo y con qué señales.

Monitoring no es observabilidad

El monitoring responde preguntas que ya sabías que ibas a hacer: ¿el contenedor está arriba?, ¿la CPU pasó 80%?, ¿el endpoint responde? La observabilidad te deja responder preguntas que no anticipaste: ¿por qué solo algunas requests de /checkout tardaron 8 segundos entre las 14:02 y las 14:07?

La materia prima son los datos que tu sistema emite —la telemetría— y las tres señales clásicas son métricas, logs y trazas. Cada una responde una pregunta distinta, y el valor real aparece cuando las conectas.

Regla mental del curso

La métrica te dice QUE hay un problema. La traza te dice DÓNDE está. El log te explica POR QUÉ. El SLO y el burn rate te dicen QUÉ TAN URGENTE es.

Los cuatro Golden Signals

Latencia (cuánto tardan las requests), tráfico (cuánto trabajo entra), errores (cuántas fallan) y saturación (qué tan cerca del límite estás). Con esos cuatro números respondes en diez segundos si un servicio está sano.

Dos métodos derivados te ordenan el trabajo: RED (Rate, Errors, Duration) mira el servicio desde el usuario; USE (Utilization, Saturation, Errors) mira el recurso — CPU, memoria, disco, conexiones de base. Un buen dashboard usa los dos, en ese orden: primero impacto, después causa.

Antipatrón frecuente

Tener treinta paneles y ninguna respuesta. Un dashboard bonito no es observabilidad: si nadie puede pasar de "algo anda mal" a "esta request se fue en esta llamada externa" en menos de cinco minutos, todavía no la tienes.

Percentiles, no promedios

Si 99 requests tardan 100 ms y una tarda 10 s, el promedio te miente y ese usuario se fue. Por eso se mide p50 (experiencia típica), p95 (la que se usa para acuerdos) y p99 (la cola larga, donde viven los timeouts y los reintentos).

Ejercicio 1

Mapea los Golden Signals de un servicio tuyo

Elige un servicio real que ya tengas corriendo. Escribe, para cada señal, qué número lo representa hoy y de dónde saldría: latencia (p95 de qué endpoint), tráfico (req/s), errores (qué cuenta como error: ¿solo 5xx?, ¿también 4xx de negocio?) y saturación (CPU, memoria, pool de conexiones). Si no sabes de dónde sale un número, márcalo como hueco.

Ejercicio 2

Dibuja el flujo de telemetría actual

Diagrama el camino que hace hoy cada señal desde tu app hasta donde alguien la mira: app → ¿qué colector? → ¿qué backend? → ¿qué UI? Marca con rojo los tramos que no existen. Al terminar el módulo 8 este mismo diagrama tiene que estar completo.

Entregable del módulo

Va al proyecto final: Esa definición de request fallida es literalmente el SLI que vas a medir en el módulo 4 y el que va a disparar tus alertas en el módulo 5. Elígela con cuidado.

Módulo 2 · Fase 2 · Métricas

Métricas con Prometheus

Instrumentar tu app y hacer las primeras preguntas en PromQL.

Tres tipos de métrica alcanzan para casi todo

Un counter solo sube y cuenta eventos: requests, errores, mensajes procesados. Un gauge sube y baja y mide un estado instantáneo: conexiones abiertas, items en cola, memoria. Un histogram reparte observaciones en buckets y es lo que te permite calcular percentiles de latencia del lado del servidor.

La instrumentación mínima de cualquier servicio HTTP son dos métricas: un counter de requests con labels route, method y status, y un histogram de duración con las mismas labels.

Cardinalidad: el error que mata Prometheus

Cada combinación de labels es una serie temporal. Poner user_id, order_id o una URL con IDs adentro como label genera millones de series y te tumba la instancia. Los identificadores van en logs y trazas, nunca en labels de métrica. Usa la ruta (/orders/:id), no la URL.

Prometheus en Docker Compose

services:
  prometheus:
    image: prom/prometheus:latest
    command:
      - --config.file=/etc/prometheus/prometheus.yml
      - --storage.tsdb.retention.time=15d
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prom-data:/prometheus
    ports: ["9090:9090"]

El scrape config apunta a tu servicio por nombre de red interna: en Compose (y en un stack de Coolify o Portainer) los contenedores se ven entre sí por su nombre de servicio, así que targets: ['api:3000'] alcanza. Nunca expongas /metrics a internet sin protección.

Las tres queries que vas a usar siempre

# tráfico (req/s)
sum(rate(http_requests_total[5m]))

# error rate (proporción de 5xx)
sum(rate(http_requests_total{status=~"5.."}[5m]))
/ sum(rate(http_requests_total[5m]))

# latencia p95
histogram_quantile(0.95,
  sum by (le) (rate(http_request_duration_seconds_bucket[5m])))
Detalle que confunde a todos

rate() sobre un counter da la pendiente por segundo, no el total. Siempre rate() antes de sum(), nunca al revés: sumar counters crudos de réplicas que se reinician te da números fantasma.

Ejercicio 1

Instrumenta un servicio real

Agrega prom-client (Node) o prometheus_client (Python) a un servicio tuyo. Expón /metrics con un counter http_requests_total{route,method,status} y un histogram http_request_duration_seconds. Verifica que la ruta esté normalizada (nada de IDs en la label) y que los buckets del histogram cubran tu rango real de latencia.

Ejercicio 2

Levanta Prometheus y escribe las tres queries

Arma el docker-compose.yml con Prometheus scrapeando tu servicio cada 15 s. Mándale carga (hey, k6 o un bucle con curl) y responde en la UI de Prometheus: ¿cuál es tu RPS?, ¿cuál tu error rate?, ¿cuál tu p95? Guarda las tres queries con un comentario de qué contesta cada una.

Entregable del módulo

Va al proyecto final: Ese histogram es el que va a alimentar los exemplars del módulo 8. Si lo dejas bien nombrado y con buckets sensatos ahora, la correlación después sale sola.

Módulo 3 · Fase 2 · Métricas

Grafana: dashboards que responden preguntas

Un tablero no es una colección de gráficos: es un orden de lectura.

El orden importa más que los paneles

Un dashboard operativo se lee de arriba hacia abajo como un triage. Capa 1: impacto — disponibilidad, error rate, latencia, presupuesto de error. Capa 2: RED — tráfico, errores y duración por ruta. Capa 3: saturación — CPU, memoria, réplicas, reinicios, pool de conexiones. Capa 4 y 5 (a partir del módulo 6): logs y trazas del período que estás mirando.

La prueba del dashboard

Si a las 3 AM alguien abre el tablero, la primera pantalla —sin scrollear— tiene que responder: ¿hay usuarios afectados y desde cuándo? Todo lo demás va abajo.

Grafana también va en el compose

  grafana:
    image: grafana/grafana:latest
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
      - GF_USERS_ALLOW_SIGN_UP=false
    volumes:
      - grafana-data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning:ro
    ports: ["3001:3000"]

El volumen de provisioning es lo que separa un juguete de algo operable: datasources y dashboards definidos por archivo, versionados en git, reproducibles en otro VPS. Un dashboard hecho a mano en la UI y no exportado se pierde el día que el volumen se rompe.

Variables: un tablero, muchos servicios

En vez de clonar el dashboard por servicio, define una variable $service con una query label_values(http_requests_total, service) y úsala en todas las consultas. El mismo tablero sirve para tu API, tu worker y el bot de WhatsApp.

Antipatrón

Paneles con ejes automáticos y sin unidad. Si el eje no dice segundos o porcentaje, y el umbral del SLO no está dibujado como línea, nadie sabe si 0.42 está bien o mal.

Ejercicio 1

Dashboard v1 en cuatro paneles

Construye un tablero con: disponibilidad (1 − error ratio) como stat grande, RPS por ruta, error rate con umbral pintado, y p95/p99 en el mismo gráfico. Pon unidades correctas en cada eje y una línea de umbral en latencia. Exporta el JSON a grafana/dashboards/red-v1.json.

Ejercicio 2

Hazlo reproducible

Pasa el datasource de Prometheus y el dashboard a archivos de provisioning. Borra el volumen de Grafana, levanta de nuevo el stack y confirma que todo vuelve solo. Si no vuelve, todavía tienes configuración que vive únicamente en la UI.

Entregable del módulo

Va al proyecto final: Este tablero es la base del dashboard final. En el módulo 4 le agregas la fila de SLO y presupuesto de error, y en el 8 los paneles correlacionados.

Módulo 4 · Fase 3 · Confiabilidad medida

SLI, SLO, error budget y burn rate

Convertir 'anda bien' en un número que se puede discutir con producto.

Tres siglas que no son sinónimos

El SLI es lo que mides (disponibilidad = 99,92%). El SLO es la meta interna que te pones (99,9% en 30 días). El SLA es un compromiso contractual con dinero o créditos de por medio. Casi ningún servicio necesita SLA; casi todos deberían tener SLO.

El presupuesto de error

Si el SLO es 99,9%, tienes permitido fallar el 0,1%. En 30 días eso son 43 minutos y 12 segundos de indisponibilidad. Ese presupuesto es una herramienta de decisión: mientras te sobre budget, puedes desplegar rápido y tomar riesgos; cuando lo quemaste, la prioridad pasa a ser estabilizar.

SLO 99.9%   → 43m 12s / 30 días
SLO 99.95%  → 21m 36s / 30 días
SLO 99.99%  →  4m 19s / 30 días
No elijas 99,99% porque suena bien

Cada nueve extra multiplica el costo: redundancia, guardias, despliegues más lentos. El SLO se elige mirando qué tolera el usuario y qué está dispuesto a pagar el negocio, no lo que suena impresionante en una reunión.

Burn rate: la velocidad de quema

El burn rate es cuánto más rápido de lo permitido estás gastando el presupuesto: error_ratio / (1 − SLO). Un burn rate de 1× consume el budget exactamente en 30 días. Un 14,4× lo consume en poco más de dos días — eso amerita despertar a alguien. Un 3× sostenido no es urgente esta noche, pero destruye el mes.

Recording rules: calcula una vez

groups:
  - name: slo
    interval: 30s
    rules:
      - record: job:http_error_ratio:rate5m
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[5m]))
          / sum(rate(http_requests_total[5m]))
      - record: job:http_availability:ratio30d
        expr: 1 - (
          sum(rate(http_requests_total{status=~"5.."}[30d]))
          / sum(rate(http_requests_total[30d])))
Por qué vale la pena

Con las reglas grabadas, dashboards y alertas consultan un nombre corto en vez de recalcular una query pesada cada 10 segundos. Además unifica la definición: una sola verdad sobre qué es "disponibilidad" en todo tu stack.

Ejercicio 1

Define y justifica tu SLO

Escribe en docs/slo.md dos SLO para tu servicio: uno de disponibilidad y uno de latencia (por ejemplo, 99% de las requests bajo 500 ms). Para cada uno, anota el presupuesto de error en minutos por mes y una frase de por qué ese número y no uno más alto. Si no puedes justificarlo, todavía no es un SLO.

Ejercicio 2

Recording rules + panel de presupuesto

Crea rules/slo.yml con error ratio a 5m, 30m, 1h, 6h y disponibilidad a 30d. Cárgalas en Prometheus, verifica en /rules que evalúan sin error, y suma al dashboard un panel de presupuesto de error restante (1 − (error_ratio_30d / (1 − SLO))) en porcentaje.

Entregable del módulo

Va al proyecto final: Las reglas de este módulo son exactamente las que van a disparar las alertas del módulo 5. Sin SLO no hay alerta buena: solo umbrales inventados.

Módulo 5 · Fase 3 · Confiabilidad medida

Alertas que no rompen el sueño sin motivo

Alertar sobre síntomas del usuario, no sobre síntomas de la máquina.

El test de las tres de la mañana

Antes de crear una alerta, responde: si esto suena a las 3 AM, ¿alguien tiene que levantarse y hacer algo ahora? Si la respuesta es no, no es una página: es un ticket, o directamente ruido. La fatiga de alertas es la forma más rápida de que un equipo deje de mirar el celular.

Antipatrón clásico

CPU > 70%. La CPU alta sola no significa impacto: puede ser el pico normal del mediodía. Alerta sobre disponibilidad, latencia y burn rate — o sea, sobre lo que el usuario sufre.

Alertas multi-ventana de burn rate

La receta estándar de Google usa dos ventanas por severidad, para detectar rápido sin dispararse por un pico de treinta segundos: la ventana larga confirma que el problema es real, la corta confirma que sigue pasando.

groups:
  - name: slo-alerts
    rules:
      - alert: ErrorBudgetFastBurn
        expr: |
          job:http_error_ratio:rate1h > (14.4 * 0.001)
          and job:http_error_ratio:rate5m > (14.4 * 0.001)
        for: 2m
        labels: { severity: page }
        annotations:
          summary: "Quema rápida del presupuesto de error"
          runbook: "https://git.tu-dominio/runbooks/availability.md"

      - alert: ErrorBudgetSlowBurn
        expr: |
          job:http_error_ratio:rate6h > (6 * 0.001)
          and job:http_error_ratio:rate30m > (6 * 0.001)
        for: 15m
        labels: { severity: ticket }

Alertmanager decide quién se entera

Prometheus detecta la condición; Alertmanager hace el resto: agrupa alertas relacionadas para no mandar veinte mensajes, deduplica, silencia durante mantenimientos e inhibe alertas hijas cuando ya disparó la padre. La ruta se define por label: severity: page va al canal que despierta, severity: ticket al que se lee en horario laboral.

Atajo útil con n8n

En vez de configurar cada integración dentro de Alertmanager, manda un único webhook_configs a un flujo de n8n. Ahí formateas el mensaje, enriqueces con el link al dashboard y al runbook, y enrutas a WhatsApp, Slack o email según severidad y horario. Una sola integración que mantener.

Toda alerta necesita un runbook

La anotación runbook no es decorativa: es el link que la persona de guardia abre medio dormida. Si la alerta no tiene runbook, todavía no está terminada.

Ejercicio 1

Escribe dos alertas de burn rate

Crea rules/alerts.yml con una alerta fast burn (14,4× en 1h y 5m, severidad page) y una slow burn (6× en 6h y 30m, severidad ticket), ambas con anotación de summary y link a runbook. Valida con promtool check rules antes de recargar.

Ejercicio 2

Ruteo y prueba real

Configura alertmanager.yml con dos rutas por severidad y un webhook a n8n que formatee y entregue el mensaje. Después rompe algo a propósito (mata una dependencia, devuelve 500 en un porcentaje de requests) y cronometra: cuánto tardó desde el primer error hasta el mensaje en tu teléfono. Ese número es tu MTTD.

Entregable del módulo

Va al proyecto final: El MTTD que mides aquí es la primera métrica de tu proceso de incidentes del módulo 9. Guárdalo: al final del curso lo vas a comparar contra el del GameDay.

Módulo 6 · Fase 4 · Logs y trazas

Logs estructurados y Loki

Dejar de leer texto suelto y empezar a consultar eventos.

Un log es un evento, no una frase

Log en texto libre: Error al procesar el pedido del usuario. Log estructurado:

{"ts":"2026-09-08T14:03:11Z","level":"error","service":"api",
 "route":"/orders/:id","status":500,"duration_ms":831,
 "trace_id":"b2fe643bc4d341a1f7076f265910e649","order_id":"o_8812",
 "msg":"payment gateway timeout"}

El segundo se puede filtrar, agrupar y contar. Los campos mínimos que no pueden faltar: timestamp, level, service, route, status, duration_ms y trace_id. Ese último campo es el que va a conectar este log con su traza en el módulo 8.

Loki no es Elasticsearch

Loki no indexa el contenido del log: indexa solo las labels del stream y guarda el resto comprimido. Por eso es barato de correr en un VPS, y por eso las labels tienen que ser pocas y de baja cardinalidad: service, env, level. Nada más.

El error que hace explotar Loki

Poner trace_id, user_id o order_id como label de stream. Cada valor distinto crea un stream nuevo y el índice se vuelve inmanejable. Esos campos van dentro del JSON del log y se filtran con | json | trace_id="...", que es exactamente igual de rápido para lo que necesitas.

Recolección en Docker

  loki:
    image: grafana/loki:latest
    command: -config.file=/etc/loki/local-config.yaml
    volumes: [ "loki-data:/loki" ]

  alloy:
    image: grafana/alloy:latest
    volumes:
      - ./alloy/config.alloy:/etc/alloy/config.alloy:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
    command: run /etc/alloy/config.alloy

Alloy descubre los contenedores por el socket de Docker, les pega labels a partir del nombre y las etiquetas del contenedor, y empuja a Loki. Alternativa más simple si tu VPS ya está ordenado: el driver de logs de Docker apuntando directo a Loki.

LogQL en tres movimientos

# todos los errores del servicio
{service="api", level="error"}

# solo 5xx, parseando el JSON
{service="api"} | json | status >= 500

# tasa de errores por ruta, últimos 5 minutos
sum by (route) (
  rate({service="api"} | json | status >= 500 [5m]))
Retención: decídela ahora

En un VPS el disco es finito. Define retención por adelantado (por ejemplo 14 días de logs y 30 de métricas) en la config de Loki, no cuando te quedes sin espacio un domingo.

Ejercicio 1

Pasa tu app a logging estructurado

Reemplaza los console.log / print por un logger JSON (pino, winston, structlog). Asegúrate de que cada request loguee una línea con route, status, duration_ms y trace_id, y que los errores incluyan el stack en un campo aparte, no concatenado al mensaje.

Ejercicio 2

Loki + tres consultas útiles

Suma Loki y Alloy al compose, agrega el datasource en Grafana y guarda tres consultas LogQL: todos los 5xx de la última hora, los errores de una ruta específica, y la tasa de errores por ruta. Suma un panel de logs al dashboard, debajo de los paneles de métricas.

Entregable del módulo

Va al proyecto final: Todavía el trace_id de tus logs no apunta a ningún lado: en el módulo 7 nace la traza, y en el 8 el campo se vuelve un link clickeable.

Módulo 7 · Fase 4 · Logs y trazas

Trazas distribuidas con OpenTelemetry y Tempo

Saber en qué se fue el tiempo, span por span.

Traza, span y contexto

Una traza es el recorrido completo de una request a través de tu sistema. Cada etapa medible es un span: la request HTTP entrante, la query a Postgres, el GET a la API de pagos, el job que se encoló. Los spans forman un árbol, y ahí se ve inmediatamente dónde se fue el tiempo:

POST /orders                    2.41s
├── auth.verify                  38ms
├── db.query orders_insert      142ms
├── http POST payments.api      2.14s   ←
└── queue.publish order.created  21ms

La conclusión no es "la app está lenta": es "la pasarela de pagos se está comiendo el 89% del tiempo". Son dos conversaciones completamente distintas.

OpenTelemetry: instrumentar una vez

OTel es el estándar abierto que te evita quedar atado a un proveedor: instrumentas con su SDK y después decides a dónde mandas los datos — Tempo hoy, Datadog o Grafana Cloud mañana, sin tocar el código de la app.

La auto-instrumentación cubre lo obvio (servidor HTTP, cliente HTTP, driver de base, Redis) con muy poco código. Los spans manuales los agregas donde está tu lógica cara: el cálculo pesado, la llamada al LLM, el render del PDF.

El span que siempre falta

Nadie instrumenta lo que no sospecha. Pon un span manual alrededor de cada llamada a un tercero y de cada operación que pueda tardar más de 100 ms. Los spans que nunca creaste son exactamente los que vas a extrañar durante el incidente.

El Collector como amortiguador

  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest
    command: ["--config=/etc/otel/config.yaml"]
    volumes: [ "./otel/config.yaml:/etc/otel/config.yaml:ro" ]
    ports: ["4318:4318"]   # OTLP/HTTP

  tempo:
    image: grafana/tempo:latest
    command: ["-config.file=/etc/tempo.yaml"]
    volumes:
      - ./tempo/tempo.yaml:/etc/tempo.yaml:ro
      - tempo-data:/var/tempo

La app habla OTLP con el Collector, y el Collector decide qué hacer: filtrar spans de health checks, samplear, agregar atributos de entorno, y exportar a Tempo. Cambiar de backend después es cambiar un exporter, no redeployar la aplicación.

TraceQL para encontrar la aguja

{ resource.service.name = "api" && duration > 1s }
{ span.http.status_code >= 500 }
{ name = "http POST payments.api" && duration > 2s }
Sampling

Guardar el 100% de las trazas en un VPS chico llena el disco rápido. Muestrea (10% es un punto de partida razonable) pero conserva siempre el 100% de las trazas con error: son las que vas a mirar.

Ejercicio 1

Instrumenta con OTel y manda a Tempo

Agrega el SDK de OpenTelemetry con auto-instrumentación HTTP y de base de datos, exportando OTLP/HTTP al Collector. Suma Collector y Tempo al compose y confirma en Grafana Explore que ves trazas completas con sus spans hijos.

Ejercicio 2

Encuentra tu operación más lenta

Agrega un span manual a la operación más cara de tu servicio, con atributos útiles (tamaño del payload, proveedor externo, si hubo cache hit). Después busca con TraceQL las trazas de más de 1 s y anota en qué span se va el tiempo. Esa es tu primera hipótesis de optimización basada en datos.

Entregable del módulo

Va al proyecto final: Ya tienes las tres señales, pero todavía separadas: tres pestañas y mucho copiar y pegar. El módulo 8 las une.

Módulo 8 · Fase 4 · Logs y trazas

Correlación: métrica → traza → log

El salto de tres clicks que baja el MTTR de horas a minutos.

Una llave común: trace_id

Las tres señales que armaste hasta aquí viven en bases distintas. Lo que las conecta es un identificador que viaja con la request: el trace_id. Si tu histogram lo adjunta como exemplar, tu log lo escribe como campo y tu traza lo tiene por definición, puedes recorrer el camino completo sin escribir una sola query a mano.

Esto es lo que baja el MTTR

Agregar más paneles no acorta un incidente. Lo que lo acorta es poder ir del pico en el gráfico a la request concreta, y de ahí a la línea de log del error, en tres clicks. Todo este módulo existe para eso.

Exemplars: de la métrica a la traza

Un exemplar es un punto concreto adjuntado a un bucket del histogram, con el trace_id de una request real que cayó ahí. En el gráfico de latencia aparece como un puntito sobre la curva: haces click y se abre la traza que produjo ese valor.

# Prometheus necesita el feature habilitado
command:
  - --enable-feature=exemplar-storage

# y el scrape en formato OpenMetrics
scrape_configs:
  - job_name: api
    static_configs: [{ targets: ["api:3000"] }]

Del lado de la app, la librería de Prometheus tiene que registrar la observación con el exemplar del trace activo. En Node con prom-client se pasa como tercer argumento del observe(); en Python, con exemplar=.

Derived fields: del log a la traza

En la configuración del datasource de Loki defines un campo derivado que detecta el trace_id en el JSON y lo convierte en un botón que abre Tempo:

derivedFields:
  - name: TraceID
    matcherRegex: '"trace_id":"(\w+)"'
    url: '${__value.raw}'
    datasourceUid: tempo

Y en el datasource de Tempo activas el camino inverso (Trace to logs): desde un span, saltar a los logs de ese mismo trace_id en la ventana de tiempo del span. Ida y vuelta cerrada.

Verifica el reloj

Si los contenedores tienen relojes desfasados, el salto traza → logs devuelve vacío aunque todo esté bien configurado: Grafana busca en una ventana de tiempo que no coincide. Sincroniza NTP en el VPS antes de volverte loco depurando la config.

El recorrido completo

Alerta de burn rate
      ↓  (link del runbook)
Dashboard: p95 en 12s desde las 14:02
      ↓  (click en el exemplar)
Traza: POST /orders — 11.8s en payments.api
      ↓  (Trace to logs)
Log: "payment gateway timeout" order_id=o_8812
      ↓
Causa encontrada — 4 minutos desde la alerta
Ejercicio 1

Habilita exemplars y haz el salto

Activa exemplar-storage en Prometheus, adjunta el trace_id al observar la latencia y enciende Show exemplars en el panel de p95. Genera carga lenta y confirma que puedes hacer click en un punto del gráfico y aterrizar en la traza correcta.

Ejercicio 2

Cierra el círculo entre log y traza

Configura el derived field en el datasource de Loki y Trace to logs en el de Tempo. Después haz el recorrido completo cronometrado: desde el panel de latencia hasta la línea de log de la causa. Anota cuántos clicks y cuántos segundos te llevó — ese número es el que vas a defender en el proyecto final.

Entregable del módulo

Va al proyecto final: Este recorrido es literalmente el cuerpo de tu runbook del módulo 9: en vez de escribir 'revisa los logs', vas a escribir los tres clicks exactos.

Módulo 9 · Fase 5 · Operación

Respuesta a incidentes, severidad y runbooks

El proceso que convierte el pánico en pasos ordenados.

Las cuatro etapas y los tres relojes

Un incidente se detecta, se reconoce, se mitiga y se resuelve — y cada tramo tiene su métrica. MTTD (detectar): del primer error al alerta. MTTA (reconocer): del alerta a que alguien lo tome. MTTR (restaurar): del inicio al servicio normal. Si no mides los tres por separado, no sabes cuál mejorar: un MTTD de 20 minutos no se arregla con más gente de guardia, se arregla con mejores alertas.

Mitigar primero, entender después

Durante el incidente, el objetivo es restaurar el servicio, no encontrar la causa raíz. Rollback, feature flag apagado, escalar réplicas, cortar el tráfico al proveedor caído. La investigación va en el post-mortem, con el servicio ya sano.

Severidad: defínela antes de necesitarla

SEV1  Servicio caído o pérdida de datos.
      Todos los usuarios. Se despierta gente. Comms de inmediato.
SEV2  Degradación seria: SLO en riesgo, funcionalidad
      importante rota. Se atiende ya, en horario o fuera.
SEV3  Impacto acotado o con workaround.
      Ticket, se resuelve en horario laboral.

El criterio es siempre impacto en el usuario, nunca cuánto código hay que tocar. Un typo en el CSS del checkout que impide comprar es SEV1; un worker de reportes caído puede ser SEV3.

Anatomía de un runbook que sirve

Un runbook es para alguien que abre el celular a las 3 AM, no para el que escribió el sistema. Cinco secciones:

  1. Síntoma — qué alerta disparó y qué está viendo el usuario.
  2. Verificación — las queries exactas, copiables, con links al dashboard.
  3. Mitigación — comandos concretos, en orden, del menos al más destructivo.
  4. Escalación — a quién llamar y a partir de qué minuto.
  5. Post-incidente — qué guardar antes de que se pierda: trace_ids, capturas, ventana horaria.
Antipatrón

Un runbook que dice "revisar los logs y verificar si el servicio está sano" no es un runbook, es un deseo. Si un paso no se puede copiar y pegar, todavía no está terminado.

Ejercicio 1

Escribe un runbook real

Elige el modo de falla más probable de tu servicio (dependencia externa caída, base saturada, disco lleno) y escribe el runbook completo con las cinco secciones. Las queries de verificación tienen que ser las que armaste en los módulos 2, 6 y 8, copiables tal cual.

Ejercicio 2

Simulacro cronometrado

Rompe el servicio a propósito y maneja el incidente entero como si fuera real: espera el alerta, tómalo, sigue tu propio runbook, mitiga. Registra los tres tiempos y anota honestamente qué paso del runbook no servía. Corrígelo el mismo día.

Entregable del módulo

Va al proyecto final: El simulacro de este módulo es el ensayo. En el módulo 10 lo repites en serio, con fallas que no elegiste tú, y escribes el post-mortem.

Módulo 10 · Fase 5 · Operación

Post-mortem y GameDay

Que el incidente deje aprendizaje y no solo cansancio.

Post-mortem sin culpables, en serio

"Blameless" no es un gesto amable: es lo único que hace que la gente cuente lo que realmente pasó. Si el que apretó el botón sabe que va a quedar señalado, la próxima vez el timeline va a tener huecos justo donde estaba la información útil. La pregunta correcta nunca es quién se equivocó, sino qué hizo que esa acción pareciera razonable en ese momento.

Las seis secciones

1. Resumen        — 3 líneas: qué pasó, a quién afectó, cuánto duró.
2. Impacto        — usuarios, requests fallidas, presupuesto de error
                    consumido, dinero si aplica.
3. Timeline       — con horas exactas: primer error, alerta, ack,
                    mitigación, resolución. De ahí salen MTTD/MTTA/MTTR.
4. Causa          — la técnica y también la contribuyente (por qué el
                    sistema permitió que pasara).
5. Qué funcionó   — sí, esta sección va. La alerta que disparó bien y
                    el runbook que sirvió también son resultados.
6. Action items   — cada uno con dueño, fecha y ticket. Sin dueño no
                    es un action item, es un comentario.
La única métrica que importa del post-mortem

El porcentaje de action items cerrados. Un documento hermoso con doce tareas que nadie tocó en tres meses significa que el incidente va a volver a pasar exactamente igual.

GameDay: romper a propósito, en horario

Un GameDay es un incidente planificado: eliges un día, avisas al equipo, inyectas fallas reales y verificas si tu observabilidad las detecta. Es la única forma honesta de saber si el stack sirve — porque el día del incidente real no quieres descubrir que el alerta nunca estuvo bien configurado.

Hipótesis:  "Si el proveedor de pagos empieza a devolver 500,
             el alerta fast burn dispara en menos de 5 minutos y
             el runbook nos lleva a la causa en menos de 10."
Inyección:  proxy que devuelve 500 en el 30% de las llamadas
Observación: ¿disparó? ¿en cuánto? ¿el runbook alcanzó?
Aprendizaje: qué faltó — un span, un panel, un paso del runbook
Reglas del GameDay

Avisa siempre (no es una prueba sorpresa a la gente), ten el plan de rollback escrito antes de empezar, pon una hora de corte fija, y silencia los canales de alerta para no confundir a quien no está participando.

Tres fallas para empezar

Dependencia externa lenta o rota, saturación de recursos (CPU al límite o pool de conexiones agotado), y pérdida de un contenedor (matarlo y ver si el sistema se recupera solo). Con esas tres ya vas a encontrar al menos un hueco en tu instrumentación.

Ejercicio 1

Corre un GameDay de 60 minutos

Escribe el plan antes: tres hipótesis, tres inyecciones, criterio de éxito para cada una y plan de rollback. Ejecútalo cronometrando todo. Anota qué falla NO detectó tu stack — esa es la más valiosa del día.

Ejercicio 2

Escribe el post-mortem del GameDay

Usa las seis secciones sobre lo que pasó realmente, con los tiempos medidos. Los action items tienen que ser concretos: 'agregar span a la llamada de pagos', 'bajar el for: del alerta de 5m a 2m', con dueño y fecha. Cierra al menos uno esa misma semana.

Entregable del módulo

Va al proyecto final: Con esto cierras el ciclo completo: instrumentar, medir, alertar, correlacionar, responder y aprender. Lo que queda es empaquetarlo como un stack que otro pueda levantar.

Proyecto final

Proyecto final: el stack completo sobre un servicio real

El entregable final no es un laboratorio de juguete: es la observabilidad de un servicio tuyo que realmente corre, empaquetada de forma que otra persona la pueda levantar con un docker compose up -d y entender en quince minutos.

Lo que tiene que estar corriendo

  • Servicio instrumentado: métricas RED, logs JSON con trace_id, trazas OTel
  • Prometheus con recording rules de SLO y exemplars habilitados
  • Grafana provisionado por archivos: datasources, dashboard de cinco capas, variable de servicio
  • Loki + Alloy con retención definida, Tempo con sampling
  • Alertmanager con dos severidades ruteando a un flujo de n8n
  • Todo el stack detrás de autenticación, nada de puertos abiertos al mundo

Lo que tiene que estar escrito

  • docs/slo.md — dos SLO justificados con su presupuesto de error
  • runbooks/ — al menos dos modos de falla, con queries copiables
  • docs/postmortem-template.md y el post-mortem del GameDay
  • README.md — cómo levantar todo desde cero en un VPS limpio

Criterios de aprobación

Criterio

Tu progreso queda guardado en este navegador.