Lab 2: Sitio Secundario (Fallback)
Lab 2: Sitio Secundario – S3 + CloudFront
En este lab creamos la página de fallback: lo que verán los usuarios cuando el sitio primario no esté disponible. Es una página estática muy simple alojada en S3 y distribuida por CloudFront.
¿Qué vamos a crear?
Internet → CloudFront → S3 (estático)- S3 Bucket con acceso solo desde CloudFront (OAC)
- CloudFront Distribution con HTTPS automático
- Página HTML estilo “mantenimiento” con branding
¿Por qué S3 + CloudFront?
| Ventaja | Detalle |
|---|---|
| Costo casi cero | S3 cobra centavos por GB; CloudFront tiene free tier |
| Alta disponibilidad | CloudFront tiene SLA de 99.99% |
| Sin servidores | Completamente serverless, no hay nada que “caer” |
| HTTPS gratis | CloudFront incluye certificado SSL automático |
Paso 1: Variables de entorno
export AWS_REGION="us-east-1"
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
export STACK_NAME_SECONDARY="route53-arc-secondary"
export BUCKET_NAME="route53-arc-fallback-${AWS_ACCOUNT_ID}"
echo "Bucket name: $BUCKET_NAME"Paso 2: Crear el template CloudFormation
cat > /tmp/secondary-site.yaml << 'TEMPLATE_EOF'
AWSTemplateFormatVersion: '2010-09-09'
Description: 'roLearning - Route53 ARC Workshop - Sitio Secundario (S3 + CloudFront)'
Parameters:
BucketName:
Type: String
Description: "Nombre único para el bucket S3"
Resources:
# ──────────────────────────────────────────────
# S3 Bucket para el sitio estático
# ──────────────────────────────────────────────
FallbackBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref BucketName
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
Tags:
- Key: Workshop
Value: route53-arc
- Key: Site
Value: secondary
# ──────────────────────────────────────────────
# Origin Access Control para CloudFront
# ──────────────────────────────────────────────
CloudFrontOAC:
Type: AWS::CloudFront::OriginAccessControl
Properties:
OriginAccessControlConfig:
Name: route53-arc-fallback-oac
OriginAccessControlOriginType: s3
SigningBehavior: always
SigningProtocol: sigv4
# ──────────────────────────────────────────────
# Bucket Policy - permite acceso solo desde CloudFront
# ──────────────────────────────────────────────
FallbackBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref FallbackBucket
PolicyDocument:
Statement:
- Sid: AllowCloudFrontServicePrincipal
Effect: Allow
Principal:
Service: cloudfront.amazonaws.com
Action: s3:GetObject
Resource: !Sub "${FallbackBucket.Arn}/*"
Condition:
StringEquals:
"AWS:SourceArn": !Sub "arn:aws:cloudfront::${AWS::AccountId}:distribution/${FallbackDistribution}"
# ──────────────────────────────────────────────
# CloudFront Distribution
# ──────────────────────────────────────────────
FallbackDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: true
DefaultRootObject: index.html
Comment: "roLearning - Route53 ARC Fallback Site"
HttpVersion: http2
Origins:
- Id: S3FallbackOrigin
DomainName: !GetAtt FallbackBucket.RegionalDomainName
OriginAccessControlId: !Ref CloudFrontOAC
S3OriginConfig:
OriginAccessIdentity: ""
DefaultCacheBehavior:
ViewerProtocolPolicy: redirect-to-https
TargetOriginId: S3FallbackOrigin
CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 # CachingOptimized
AllowedMethods: [GET, HEAD]
CachedMethods: [GET, HEAD]
CustomErrorResponses:
- ErrorCode: 403
ResponseCode: 200
ResponsePagePath: /index.html
- ErrorCode: 404
ResponseCode: 200
ResponsePagePath: /index.html
Tags:
- Key: Workshop
Value: route53-arc
- Key: Site
Value: secondary
Outputs:
FallbackBucketName:
Description: "Nombre del bucket S3"
Value: !Ref FallbackBucket
Export:
Name: route53-arc-fallback-bucket
CloudFrontDomain:
Description: "Dominio de la distribución CloudFront"
Value: !GetAtt FallbackDistribution.DomainName
Export:
Name: route53-arc-cf-domain
CloudFrontDistributionId:
Description: "ID de la distribución CloudFront"
Value: !Ref FallbackDistribution
Export:
Name: route53-arc-cf-distribution-id
TEMPLATE_EOF
echo "✅ Template creado: $(wc -l < /tmp/secondary-site.yaml) líneas"Paso 3: Desplegar el stack secundario
aws cloudformation deploy \
--template-file /tmp/secondary-site.yaml \
--stack-name $STACK_NAME_SECONDARY \
--capabilities CAPABILITY_IAM \
--region $AWS_REGION \
--parameter-overrides BucketName=$BUCKET_NAME \
--tags Workshop=route53-arc Environment=secondary
echo "Desplegando sitio secundario..."Información
CloudFront puede tardar 5-10 minutos en desplegarse completamente. El stack dirá CREATE_COMPLETE antes de que CloudFront esté 100% activo. Esto es normal.
Paso 4: Subir la página de fallback a S3
Una vez que el stack está CREATE_COMPLETE, creamos el contenido:
# Obtener nombre del bucket
export CF_BUCKET=$(aws cloudformation describe-stacks \
--stack-name $STACK_NAME_SECONDARY \
--region $AWS_REGION \
--query 'Stacks[0].Outputs[?OutputKey==`FallbackBucketName`].OutputValue' \
--output text)
export CF_DOMAIN=$(aws cloudformation describe-stacks \
--stack-name $STACK_NAME_SECONDARY \
--region $AWS_REGION \
--query 'Stacks[0].Outputs[?OutputKey==`CloudFrontDomain`].OutputValue' \
--output text)
echo "Bucket: $CF_BUCKET"
echo "CloudFront: $CF_DOMAIN"Crea el HTML de la página de mantenimiento:
cat > /tmp/fallback-index.html << 'HTML_EOF'
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mantenimiento – roLearning</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Arial, sans-serif;
background: #0f172a;
color: #e2e8f0;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}
.container {
text-align: center;
padding: 40px 60px;
background: #1e293b;
border-radius: 16px;
box-shadow: 0 8px 32px rgba(0,0,0,0.5);
max-width: 600px;
width: 90%;
}
.icon { font-size: 64px; margin-bottom: 20px; }
h1 { font-size: 2em; color: #f59e0b; margin-bottom: 12px; }
p { color: #94a3b8; line-height: 1.6; margin-bottom: 10px; }
.badge {
display: inline-block;
background: #f59e0b;
color: #0f172a;
padding: 6px 20px;
border-radius: 20px;
font-weight: bold;
font-size: 0.85em;
margin: 20px 0;
}
.footer {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #334155;
color: #475569;
font-size: 0.8em;
}
</style>
</head>
<body>
<div class="container">
<div class="icon">⚠️</div>
<h1>Sitio en Mantenimiento</h1>
<div class="badge">MANTENIMIENTO PROGRAMADO</div>
<p>Estamos realizando tareas de mantenimiento para mejorar nuestros servicios.</p>
<p>Nuestro equipo está trabajando para restablecer el servicio a la brevedad.</p>
<p><strong>Por favor, inténtelo de nuevo en unos minutos.</strong></p>
<div class="footer">
<p>roLearning – AWS Route 53 ARC Workshop</p>
<p>Sitio de Contingencia | Servido por CloudFront + S3</p>
</div>
</div>
</body>
</html>
HTML_EOF
# Subir a S3
aws s3 cp /tmp/fallback-index.html s3://$CF_BUCKET/index.html \
--content-type "text/html" \
--region $AWS_REGION
echo "✅ Página de fallback subida a S3"Paso 5: Invalidar caché de CloudFront (si aplica)
export CF_DIST_ID=$(aws cloudformation describe-stacks \
--stack-name $STACK_NAME_SECONDARY \
--region $AWS_REGION \
--query 'Stacks[0].Outputs[?OutputKey==`CloudFrontDistributionId`].OutputValue' \
--output text)
aws cloudfront create-invalidation \
--distribution-id $CF_DIST_ID \
--paths "/*" \
--region $AWS_REGION
echo "✅ Invalidación de caché creada"✅ Verificación del Lab 2
echo "=== Verificación Lab 2: Sitio Secundario ==="
# 1. Stack en CREATE_COMPLETE
STATUS=$(aws cloudformation describe-stacks \
--stack-name $STACK_NAME_SECONDARY --region $AWS_REGION \
--query 'Stacks[0].StackStatus' --output text)
[ "$STATUS" = "CREATE_COMPLETE" ] \
&& echo "✅ Stack CloudFormation: $STATUS" \
|| echo "❌ Stack status: $STATUS"
# 2. Bucket existe y tiene el archivo
aws s3 ls s3://$CF_BUCKET/index.html > /dev/null 2>&1 \
&& echo "✅ index.html en S3: OK" \
|| echo "❌ index.html no encontrado en S3"
# 3. CloudFront responde (puede tardar unos minutos)
CF_STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://$CF_DOMAIN/ 2>/dev/null)
[ "$CF_STATUS" = "200" ] \
&& echo "✅ CloudFront responde HTTPS $CF_STATUS" \
|| echo "⚠️ CloudFront HTTP code: $CF_STATUS (puede tardar 5-10 min en activarse)"
echo ""
echo "📌 Guarda estos valores para los próximos labs:"
echo " CF_BUCKET = $CF_BUCKET"
echo " CF_DOMAIN = $CF_DOMAIN"
echo " CF_DIST_ID = $CF_DIST_ID"
echo ""
echo "🌐 Abre en tu browser: https://$CF_DOMAIN"La página de fallback debería verse con fondo oscuro y el mensaje “⚠️ Sitio en Mantenimiento”.
Siguiente paso → Lab 3: Route 53 Health Checks + ARC