Módulo 3: Observability As Sli Input
8. Proyecto: el pipeline de observabilidad-a-SLI de Andes Cargo
Descripción
Seis lecciones construyeron un pipeline real; este proyecto lo documenta y lo guarda como dos artefactos de portafolio. El primero, observability/OBSERVABILITY-RUNBOOK.md, contesta la pregunta que cualquier persona nueva en el equipo haría el primer día: "¿cómo medimos, de verdad, el SLI de process-shipment-manifest?" — qué se instrumenta, dónde vive cada dato, qué consulta exacta lo lee. El segundo, observability/dashboard.json, es el panel real de Grafana de la lección 6, exportado como el mismo archivo que ya usaste para crearlo — reproducible, no una captura de pantalla.
Conexión con el módulo
Este es el entregable que cierra el Módulo 3 de la misma forma que SLO.md cerró el Módulo 2: no un resumen en prosa de lo que ya viste, sino el documento que el resto de la guía va a citar sin volver a explicarlo. El Módulo 4 lee este mismo runbook al decidir sobre qué métrica exacta construir la alerta de burn rate. El Módulo 7 lo cita en el runbook operativo de incidentes.
Paso 1 — Por qué un runbook de observabilidad, y no solo el código ya escrito
Las lecciones 3 a 6 ya dejaron el código completo: upload_manifest_batch.py, manifest-log-events.json, instrument_manifest_flow.py, manifest_metrics_exporter.py, docker-compose.yml. Pero código sin un mapa es difícil de operar bajo presión — exactamente el problema que un runbook, según la lección 5 del Módulo 7 va a formalizar más adelante, existe para resolver. Este proyecto no espera hasta el Módulo 7 para practicar esa disciplina: documenta, en un solo lugar corto, la respuesta a tres preguntas que alguien haría en medio de un incidente real — ¿qué se está midiendo?, ¿dónde vive ese dato ahora mismo?, ¿qué comando exacto lo trae de vuelta?
Paso 2 — El runbook completo
En andes-cargo-infra/, crea observability/OBSERVABILITY-RUNBOOK.md:
# OBSERVABILITY-RUNBOOK.md — How We Measure the process-shipment-manifest SLI
**Status:** Accepted · **Governs:** Module 3 through Module 8 of `sre-and-incident-response-guide`
**Source:** Module 3, lessons 2-7 (the three pillars, real metrics, real logs, real traces,
Prometheus/Grafana, and the calculator run with real telemetry)
**Related:** `SLO.md` (Module 2) defines the SLI this runbook feeds; Module 4 alerts on it.
## What is instrumented
Three pillars, each covering exactly one question a Site Reliability Engineer needs to
answer about `process-shipment-manifest`'s SLI (good events / valid events):
| Pillar | What is instrumented | Question it answers |
|---|---|---|
| Metrics | `AWS/Lambda`/`Invocations`, `AWS/Lambda`/`Errors` (CloudWatch); `manifest_invocations_total`, `manifest_errors_total` (Prometheus) | How many valid events, how many good events? |
| Logs | `/aws/lambda/process-shipment-manifest` — the `Invalid manifest ...` line `lambda_handler` prints before raising, and the `REPORT` line the Lambda runtime appends automatically | Which specific invocation failed, and why? |
| Traces | `observability/instrument_manifest_flow.py` — OTel spans for `shipment-manifest-upload -> process-shipment-manifest -> dynamodb-put-item` | At which exact step of the flow did one specific invocation break? |
## Where the data lives
| Source | Status | Reason |
|---|---|---|
| CloudWatch Metrics (`AWS/Lambda`) | Representative in this repo's authoring environment | No `LOCALSTACK_AUTH_TOKEN` exported here; confirmed on LocalStack's Hobby plan |
| CloudWatch Logs (`/aws/lambda/process-shipment-manifest`) | Representative, same reason | Same as above |
| Prometheus (`manifest_invocations_total`, `manifest_errors_total`) | Real, running | `manifest_metrics_exporter.py` + `docker compose` (`observability/docker-compose.yml`) |
| Jaeger v2 (`jaegertracing/jaeger:2.20.0`) | Real, running | `observability/instrument_manifest_flow.py` + `docker compose` |
| Grafana (`13.1.3`) | Real, running | Dashboard `andes-cargo-manifest-sli`, saved as `observability/dashboard.json` |
## What query reads it
```bash
# Metrics (representative)
awslocal cloudwatch get-metric-statistics --namespace AWS/Lambda --metric-name Errors \
--dimensions Name=FunctionName,Value=process-shipment-manifest \
--start-time 2026-08-14T14:00:00Z --end-time 2026-08-14T15:00:00Z --period 3600 --statistics Sum
# Metrics (real, PromQL)
curl -s 'http://localhost:9090/api/v1/query?query=manifest_errors_total/manifest_invocations_total'
# Logs (representative awslocal + real jq)
awslocal logs filter-log-events --log-group-name /aws/lambda/process-shipment-manifest \
--filter-pattern '?"Invalid manifest" ?"Status: error"'
jq -r '.events[] | select(.message | contains("Status: error")) | .message | capture("RequestId: (?<requestId>[a-f0-9-]+)") | .requestId' manifest-log-events.json
# Traces (real)
curl -s "http://localhost:16686/api/traces?service=andes-cargo-app-server&limit=10"
```
## Known limitations
- The 20-invocation batch used across this module is a deliberate verification batch (3
malformed on purpose), not a sample of real production traffic. Module 3, lesson 7
showed the correct way to use it: as one additional day inside the Module 2 30-day
dataset, never as a standalone monthly SLI.
- CloudWatch metrics and logs are representative in this authoring environment (no
`LOCALSTACK_AUTH_TOKEN`). Prometheus, Grafana, and Jaeger are real and running.
- This runbook documents the SLI-measurement pipeline. It does not alert on it — that is
Module 4's `scripts/burn_rate_evaluator.py` and the Alertmanager/CloudWatch Alarm rules.
Paso 3 — Verificando el runbook
wc -l observability/OBSERVABILITY-RUNBOOK.md
grep -c '^## ' observability/OBSERVABILITY-RUNBOOK.md
Qué esperar (literal — el contenido lo escribiste tú, la forma es determinista):
58
4
Cincuenta y ocho líneas, cuatro secciones (What is instrumented, Where the data lives, What query reads it, Known limitations) — corto a propósito, porque un runbook que nadie lee bajo presión no cumple su función, la misma lección que el Módulo 7 va a desarrollar a fondo.
Paso 4 — Guardando el panel de Grafana como artefacto reproducible
En observability/dashboard.json, guarda exactamente el mismo cuerpo JSON que usaste en la lección 6, Paso 6, para crear el dashboard:
{
"dashboard": {
"id": null,
"uid": "andes-cargo-manifest-sli",
"title": "Andes Cargo -- process-shipment-manifest SLI input",
"tags": ["andes-cargo", "sre"],
"timezone": "browser",
"panels": [
{
"id": 1,
"type": "stat",
"title": "Invocations vs errors (fixed batch)",
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 },
"targets": [
{ "expr": "manifest_invocations_total - manifest_errors_total", "legendFormat": "good", "refId": "A" },
{ "expr": "manifest_errors_total", "legendFormat": "errors", "refId": "B" }
],
"fieldConfig": { "defaults": { "unit": "short" }, "overrides": [] }
},
{
"id": 2,
"type": "gauge",
"title": "Observed error rate",
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 0 },
"targets": [
{ "expr": "manifest_errors_total / manifest_invocations_total", "legendFormat": "error rate", "refId": "A" }
],
"fieldConfig": { "defaults": { "unit": "percentunit", "min": 0, "max": 1 }, "overrides": [] }
}
],
"schemaVersion": 39,
"version": 1,
"refresh": ""
},
"overwrite": true
}
Verificando que el archivo reproduce exactamente el dashboard real (el mismo comando de la lección 6, apuntado al archivo en vez de a un bloque en línea):
curl -s -u admin:andescargo -X POST http://localhost:3000/api/dashboards/db \
-H "Content-Type: application/json" \
-d @observability/dashboard.json
Qué esperar (literal — verificado en este entorno, exactamente el mismo uid y slug de la lección 6):
{"folderUid": "", "id": 3189596175294464, "slug": "andes-cargo-process-shipment-manifest-sli-input", "status": "success", "uid": "andes-cargo-manifest-sli", "url": "/d/andes-cargo-manifest-sli/andes-cargo-process-shipment-manifest-sli-input", "version": 1}
Este es el sentido de "artefacto reproducible": cualquier persona con acceso a este archivo y a una instancia de Grafana corriendo puede recrear exactamente el mismo panel, con el mismo uid, sin depender de que alguien recuerde hacer clic en los lugares correctos de una interfaz.
El cierre del Módulo 3
Con OBSERVABILITY-RUNBOOK.md y dashboard.json escritos, este módulo entrega exactamente lo que la lección 1 prometió: no una disciplina completa de instrumentación, sino el pipeline mínimo que convierte invocaciones reales de process-shipment-manifest en los dos números que el SLI de SLO.md necesita —confirmado tres veces, por tres caminos (CloudWatch representativo, logs reales con jq, Prometheus real)—, y demostrado corriendo la calculadora del Módulo 2, sin ningún cambio de código, con esos datos reales. Entras al Módulo 4 con un pipeline de telemetría real y documentado — la base sobre la que la alerta de burn rate se construye, sin tener que reconstruir ninguna de estas seis lecciones.
Errores comunes
Escribir el runbook antes de haber corrido las seis lecciones anteriores, "para adelantar trabajo" (de documentar lo que todavía no verificaste). Qué pasa: alguien escribe OBSERVABILITY-RUNBOOK.md basándose en lo que espera que las lecciones anteriores hicieran, sin haber corrido docker compose up, el exportador, o el script de trazas de verdad. Cómo detectarlo: si no puedes reproducir, corriendo tú mismo los comandos del runbook, los mismos resultados que las lecciones 3 a 6 ya mostraron. Cómo corregirlo: el mismo estándar que SLO.md ya exigió en el Módulo 2 —cada cifra citada en un documento de portafolio debe ser trazable hasta una ejecución real— aplica aquí: cada fila de la tabla "Where the data lives" debe corresponder a algo que de verdad corriste, no a una suposición razonable sobre cómo debería funcionar.
Guardar dashboard.json como una exportación completa de Grafana, en vez del cuerpo limpio que se usó para crearlo (de confundir "backup" con "artefacto reproducible"). Qué pasa: alguien, al exportar el dashboard desde la UI de Grafana, guarda el JSON completo que incluye metadatos internos ("meta": {"canSave": true, "canEdit": true, ...}, "createdBy", timestamps de la instancia), en vez del cuerpo mínimo con "dashboard" y "overwrite" que la API espera para crear uno nuevo. Cómo detectarlo: si tu dashboard.json no funciona cuando lo pasas de vuelta a POST /api/dashboards/db en una instancia distinta de Grafana. Cómo corregirlo: el archivo de esta lección es, deliberadamente, el cuerpo de la petición que ya usaste para crear el dashboard —no una exportación de sus metadatos—, precisamente porque ese es el formato que garantiza reproducibilidad en cualquier instancia de Grafana, no solo en la que lo generó.
Tratar las "Known limitations" del runbook como una disculpa, en vez de como información operativa (de subestimar el valor de declarar lo representativo). Qué pasa: alguien, al escribir la sección final del runbook, la trata como un descargo de responsabilidad genérico ("algunas partes de este documento pueden no estar 100% actualizadas"), en vez de nombrar exactamente qué es representativo y por qué. Cómo detectarlo: si tu sección "Known limitations" no menciona LOCALSTACK_AUTH_TOKEN, ni distingue explícitamente qué SÍ corre de verdad (Prometheus, Grafana, Jaeger) de qué queda representativo (CloudWatch). Cómo corregirlo: la misma disciplina de honestidad que gobierna toda esta guía —declarar la razón técnica exacta de cada pieza representativa, en el momento en que aparece— aplica con la misma fuerza dentro de un runbook: alguien operando bajo presión necesita saber, con precisión, en qué puede confiar sin verificar dos veces y qué todavía depende de una pieza de infraestructura ausente en este entorno.
Ejercicios
Ejercicio 1 — Verifica el runbook completo contra tu propia ejecución de las lecciones 3 a 6. Para cada fila de la tabla "Where the data lives", confirma que puedes reproducir, con tus propios comandos, el estado (representativo o real) que el runbook declara.
Ver solución
Las cinco filas, verificadas: CloudWatch Metrics y CloudWatch Logs son representativas porque, en cualquier entorno sin LOCALSTACK_AUTH_TOKEN exportado, el intento de correr awslocal cloudwatch get-metric-statistics o awslocal logs filter-log-events fallaría al no poder conectarse a un LocalStack que nunca arrancó —una verificación negativa, pero verificación al fin—. Prometheus, Jaeger y Grafana son reales porque docker compose ps (lección 6) muestra los tres contenedores en estado Running/Up, y las consultas HTTP directas (/-/ready, /api/health, /api/services) de las lecciones 5 y 6 respondieron con datos genuinos, no simulados. Este ejercicio no es una formalidad — confirma que el runbook, como documento de portafolio, resiste ser auditado línea por línea contra comandos reales, el mismo estándar que SLO.md ya estableció en el Módulo 2.
Ejercicio 2 — Defiende, frente a un entrevistador técnico, por qué este runbook separa explícitamente "lo que se instrumenta" de "dónde vive el dato" de "qué consulta lo lee". Un entrevistador pregunta: "¿no sería más simple un solo documento con todos los comandos, sin tres tablas separadas?".
Ver solución
Una respuesta completa: "Las tres preguntas son genuinamente distintas, y alguien que opera bajo presión las necesita en ese orden. 'Qué se instrumenta' es la pregunta de diseño —¿cubrimos los tres pilares que importan?—, que casi nunca cambia. 'Dónde vive el dato' es la pregunta de infraestructura —¿está en CloudWatch, en Prometheus, en Jaeger?—, que sí puede cambiar si el equipo migra de herramienta, sin que la pregunta de diseño cambie con ella. 'Qué consulta lo lee' es la pregunta operativa inmediata —el comando exacto que alguien copia y pega a las 3 AM—, que depende directamente de la respuesta anterior. Fusionar las tres en un solo bloque de texto obligaría a releer todo el documento cada vez que una sola pieza de infraestructura cambiara; separadas, cada tabla se actualiza de forma independiente sin tocar las otras dos." La estructura del runbook no es decoración — es la misma separación de responsabilidades que la lección 2 de este módulo ya estableció entre métricas, logs y trazas, aplicada ahora a la documentación misma.
Ejercicio 3 — Explica por qué este proyecto no incluye una alerta, aunque el runbook menciona que los datos podrían usarse para disparar una. ¿Por qué el Módulo 3 se detiene en "medir" y deja "alertar" para el Módulo 4?
Ver solución
La misma razón que la lección 1 de este módulo ya declaró como frontera: este módulo existe para contestar una pregunta específica —"¿tengo datos reales para calcular un SLI?"—, no para construir el sistema completo de respuesta a incidentes. Una alerta necesita más que un pipeline de datos: necesita una regla de umbral o de burn rate (el patrón multi-ventana de Google SRE que el Módulo 4 va a formalizar), un canal de notificación, y una decisión explícita sobre cuándo el consumo del presupuesto de error es lo bastante rápido como para justificar despertar a alguien. Ninguna de esas tres piezas existe todavía en este módulo — construirlas aquí, "ya que estamos", sería exactamente el tipo de sobre-alcance que la lección 1 de este módulo advirtió al trazar la frontera con monitoring-observability-guide. La sección "Known limitations" del runbook lo declara con la misma honestidad: este documento mide, no alerta — el Módulo 4 es, con toda intención, un módulo distinto.
Resumen y siguiente paso
Este proyecto final del módulo escribió OBSERVABILITY-RUNBOOK.md —qué se instrumenta, dónde vive cada dato, qué consulta exacta lo lee, con la honestidad completa sobre qué es representativo y qué es real— y guardó observability/dashboard.json, el panel de Grafana de la lección 6, como un artefacto reproducible, verificado recreando el mismo dashboard con el mismo uid a partir del archivo. Verificaste ambos documentos con la misma disciplina determinista del resto de esta guía: 58 líneas y 4 secciones en el runbook, un "status": "success" idéntico al de la lección 6 al recrear el dashboard desde el archivo.
Antes de cerrar este módulo deberías poder: explicar, de memoria, las tres preguntas que separan las tres tablas del runbook; nombrar qué fuentes de datos de este módulo son reales y cuáles representativas, con la razón técnica exacta de cada una; y reproducir el panel de Grafana a partir de dashboard.json, sin depender de la interfaz.
Con esto, el Módulo 3 de sre-and-incident-response-guide queda completo: la observabilidad como insumo del SLI, nunca como disciplina autónoma, con un pipeline real corriendo sobre un batch fijo y determinista, y el hilo cerrado entre lo que SLO.md definió en el Módulo 2 y lo que este módulo midió con datos genuinos. El Módulo 4 toma exactamente este pipeline y construye la primera alerta real de esta guía: una regla que dispara por la razón correcta —consumo acelerado del presupuesto de error, el mismo patrón burn rate de la lección 6 del Módulo 2— en vez de un umbral arbitrario.
Recursos
- Este módulo, lecciones 2 a 7 — la fuente directa de cada fila de las tres tablas de
OBSERVABILITY-RUNBOOK.md. - Este mismo repositorio, Módulo 2, lección 8 (
08-project-andes-cargos-slo-md.md) —SLO.md, el documento que este runbook alimenta con datos reales. - Grafana HTTP API — Dashboard — la referencia oficial del formato de
dashboard.jsonque esta lección guarda. cloud-security-and-guardrails-guide(NIEVA), Módulo 1, proyecto yfinops-and-cost-guardrails-guide(NIEVA), Módulo 1, proyecto — el mismo formato de documento de portafolio corto y verificable, ya usado dos veces en este ecosistema.