CloudWatch en AWS para EC2: alertas por consola y terminal con prueba real


Contexto

Configurar alarmas en AWS no es dificil. Lo dificil es validarlas de verdad y no dejar una falsa sensacion de cobertura.

Muchos equipos crean alarmas en CloudWatch, las conectan a SNS y dan por terminado el trabajo. El problema aparece despues: cuando llega el incidente, la alerta no salta por un detalle de configuracion, por un umbral mal calibrado o porque nunca se probo el flujo completo.

En este tutorial explico un patron simple y reutilizable para EC2:

  1. Crear un canal de notificacion con SNS.
  2. Crear alarmas en CloudWatch (por consola y por CLI).
  3. Validar con una prueba real de CPU.
  4. Dejar una base lista para replicar en otros entornos.

Todo el contenido esta anonimizado. Los nombres de recursos, IDs y correos son ejemplos genericos para que puedas adaptarlo a tu entorno.

Punto clave: una alarma sin prueba real es configuracion, no observabilidad operativa.

Enfoque propuesto

El enfoque que mejor me funciona para equipos y proyectos reales combina dos caminos:

  • Consola AWS para aprender el flujo, validar campos y entender la evaluacion visual.
  • CLI (AWS CLI) para repetir el proceso con precision y documentar comandos reutilizables.

En este post uso ambos de forma deliberada:

  1. Creo el canal SNS y una primera alarma desde consola.
  2. Creo el resto de alarmas por terminal.
  3. Hago una prueba de carga en la instancia para comprobar que la notificacion llega.
3
Alarmas EC2 base
2
Vias de implementacion (Console + CLI)
1
Prueba real de CPU para validar notificaciones

Arquitectura minima del flujo

La arquitectura es intencionadamente simple:

  • EC2 emite metricas nativas (por ejemplo, CPUUtilization).
  • CloudWatch Alarms evalua umbrales y cambia estado (OK, ALARM, INSUFFICIENT_DATA).
  • SNS envia la notificacion por email.

Para una primera version de observabilidad operativa, este flujo cubre mucho mas de lo que parece si lo validas bien.

Implementacion paso a paso

1) Crear SNS topic y suscripcion email (Console)

Objetivo: tener un canal de notificacion simple y verificable antes de crear alarmas.

Pasos en AWS Console (resumen):

  1. Ir a Amazon SNS.
  2. Crear un topic Standard.
  3. Asignar un nombre generico (ejemplo: dev-infra-alerts).
  4. Crear una suscripcion Email.
  5. Confirmar la suscripcion desde el correo recibido.
  6. Ejecutar un Publish message de prueba.

Recomendacion:

  • Empieza solo con email.
  • Cuando el flujo este validado, anade Teams/Slack/Chatbot.

2) Crear alarma EC2 desde Console (primera alarma)

Para aprender el flujo visual, crea la primera alarma desde CloudWatch > Alarms.

Alarma 1: CPU warning

Ejemplo de configuracion:

  • Metrica: AWS/EC2 -> CPUUtilization
  • Estadistica: Average
  • Periodo:
    • 60s si la instancia publica metricas cada 1 minuto (Detailed Monitoring)
    • 300s si la instancia usa Basic Monitoring (CPU en bloques de 5 min)
  • Umbral: > 75
  • Evaluacion:
    • 5 de 5 si usas 60s
    • 1 de 1 si usas 300s
  • Missing data: not breaching (en UI suele aparecer como “Correctos (dentro del limite)”)
  • Accion: enviar a topic SNS

En la validacion real de este tutorial se uso Basic Monitoring, por lo que la alarma se ajusto a period=300 y 1 de 1.

Nombre sugerido:

  • dev-ec2-bastion-cpu-warning

Etiquetas recomendadas:

  • Environment=dev
  • Service=ec2
  • Resource=bastion
  • Metric=CPUUtilization
  • Severity=warning

3) Crear alarmas EC2 desde terminal (CLI)

Una vez validado el flujo en consola, pasar a CLI te da dos ventajas:

  1. Trazabilidad del comando exacto.
  2. Repetibilidad entre entornos.

En este ejemplo usamos tres alarmas:

  • CPU warning
  • CPU critical
  • StatusCheckFailed critical

Nota: uso nombres genericos e identificadores de ejemplo. Sustituye INSTANCE_ID y SNS_TOPIC_ARN por los tuyos.

Variables (opcional, para no repetir)

REGION="eu-west-1"
INSTANCE_ID="i-xxxxxxxxxxxxxxxxx"
SNS_TOPIC_ARN="arn:aws:sns:eu-west-1:123456789012:dev-infra-alerts"

3.1 CPU warning (>75%) - configuracion final validada (Basic Monitoring 5m)

En una primera version puedes plantear 60s + 5/5, pero si la instancia publica CPU en Basic Monitoring (5 min), la configuracion real que valida correctamente es:

  • period = 300
  • evaluation-periods = 1
  • datapoints-to-alarm = 1

Esto mantiene el objetivo funcional: si la media del bloque de 5 minutos supera 75%, salta la alarma.

aws cloudwatch put-metric-alarm \
  --region "$REGION" \
  --alarm-name "dev-ec2-bastion-cpu-warning" \
  --alarm-description "DEV bastion CPUUtilization warning >75% durante 5 min (basic monitoring 5m)" \
  --namespace "AWS/EC2" \
  --metric-name "CPUUtilization" \
  --dimensions Name=InstanceId,Value="$INSTANCE_ID" \
  --statistic Average \
  --period 300 \
  --evaluation-periods 1 \
  --datapoints-to-alarm 1 \
  --threshold 75 \
  --comparison-operator GreaterThanThreshold \
  --treat-missing-data notBreaching \
  --alarm-actions "$SNS_TOPIC_ARN"

Comando de verificacion (muy recomendable):

aws cloudwatch describe-alarms \
  --region "$REGION" \
  --alarm-names "dev-ec2-bastion-cpu-warning" \
  --query "MetricAlarms[0].{Name:AlarmName,State:StateValue,Period:Period,Eval:EvaluationPeriods,Datapoints:DatapointsToAlarm,Threshold:Threshold,Reason:StateReason}"

3.2 CPU critical (>85%) - configuracion final validada (Basic Monitoring 5m)

Para mantener el criterio de “10 minutos sostenidos” usando bloques de 5 minutos:

  • period = 300
  • evaluation-periods = 2
  • datapoints-to-alarm = 2

Esto equivale a 2 bloques consecutivos de 5 minutos por encima del umbral.

aws cloudwatch put-metric-alarm \
  --region "$REGION" \
  --alarm-name "dev-ec2-bastion-cpu-critical" \
  --alarm-description "DEV bastion CPUUtilization critical >85% durante 10 min (basic monitoring 5m)" \
  --namespace "AWS/EC2" \
  --metric-name "CPUUtilization" \
  --dimensions Name=InstanceId,Value="$INSTANCE_ID" \
  --statistic Average \
  --period 300 \
  --evaluation-periods 2 \
  --datapoints-to-alarm 2 \
  --threshold 85 \
  --comparison-operator GreaterThanThreshold \
  --treat-missing-data notBreaching \
  --alarm-actions "$SNS_TOPIC_ARN"

Comando de verificacion:

aws cloudwatch describe-alarms \
  --region "$REGION" \
  --alarm-names "dev-ec2-bastion-cpu-critical" \
  --query "MetricAlarms[0].{Name:AlarmName,State:StateValue,Period:Period,Eval:EvaluationPeriods,Datapoints:DatapointsToAlarm,Threshold:Threshold,Reason:StateReason}"

3.3 StatusCheckFailed critical (>0, 1/1)

aws cloudwatch put-metric-alarm \
  --region "$REGION" \
  --alarm-name "dev-ec2-bastion-statuscheckfailed-critical" \
  --alarm-description "DEV bastion StatusCheckFailed critical >0" \
  --namespace "AWS/EC2" \
  --metric-name "StatusCheckFailed" \
  --dimensions Name=InstanceId,Value="$INSTANCE_ID" \
  --statistic Maximum \
  --period 60 \
  --evaluation-periods 1 \
  --datapoints-to-alarm 1 \
  --threshold 0 \
  --comparison-operator GreaterThanThreshold \
  --treat-missing-data notBreaching \
  --alarm-actions "$SNS_TOPIC_ARN"

4) Etiquetar alarmas por CLI (recomendado)

put-metric-alarm no siempre es la mejor opcion para tags en todos los flujos, asi que una practica robusta es etiquetar despues con tag-resource.

ACCOUNT_ID="123456789012"
ALARM_NAME="dev-ec2-bastion-cpu-critical"
ALARM_ARN="arn:aws:cloudwatch:${REGION}:${ACCOUNT_ID}:alarm:${ALARM_NAME}"

aws cloudwatch tag-resource \
  --region "$REGION" \
  --resource-arn "$ALARM_ARN" \
  --tags \
    Key=Environment,Value=dev \
    Key=Service,Value=ec2 \
    Key=Resource,Value=bastion \
    Key=Metric,Value=CPUUtilization \
    Key=Severity,Value=critical

5) Verificar configuracion y acciones (CLI)

Antes de hacer pruebas, verifica que la alarma existe y que realmente tiene accion SNS.

aws cloudwatch describe-alarms \
  --region "$REGION" \
  --alarm-names "dev-ec2-bastion-cpu-critical" \
  --query "MetricAlarms[0].{Name:AlarmName,State:StateValue,Actions:AlarmActions,Period:Period,Eval:EvaluationPeriods,Datapoints:DatapointsToAlarm,Threshold:Threshold}"

Tambien puedes revisar tags:

aws cloudwatch list-tags-for-resource \
  --region "$REGION" \
  --resource-arn "$ALARM_ARN"

Validacion con stress test (CPU)

La parte importante del tutorial no es solo crear alarmas, sino comprobar que saltan.

Opcion simple sin instalar herramientas: yes

Si no tienes stress-ng, puedes generar carga CPU con procesos yes.

Ejemplo (instancia de 8 vCPU):

for i in $(seq 1 6); do yes > /dev/null & done

Si se queda justo en el umbral, sube a 7:

for i in $(seq 1 7); do yes > /dev/null & done

Para parar la carga:

pkill yes

Como validar el disparo

  1. Mantener la carga el tiempo suficiente para la evaluacion (segun tu configuracion real: por ejemplo 1 de 1 bloque de 300s para warning, 2 de 2 bloques para critical en Basic Monitoring).
  2. Revisar el estado en CloudWatch Alarms.
  3. Confirmar el email recibido desde SNS.
  4. Documentar hora de inicio, hora de disparo y hora de vuelta a OK.

Monitorizar el estado de las alarmas desde terminal (muy util durante la prueba)

Mientras ejecutas la carga, este comando te permite ver en tiempo real si warning y critical cambian de estado:

WARNING_ALARM_NAME="dev-ec2-bastion-cpu-warning"
CRITICAL_ALARM_NAME="dev-ec2-bastion-cpu-critical"

aws cloudwatch describe-alarms \
  --region "$REGION" \
  --alarm-names "$CRITICAL_ALARM_NAME" "$WARNING_ALARM_NAME" \
  --query "MetricAlarms[].{Name:AlarmName,State:StateValue,Updated:StateUpdatedTimestamp}" \
  --output table

Ejemplo de salida cuando la alarma warning ya ha saltado y critical sigue en OK:

-------------------------------------------------------------------------------
|                               DescribeAlarms                                 |
+-------------------------------+--------+------------------------------------+
|             Name              | State  |              Updated               |
+-------------------------------+--------+------------------------------------+
|  dev-ec2-host-cpu-critical    |  OK    |  2026-02-23T14:03:53.405000+00:00  |
|  dev-ec2-host-cpu-warning     |  ALARM |  2026-02-23T15:17:11.184000+00:00  |
+-------------------------------+--------+------------------------------------+

Este tipo de salida es muy util para documentar pruebas y construir evidencias tecnicas en runbooks o issues.

Error comun (y muy real): el umbral visual “parece” cumplido

Si la alarma usa GreaterThanThreshold (>) y la grafica se queda visualmente en 75, puede que no dispare.

Motivo:

  • CloudWatch evalua el valor real del datapoint, no el redondeo visual.
  • 75.0 no es mayor que 75.

Solucion:

  • subir un poco la carga para quedar claramente por encima del umbral.

Ajuste real muy frecuente: Basic Monitoring vs Detailed Monitoring

Un caso muy comun en AWS es este:

  • Tu alarma esta pensada para 1 min
  • Pero la instancia publica CPU cada 5 min (Basic Monitoring)

En ese caso, puedes adaptar temporalmente la alarma para validar el flujo sin activar Detailed Monitoring:

  • warning: period=300, 1/1
  • critical: period=300, 2/2

Todo lo demas se mantiene igual (umbral, metrica, accion SNS, treat-missing-data).

Y la alarma de StatusCheckFailed, como se prueba?

No recomiendo forzar un fallo real de instancia para validar esta alarma en un entorno que quieras mantener estable.

Una alternativa segura es una prueba sintetica del estado de la alarma:

aws cloudwatch set-alarm-state \
  --region "$REGION" \
  --alarm-name "dev-ec2-bastion-statuscheckfailed-critical" \
  --state-value ALARM \
  --state-reason "Synthetic test for notification path"

Esto no simula un fallo real de EC2, pero si valida:

  • cambio de estado
  • accion SNS
  • recepcion de la notificacion

Buenas practicas y riesgos

  • Usa naming consistente por entorno/servicio/recurso/severidad desde el primer dia.
  • Configura y prueba primero SNS/email antes de crear alarmas.
  • Verifica acciones y tags por CLI antes del stress test.
  • No fuerces fallos reales para probar StatusCheckFailed si no tienes una ventana controlada.
  • Recuerda que > no es lo mismo que >=; CloudWatch evalua el valor exacto.
  • Documenta cada prueba con timestamps y resultado para poder replicarla en otros entornos.

Resultado esperado

Con este flujo tienes una base operativa muy util para EC2:

  • canal de notificacion validado (SNS -> Email)
  • alarmas de CPU y salud de instancia creadas
  • pruebas reales para verificar que las alertas funcionan
  • comandos CLI listos para replicar en staging y prod

A partir de aqui puedes ampliar cobertura a:

  • CPUCreditBalance (si usas instancias burstable t*)
  • memoria y disco con CloudWatch Agent
  • otros servicios como RDS, ElastiCache y ALB

Siguiente paso

En una siguiente iteracion, el paso natural es pasar estas alarmas a Terraform para versionarlas y desplegarlas por entorno con menos deriva.

Antes de llegar a Terraform, mi recomendacion es cerrar primero la validacion operativa en no productivo:

  1. Probar warning y critical con carga controlada.
  2. Validar notificaciones end-to-end.
  3. Documentar evidencias y ajustes (por ejemplo, Basic Monitoring vs Detailed Monitoring).
  4. Replicar el patron en el siguiente entorno objetivo de tu pipeline (por ejemplo staging y despues prod, o el orden que aplique en tu organizacion).

¿Aplicamos esta idea en tu contexto?

Si quieres convertir este enfoque en un plan accionable para tu equipo, puedo ayudarte a definir prioridades, riesgos y siguiente iteración.