Lab 4: Notificaciones SNS + Lambda Failover
Lab 4: Notificaciones SNS + Lambda Failover & 1-Click Failback
En este laboratorio conectamos la automatización completa:
- CloudWatch Alarm → SNS Topic: Cuando el sitio primario falla, CloudWatch notifica a SNS.
- SNS → Email / SMS: Envía una alerta inmediata al equipo de operaciones.
- SNS → Lambda Failover (
handler.py): Ejecuta el failover automático cambiando el Routing Control Primario aOFFy Secundario aON. - Lambda Recovery Detection (
OK): Notifica cuando el sitio primario vuelve a estar en verde pero sin hacer failback automático (para prevenir flapping). - Lambda 1-Click Failback (
failback_handler.py): Función con Function URL pública que permite realizar el failback con un solo clic desde el correo de notificación, más un documento de SSM Automation.
Cargar variables de entorno
source ~/global.env
echo "Email de notificación: $NOTIFICATION_EMAIL"Código Python de las Funciones Lambda
1. lambda/handler.py (Failover Handler & Recovery Notifier)
mkdir -p /tmp/lambda-arc
cat > /tmp/lambda-arc/handler.py << 'EOF'
"""
roLearning – Route53 ARC Workshop
Lambda: Failover Handler & Recovery Notifier
"""
import json, os, boto3, logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
PRIMARY_RC_ARN = os.environ.get("PRIMARY_RC_ARN")
SECONDARY_RC_ARN = os.environ.get("SECONDARY_RC_ARN")
CLUSTER_ENDPOINT = os.environ.get("CLUSTER_ENDPOINT")
SNS_TOPIC_ARN = os.environ.get("SNS_TOPIC_ARN")
FAILBACK_URL = os.environ.get("FAILBACK_URL", "")
REGION = os.environ.get("AWS_REGION", "us-east-1")
def arc_client():
return boto3.client("route53-recovery-cluster", endpoint_url=CLUSTER_ENDPOINT, region_name=REGION)
def get_state(client, arn):
return client.get_routing_control_state(RoutingControlArn=arn)["RoutingControlState"]
def execute_failover(client):
client.update_routing_control_states(
UpdateRoutingControlStateEntries=[
{"RoutingControlArn": PRIMARY_RC_ARN, "RoutingControlState": "Off"},
{"RoutingControlArn": SECONDARY_RC_ARN, "RoutingControlState": "On"},
]
)
logger.info("Failover automatico ejecutado")
def notify(subject, message):
if SNS_TOPIC_ARN:
boto3.client("sns", region_name=REGION).publish(
TopicArn=SNS_TOPIC_ARN, Subject=subject, Message=message)
def lambda_handler(event, context):
logger.info(json.dumps(event))
alarm_state = "UNKNOWN"
try:
for r in event.get("Records", []):
if r.get("EventSource") == "aws:sns":
msg_body = json.loads(r["Sns"]["Message"])
alarm_state = msg_body.get("NewStateValue", "UNKNOWN")
except Exception:
alarm_state = event.get("alarm_state", "UNKNOWN")
arc = arc_client()
if alarm_state == "OK":
if get_state(arc, PRIMARY_RC_ARN) == "Off":
if FAILBACK_URL:
failback_section = (
"\n FAILBACK 1-CLICK:\n" + FAILBACK_URL + "\n\n"
"Abri este enlace en tu navegador para devolver el trafico al sitio primario.\n"
"No requiere comandos ni acceso a la consola de AWS.\n"
)
else:
failback_section = (
"\nPara ejecutar el failback, inicia el documento SSM 'route53-arc-failback-ssm' "
"desde AWS Systems Manager -> Automation.\n"
)
notify(
subject="[SITIO PRIMARIO RECUPERADO] Listo para Failback 1-Click",
message=(
"AVISO DE RECUPERACION\n"
"======================================\n"
"El sitio primario vuelve a estar SALUDABLE (Health Check OK).\n\n"
"ATENCION: El trafico SE MANTIENE en el sitio de contingencia por seguridad.\n"
"El failback es un proceso deliberado que requiere tu aprobacion.\n"
+ failback_section +
"======================================\n"
"roLearning - AWS Route 53 ARC Workshop"
)
)
return {"statusCode": 200, "body": "RECOVERY_NOTIFICATION_SENT"}
return {"statusCode": 200, "body": "PRIMARY_ALREADY_ON"}
if alarm_state == "ALARM":
if get_state(arc, PRIMARY_RC_ARN) == "Off":
return {"statusCode": 200, "body": "Already failed over"}
execute_failover(arc)
notify(
subject="[FAILOVER ACTIVADO] Sitio primario caido - trafico redirigido",
message=(
"ALERTA DE INCIDENTE\n"
"======================================\n"
"Failover automatico ejecutado por caida del sitio primario.\n\n"
"Estado actual de Routing Controls:\n"
" - Primario: OFF\n"
" - Secundario: ON\n\n"
"El trafico fue redirigido al sitio de contingencia.\n"
"Cuando el sitio primario se recupere, recibiras otro email\n"
"con el enlace de Failback 1-Click.\n"
"======================================\n"
"roLearning - AWS Route 53 ARC Workshop"
)
)
return {"statusCode": 200, "body": "FAILOVER_EXECUTED"}
return {"statusCode": 200, "body": f"No action for state: {alarm_state}"}
EOF2. lambda/failback_handler.py (Failback 1-Click Handler)
cat > /tmp/lambda-arc/failback_handler.py << 'EOF'
"""
roLearning – Route53 ARC Workshop
Lambda: Failback 1-Click Handler (Function URL & SSM Automation)
"""
import json, os, boto3, logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
PRIMARY_RC_ARN = os.environ.get("PRIMARY_RC_ARN")
SECONDARY_RC_ARN = os.environ.get("SECONDARY_RC_ARN")
CLUSTER_ENDPOINT = os.environ.get("CLUSTER_ENDPOINT")
REGION = os.environ.get("AWS_REGION", "us-east-1")
def lambda_handler(event, context):
try:
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"},
]
)
html = """<!DOCTYPE html>
<html lang="es">
<head><meta charset="UTF-8"><title>Failback Exitoso</title>
<style>
body{font-family:sans-serif;background:#0f172a;color:#e2e8f0;
display:flex;align-items:center;justify-content:center;height:100vh;margin:0}
.card{background:#1e293b;padding:40px;border-radius:12px;
text-align:center;box-shadow:0 4px 20px rgba(0,0,0,.5)}
h1{color:#22c55e}
</style></head>
<body><div class="card">
<h1>Failback Exitoso</h1>
<p>El trafico ha sido devuelto al Sitio Primario (EC2 + ALB).</p>
<p>Routing Controls: Primario = <strong>ON</strong> | Secundario = <strong>OFF</strong></p>
<p style="font-size:.8em;color:#64748b">roLearning - AWS Route 53 ARC Workshop</p>
</div></body></html>"""
return {"statusCode": 200, "headers": {"Content-Type": "text/html"}, "body": html}
except Exception as e:
logger.error(f"Error: {e}")
return {"statusCode": 500, "body": f"Error: {str(e)}"}
EOF
cd /tmp/lambda-arc && zip -q /tmp/failover-handler.zip handler.py failback_handler.pyOpción A: CloudFormation (Base)
1. Subir paquete Lambda a S3 y crear template
LAMBDA_BUCKET="route53-arc-lambda-${AWS_ACCOUNT_ID}"
aws s3 mb s3://$LAMBDA_BUCKET --region $AWS_REGION 2>/dev/null || true
aws s3 cp /tmp/failover-handler.zip s3://$LAMBDA_BUCKET/failover-handler.zip --region $AWS_REGION
cat > /tmp/sns-lambda.yaml << 'TEMPLATE_EOF'
AWSTemplateFormatVersion: '2010-09-09'
Description: 'roLearning - Route53 ARC Workshop - SNS + Lambda Failover & Failback'
Parameters:
NotificationEmail: { Type: String }
PrimaryRCAarn: { Type: String }
SecondaryRCAarn: { Type: String }
ClusterEndpoint: { Type: String, Description: "Endpoint del ARC Cluster (obtenido con describe-cluster)" }
CloudWatchAlarmName: { Type: String }
LambdaBucket: { Type: String }
Resources:
AlertTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: route53-arc-alerts
DisplayName: "Route53 ARC - Alertas"
EmailSubscription:
Type: AWS::SNS::Subscription
Properties:
TopicArn: !Ref AlertTopic
Protocol: email
Endpoint: !Ref NotificationEmail
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: route53-arc-lambda-failover-role-cfn
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal: { Service: lambda.amazonaws.com }
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: ARCFailoverPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- route53-recovery-cluster:GetRoutingControlState
- route53-recovery-cluster:UpdateRoutingControlStates
Resource: "*"
- Effect: Allow
Action: sns:Publish
Resource: !Ref AlertTopic
# Failback se crea primero para obtener su URL e inyectarla en el Failover handler
FailbackLambda:
Type: AWS::Lambda::Function
Properties:
FunctionName: route53-arc-failback-handler
Runtime: python3.12
Handler: failback_handler.lambda_handler
Role: !GetAtt LambdaExecutionRole.Arn
Timeout: 60
MemorySize: 128
Code:
S3Bucket: !Ref LambdaBucket
S3Key: failover-handler.zip
Environment:
Variables:
PRIMARY_RC_ARN: !Ref PrimaryRCAarn
SECONDARY_RC_ARN: !Ref SecondaryRCAarn
CLUSTER_ENDPOINT: !Ref ClusterEndpoint
FailbackLambdaUrl:
Type: AWS::Lambda::Url
Properties:
TargetFunctionArn: !GetAtt FailbackLambda.Arn
AuthType: NONE
FailbackLambdaUrlPermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !GetAtt FailbackLambda.Arn
Action: lambda:InvokeFunctionUrl
Principal: "*"
FunctionUrlAuthType: NONE
FailoverLambda:
Type: AWS::Lambda::Function
DependsOn: FailbackLambdaUrl
Properties:
FunctionName: route53-arc-failover-handler
Runtime: python3.12
Handler: handler.lambda_handler
Role: !GetAtt LambdaExecutionRole.Arn
Timeout: 60
MemorySize: 128
Code:
S3Bucket: !Ref LambdaBucket
S3Key: failover-handler.zip
Environment:
Variables:
PRIMARY_RC_ARN: !Ref PrimaryRCAarn
SECONDARY_RC_ARN: !Ref SecondaryRCAarn
CLUSTER_ENDPOINT: !Ref ClusterEndpoint
SNS_TOPIC_ARN: !Ref AlertTopic
FAILBACK_URL: !GetAtt FailbackLambdaUrl.FunctionUrl
LambdaSNSPermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !GetAtt FailoverLambda.Arn
Action: lambda:InvokeFunction
Principal: sns.amazonaws.com
SourceArn: !Ref AlertTopic
LambdaSNSSubscription:
Type: AWS::SNS::Subscription
Properties:
TopicArn: !Ref AlertTopic
Protocol: lambda
Endpoint: !GetAtt FailoverLambda.Arn
AlarmSNSAction:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: !Sub "${CloudWatchAlarmName}-with-sns"
AlarmDescription: "Sitio primario caido - dispara SNS + Lambda failover"
Namespace: AWS/Route53
MetricName: HealthCheckStatus
Dimensions:
- Name: HealthCheckId
Value: { Fn::ImportValue: route53-arc-hc-id }
Statistic: Minimum
Period: 60
EvaluationPeriods: 3
Threshold: 1
ComparisonOperator: LessThanThreshold
TreatMissingData: breaching
AlarmActions: [!Ref AlertTopic]
OKActions: [!Ref AlertTopic]
SSMFailbackDocument:
Type: AWS::SSM::Document
Properties:
Name: route53-arc-failback-ssm
DocumentType: Automation
Content:
schemaVersion: '0.3'
description: "Ejecutar Failback 1-Click para AWS Route 53 ARC"
mainSteps:
- name: InvokeFailbackLambda
action: aws:invokeLambdaFunction
inputs:
FunctionName: !Ref FailbackLambda
Payload: "{}"
Outputs:
SNSTopicArn:
Value: !Ref AlertTopic
Export: { Name: route53-arc-sns-topic }
LambdaName:
Value: !Ref FailoverLambda
Export: { Name: route53-arc-lambda-name }
FailbackLambdaName:
Value: !Ref FailbackLambda
Export: { Name: route53-arc-failback-lambda-name }
FailbackUrl:
Value: !GetAtt FailbackLambdaUrl.FunctionUrl
Export: { Name: route53-arc-failback-url }
TEMPLATE_EOF2. Desplegar Stack CloudFormation
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 CW_ALARM_NAME=$(aws cloudformation describe-stacks \
--stack-name route53-arc-routing --region $AWS_REGION \
--query 'Stacks[0].Outputs[?OutputKey==`CloudWatchAlarmName`].OutputValue' --output text)
# Obtener el endpoint real del ARC Cluster — NO se puede derivar del ARN
export CLUSTER_ENDPOINT=$(aws route53-recovery-control-config describe-cluster \
--cluster-arn $ARC_CLUSTER_ARN --region $AWS_REGION \
--query 'Cluster.ClusterEndpoints[?Region==`us-east-1`].Endpoint' \
--output text)
echo "✅ CLUSTER_ENDPOINT: $CLUSTER_ENDPOINT"
aws cloudformation deploy \
--template-file /tmp/sns-lambda.yaml \
--stack-name route53-arc-automation \
--capabilities CAPABILITY_NAMED_IAM \
--region $AWS_REGION \
--parameter-overrides \
NotificationEmail=$NOTIFICATION_EMAIL \
PrimaryRCAarn=$PRIMARY_RC_ARN \
SecondaryRCAarn=$SECONDARY_RC_ARN \
ClusterEndpoint=$CLUSTER_ENDPOINT \
CloudWatchAlarmName=$CW_ALARM_NAME \
LambdaBucket=$LAMBDA_BUCKET
export LAMBDA_NAME="route53-arc-failover-handler"
export FAILBACK_URL=$(aws cloudformation describe-stacks \
--stack-name route53-arc-automation --region $AWS_REGION \
--query 'Stacks[0].Outputs[?OutputKey==`FailbackUrl`].OutputValue' \
--output text 2>/dev/null || echo "")
echo "🔗 Failback URL: $FAILBACK_URL"
# Persistir en global.env
grep -q "LAMBDA_NAME" ~/global.env 2>/dev/null && \
{ sed -i '' "s|.*LAMBDA_NAME.*|export LAMBDA_NAME=\"${LAMBDA_NAME}\"|" ~/global.env 2>/dev/null || sed -i "s|.*LAMBDA_NAME.*|export LAMBDA_NAME=\"${LAMBDA_NAME}\"|" ~/global.env; } || \
echo "export LAMBDA_NAME=\"${LAMBDA_NAME}\"" >> ~/global.env
echo "✅ LAMBDA_NAME guardado en ~/global.env"Aviso
Paso Obligatorio — confirmá la suscripción SNS ahora: Revisá tu casilla de email ($NOTIFICATION_EMAIL). Buscá el correo con asunto “AWS Notification - Subscription Confirmation” y hacé clic en “Confirm subscription”. Sin este paso, las alertas de failover no llegarán. El email puede tardar unos minutos en llegar.
Opción B: Terraform (Segunda Opción)
Detalles
🚫 Opción B requiere terminal local — no uses CloudShell con Terraform. Ver Lab 0 para más detalles.
1. Configurar Módulo Terraform
cd ~/route53-arc-tf/04_automation
mkdir -p lambda
cp /tmp/lambda-arc/handler.py lambda/handler.py
cp /tmp/lambda-arc/failback_handler.py lambda/failback_handler.py
cd lambda && zip -q ../failover.zip handler.py failback_handler.py && cd ..
cat > variables.tf << 'EOF'
variable "aws_region" { type = string; default = "us-east-1" }
variable "notification_email" { type = string }
variable "project_tag" { type = string; default = "route53-arc" }
EOF
cat > main.tf << 'EOF'
terraform {
required_version = ">= 1.5"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
}
provider "aws" {
region = var.aws_region
default_tags { tags = { Workshop = var.project_tag, ManagedBy = "Terraform" } }
}
data "aws_caller_identity" "current" {}
data "terraform_remote_state" "arc" {
backend = "local"
config = { path = "../03_arc/terraform.tfstate" }
}
locals {
primary_rc_arn = data.terraform_remote_state.arc.outputs.primary_rc_arn
secondary_rc_arn = data.terraform_remote_state.arc.outputs.secondary_rc_arn
cluster_endpoint = data.terraform_remote_state.arc.outputs.arc_cluster_endpoints[0]["Endpoint"]
hc_id = data.terraform_remote_state.arc.outputs.health_check_id
cw_alarm_base = data.terraform_remote_state.arc.outputs.cloudwatch_alarm_name
}
resource "aws_sns_topic" "alerts" {
name = "route53-arc-alerts"
display_name = "Route53 ARC - Alertas"
}
resource "aws_sns_topic_subscription" "email" {
topic_arn = aws_sns_topic.alerts.arn
protocol = "email"
endpoint = var.notification_email
}
resource "aws_s3_bucket" "lambda_code" {
bucket = "route53-arc-lambda-${data.aws_caller_identity.current.account_id}"
force_destroy = true
}
resource "aws_s3_object" "failover_zip" {
bucket = aws_s3_bucket.lambda_code.id
key = "failover-handler.zip"
source = "${path.module}/failover.zip"
etag = filemd5("${path.module}/failover.zip")
}
resource "aws_iam_role" "lambda" {
name = "route53-arc-lambda-failover-role-tf"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{ Effect = "Allow", Principal = { Service = "lambda.amazonaws.com" }, Action = "sts:AssumeRole" }]
})
}
resource "aws_iam_role_policy_attachment" "basic" {
role = aws_iam_role.lambda.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
resource "aws_iam_role_policy" "arc_sns" {
name = "arc-sns-policy"
role = aws_iam_role.lambda.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{ Effect = "Allow",
Action = ["route53-recovery-cluster:GetRoutingControlState",
"route53-recovery-cluster:UpdateRoutingControlStates"],
Resource = "*" },
{ Effect = "Allow", Action = "sns:Publish", Resource = aws_sns_topic.alerts.arn }
]
})
}
# Failback se crea primero para que su URL pueda inyectarse en el Failover handler
resource "aws_lambda_function" "failback" {
function_name = "route53-arc-failback-handler"
runtime = "python3.12"
handler = "failback_handler.lambda_handler"
role = aws_iam_role.lambda.arn
timeout = 60
memory_size = 128
s3_bucket = aws_s3_bucket.lambda_code.id
s3_key = aws_s3_object.failover_zip.key
environment {
variables = {
PRIMARY_RC_ARN = local.primary_rc_arn
SECONDARY_RC_ARN = local.secondary_rc_arn
CLUSTER_ENDPOINT = local.cluster_endpoint
}
}
}
resource "aws_lambda_function_url" "failback_url" {
function_name = aws_lambda_function.failback.function_name
authorization_type = "NONE"
}
resource "aws_lambda_permission" "failback_url_perm" {
statement_id = "FunctionURLAllowPublicAccess"
action = "lambda:InvokeFunctionUrl"
function_name = aws_lambda_function.failback.function_name
principal = "*"
function_url_auth_type = "NONE"
}
resource "aws_lambda_function" "failover" {
depends_on = [aws_lambda_function_url.failback_url]
function_name = "route53-arc-failover-handler"
runtime = "python3.12"
handler = "handler.lambda_handler"
role = aws_iam_role.lambda.arn
timeout = 60
memory_size = 128
s3_bucket = aws_s3_bucket.lambda_code.id
s3_key = aws_s3_object.failover_zip.key
environment {
variables = {
PRIMARY_RC_ARN = local.primary_rc_arn
SECONDARY_RC_ARN = local.secondary_rc_arn
CLUSTER_ENDPOINT = local.cluster_endpoint
SNS_TOPIC_ARN = aws_sns_topic.alerts.arn
FAILBACK_URL = aws_lambda_function_url.failback_url.function_url
}
}
}
resource "aws_lambda_permission" "sns_invoke" {
statement_id = "AllowSNSInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.failover.function_name
principal = "sns.amazonaws.com"
source_arn = aws_sns_topic.alerts.arn
}
resource "aws_sns_topic_subscription" "lambda_sub" {
topic_arn = aws_sns_topic.alerts.arn
protocol = "lambda"
endpoint = aws_lambda_function.failover.arn
}
resource "aws_ssm_document" "failback" {
name = "route53-arc-failback-ssm"
document_type = "Automation"
content = jsonencode({
schemaVersion = "0.3"
description = "Ejecutar Failback 1-Click para AWS Route 53 ARC"
mainSteps = [{
name = "InvokeFailbackLambda"
action = "aws:invokeLambdaFunction"
inputs = { FunctionName = aws_lambda_function.failback.function_name, Payload = "{}" }
}]
})
}
resource "aws_cloudwatch_metric_alarm" "with_action" {
alarm_name = "${local.cw_alarm_base}-with-sns"
alarm_description = "Sitio primario caido - dispara SNS + Lambda failover"
namespace = "AWS/Route53"
metric_name = "HealthCheckStatus"
statistic = "Minimum"
period = 60
evaluation_periods = 3
threshold = 1
comparison_operator = "LessThanThreshold"
treat_missing_data = "breaching"
dimensions = { HealthCheckId = local.hc_id }
alarm_actions = [aws_sns_topic.alerts.arn]
ok_actions = [aws_sns_topic.alerts.arn]
}
EOF
cat > outputs.tf << 'EOF'
output "sns_topic_arn" { value = aws_sns_topic.alerts.arn }
output "lambda_name" { value = aws_lambda_function.failover.function_name }
output "failback_lambda_name" { value = aws_lambda_function.failback.function_name }
output "failback_url" { value = aws_lambda_function_url.failback_url.function_url }
EOF2. Aplicar los cambios en Terraform
terraform init
terraform apply -var="notification_email=$NOTIFICATION_EMAIL" -auto-approve
export LAMBDA_NAME=$(terraform output -raw lambda_name)
export FAILBACK_URL=$(terraform output -raw failback_url)
echo "🔗 Failback URL: $FAILBACK_URL"
# Persistir en global.env
grep -q "LAMBDA_NAME" ~/global.env 2>/dev/null && \
{ sed -i '' "s|.*LAMBDA_NAME.*|export LAMBDA_NAME=\"${LAMBDA_NAME}\"|" ~/global.env 2>/dev/null || sed -i "s|.*LAMBDA_NAME.*|export LAMBDA_NAME=\"${LAMBDA_NAME}\"|" ~/global.env; } || \
echo "export LAMBDA_NAME=\"${LAMBDA_NAME}\"" >> ~/global.env
echo "✅ LAMBDA_NAME guardado en ~/global.env"Aviso
Paso Obligatorio — confirmá la suscripción SNS ahora: Revisá tu casilla de email ($NOTIFICATION_EMAIL). Buscá el correo con asunto “AWS Notification - Subscription Confirmation” y hacé clic en “Confirm subscription”. Sin este paso, las alertas de failover no llegarán. El email puede tardar unos minutos en llegar.
✅ Verificación del Lab 4
echo "=== Verificación Lab 4: SNS + Lambda Failover & Failback ==="
aws lambda get-function --function-name $LAMBDA_NAME --region $AWS_REGION \
--query 'Configuration.State' --output text | grep -q Active \
&& echo "✅ Lambda Failover ($LAMBDA_NAME): Active" \
|| echo "❌ Lambda Failover no encontrada"
aws lambda get-function --function-name route53-arc-failback-handler --region $AWS_REGION \
--query 'Configuration.State' --output text | grep -q Active \
&& echo "✅ Lambda Failback: Active" \
|| echo "❌ Lambda Failback no encontrada"
# Verificar que la FAILBACK_URL esté inyectada en el Failover handler
aws lambda get-function-configuration \
--function-name $LAMBDA_NAME --region $AWS_REGION \
--query 'Environment.Variables.FAILBACK_URL' --output text \
| grep -v "^None$" \
&& echo "✅ FAILBACK_URL inyectada en Lambda Failover" \
|| echo "❌ FAILBACK_URL falta en Lambda Failover"
# Probar invocación en seco (alarm_state=OK, sin failover previo — responderá PRIMARY_ALREADY_ON)
aws lambda invoke \
--function-name $LAMBDA_NAME \
--payload '{"alarm_state":"OK"}' \
--cli-binary-format raw-in-base64-out \
/tmp/lambda-test.json --region $AWS_REGION > /dev/null
echo "Respuesta del test Lambda:"; cat /tmp/lambda-test.json; echo
echo "ℹ️ Si la respuesta es PRIMARY_ALREADY_ON, es correcto — el primario está UP y no hay failover activo."Aviso
Antes de avanzar al Lab 5: confirmá la suscripción SNS en tu casilla de email. Sin ese paso, no recibirás las alertas de failover. El email de confirmación puede tardar unos minutos en llegar.
Siguiente paso → Lab 5: Simulación de Incidente