Gontzal Bilbao

Gontzal Bilbao

  • Email Email
  • Ubicación País Vasco, ES

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


Contexto

Configurar alarmas en AWS no es difícil. Lo difícil es validarlas de verdad y no dejar una falsa sensación de cobertura.

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

En este tutorial explico un patrón simple y reutilizable para EC2:

  1. Crear un canal de notificación 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 genéricos para que puedas adaptarlo a tu entorno.

Punto clave: una alarma sin prueba real es configuración, 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 evaluación visual.
  • CLI (AWS CLI) para repetir el proceso con precisión 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 notificación llega.
3
Alarmas EC2 base
2
Vías de implementación (Console + CLI)
1
Prueba real de CPU para validar notificaciones

Arquitectura mínima del flujo

La arquitectura es intencionadamente simple:

  • EC2 emite métricas nativas (por ejemplo, CPUUtilization).
  • CloudWatch Alarms evalua umbrales y cambia estado (OK, ALARM, INSUFFICIENT_DATA).
  • SNS envia la notificación por email.

Para una primera versión de observabilidad operativa, este flujo cubre mucho más de lo que parece si lo validas bien.

Implementación paso a paso

1) Crear SNS topic y suscripción email (Console)

Objetivo: tener un canal de notificación 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 genérico (ejemplo: dev-infra-alerts).
  4. Crear una suscripción Email.
  5. Confirmar la suscripción desde el correo recibido.
  6. Ejecutar un Publish message de prueba.

Recomendación:

  • Empieza solo con email.
  • Cuando el flujo este validado, añade 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 configuración:

  • Métrica: AWS/EC2 -> CPUUtilization
  • Estadistica: Average
  • Periodo:
    • 60s si la instancia publica métricas cada 1 minuto (Detailed Monitoring)
    • 300s si la instancia usa Basic Monitoring (CPU en bloques de 5 min)
  • Umbral: > 75
  • Evaluación:
    • 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)”)
  • Acción: enviar a topic SNS

En la validación 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 genéricos 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%) - configuración final validada (Basic Monitoring 5m)

En una primera versión puedes plantear 60s + 5/5, pero si la instancia publica CPU en Basic Monitoring (5 min), la configuración 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 verificación (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%) - configuración 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 verificación:

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 opción para tags en todos los flujos, así que una práctica robusta es etiquetar después 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 configuración y acciones (CLI)

Antes de hacer pruebas, verifica que la alarma existe y que realmente tiene acción 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}"

También puedes revisar tags:

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

Validación con stress test (CPU)

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

Opción 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 evaluación (según tu configuración 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 útil 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 útil para documentar pruebas y construir evidencias técnicas en runbooks o issues.

Error común (y muy real): el umbral visual “parece” cumplido

Si la alarma usa GreaterThanThreshold (>) y la gráfica 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.

Solución:

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

Ajuste real muy frecuente: Basic Monitoring vs Detailed Monitoring

Un caso muy común 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, métrica, acción SNS, treat-missing-data).

Y la alarma de StatusCheckFailed, ¿cómo 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 sintética 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
  • acción SNS
  • recepción de la notificación

Buenas prácticas y riesgos

  • Usa naming consistente por entorno/servicio/recurso/severidad desde el primer día.
  • 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 útil para EC2:

  • canal de notificación 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 aquí 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 iteración, el paso natural es pasar estas alarmas a Terraform para versionarlas y desplegarlas por entorno con menos deriva.

Antes de llegar a Terraform, mi recomendación es cerrar primero la validación 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 patrón en el siguiente entorno objetivo de tu pipeline (por ejemplo staging y después prod, o el orden que aplique en tu organización).

¿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.