Implementar alarmas de infraestructura con Terraform en AWS (SNS + CloudWatch) paso a paso
Introducción
Una vez definido el enfoque de observabilidad y rollout, el siguiente paso es convertirlo en código reutilizable. En esta guía implementamos un baseline de alertas EC2 con Terraform usando una arquitectura simple:
SNSpara notificacionesCloudWatch Alarmspara evaluación de métricas- módulo reutilizable para despliegue por entorno
Todo el contenido usa ejemplos sanitizados (sin datos reales, IDs reales ni variables sensibles).
Si quieres primero el marco de decisiones y trade-offs (naming, rollout, InstanceId vs ASG, estrategia de validación), revisa el artículo conceptual:
Objetivo del tutorial
Implementar una base de monitorización EC2 reusable para un entorno (por ejemplo stg) con:
- Topic SNS de alertas de infraestructura.
- Suscripción por email.
- Alarmas CloudWatch para:
CPUUtilization(warning/critical)StatusCheckFailedCPUCreditBalance(si aplica)
Arquitectura mínima
El flujo es directo:
EC2 metrics -> CloudWatch Alarms -> SNS -> Email
Es una base excelente para:
- validar naming
- validar canales
- validar thresholds
- preparar una extensión posterior a RDS / Redis / ALB
Estructura Terraform recomendada
Una estructura sencilla y mantenible:
terraform/
main/
main.tf
variables.tf
modules/
monitoring_ec2_alerts/
variables.tf
sns.tf
alarms_bastion.tf
alarms_api_asg.tf
outputs.tf
Variables de entrada (sanitizadas)
En el root module, define inputs claros. Ejemplo:
variable "infra_alerts_email" {
description = "Email endpoint for infrastructure alerts SNS subscription"
type = string
}
En Terraform Cloud:
infra_alerts_email-> Terraform variable- credenciales
AWS_*-> Environment variables
Paso 1: Crear el topic SNS y la suscripción
Archivo ejemplo modules/monitoring_ec2_alerts/sns.tf:
resource "aws_sns_topic" "infra_alerts" {
name = var.topic_name
}
resource "aws_sns_topic_subscription" "infra_alerts_email" {
topic_arn = aws_sns_topic.infra_alerts.arn
protocol = "email"
endpoint = var.alert_email_endpoint
}
Inputs asociados (variables.tf del módulo):
variable "topic_name" {
type = string
default = "stg-infra-alerts"
}
variable "alert_email_endpoint" {
type = string
}
Paso 2: Alarmas para una instancia fija (bastion)
Para una instancia fija suele ser buena opción usar InstanceId.
Ejemplo de alarma de CPU warning:
resource "aws_cloudwatch_metric_alarm" "bastion_cpu_warning" {
alarm_name = "stg-ec2-bastion-cpu-warning"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 1
datapoints_to_alarm = 1
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
period = 300
statistic = "Average"
threshold = 75
treat_missing_data = "missing"
dimensions = {
InstanceId = var.bastion_instance_id
}
alarm_actions = [aws_sns_topic.infra_alerts.arn]
ok_actions = [aws_sns_topic.infra_alerts.arn]
}
Puntos a destacar:
period=300encaja bien cuando quieres una ventana de 5 minutos.ok_actionsayuda a cerrar el ciclo de notificación (recibirOKtras recuperación).
Paso 3: Alarmas para API detras de Auto Scaling Group
Cuando la API vive detras de un ASG, una opción robusta es usar AutoScalingGroupName en las dimensiones para no depender de una instancia concreta.
Ejemplo de CPU warning por ASG:
resource "aws_cloudwatch_metric_alarm" "api_cpu_warning" {
alarm_name = "stg-ec2-api-cpu-warning"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 1
datapoints_to_alarm = 1
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
period = 300
statistic = "Average"
threshold = 75
treat_missing_data = "missing"
dimensions = {
AutoScalingGroupName = var.api_asg_name
}
alarm_actions = [aws_sns_topic.infra_alerts.arn]
ok_actions = [aws_sns_topic.infra_alerts.arn]
}
Ejemplo de StatusCheckFailed por ASG:
resource "aws_cloudwatch_metric_alarm" "api_statuscheckfailed_critical" {
alarm_name = "stg-ec2-api-statuscheckfailed-critical"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 1
datapoints_to_alarm = 1
metric_name = "StatusCheckFailed"
namespace = "AWS/EC2"
period = 60
statistic = "Maximum"
threshold = 0
dimensions = {
AutoScalingGroupName = var.api_asg_name
}
alarm_actions = [aws_sns_topic.infra_alerts.arn]
}
Paso 4: Nombrado y severidades
Usa un patrón consistente:
<env>-<service>-<resource>-<metric>-<severity>
Ejemplos:
stg-ec2-bastion-cpu-warningstg-ec2-bastion-cpu-criticalstg-ec2-api-cpucreditbalance-warningstg-ec2-api-statuscheckfailed-critical
Esto facilita:
- busquedas en CloudWatch
- filtros por severidad
- mantenimiento por entorno
Paso 5: Integración del módulo en el root
Ejemplo de integración en terraform/main/main.tf:
module "monitoring_ec2_alerts" {
source = "../modules/monitoring_ec2_alerts"
alert_email_endpoint = var.infra_alerts_email
bastion_instance_id = module.ec2_instances.output_bastion_instance_id
api_asg_name = module.autoscaling_groups.output_asg_apiasg_name
}
Este patrón mantiene el root como orquestador y evita hardcodear IDs en el módulo.
Paso 6: Flujo de validación y despliegue
Secuencia recomendada:
terraform fmtterraform validateterraform plan- revisar diff
terraform apply
Checklist operativo tras apply:
- Existe el topic SNS esperado
- Existe la suscripción email
- La suscripción esta confirmada
- Existen las alarmas
stg-ec2-*
Verificación funcional (sin exponer datos reales)
Una vez aplicado:
- Publica un mensaje de prueba en el topic SNS.
- Verifica recepción por email.
- Comprueba que CloudWatch muestra las alarmas creadas con el naming esperado.
Después de validar el canal, puedes preparar una prueba controlada de CPU en un entorno no productivo para confirmar el flujo end-to-end.
Si quieres aprender esa validación desde consola/CLI antes de automatizarlo con Terraform, puedes apoyarte en este post:
Errores comunes de implementación (genéricos)
1) Mezclar variables de Terraform con variables de entorno en Terraform Cloud
Solución:
- inputs del código -> Terraform Variables
- credenciales y runtime -> Environment Variables
2) Crear alarmas sin validar la suscripción SNS
Si el email no esta confirmado, la alarma puede disparar y aún así no recibir nada.
3) Naming inconsistente entre entornos
Si no normalizas nombres desde el inicio, luego es difícil filtrar, auditar y automatizar reportes.
Extensión natural del baseline
Con esta base validada, el siguiente paso suele ser ampliar cobertura a:
- RDS (CPU, conexiones, almacenamiento)
- Redis/ElastiCache (memoria, evictions, CPU)
- ALB (latencia y 5xx)
La clave es mantener el mismo patrón:
- módulo reutilizable
- naming estándar
- rollout por entorno
Cierre
Implementar alertas con Terraform no consiste solo en “traducir clicks a HCL”. La mejora real viene de tener un flujo repetible, revisable y desplegable por entornos.
Este baseline (SNS + CloudWatch + naming + rollout) es una base muy buena para escalar observabilidad operativa sin introducir complejidad innecesaria.
Si quieres reforzar la parte de diseño y decisiones antes de implementar, revisa el artículo complementario:
¿Evolucionamos tu plataforma de datos?
Si quieres mejorar arquitectura, calidad y coste de tu pipeline, puedo ayudarte a aterrizar una hoja de ruta por fases.