Lab 5: Simulación de Incidente

Lab 5: Simulación de Incidente y Verificación del Failover

¡Llegó el momento de la verdad! Vamos a simular una caída del sitio primario y verificar que todo el flujo funciona: detección → notificación → failover automático.


El plan de la simulación

Hay dos formas de simular la caída:

Método Descripción Velocidad
A. Detener nginx Parar el servicio nginx en EC2 Health Check falla en ~2 min
B. Forzar alarma CloudWatch Poner la alarma directamente en ALARM Inmediato

Usaremos ambos: primero el método B para ver el flujo completo rápido, y luego el A para la experiencia “real”.


Pre-check: Estado inicial

Antes de simular, confirma que todo está en estado normal:

export AWS_REGION="us-east-1"
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

# Recuperar valores
export PRIMARY_RC_ARN=$(aws cloudformation describe-stacks \
  --stack-name route53-arc-routing --region $AWS_REGION \
  --query 'Stacks[0].Outputs[?OutputKey==`PrimaryRoutingControlArn`].OutputValue' \
  --output text)

export SECONDARY_RC_ARN=$(aws cloudformation describe-stacks \
  --stack-name route53-arc-routing --region $AWS_REGION \
  --query 'Stacks[0].Outputs[?OutputKey==`SecondaryRoutingControlArn`].OutputValue' \
  --output text)

export ARC_CLUSTER_ARN=$(aws cloudformation describe-stacks \
  --stack-name route53-arc-routing --region $AWS_REGION \
  --query 'Stacks[0].Outputs[?OutputKey==`ARCClusterArn`].OutputValue' \
  --output text)

export CLUSTER_ENDPOINT=$(aws route53-recovery-control-config describe-cluster \
  --cluster-arn $ARC_CLUSTER_ARN --region $AWS_REGION \
  --query 'Cluster.ClusterEndpoints[0].Endpoint' --output text)

export PRIMARY_ALB_DNS=$(aws cloudformation describe-stacks \
  --stack-name route53-arc-primary --region $AWS_REGION \
  --query 'Stacks[0].Outputs[?OutputKey==`ALBDNSName`].OutputValue' \
  --output text)

export LAMBDA_NAME=$(aws cloudformation describe-stacks \
  --stack-name route53-arc-automation --region $AWS_REGION \
  --query 'Stacks[0].Outputs[?OutputKey==`LambdaName`].OutputValue' \
  --output text)

export EC2_ID=$(aws cloudformation describe-stacks \
  --stack-name route53-arc-primary --region $AWS_REGION \
  --query 'Stacks[0].Outputs[?OutputKey==`EC2InstanceID`].OutputValue' \
  --output text)

echo "=== Estado inicial ==="
echo "ALB DNS:         $PRIMARY_ALB_DNS"
echo "EC2 ID:          $EC2_ID"
echo "Cluster EP:      $CLUSTER_ENDPOINT"
echo ""

# Verificar Routing Controls
echo "--- Routing Control States ---"
aws route53-recovery-cluster get-routing-control-state \
  --routing-control-arn $PRIMARY_RC_ARN \
  --endpoint-url $CLUSTER_ENDPOINT \
  --region $AWS_REGION \
  --query 'RoutingControlState' --output text \
  | xargs -I{} echo "Primario:   {}"

aws route53-recovery-cluster get-routing-control-state \
  --routing-control-arn $SECONDARY_RC_ARN \
  --endpoint-url $CLUSTER_ENDPOINT \
  --region $AWS_REGION \
  --query 'RoutingControlState' --output text \
  | xargs -I{} echo "Secundario: {}"

# Verificar que el sitio primario responde
echo ""
echo "--- Sitio primario ---"
curl -s -o /dev/null -w "HTTP %{http_code} en %{time_total}s\n" \
  http://$PRIMARY_ALB_DNS/

El estado esperado antes de simular:

  • Primario: On
  • Secundario: Off
  • ALB responde: HTTP 200

Simulación A: Forzar alarma CloudWatch (método rápido)

Este método dispara el failover inmediatamente sin esperar al Health Check:

# Nombre de la alarma con SNS
ALARM_NAME="route53-arc-primary-unhealthy-with-sns"

echo "=== INICIO SIMULACIÓN: Forzando alarma CloudWatch ==="
echo "Hora de inicio: $(date)"

# Poner la alarma en estado ALARM manualmente
aws cloudwatch set-alarm-state \
  --alarm-name $ALARM_NAME \
  --state-value ALARM \
  --state-reason "Simulación de incidente – Workshop roLearning" \
  --region $AWS_REGION

echo "✅ Alarma forzada a estado ALARM"
echo "⏳ Esperando que Lambda reaccione (10-15 segundos)..."
sleep 15

Verificar que Lambda ejecutó el failover:

echo "=== Verificando resultado del failover ==="

# Estado de los Routing Controls después del failover
echo "--- Routing Control States (post-failover) ---"
aws route53-recovery-cluster get-routing-control-state \
  --routing-control-arn $PRIMARY_RC_ARN \
  --endpoint-url $CLUSTER_ENDPOINT \
  --region $AWS_REGION \
  --query 'RoutingControlState' --output text \
  | xargs -I{} echo "Primario:   {} (esperado: Off)"

aws route53-recovery-cluster get-routing-control-state \
  --routing-control-arn $SECONDARY_RC_ARN \
  --endpoint-url $CLUSTER_ENDPOINT \
  --region $AWS_REGION \
  --query 'RoutingControlState' --output text \
  | xargs -I{} echo "Secundario: {} (esperado: On)"

Verificar los logs de Lambda:

echo ""
echo "--- Logs de Lambda (últimas invocaciones) ---"
LOG_GROUP="/aws/lambda/$LAMBDA_NAME"

# Obtener el log stream más reciente
LATEST_STREAM=$(aws logs describe-log-streams \
  --log-group-name $LOG_GROUP \
  --order-by LastEventTime \
  --descending \
  --max-items 1 \
  --region $AWS_REGION \
  --query 'logStreams[0].logStreamName' \
  --output text 2>/dev/null)

if [ "$LATEST_STREAM" != "None" ] && [ -n "$LATEST_STREAM" ]; then
  aws logs get-log-events \
    --log-group-name $LOG_GROUP \
    --log-stream-name "$LATEST_STREAM" \
    --region $AWS_REGION \
    --query 'events[*].message' \
    --output text | tail -20
else
  echo "⚠️  No hay logs todavía. Lambda aún no fue invocada."
fi

Simulación B: Detener nginx en EC2 (método “real”)

Primero, restablece el estado normal del Lab A:

# Restaurar estado normal antes de probar el método B
aws route53-recovery-cluster update-routing-control-states \
  --update-routing-control-state-entries \
    "[{\"RoutingControlArn\":\"$PRIMARY_RC_ARN\",\"RoutingControlState\":\"On\"},
      {\"RoutingControlArn\":\"$SECONDARY_RC_ARN\",\"RoutingControlState\":\"Off\"}]" \
  --endpoint-url $CLUSTER_ENDPOINT \
  --region $AWS_REGION

# Restablecer la alarma
aws cloudwatch set-alarm-state \
  --alarm-name $ALARM_NAME \
  --state-value OK \
  --state-reason "Estado restaurado para nueva simulación" \
  --region $AWS_REGION

echo "✅ Estado restaurado a normal"
sleep 5

Ahora detén nginx en la instancia EC2 usando SSM:

echo "=== INICIO SIMULACIÓN B: Deteniendo nginx en EC2 ==="
echo "Hora de inicio: $(date)"

# Enviar comando via SSM (sin Key Pair necesario)
COMMAND_ID=$(aws ssm send-command \
  --instance-ids $EC2_ID \
  --document-name AWS-RunShellScript \
  --parameters 'commands=["systemctl stop nginx && echo NGINX_STOPPED"]' \
  --region $AWS_REGION \
  --query 'Command.CommandId' \
  --output text)

echo "Comando SSM enviado: $COMMAND_ID"
echo "Esperando confirmación..."
sleep 10

# Verificar resultado del comando
aws ssm get-command-invocation \
  --command-id $COMMAND_ID \
  --instance-id $EC2_ID \
  --region $AWS_REGION \
  --query '{Status: Status, Output: StandardOutputContent}' \
  --output json

Monitorear el Health Check en tiempo real:

echo ""
echo "=== Monitoreando Health Check (puede tardar ~2-3 min) ==="
echo "Presiona Ctrl+C para detener el monitoreo"
echo ""

HC_ID=$(aws cloudformation describe-stacks \
  --stack-name route53-arc-routing --region $AWS_REGION \
  --query 'Stacks[0].Outputs[?OutputKey==`HealthCheckId`].OutputValue' \
  --output text)

for i in $(seq 1 20); do
  # Estado del Health Check
  HC_STATUS=$(aws route53 get-health-check-status \
    --health-check-id $HC_ID \
    --query 'HealthCheckObservations[0].StatusReport.Status' \
    --output text 2>/dev/null | cut -c1-30)

  # Estado del ALB
  HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
    --connect-timeout 3 --max-time 5 \
    http://$PRIMARY_ALB_DNS/ 2>/dev/null)

  # Estado de los Routing Controls
  PRIMARY_STATE=$(aws route53-recovery-cluster get-routing-control-state \
    --routing-control-arn $PRIMARY_RC_ARN \
    --endpoint-url $CLUSTER_ENDPOINT \
    --region $AWS_REGION \
    --query 'RoutingControlState' --output text 2>/dev/null)

  SECONDARY_STATE=$(aws route53-recovery-cluster get-routing-control-state \
    --routing-control-arn $SECONDARY_RC_ARN \
    --endpoint-url $CLUSTER_ENDPOINT \
    --region $AWS_REGION \
    --query 'RoutingControlState' --output text 2>/dev/null)

  echo "[$(date '+%H:%M:%S')] HC: $HC_STATUS | ALB: HTTP$HTTP_CODE | Primary RC: $PRIMARY_STATE | Secondary RC: $SECONDARY_STATE"

  # Si el failover ocurrió, salir
  if [ "$PRIMARY_STATE" = "Off" ] && [ "$SECONDARY_STATE" = "On" ]; then
    echo ""
    echo "🎉 ¡FAILOVER DETECTADO! El sistema reaccionó automáticamente."
    break
  fi

  sleep 30
done

✅ Verificación del Lab 5

echo "=== Verificación Lab 5: Simulación de Incidente ==="
echo ""

# 1. Routing Controls en estado de failover
PRIMARY_STATE=$(aws route53-recovery-cluster get-routing-control-state \
  --routing-control-arn $PRIMARY_RC_ARN \
  --endpoint-url $CLUSTER_ENDPOINT \
  --region $AWS_REGION \
  --query 'RoutingControlState' --output text 2>/dev/null)

SECONDARY_STATE=$(aws route53-recovery-cluster get-routing-control-state \
  --routing-control-arn $SECONDARY_RC_ARN \
  --endpoint-url $CLUSTER_ENDPOINT \
  --region $AWS_REGION \
  --query 'RoutingControlState' --output text 2>/dev/null)

[ "$PRIMARY_STATE" = "Off" ] \
  && echo "✅ Routing Control Primario: $PRIMARY_STATE (failover activo)" \
  || echo "⚠️  Routing Control Primario: $PRIMARY_STATE"

[ "$SECONDARY_STATE" = "On" ] \
  && echo "✅ Routing Control Secundario: $SECONDARY_STATE (tráfico redirigido)" \
  || echo "⚠️  Routing Control Secundario: $SECONDARY_STATE"

# 2. Lambda fue invocada
LAMBDA_INVOCATIONS=$(aws cloudwatch get-metric-statistics \
  --namespace AWS/Lambda \
  --metric-name Invocations \
  --dimensions Name=FunctionName,Value=$LAMBDA_NAME \
  --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S 2>/dev/null || date -u -v-1H +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
  --period 3600 \
  --statistics Sum \
  --region $AWS_REGION \
  --query 'Datapoints[0].Sum' \
  --output text 2>/dev/null)
echo ""
echo "📊 Invocaciones Lambda última hora: ${LAMBDA_INVOCATIONS:-0}"

echo ""
echo "📬 Recuerda revisar tu email para la notificación SNS."
echo ""
echo "Para continuar al failback → Lab 6"

Consejo

¿Recibiste el email? Debería haber llegado un mensaje con asunto “🚨 [FAILOVER ACTIVADO] Sitio primario caído” con todos los detalles del evento. Si no llegó, revisa la suscripción SNS en la consola.


Siguiente paso → Lab 6: Failback