Lab 5: Simulación de Incidente

Lab 5: Simulación de Incidente y Failover Automático

En este laboratorio provocamos la caída del sitio web primario para observar la secuencia completa de failover automático en tiempo real.

Caída Primario → Route 53 HC Falla → CW Alarm → SNS → Lambda Failover → Primario=OFF | Secundario=ON

Cargar variables de entorno

source ~/global.env

Recuperar variables de sesiones anteriores

Si abriste una terminal nueva, recuperá los ARNs de los recursos ya desplegados:

# Fuente: CloudFormation (Opción A)
if aws cloudformation describe-stacks --stack-name route53-arc-routing --region $AWS_REGION > /dev/null 2>&1; then
  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)
  # Dominio fallback desde Amplify o CloudFront
  AMPLIFY_DOMAIN=$(aws cloudformation describe-stacks \
    --stack-name route53-arc-secondary --region $AWS_REGION \
    --query 'Stacks[0].Outputs[?OutputKey==`AmplifyDefaultDomain`].OutputValue' \
    --output text 2>/dev/null || echo "")
  CF_DOMAIN=$(aws cloudformation describe-stacks \
    --stack-name route53-arc-secondary --region $AWS_REGION \
    --query 'Stacks[0].Outputs[?OutputKey==`CloudFrontDomain`].OutputValue' \
    --output text 2>/dev/null || echo "")
  if [ -n "$AMPLIFY_DOMAIN" ] && [ "$AMPLIFY_DOMAIN" != "None" ]; then
    export FALLBACK_DOMAIN="https://main.${AMPLIFY_DOMAIN}"
  elif [ -n "$CF_DOMAIN" ] && [ "$CF_DOMAIN" != "None" ]; then
    export FALLBACK_DOMAIN="https://${CF_DOMAIN}"
  fi
# Fuente: Terraform (Opción B)
elif [ -f ~/route53-arc-tf/03_arc/terraform.tfstate ]; then
  export PRIMARY_RC_ARN=$(cd ~/route53-arc-tf/03_arc && terraform output -raw primary_rc_arn 2>/dev/null)
  export SECONDARY_RC_ARN=$(cd ~/route53-arc-tf/03_arc && terraform output -raw secondary_rc_arn 2>/dev/null)
  export ARC_CLUSTER_ARN=$(cd ~/route53-arc-tf/03_arc && terraform output -raw arc_cluster_arn 2>/dev/null)
  export FALLBACK_DOMAIN=$(cd ~/route53-arc-tf/02_secondary && terraform output -raw fallback_domain 2>/dev/null)
fi

# Obtener cluster endpoint
if [ -n "$ARC_CLUSTER_ARN" ]; then
  read CLUSTER_ENDPOINT ENDPOINT_REGION <<< $(aws route53-recovery-control-config describe-cluster \
    --cluster-arn $ARC_CLUSTER_ARN --region $AWS_REGION \
    --query 'Cluster.ClusterEndpoints' --output json | python3 -c "
import json, sys
eps = json.load(sys.stdin)
target = next((e for e in eps if e.get('Region') == 'us-east-1'), eps[0])
print(target.get('Endpoint'), target.get('Region'))
")
fi

echo "PRIMARY_RC_ARN:    ${PRIMARY_RC_ARN:-❌ no seteado}"
echo "SECONDARY_RC_ARN:  ${SECONDARY_RC_ARN:-❌ no seteado}"
echo "CLUSTER_ENDPOINT:  ${CLUSTER_ENDPOINT:-❌ no seteado}"
echo "FALLBACK_DOMAIN:   ${FALLBACK_DOMAIN:-❌ no seteado}"

Métodos para Simular el Incidente

Elegí uno de los 3 métodos siguientes para simular la caída:

Método 1: Detener Nginx en EC2 vía SSM Run Command (Recomendado)

Simula una falla real del servidor web sin apagar la VM. El Health Check detectará la caída en ~90 segundos y CloudWatch disparará la alarma tras 3 evaluaciones consecutivas (~3 minutos total).

# Obtener ID de la instancia EC2
if [ -f ~/route53-arc-tf/01_primary/terraform.tfstate ]; then
  EC2_ID=$(cd ~/route53-arc-tf/01_primary && terraform output -raw ec2_instance_id 2>/dev/null)
else
  EC2_ID=$(aws cloudformation describe-stacks \
    --stack-name route53-arc-primary --region $AWS_REGION \
    --query 'Stacks[0].Outputs[?OutputKey==`EC2InstanceID`].OutputValue' --output text)
fi

echo "Deteniendo Nginx en EC2: $EC2_ID..."
aws ssm send-command \
  --instance-ids $EC2_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["systemctl stop nginx"]' \
  --region $AWS_REGION

echo "✅ Nginx detenido. El Health Check fallará en ~90s → CloudWatch Alarm en ~3 min → Failover Lambda."
Consejo

Después de ejecutar el Método 1, bajá directamente a la sección “Monitoreo en Tiempo Real” y ejecutá el loop de seguimiento. El failover tardará ~3 minutos en completarse — el loop te mostrará el momento exacto en que los Routing Controls cambien a Primario: Off | Secundario: On.


Método 2: Forzar Estado ALARM en CloudWatch (más rápido)

Ideal para pruebas rápidas — el failover ocurre en segundos sin esperar el Health Check.

CW_ALARM_NAME="route53-arc-primary-unhealthy-with-sns"

aws cloudwatch set-alarm-state \
  --alarm-name $CW_ALARM_NAME \
  --state-value ALARM \
  --state-reason "Simulación de incidente – roLearning Workshop" \
  --region $AWS_REGION

echo "✅ Estado ALARM forzado. SNS y Lambda Failover se dispararán en segundos."

Método 3: Invocación Directa de la Lambda

Simula el evento SNS invocando la función directamente, útil para aislar y testear la lógica Lambda.

aws lambda invoke \
  --function-name route53-arc-failover-handler \
  --payload '{"alarm_state":"ALARM"}' \
  --cli-binary-format raw-in-base64-out \
  /tmp/failover-result.json \
  --region $AWS_REGION

cat /tmp/failover-result.json; echo

Monitoreo en Tiempo Real del Failover

echo "=== Monitoreando Routing Controls ==="

if [ -z "$PRIMARY_RC_ARN" ] || [ -z "$CLUSTER_ENDPOINT" ]; then
  echo "❌ Variables de entorno no seteadas. Ejecutá el bloque 'Recuperar variables' al inicio de este lab."
  exit 1
fi

for i in {1..12}; do
  P_STATE=$(aws route53-recovery-cluster get-routing-control-state \
    --routing-control-arn $PRIMARY_RC_ARN \
    --endpoint-url $CLUSTER_ENDPOINT --region $ENDPOINT_REGION \
    --query 'RoutingControlState' --output text 2>/dev/null || echo "ERROR")
  S_STATE=$(aws route53-recovery-cluster get-routing-control-state \
    --routing-control-arn $SECONDARY_RC_ARN \
    --endpoint-url $CLUSTER_ENDPOINT --region $ENDPOINT_REGION \
    --query 'RoutingControlState' --output text 2>/dev/null || echo "ERROR")

  echo "[$i/12] Primario: $P_STATE | Secundario: $S_STATE"

  if [ "$P_STATE" = "Off" ] && [ "$S_STATE" = "On" ]; then
    echo "🎉 FAILOVER EXITOSO — tráfico redirigido al sitio secundario."
    break
  fi
  sleep 15
done

✅ Verificación del Lab 5

echo "=== Verificación del Estado de Incidente ==="
echo "1. Revisá tu casilla de email ($NOTIFICATION_EMAIL)."
echo "   Deberías haber recibido: '[FAILOVER ACTIVADO] Sitio primario caido'"

if [ -n "$FALLBACK_DOMAIN" ]; then
  STATUS=$(curl -s -o /dev/null -w "%{http_code}" $FALLBACK_DOMAIN 2>/dev/null)
  [ "$STATUS" = "200" ] \
    && echo "✅ Sitio secundario responde: HTTP $STATUS" \
    || echo "⚠️  Sitio secundario HTTP: $STATUS"
else
  echo "ℹ️  FALLBACK_DOMAIN no seteado — verificá manualmente en el navegador."
fi

Siguiente paso → Lab 6: Failback