Módulo 4: Alerting On Error Budget Burn Rate

8. Proyecto: la política de *alerting* de Andes Cargo

Descripción

Siete lecciones construyeron tres motores —Python, Prometheus/Alertmanager, CloudWatch— que implementan, con distinto nivel de sofisticación, la misma pregunta: ¿el consumo del error budget cruzó el umbral que SLO.md permite? Este proyecto final del módulo los reúne en ALERTING-POLICY.md, un documento de portafolio que no describe la teoría —la lección 2 ya lo hizo— sino que prueba que los tres motores, corridos uno al lado del otro sobre los mismos dos escenarios fijos, llegan a la misma conclusión: un simulacro forzado, con resultados literales, no una afirmación sin respaldo.

Conexión con el módulo

Este documento cita directamente los resultados ya producidos en las lecciones 3, 4 y 5 —no repite ningún cálculo, solo los reúne y los prueba juntos, con un cuarto dato nuevo (el estado representativo de la alarma de CloudWatch sobre los agregados completos de ambos escenarios, nunca calculado explícitamente hasta esta lección)—. El Módulo 5 hereda este documento al diseñar las severidades de on-call; el Módulo 8 lo cita al correr el incidente sintético final contra la misma máquina completa.


Paso 1 — El dato nuevo que faltaba: la alarma de CloudWatch sobre los agregados completos

Las lecciones 3 y 4 ya evaluaron la "mala semana" y el escenario "normal" con dos motores. La lección 5 construyó la alarma de CloudWatch, pero nunca la evaluó explícitamente contra estos dos escenarios completos — solo contra el batch de 20 invocaciones del Módulo 3. Este paso cierra ese hueco, aplicando la misma fórmula de la alarma (errors / invocations, umbral 0,001) a los totales agregados que el Módulo 2 ya calculó:

EscenarioEventos válidosEventos maloserror_ratiovs. umbral 0,001Estado de la alarma
Mala semana (BAD_WEEK, M2.7)2.718118118 / 2.718 = 0,04341 (4,341%)Muy por encimaALARM
Normal (TRAFFIC_30_DAYS, M2.4)12.1951111 / 12.195 = 0,00090 (0,090%)Por debajoOK

El segundo renglón es el más revelador de todo este módulo: 0,090% está por debajo de 0,1% —el mismo margen ajustado que SLO.md ya documentó (SLI de 99,9098%, apenas por encima del SLO de 99,9%)—. La alarma de CloudWatch, evaluada sobre el agregado completo del mes "normal", queda en OK, pero por un margen tan estrecho que confirma, una vez más, la lectura central del Módulo 2: este sistema cumple su SLO, pero sin ningún margen cómodo de sobra.


Paso 2 — El documento completo

En la raíz de andes-cargo-infra/, crea ALERTING-POLICY.md:

# ALERTING-POLICY.md — process-shipment-manifest Burn Rate Alerting

**Status:** Accepted · **Governs:** Modules 5 through 8 of `sre-and-incident-response-guide`
**Source:** Module 4, lessons 2-7 (the Google SRE multi-window pattern, three engines built and run)
**Related:** `SLO.md` (Module 2) defines the SLO this policy alerts against — 99.9% monthly,
0.1% allowed error rate, the exact threshold every engine below evaluates against.

## Why burn rate, not a static threshold

A static threshold on the accumulated monthly SLI would have missed the days 17-18 incident
of Module 2 entirely — the month's SLI (99.9098%) stayed above a 99.9% threshold even with
that incident inside it. Burn rate answers a different question: not "is the SLI below
target right now", but "how fast is the budget being consumed, right now, compared to the
rate the SLO allows" (Module 4, lesson 1-2, citing `sre.google/workbook/alerting-on-slos/`).

## The three engines, and what each one is for

| Engine | Built in | What it is for | Multi-window? |
|---|---|---|---|
| `scripts/burn_rate_evaluator.py` | Lesson 3 | Prototype of the alert decision, zero infrastructure | Yes, all 3 tiers (Table 5-8) |
| Prometheus + Alertmanager | Lesson 4 | Real, portable, multi-provider alerting engine | Yes, via `and ignoring(window)` |
| CloudWatch Alarm (`observability.tf`) | Lesson 5 | Native AWS alarm, no exporter to maintain | No — single window only |

## The two fixed scenarios every engine is tested against

- **`bad_week`** (Module 2, lesson 7): a broken deployment, 7 days, burn rate short window
  17.54x, long window 43.41x.
- **`normal`** (Module 2, lesson 4): the healthy 30-day reference month, burn rate short
  window 0.00x, long window 0.90x.

## Forced drill: results, engine by engine

| Engine | `bad_week` result | `normal` result |
|---|---|---|
| `burn_rate_evaluator.py` (3 tiers: Page fast, Page slow, Ticket) | **All 3 fire** | **None fire** |
| Alertmanager (`ManifestErrorBudgetBurnRate{PageFast,PageSlow,Ticket}`) | **All 3 firing**, confirmed via `/api/v1/rules` and `/api/v2/alerts`, delivered to the webhook receiver | **None appear** in any firing/active list |
| CloudWatch Alarm (`errors / invocations >= 0.001`) | `ALARM` (4.341% >> 0.1%) | `OK` (0.090% < 0.1%, narrow margin) |

Three engines, two scenarios, one conclusion every time: `bad_week` crosses every threshold
this policy defines; `normal` crosses none. No engine disagrees with any other.

## Routing

`aws_cloudwatch_metric_alarm.manifest_error_budget_burn_rate` publishes to
`aws_sns_topic.reliability_alerts` (Lesson 5), subscribed by `email` (real, $0) and, as the
documented pattern for a real team, an HTTPS endpoint matching PagerDuty's Amazon CloudWatch
integration format (Lesson 7) — named, not built, SaaS with no $0 tier.

## What this policy does not do

It does not replace human judgment on severity response (Module 5 builds that framework);
it does not implement the two-window confirmation natively in CloudWatch (Lesson 5's honest
limitation, closed only by the managed alternative named in Lesson 6, CloudWatch Application
Signals, native since November 2024); it does not cover any SLI other than
`process-shipment-manifest`'s error rate — no latency-based burn rate exists in this project.

## Consequences

Module 5 designs the on-call rotation and severity matrix against the exact three tiers this
policy defines (Page fast, Page slow, Ticket). Module 6 measures the real Claude Code incident
against this same policy, retroactively. Module 8's capstone introduces a new synthetic
incident and expects this exact machine — unchanged — to fire correctly on it.

Paso 3 — Verificando el documento

wc -l ALERTING-POLICY.md
grep -c '^## ' ALERTING-POLICY.md
grep -c 'fire\|ALARM\|OK' ALERTING-POLICY.md

Qué esperar (literal — el contenido lo escribiste tú, la forma es determinista):

62
7
3

Sesenta y dos líneas, siete secciones (Why burn rate, The three engines, The two fixed scenarios, Forced drill, Routing, What this policy does not do, Consequences), y tres líneas que mencionan explícitamente un estado de disparo (fire, ALARM u OK) — la evidencia condensada de que este documento no es una promesa en prosa, sino el registro de un simulacro que de verdad corrió, tres veces, con el mismo resultado.


Leyendo el simulacro completo: por qué la coincidencia entre los tres motores importa

Que los tres motores lleguen a la misma conclusión no es casualidad — es la prueba de que la matemática de burn rate es la misma matemática, sin importar qué la ejecute. burn_rate_evaluator.py la calcula con listas de tuplas en memoria; Alertmanager la calcula con PromQL sobre series scrapeadas; CloudWatch la calcula con metric math sobre métricas nativas de Lambda. Ninguno de los tres "sabe" del resultado de los otros dos — cada uno llega a DISPARA/firing/ALARM para bad_week y a no dispara/(nada)/OK para normal de forma completamente independiente, porque los tres implementan, con sintaxis distinta, la misma fórmula: tasa de error observada dividida entre tasa de error permitida, comparada contra un umbral.

Este es exactamente el tipo de evidencia que un postmortem o una revisión de arquitectura pedirían: no "creemos que la alerta funciona", sino "la probamos con un escenario que sabíamos que debía disparar, y con uno que sabíamos que no debía, en los tres sistemas que la implementan, y los tres coincidieron". El Módulo 8 de esta guía va a repetir exactamente este ejercicio —un incidente sintético nuevo, nunca visto antes— para confirmar que esta coincidencia no fue un accidente de los datos elegidos, sino una propiedad real de la máquina completa.


Errores comunes

Tratar la fila normal de la tabla de CloudWatch como "un margen cómodo" solo porque el estado es OK (de leer el estado sin leer el margen). Qué pasa: alguien ve OK junto a normal y concluye que el sistema está lejos de disparar una alarma real. Cómo detectarlo: si tu lectura de "0,090% < 0,1%, OK" no menciona qué tan cerca está ese margen. Cómo corregirlo: 0,090% contra un umbral de 0,1% es un margen de apenas 0,01 puntos porcentuales — la misma lectura ajustada que SLO.md ya hizo sobre este mismo mes (4,23 de 43,2 minutos restantes, 90,2% del presupuesto ya consumido). OK es el estado correcto, pero "OK con margen amplio" y "OK a un paso de ALARM" son lecturas muy distintas, y este documento existe, en parte, para no confundirlas.

Asumir que la coincidencia entre los tres motores garantiza que nunca van a divergir en el futuro (de generalizar de más a partir de dos escenarios fijos). Qué pasa: alguien concluye, de este simulacro, que Python/Alertmanager/CloudWatch siempre van a estar de acuerdo, sin importar qué datos reciban. Cómo detectarlo: si tu conclusión de esta lección es "estos tres sistemas son intercambiables en cualquier situación". Cómo corregirlo: los tres motores implementan la misma fórmula, pero con diferencias reales de cobertura —CloudWatch, en su forma de esta guía, no tiene confirmación de dos ventanas; Alertmanager sí— que este mismo documento ya declara en su sección "What this policy does not do". Coincidir en estos dos escenarios específicos confirma que la matemática está bien implementada en los tres; no garantiza que un escenario distinto, con un patrón de fallo diferente, no revele una diferencia real de cobertura entre ellos.

Escribir ALERTING-POLICY.md sin haber corrido las lecciones 3, 4 y 5 de verdad, confiando en que los números "seguramente son correctos" (repetido del mismo error ya nombrado en SLO.md, Módulo 2). Qué pasa: alguien copia este documento sin haber corrido burn_rate_evaluator.py ni levantado Prometheus/Alertmanager por su cuenta. Cómo detectarlo: si no puedes reproducir, en tu propia máquina, el firing de las tres alertas de Alertmanager sobre bad_week. Cómo corregirlo: cada cifra de este documento proviene de un motor que tú mismo corriste en las lecciones anteriores — antes de dar por bueno ALERTING-POLICY.md, confirma que tu propia copia de cada motor produce, sin diferencias, los mismos resultados citados aquí.


Ejercicios

Ejercicio 1 — Verifica a mano la fila de CloudWatch de la tabla del Paso 1 para el escenario bad_week. BAD_WEEK tiene 2.718 eventos válidos y 2.600 eventos buenos (Módulo 2, lección 7). Calcula error_ratio y confirma que supera 0,001.

Ver solución

Eventos malos = 2.718 − 2.600 = 118. error_ratio = 118 / 2.718 ≈ 0,043414 (4,3414%), muy por encima del umbral de 0,001 (0,1%) — confirma la fila ALARM de la tabla. Esta cifra, además, coincide con el mismo orden de magnitud que el burn rate de ventana larga ya calculado en la lección 3 de este módulo (43,41x): un burn rate de 43,41x significa, por definición, que la tasa de error observada es 43,41 veces la tasa permitida (0,1%) — 0,1% × 43,41 ≈ 4,341%, exactamente el error_ratio que este ejercicio acaba de calcular por un camino distinto. Dos fórmulas, el mismo número, otra confirmación de que la matemática es consistente entre los tres motores de este módulo.

Ejercicio 2 — Un colega propone eliminar el motor de CloudWatch de esta política, argumentando que Alertmanager ya cubre el patrón completo de dos ventanas y es "estrictamente mejor". Usando la sección "What this policy does not do" de este documento, ¿qué argumento usarías para mantener los tres motores?

Ver solución

CloudWatch tiene una limitación real (una sola ventana) frente a Alertmanager, pero tiene una ventaja que Alertmanager no tiene: es nativo de AWS, sin exportador Python que mantener, sin contenedor Docker que operar, y evalúa directamente las métricas que el propio Lambda ya publica automáticamente. "Estrictamente mejor" ignora que ambos motores tienen costos operativos distintos: Alertmanager depende de que alguien mantenga el docker-compose.yml, el exportador, y la disponibilidad de ese stack completo; CloudWatch depende únicamter de que el Lambda exista, sin ninguna pieza adicional que pueda fallar por su cuenta. Un argumento completo citaría, además, que ambos motores ya demostraron en este simulacro llegar a la misma conclusión sobre los mismos datos — tener los dos no es redundancia inútil, es la misma lógica de "no depender de un solo motor" que la lección 1 de este módulo ya estableció, ahora con evidencia de que ambos, de hecho, funcionan.

Ejercicio 3 — Explica por qué este documento cita SLO.md en su encabezado (Related) en vez de repetir la definición del SLO dentro de ALERTING-POLICY.md. ¿Qué principio de diseño de documentos, ya usado en este ecosistema, es este?

Ver solución

Es el mismo principio de una única fuente de verdad que SLO.md ya estableció para sí mismo en el Módulo 2: cada documento de portafolio de esta guía define una cosa con autoridad, y cualquier documento posterior que la necesite la cita, nunca la copia ni la redefine por su cuenta. ALERTING-POLICY.md necesita el SLO (99,9% mensual, 0,1% de tasa de error permitida) para justificar cada uno de sus umbrales, pero repetir esa definición aquí crearía dos fuentes de verdad —si SLO.md cambiara alguna vez, ALERTING-POLICY.md quedaría desactualizado sin que nadie lo notara—. Citar la fuente, en cambio, garantiza que un cambio futuro al SLO se refleja automáticamente en el razonamiento de este documento, sin duplicar ni arriesgar una inconsistencia entre los dos.


Resumen y siguiente paso

Este proyecto final del módulo escribió ALERTING-POLICY.md: el documento que reúne los tres motores de burn rate construidos en las lecciones 3, 4 y 5, probados con un simulacro forzado sobre los dos escenarios fijos de este módulo. El resultado, coincidente en los tres: bad_week dispara las tres severidades en Python, las tres alertas en Alertmanager (confirmadas de punta a punta hasta el receptor), y pasa a ALARM en CloudWatch (4,341% de tasa de error, muy por encima del 0,1% permitido); normal no dispara nada en ninguno de los tres, con CloudWatch quedando en OK por un margen de apenas 0,01 puntos porcentuales —la misma lectura ajustada que SLO.md ya documentó desde el Módulo 2—.

Antes de cerrar este módulo deberías poder: explicar por qué la coincidencia entre los tres motores es evidencia real, no casualidad; calcular a mano el error_ratio de CloudWatch para cualquiera de los dos escenarios; y nombrar la limitación real que cada motor tiene frente a los otros dos.

Con esto, el Módulo 4 de sre-and-incident-response-guide queda completo: la matemática de burn rate del Módulo 2, convertida en tres motores reales de alerta, probados con evidencia, no con promesas. El Módulo 5 construye el marco genérico de respuesta a incidentes —roles, severidades, on-call— que va a operar exactamente las alertas que este módulo dejó funcionando, antes de aplicarlo al incidente real del Módulo 6.

Recursos

  1. Este módulo, lecciones 2 a 7 — la fuente directa de cada cifra y cada decisión de este documento.
  2. Este mismo repositorio, Módulo 2, lección 8 (08-project-andes-cargos-slo-md.md) — SLO.md, el documento que ALERTING-POLICY.md cita en vez de repetir.
  3. Este mismo repositorio, Módulo 3, lección 8 (08-project-andes-cargos-observability-to-sli-pipeline.md) — OBSERVABILITY-RUNBOOK.md, el mismo formato de documento de portafolio que ALERTING-POLICY.md sigue.
  4. Google SRE Workbook — Alerting on SLOs — la fuente de la matemática que los tres motores de esta política implementan.