El problema
Creé tres alarmas para una instancia EC2, por consola y por CLI, y le metí carga hasta que llegó el email. Por el camino tuve que cambiar el periodo de las alarmas, porque la instancia publicaba la CPU cada 5 minutos.
Crear una alarma es fácil. Lo que no se ve hasta probarla es si salta: un umbral mal calibrado o un canal sin confirmar la dejan muda.
Las severidades y el naming salen de Alertas preventivas en AWS como código con Terraform. La versión en código está en Alarmas de infraestructura con Terraform. Lo probé en una cuenta de laboratorio, con nombres e IDs de ejemplo.
Una alarma sin prueba real es configuración, no observabilidad.
El canal, por consola
Empiezo por el canal, porque sin él una alarma que salta no avisa a nadie. En Amazon SNS:
- Creo un topic
Standardllamadodemo-infra-alerts. - Le añado una suscripción
Email. - Confirmo la suscripción desde el correo que llega.
- Lanzo un
Publish messagede prueba y compruebo que lo recibo.
Solo email al principio. Slack o Chatbot se añaden cuando el flujo ya funciona.
La primera alarma, por consola
La primera la creo en CloudWatch > Alarms, para ver cómo evalúa CloudWatch la métrica sobre la gráfica.
- Métrica:
AWS/EC2 > CPUUtilization, estadísticaAverage. - Umbral: mayor que
75. - Periodo y evaluación:
300 sy1 de 1(ver abajo por qué). - Datos ausentes:
notBreaching, que la consola muestra como «Correctos (dentro del límite)». - Acción: el topic
demo-infra-alerts. - Nombre:
demo-ec2-jumpbox-cpu-warning.
Mi primera versión era 60 s y 5 de 5. No saltaba nunca: la instancia usaba Basic Monitoring y solo publicaba la CPU cada 5 minutos.
| Monitorización | Dato de CPU | warning (5 min) | critical (10 min) |
|---|---|---|---|
| Detailed | cada minuto | 60 s, 5 de 5 | 60 s, 10 de 10 |
| Basic | cada 5 minutos | 300 s, 1 de 1 | 300 s, 2 de 2 |
Con 300 s el objetivo es el mismo: si la media de un bloque de 5 minutos pasa de 75 %, salta. No hace falta pagar Detailed Monitoring para validar el flujo.
Con un periodo más corto que el dato, la alarma nunca reúne los puntos que pide. Y con notBreaching, cada hueco cuenta como «todo bien».
El resto, por CLI
Por terminal el comando exacto queda escrito y se repite igual en otro entorno. Primero declaro las variables una vez:
# terminal con la AWS CLI configurada
REGION="eu-central-1"
ACCOUNT_ID="123456789012"
INSTANCE_ID="i-xxxxxxxxxxxxxxxxx"
SNS_TOPIC_ARN="arn:aws:sns:eu-central-1:123456789012:demo-infra-alerts"
La warning de CPU, igual que la de consola:
# terminal: CPU > 75 % durante un bloque de 5 min
aws cloudwatch put-metric-alarm \
--region "$REGION" \
--alarm-name "demo-ec2-jumpbox-cpu-warning" \
--alarm-description "CPU > 75 % durante 5 min (basic monitoring)" \
--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"
La critical pide dos bloques de 5 minutos seguidos por encima de 85 %:
# terminal: CPU > 85 % durante dos bloques de 5 min
aws cloudwatch put-metric-alarm \
--region "$REGION" \
--alarm-name "demo-ec2-jumpbox-cpu-critical" \
--alarm-description "CPU > 85 % durante 10 min (basic monitoring)" \
--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"
Y la de salud de la instancia, que salta con un solo fallo:
# terminal: StatusCheckFailed > 0 en 1 min
aws cloudwatch put-metric-alarm \
--region "$REGION" \
--alarm-name "demo-ec2-jumpbox-statuscheckfailed-critical" \
--alarm-description "StatusCheckFailed > 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"
Etiquetar y verificar
Las etiquetas las pongo después, con tag-resource. Uso las cinco piezas del naming:
# terminal
ALARM_NAME="demo-ec2-jumpbox-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=demo Key=Service,Value=ec2 \
Key=Resource,Value=jumpbox Key=Metric,Value=CPUUtilization \
Key=Severity,Value=critical
Antes de meter carga compruebo que la alarma existe y, sobre todo, que tiene la acción SNS:
# terminal
aws cloudwatch describe-alarms \
--region "$REGION" \
--alarm-names "$ALARM_NAME" \
--query "MetricAlarms[0].{Name:AlarmName,State:StateValue,Actions:AlarmActions,Period:Period,Eval:EvaluationPeriods,Datapoints:DatapointsToAlarm,Threshold:Threshold}"
aws cloudwatch list-tags-for-resource --region "$REGION" --resource-arn "$ALARM_ARN"
La prueba de carga
Para generar CPU no instalé nada: cada proceso yes ocupa un núcleo entero. En una instancia de 4 vCPU, tres procesos dejan la media alrededor del 75 %.
# terminal de la instancia
for i in $(seq 1 3); do yes > /dev/null & done
# si la gráfica se queda justo en el umbral, un cuarto proceso
yes > /dev/null &
# para parar la carga
pkill yes
Mientras dura la carga, sigo el estado de las dos alarmas desde mi terminal:
# terminal
aws cloudwatch describe-alarms \
--region "$REGION" \
--alarm-names "demo-ec2-jumpbox-cpu-critical" "demo-ec2-jumpbox-cpu-warning" \
--query "MetricAlarms[].{Name:AlarmName,State:StateValue,Updated:StateUpdatedTimestamp}" \
--output table
Tras el primer bloque de 5 minutos, la warning ya ha saltado y la critical sigue esperando el segundo:
# salida de ejemplo
+---------------------------------+---------+------------------------------------+
| Name | State | Updated |
+---------------------------------+---------+------------------------------------+
| demo-ec2-jumpbox-cpu-critical | OK | 2026-01-15T10:00:00.000000+00:00 |
| demo-ec2-jumpbox-cpu-warning | ALARM | 2026-01-15T10:20:00.000000+00:00 |
+---------------------------------+---------+------------------------------------+
Doy la prueba por buena cuando llega el email de SNS y, al parar la carga, la alarma vuelve a OK. Apunto tres horas: inicio de la carga, disparo y vuelta a OK. Con eso la prueba se repite igual en otro entorno.
Si la gráfica se queda en 75, puede que no salte. GreaterThanThreshold es «mayor que», y 75,0 no es mayor que 75.
StatusCheckFailed, sin romper la instancia
Forzar un fallo real de la instancia no compensa en algo que quiero mantener estable. En su lugar, cambio el estado de la alarma a mano:
# terminal
aws cloudwatch set-alarm-state \
--region "$REGION" \
--alarm-name "demo-ec2-jumpbox-statuscheckfailed-critical" \
--state-value ALARM \
--state-reason "Prueba sintética del canal de aviso"
No simula un fallo de EC2, pero valida lo que importa aquí: el cambio de estado, la acción SNS y la llegada del aviso. En la siguiente evaluación, la alarma vuelve sola a su estado real.
Lo que aprendí
Con Basic Monitoring, una alarma de 60 s y 5 de 5 no salta nunca: tuve que pasarla a 300 s y 1 de 1. Y una CPU clavada en 75,0 no supera un umbral de «mayor que 75».
Si tienes dudas o lo estás montando tú, cuéntamelo.