Lab 6: Failback – Volver al Sitio Primario
Lab 6: Failback – Restaurar el Tráfico al Sitio Primario
El failover fue automático. El failback es siempre manual e intencional: primero reparamos el sitio primario, verificamos que funciona, y solo entonces devolvemos el tráfico.
¿Por qué el failback es manual?
El failover automático protege la disponibilidad. El failback manual protege contra restauraciones prematuras: nadie quiere que el tráfico vuelva a un servidor que “cree” que está bien pero en realidad sigue fallando.
FAILOVER (automático) FAILBACK (manual, deliberado)
───────────────────── ──────────────────────────────
Sistema detecta caída Operador verifica recuperación
↓ ↓
Lambda actúa solo Operador ejecuta el script
↓ ↓
Protege al usuario Confirma que el primario está OKPaso 1: Verificar el estado actual
export AWS_REGION="us-east-1"
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# Recuperar todos los valores necesarios
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 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 actual (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: {}"
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: {}"Paso 2: Reparar el sitio primario
Si usaste el Lab 5 método B (nginx detenido), debes reiniciar el servicio:
echo "=== Reparando sitio primario: reiniciando nginx ==="
COMMAND_ID=$(aws ssm send-command \
--instance-ids $EC2_ID \
--document-name AWS-RunShellScript \
--parameters 'commands=["systemctl start nginx && systemctl status nginx --no-pager | head -5"]' \
--region $AWS_REGION \
--query 'Command.CommandId' \
--output text)
echo "Comando SSM enviado: $COMMAND_ID"
sleep 10
aws ssm get-command-invocation \
--command-id $COMMAND_ID \
--instance-id $EC2_ID \
--region $AWS_REGION \
--query '{Status: Status, Output: StandardOutputContent}' \
--output jsonPaso 3: Verificar que el primario responde
Antes de hacer el failback, confirma que el sitio primario está realmente funcionando:
echo "=== Verificando recuperación del sitio primario ==="
# Esperar a que el ALB registre el servidor como healthy
echo "Esperando que el ALB marque el EC2 como healthy..."
for i in $(seq 1 10); do
TG_ARN=$(aws elbv2 describe-target-groups \
--names route53-arc-primary-tg --region $AWS_REGION \
--query 'TargetGroups[0].TargetGroupArn' --output text 2>/dev/null)
TG_HEALTH=$(aws elbv2 describe-target-health \
--target-group-arn $TG_ARN --region $AWS_REGION \
--query 'TargetHealthDescriptions[0].TargetHealth.State' \
--output text 2>/dev/null)
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
--connect-timeout 3 --max-time 5 \
http://$PRIMARY_ALB_DNS/ 2>/dev/null)
echo "[$(date '+%H:%M:%S')] Target Group: $TG_HEALTH | HTTP: $HTTP_CODE"
if [ "$TG_HEALTH" = "healthy" ] && [ "$HTTP_CODE" = "200" ]; then
echo "✅ ¡Sitio primario recuperado y funcionando!"
break
fi
sleep 15
doneAviso
No hagas el failback hasta ver Target Group: healthy y HTTP: 200. Si vuelves el tráfico antes de que el primario esté listo, los usuarios seguirán viendo errores.
Paso 4: Ejecutar el Failback
Una vez confirmado que el primario está sano, ejecuta el failback:
echo "=== EJECUTANDO FAILBACK ==="
echo "Hora: $(date)"
# Volver a activar el Routing Control primario
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
echo "✅ Failback ejecutado: Primario=ON, Secundario=OFF"Paso 5: Restablecer la alarma CloudWatch
ALARM_NAME="route53-arc-primary-unhealthy-with-sns"
aws cloudwatch set-alarm-state \
--alarm-name $ALARM_NAME \
--state-value OK \
--state-reason "Sitio primario recuperado - failback completado" \
--region $AWS_REGION
echo "✅ Alarma CloudWatch restablecida a OK"Paso 6: Lambda de Failback (automatizable)
Si quisieras automatizar el failback (con validaciones adicionales), aquí tienes el código de una Lambda de failback que puedes invocar manualmente:
cat > /tmp/failback-manual.py << 'PYTHON_EOF'
"""
Script de Failback para Route53 ARC Workshop
Ejecutar desde CloudShell como: python3 /tmp/failback-manual.py
Verifica que el sitio primario esté saludable antes de cambiar los Routing Controls.
"""
import boto3
import urllib.request
import sys
import time
REGION = "us-east-1"
PRIMARY_RC_ARN = "REEMPLAZAR_CON_PRIMARY_RC_ARN"
SECONDARY_RC_ARN = "REEMPLAZAR_CON_SECONDARY_RC_ARN"
CLUSTER_ARN = "REEMPLAZAR_CON_CLUSTER_ARN"
ALB_DNS = "REEMPLAZAR_CON_ALB_DNS"
def check_primary_health(alb_dns, retries=3):
"""Verifica que el ALB primario responde con HTTP 200."""
print(f"Verificando salud del primario: http://{alb_dns}/health")
for attempt in range(retries):
try:
req = urllib.request.urlopen(
f"http://{alb_dns}/health", timeout=10
)
if req.getcode() == 200:
print(f" ✅ Intento {attempt+1}: HTTP 200 OK")
return True
except Exception as e:
print(f" ❌ Intento {attempt+1}: {e}")
time.sleep(5)
return False
def get_cluster_endpoint(cluster_arn):
"""Obtiene el primer endpoint del cluster ARC."""
client = boto3.client('route53-recovery-control-config', region_name=REGION)
resp = client.describe_cluster(ClusterArn=cluster_arn)
return resp['Cluster']['ClusterEndpoints'][0]['Endpoint']
def execute_failback(cluster_endpoint):
"""Ejecuta el failback cambiando los Routing Controls."""
arc = boto3.client(
'route53-recovery-cluster',
endpoint_url=cluster_endpoint,
region_name=REGION
)
arc.update_routing_control_states(
UpdateRoutingControlStateEntries=[
{'RoutingControlArn': PRIMARY_RC_ARN, 'RoutingControlState': 'On'},
{'RoutingControlArn': SECONDARY_RC_ARN, 'RoutingControlState': 'Off'}
]
)
print("✅ Failback completado: Primario=ON, Secundario=OFF")
def main():
print("=== Failback Manager – roLearning Route53 ARC Workshop ===\n")
# 1. Verificar salud
if not check_primary_health(ALB_DNS):
print("\n❌ El sitio primario NO está saludable. Abortando failback.")
print(" Corrija el problema antes de intentar el failback.")
sys.exit(1)
print(f"\n✅ Sitio primario saludable. Procediendo con failback...\n")
# 2. Obtener endpoint del cluster
endpoint = get_cluster_endpoint(CLUSTER_ARN)
print(f"Cluster endpoint: {endpoint}")
# 3. Confirmar (en un script real, podrías pedir confirmación)
print("\n⚠️ ATENCIÓN: Esto redirigirá el tráfico al sitio PRIMARIO.")
confirm = input("Escribe 'CONFIRMAR' para continuar: ")
if confirm.strip() != 'CONFIRMAR':
print("Failback cancelado.")
sys.exit(0)
# 4. Ejecutar failback
execute_failback(endpoint)
print("\n🎉 Failback completado exitosamente.")
print(" Los usuarios ahora ven el sitio primario nuevamente.")
if __name__ == '__main__':
main()
PYTHON_EOF
echo "✅ Script de failback creado en /tmp/failback-manual.py"
echo " Edita las variables al inicio antes de ejecutarlo."✅ Verificación del Lab 6
echo "=== Verificación Lab 6: Failback ==="
# 1. Estado post-failback
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" = "On" ] \
&& echo "✅ Routing Control Primario: $PRIMARY_STATE" \
|| echo "❌ Routing Control Primario: $PRIMARY_STATE"
[ "$SECONDARY_STATE" = "Off" ] \
&& echo "✅ Routing Control Secundario: $SECONDARY_STATE" \
|| echo "❌ Routing Control Secundario: $SECONDARY_STATE"
# 2. Sitio primario accesible
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
--connect-timeout 5 http://$PRIMARY_ALB_DNS/ 2>/dev/null)
[ "$HTTP_CODE" = "200" ] \
&& echo "✅ Sitio primario responde: HTTP $HTTP_CODE" \
|| echo "⚠️ Sitio primario HTTP code: $HTTP_CODE"
echo ""
echo "🎉 ¡Workshop completado! El ciclo completo funcionó:"
echo " Detección → Notificación → Failover → Reparación → Failback"Resumen del ciclo completo
Estado Normal → Incidente → Failover → Reparación → Failback
───────────── ───────── ───────── ────────── ────────
Primario: ON nginx cae Lambda nginx start Manual
Secundario: OFF HC falla activa ALB healthy RC swap
CW Alarm SNS email verified Primario: ON
ALARM RC swap ✅ Secundario: OFFSiguiente paso → Lab 7: Limpieza