Implementar alarmas de infraestructura con Terraform en AWS (SNS + CloudWatch) paso a paso
Introduccion
Una vez definido el enfoque de observabilidad y rollout, el siguiente paso es convertirlo en codigo reutilizable. En esta guia implementamos un baseline de alertas EC2 con Terraform usando una arquitectura simple:
SNSpara notificacionesCloudWatch Alarmspara evaluacion de metricas- modulo 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 validacion), revisa el articulo conceptual:
Objetivo del tutorial
Implementar una base de monitorizacion EC2 reusable para un entorno (por ejemplo stg) con:
- Topic SNS de alertas de infraestructura.
- Suscripcion por email.
- Alarmas CloudWatch para:
CPUUtilization(warning/critical)StatusCheckFailedCPUCreditBalance(si aplica)
Arquitectura minima
El flujo es directo:
EC2 metrics -> CloudWatch Alarms -> SNS -> Email
Es una base excelente para:
- validar naming
- validar canales
- validar thresholds
- preparar una extension 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 suscripcion
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 modulo):
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 opcion 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 notificacion (recibirOKtras recuperacion).
Paso 3: Alarmas para API detras de Auto Scaling Group
Cuando la API vive detras de un ASG, una opcion 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 patron 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: Integracion del modulo en el root
Ejemplo de integracion 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 patron mantiene el root como orquestador y evita hardcodear IDs en el modulo.
Paso 6: Flujo de validacion y despliegue
Secuencia recomendada:
terraform fmtterraform validateterraform plan- revisar diff
terraform apply
Checklist operativo tras apply:
- Existe el topic SNS esperado
- Existe la suscripcion email
- La suscripcion esta confirmada
- Existen las alarmas
stg-ec2-*
Verificacion funcional (sin exponer datos reales)
Una vez aplicado:
- Publica un mensaje de prueba en el topic SNS.
- Verifica recepcion por email.
- Comprueba que CloudWatch muestra las alarmas creadas con el naming esperado.
Despues 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 validacion desde consola/CLI antes de automatizarlo con Terraform, puedes apoyarte en este post:
Errores comunes de implementacion (genericos)
1) Mezclar variables de Terraform con variables de entorno en Terraform Cloud
Solucion:
- inputs del codigo -> Terraform Variables
- credenciales y runtime -> Environment Variables
2) Crear alarmas sin validar la suscripcion SNS
Si el email no esta confirmado, la alarma puede disparar y aun asi no recibir nada.
3) Naming inconsistente entre entornos
Si no normalizas nombres desde el inicio, luego es dificil filtrar, auditar y automatizar reportes.
Extension 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 patron:
- modulo reutilizable
- naming estandar
- 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 articulo 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.