Módulo 4: Alerting On Error Budget Burn Rate
7. Manos a la obra: enrutando la alerta
Descripción
La alarma de CloudWatch de la lección 5 ya publica en aws_sns_topic.reliability_alerts cuando cruza el umbral — pero un tema de SNS sin ninguna suscripción es una alarma que grita en una habitación vacía. Esta lección cierra ese último tramo: declara una suscripción real de SNS (aws_sns_topic_subscription), valida el patrón exacto que un equipo real usaría para conectar esa alarma a PagerDuty, y nombra, con honestidad sobre su costo, el destino final que un equipo de producción de verdad usaría —PagerDuty u Opsgenie— sin construirlo, porque ninguno de los dos tiene una capa gratuita equivalente al resto de este laboratorio $0.
Conexión con el módulo
Esta lección extiende observability.tf —el mismo archivo de la lección 5, sin declarar ningún tema de SNS nuevo—, agregando la pieza que faltaba entre "la alarma dispara" y "alguien se entera". terraform validate y terraform plan corrieron de verdad contra este HCL nuevo. La lección 8, el proyecto de este módulo, documenta esta cadena completa —alarma → tema → suscripción— como parte de la política de alerting de Andes Cargo.
Paso 1 — El eslabón que faltaba: una suscripción real sobre el tema ya declarado
aws_sns_topic.reliability_alerts ya existe desde la lección 5. Esta lección le agrega una suscripción, con dos rutas documentadas —una que de verdad funcionaría contra una cuenta real de AWS sin ningún costo adicional, y otra que modela el patrón exacto que conectaría esta alarma a PagerDuty:
# Ruta 1: email, $0, funcionaria de verdad contra una cuenta real de AWS
# (SNS no cobra por notificaciones de email hasta un volumen alto -- ver Recursos).
resource "aws_sns_topic_subscription" "reliability_alerts_email" {
topic_arn = aws_sns_topic.reliability_alerts.arn
protocol = "email"
endpoint = "reliability-team@andescargo.io"
}
# Ruta 2: el patron real que un equipo de produccion usaria para PagerDuty --
# HTTPS, con la URL de integracion generada por PagerDuty al crear el servicio
# (ver Recursos). "REPLACE_WITH_INTEGRATION_KEY" es un placeholder deliberado:
# esa clave la genera PagerDuty por cuenta, nunca un valor que esta guia pueda fijar.
resource "aws_sns_topic_subscription" "reliability_alerts_pagerduty" {
topic_arn = aws_sns_topic.reliability_alerts.arn
protocol = "https"
endpoint = "https://events.pagerduty.com/x-ere/REPLACE_WITH_INTEGRATION_KEY"
}
Dos rutas, dos honestidades distintas. La primera (email) es un recurso real y completo: protocol = "email" no depende de ningún servicio de terceros, y en una cuenta real de AWS con un LocalStack funcionando, este apply completaría con éxito y de verdad llegaría un correo de confirmación de suscripción. La segunda (https, apuntando a PagerDuty) es el patrón exacto que la documentación oficial de PagerDuty especifica para su integración de Amazon CloudWatch —protocolo HTTPS, la URL con el formato events.pagerduty.com/x-ere/<integration_key>—, pero con la clave de integración como placeholder explícito: esa clave la genera PagerDuty al crear un servicio dentro de tu propia cuenta, nunca un valor genérico que esta guía pueda declarar de antemano.
Paso 2 — terraform validate y terraform plan: ejecutados, literales
terraform validate
Qué esperar (literal):
Success! The configuration is valid.
terraform plan
Qué esperar (literal — recorte relevante; Plan: 4 to add porque incluye también la alarma y el tema SNS de la lección 5, todos en observability.tf):
# aws_sns_topic_subscription.reliability_alerts_email will be created
+ resource "aws_sns_topic_subscription" "reliability_alerts_email" {
+ arn = (known after apply)
+ confirmation_timeout_in_minutes = 1
+ confirmation_was_authenticated = (known after apply)
+ endpoint = "reliability-team@andescargo.io"
+ endpoint_auto_confirms = false
+ filter_policy_scope = (known after apply)
+ id = (known after apply)
+ owner_id = (known after apply)
+ pending_confirmation = (known after apply)
+ protocol = "email"
+ raw_message_delivery = false
+ topic_arn = (known after apply)
}
# aws_sns_topic_subscription.reliability_alerts_pagerduty will be created
+ resource "aws_sns_topic_subscription" "reliability_alerts_pagerduty" {
+ arn = (known after apply)
+ confirmation_timeout_in_minutes = 1
+ confirmation_was_authenticated = (known after apply)
+ endpoint = "https://events.pagerduty.com/x-ere/REPLACE_WITH_INTEGRATION_KEY"
+ endpoint_auto_confirms = false
+ filter_policy_scope = (known after apply)
+ id = (known after apply)
+ owner_id = (known after apply)
+ pending_confirmation = (known after apply)
+ protocol = "https"
+ raw_message_delivery = false
+ topic_arn = (known after apply)
}
Plan: 4 to add, 0 to change, 0 to destroy.
raw_message_delivery = false (el valor por defecto del recurso, sin declararlo explícitamente en el Paso 1) es, exactamente, lo que la propia documentación de PagerDuty pide: "Ensure that the Enable raw message delivery checkbox is unchecked" — con raw_message_delivery = false, SNS envuelve cada notificación en su propio sobre JSON (con metadatos de tema, timestamp, firma), el formato que la integración de CloudWatch de PagerDuty espera para parsear la alarma correctamente. Ponerlo en true entregaría el mensaje "crudo", sin ese sobre — rompiendo el parseo del lado de PagerDuty.
Paso 3 — El intento de apply: representativo, misma causa raíz
Qué esperar (representativo — misma razón que la lección 5: sin LOCALSTACK_AUTH_TOKEN, el contenedor de LocalStack no arranca en este entorno de escritura):
terraform apply -auto-approve \
-target=aws_sns_topic_subscription.reliability_alerts_email
Error: creating SNS Topic (andes-cargo-reliability-alerts): operation error SNS:
CreateTopic, exceeded maximum number of attempts, 9, https response error
StatusCode: 0, RequestID: , request send failed, Post "http://localhost:4566/":
dial tcp [::1]:4566: connect: connection refused
with aws_sns_topic.reliability_alerts,
on observability.tf line 1, in resource "aws_sns_topic" "reliability_alerts":
1: resource "aws_sns_topic" "reliability_alerts" {
El mismo connection refused de cada intento de apply de este módulo — y, otra vez como en la lección 5, Terraform ni siquiera llega a intentar la suscripción en sí: como reliability_alerts_email depende de aws_sns_topic.reliability_alerts.arn, Terraform intenta crear primero el tema, y falla ahí, antes de tocar la suscripción. La diferencia real, otra vez, no está en el error — está en lo que pasaría con un LocalStack corriendo con normalidad: la suscripción de email se aplicaría de verdad y de forma completa (SNS está confirmado en el plan Hobby de LocalStack, junto con CloudWatch); la suscripción de https hacia PagerDuty también se aplicaría del lado de AWS/LocalStack —pero el endpoint en sí (events.pagerduty.com) es un servicio externo real, fuera de cualquier simulación, y solo respondería con una confirmación válida si la clave de integración fuera una real, generada dentro de una cuenta real de PagerDuty — la razón exacta por la que esta lección no puede, ni con el token exportado, completar esa segunda ruta de punta a punta.
Por qué PagerDuty y Opsgenie quedan nombrados, no construidos
Ambos son SaaS de pago, sin capa gratuita equivalente al resto de este laboratorio $0. La documentación oficial de ambos confirma el mismo patrón de integración —una suscripción HTTPS de SNS hacia una URL específica de la cuenta—, con una diferencia relevante entre los dos: PagerDuty documenta un formato de URL predecible (events.pagerduty.com/x-ere/<key>), mientras que Opsgenie genera una URL de integración completa por cuenta, sin un formato público fijo, que solo se obtiene creando primero una integración real dentro de su consola ("copy the endpoint URL generated for your account").
EL PATRON COMPLETO, LOS TRES DESTINOS POSIBLES DE LA MISMA ALARMA
aws_cloudwatch_metric_alarm (M4.5)
|
v
aws_sns_topic.reliability_alerts (M4.5)
|
┌──────┼──────────────────┐
v v v
email PagerDuty Opsgenie
($0, (HTTPS, SaaS, (HTTPS, SaaS,
real) events.pagerduty URL generada
.com/x-ere/<key>) por cuenta)
Ni PagerDuty ni Opsgenie se construyen en esta lección — se nombran, con el patrón de integración exacto verificado contra su propia documentación oficial, y con la honestidad de que un equipo real de Andes Cargo, con presupuesto, terminaría exactamente en uno de los dos, no en un correo electrónico. La ruta de email de esta lección es el sustituto $0 completo: funciona de verdad, sin ningún costo, pero sin escalamiento automático, sin aplicación móvil, sin rotación de guardia integrada — las mismas ausencias que el Módulo 5 de esta guía, sobre on-call, va a nombrar con la misma honestidad.
Errores comunes
Declarar raw_message_delivery = true "porque suena más simple" (de romper el contrato que PagerDuty/Opsgenie esperan sin darse cuenta). Qué pasa: alguien, viendo que raw_message_delivery controla si SNS envuelve el mensaje o lo entrega tal cual, asume que "tal cual" es más simple y lo cambia a true. Cómo detectarlo: si tu suscripción hacia PagerDuty o Opsgenie deja de generar incidentes reconocibles, aunque las notificaciones sí lleguen. Cómo corregirlo: ambos servicios esperan, del lado de su integración, el sobre JSON completo que SNS agrega por defecto (raw_message_delivery = false, tal como esta lección lo deja) — quitarlo no simplifica nada, rompe el parseo automático que el lado receptor necesita para reconocer que el mensaje viene de una alarma de CloudWatch específica.
Inventar una clave de integración de PagerDuty "de ejemplo" que parezca real, en vez de usar un placeholder explícito (de fabricar un dato que parece verificable sin serlo). Qué pasa: alguien, incómodo con dejar REPLACE_WITH_INTEGRATION_KEY visible, sustituye ese texto por una cadena alfanumérica inventada que se ve como una clave real de PagerDuty. Cómo detectarlo: si tu HCL tiene un valor en endpoint que parece una clave real pero no proviene de ninguna cuenta real de PagerDuty. Cómo corregirlo: un placeholder explícito y legible (REPLACE_WITH_INTEGRATION_KEY) es más honesto que un valor inventado que aparenta ser real — la misma disciplina que este ecosistema ya sigue con cualquier credencial de ejemplo, desde access_key = "test" en cada provider "aws" de LocalStack hasta este caso.
Asumir que declarar la suscripción de email en Terraform ya significa que el correo llegó (de confundir declarar con entregar). Qué pasa: alguien lee el HCL de la Ruta 1 y asume que, en cuanto se aplique, el equipo de confiabilidad ya está recibiendo alertas por correo, sin ningún paso adicional. Cómo detectarlo: si tu entendimiento del flujo de email en SNS no incluye un paso de confirmación. Cómo corregirlo: una suscripción de email en SNS no queda activa de inmediato — SNS envía primero un correo de confirmación al endpoint declarado, y la suscripción permanece en PendingConfirmation hasta que alguien haga clic en el enlace de esa confirmación. Es un paso manual, fuera del alcance de Terraform, que ni siquiera un apply exitoso completa por sí solo.
Ejercicios
Ejercicio 1 — Explica por qué esta lección declara dos aws_sns_topic_subscription distintos sobre el mismo aws_sns_topic, en vez de reemplazar uno por el otro. ¿Qué gana Andes Cargo al tener ambas rutas activas a la vez, en una cuenta real?
Ver solución
Un tema de SNS admite múltiples suscripciones simultáneas, cada una recibiendo una copia independiente de cada mensaje publicado — no son alternativas excluyentes, son canales paralelos. Tener ambas activas a la vez le da a Andes Cargo redundancia real: si la integración con PagerDuty falla silenciosamente (una clave de integración expirada, por ejemplo), el correo electrónico sigue llegando como respaldo, sin que la alarma quede completamente muda. Es el mismo principio de "no depender de un solo motor de alerta" que la lección 1 de este módulo ya introdujo al explicar por qué existen tres motores distintos (Python, Prometheus/Alertmanager, CloudWatch) — aquí, aplicado a nivel de canal de notificación en vez de a nivel de motor de decisión.
Ejercicio 2 — Un compañero de equipo propone usar protocol = "sms" en vez de "email" para la ruta $0 de esta lección, argumentando que un SMS llega más rápido a las 3 AM. ¿Es esta ruta realmente $0, igual que email?
Ver solución
No de la misma forma. A diferencia de las notificaciones de email de SNS (gratuitas hasta un volumen mensual alto), las notificaciones de sms de SNS sí tienen un costo por mensaje enviado desde el primer SMS, sin ninguna capa gratuita equivalente — un costo pequeño, pero real, y distinto de cero. Esta es exactamente el tipo de distinción que este ecosistema completo, desde finops-and-cost-guardrails-guide, exige verificar antes de asumir que cualquier recurso de AWS es automáticamente $0: aws_sns_topic_subscription con protocol = "email" sí lo es; con protocol = "sms", no. La velocidad de entrega es una ventaja real de SMS, pero no gratuita, y esta guía declaró $0 como compromiso explícito desde el Módulo 1.
Ejercicio 3 — Diseña, en prosa, cómo verificarías —sin gastar dinero, sin una cuenta real de PagerDuty— que el HCL de la Ruta 2 de esta lección (aws_sns_topic_subscription.reliability_alerts_pagerduty) está sintácticamente correcto y coincide con el patrón oficial de PagerDuty, sin poder confirmarlo con un apply real contra el servicio externo.
Ver solución
La verificación disponible sin gastar dinero es exactamente la que esta lección ya hizo: terraform validate confirma que el recurso está bien formado según el esquema del provider de AWS (protocolo, topic_arn, endpoint como cadena válida) — eso no depende en absoluto de si la URL de destino es real o un placeholder. Para confirmar que el formato de la URL coincide con lo que PagerDuty espera, la verificación es documental, no ejecutable: contrastar el patrón declarado (https://events.pagerduty.com/x-ere/<key>) contra la documentación oficial de PagerDuty, exactamente como esta lección ya hizo citando la fuente en la sección "Por qué PagerDuty y Opsgenie quedan nombrados". La única verificación que de verdad requeriría una cuenta real de PagerDuty es la entrega efectiva del mensaje — esa sí queda, honestamente, fuera del alcance de este laboratorio $0.
Resumen y siguiente paso
Esta lección cerró la cadena que la lección 5 dejó abierta: aws_cloudwatch_metric_alarm → aws_sns_topic → aws_sns_topic_subscription, con dos rutas declaradas —una completamente $0 y funcional (email), otra que modela el patrón exacto, verificado contra la documentación oficial de PagerDuty, que un equipo real usaría para conectar esta alarma a un sistema de guardia real—. terraform validate y terraform plan corrieron de verdad, sin errores; el intento de apply falló por la misma causa circunstancial de siempre. PagerDuty y Opsgenie quedaron nombrados, con su patrón de integración real citado, nunca construidos — ambos SaaS de pago, sin capa $0 en este ecosistema.
Antes de avanzar deberías poder: explicar la diferencia de costo real entre una suscripción de email y una de sms en SNS; explicar por qué raw_message_delivery debe quedarse en false para una integración de PagerDuty/Opsgenie; y nombrar la cadena completa de tres recursos que conecta una alarma con una notificación real.
La lección 8 cierra este módulo con el proyecto: un documento que reúne las reglas de burn rate de Alertmanager (lección 4) y CloudWatch (lección 5), probadas con un simulacro forzado —el mismo resultado de este módulo, "mala semana" dispara, "normal" no dispara, confirmado una vez más, con los tres motores completos.
Recursos
- Terraform Registry —
aws_sns_topic_subscription— referencia completa del esquema del recurso. - PagerDuty — Amazon CloudWatch Integration Guide — la fuente exacta del formato de URL y la configuración de
raw_message_deliverycitados en esta lección. - Opsgenie (Atlassian) — Integrate Opsgenie with Amazon CloudWatch — el mismo patrón de integración, documentado del lado de Opsgenie.
- AWS Docs — Amazon SNS pricing — la fuente de la distinción entre
email(gratuito hasta un volumen alto) ysms(con costo desde el primer mensaje) del Ejercicio 2. - Este mismo repositorio, Módulo 4, lección 5 (
05-hands-on-a-real-cloudwatch-alarm-on-the-lambda.md) —aws_sns_topic.reliability_alerts, el recurso que esta lección extiende con una suscripción real.