Lab 3: Route 53 Health Checks + ARC
Lab 3: Route 53 Health Checks + ARC Routing Controls
Estructura del módulo
03_arc/
├── main.tf ARC Cluster, Routing Controls, Health Checks, DNS
├── variables.tf
└── outputs.tfLos outputs de los labs anteriores se leen via terraform_remote_state.
Paso 1: Setup
source ~/route53-arc-tf/global.env
cd ~/route53-arc-tf/03_arcDominio: opcional
# Sin dominio (modo demo)
export USE_DOMAIN="false"
# Con dominio real en Route 53
# export USE_DOMAIN="true"
# export DOMAIN_NAME="tudominio.com"
# export APP_SUBDOMAIN="app"
# export HOSTED_ZONE_ID="Z1234ABCDEF"Paso 2: Crear los archivos
1. variables.tf
¿Para qué sirve este archivo?
Define los parámetros de entrada del orquestador de ARC (región, modo demo o dominio real, nombre de dominio, subdominio e ID de la Hosted Zone en Route 53).
cat > variables.tf << 'EOF'
variable "aws_region" {
type = string
default = "us-east-1"
}
variable "use_domain" {
description = "true = usar dominio real | false = modo demo"
type = bool
default = false
}
variable "domain_name" {
description = "Dominio base (ej: tudominio.com)"
type = string
default = "workshop.internal"
}
variable "app_subdomain" {
description = "Subdominio del workshop (ej: app)"
type = string
default = "app"
}
variable "hosted_zone_id" {
description = "ID de la Public Hosted Zone (solo si use_domain=true)"
type = string
default = ""
}
variable "project_tag" {
type = string
default = "route53-arc"
}
EOF2. main.tf
¿Para qué sirve este archivo?
Es el núcleo de la solución de alta disponibilidad y conmutación de tráfico:
- Lectura de estados locales: Importa los outputs del Lab 1 (
alb_dns_name,alb_zone_id) y del Lab 2 (cloudfront_domain) mediantedata "terraform_remote_state".- Route 53 Health Check & CloudWatch Alarm: Monitorea continuamente la salud del ALB primario en la ruta
/health.- ARC Cluster & Routing Controls: Crea el cluster redundante de Route 53 ARC con sus dos interruptores (Primario y Secundario).
- Safety Rule (
min-one-active-rule): Regla de protección que garantiza que al menos 1 sitio se mantenga encendido (ATLEAST 1), evitando cortes totales por error humano.- Health Checks de ARC & Políticas de Failover DNS: Asocia los Routing Controls a registros de DNS Primario (Alias ALB) y Secundario (CNAME CloudFront).
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"
}
}
}
# ─── Remote state de labs anteriores ──────────────────────────────────────────
data "terraform_remote_state" "primary" {
backend = "local"
config = {
path = "../01_primary/terraform.tfstate"
}
}
data "terraform_remote_state" "secondary" {
backend = "local"
config = {
path = "../02_secondary/terraform.tfstate"
}
}
locals {
alb_dns_name = data.terraform_remote_state.primary.outputs.alb_dns_name
alb_zone_id = data.terraform_remote_state.primary.outputs.alb_zone_id
cf_domain = data.terraform_remote_state.secondary.outputs.cloudfront_domain
}
# ─── Route 53 Health Check sobre el ALB ───────────────────────────────────────
resource "aws_route53_health_check" "primary_alb" {
fqdn = local.alb_dns_name
port = 80
type = "HTTP"
resource_path = "/health"
failure_threshold = 3
request_interval = 30
tags = {
Name = "route53-arc-primary-hc"
}
}
# ─── CloudWatch Alarm sobre el Health Check ───────────────────────────────────
resource "aws_cloudwatch_metric_alarm" "primary_unhealthy" {
alarm_name = "route53-arc-primary-unhealthy"
alarm_description = "Sitio primario no responde al Health Check de Route 53"
namespace = "AWS/Route53"
metric_name = "HealthCheckStatus"
statistic = "Minimum"
period = 60
evaluation_periods = 3
threshold = 1
comparison_operator = "LessThanThreshold"
treat_missing_data = "breaching"
dimensions = {
HealthCheckId = aws_route53_health_check.primary_alb.id
}
}
# ─── ARC Cluster ──────────────────────────────────────────────────────────────
resource "aws_route53recoverycontrol_cluster" "main" {
name = "route53-arc-workshop-cluster"
}
resource "aws_route53recoverycontrol_control_panel" "main" {
name = "route53-arc-workshop-panel"
cluster_arn = aws_route53recoverycontrol_cluster.main.arn
}
# ─── Routing Controls ─────────────────────────────────────────────────────────
resource "aws_route53recoverycontrol_routing_control" "primary" {
name = "primary-routing-control"
cluster_arn = aws_route53recoverycontrol_cluster.main.arn
control_panel_arn = aws_route53recoverycontrol_control_panel.main.arn
}
resource "aws_route53recoverycontrol_routing_control" "secondary" {
name = "secondary-routing-control"
cluster_arn = aws_route53recoverycontrol_cluster.main.arn
control_panel_arn = aws_route53recoverycontrol_control_panel.main.arn
}
# ─── Safety Rule ──────────────────────────────────────────────────────────────
resource "aws_route53recoverycontrol_safety_rule" "min_one_active" {
name = "min-one-active-rule"
control_panel_arn = aws_route53recoverycontrol_control_panel.main.arn
wait_period_ms = 5000
asserted_controls = [
aws_route53recoverycontrol_routing_control.primary.arn,
aws_route53recoverycontrol_routing_control.secondary.arn,
]
rule_config {
inverted = false
threshold = 1
type = "ATLEAST"
}
}
# ─── Health Checks tipo RECOVERY_CONTROL ──────────────────────────────────────
resource "aws_route53_health_check" "primary_rc" {
type = "RECOVERY_CONTROL"
routing_control_arn = aws_route53recoverycontrol_routing_control.primary.arn
tags = { Name = "route53-arc-primary-rc-hc" }
}
resource "aws_route53_health_check" "secondary_rc" {
type = "RECOVERY_CONTROL"
routing_control_arn = aws_route53recoverycontrol_routing_control.secondary.arn
tags = { Name = "route53-arc-secondary-rc-hc" }
}
# ─── Hosted Zone demo (si NO hay dominio real) ────────────────────────────────
resource "aws_route53_zone" "demo" {
count = var.use_domain ? 0 : 1
name = var.domain_name
comment = "Hosted Zone demo – route53-arc-workshop"
}
locals {
# Usar la HZ real o la demo según configuración
hosted_zone_id = var.use_domain ? var.hosted_zone_id : aws_route53_zone.demo[0].zone_id
}
# ─── DNS Record PRIMARY ────────────────────────────────────────────────────────
resource "aws_route53_record" "primary" {
zone_id = local.hosted_zone_id
name = "${var.app_subdomain}.${var.domain_name}"
type = "A"
set_identifier = "primary"
health_check_id = aws_route53_health_check.primary_rc.id
failover_routing_policy {
type = "PRIMARY"
}
alias {
name = local.alb_dns_name
zone_id = local.alb_zone_id
evaluate_target_health = true
}
}
# ─── DNS Record SECONDARY ─────────────────────────────────────────────────────
resource "aws_route53_record" "secondary" {
zone_id = local.hosted_zone_id
name = "${var.app_subdomain}.${var.domain_name}"
type = "CNAME"
ttl = 60
set_identifier = "secondary"
health_check_id = aws_route53_health_check.secondary_rc.id
failover_routing_policy {
type = "SECONDARY"
}
records = [local.cf_domain]
}
EOF3. outputs.tf
¿Para qué sirve este archivo?
Exporta el ARN del cluster ARC, sus endpoints HTTP de gestión, los ARNs de los Routing Controls primario y secundario, el ID del Health Check y el nombre de la alarma CloudWatch. Estos outputs son necesarios para automatizar el failover con Lambda en el Lab 4.
cat > outputs.tf << 'EOF'
output "arc_cluster_arn" {
value = aws_route53recoverycontrol_cluster.main.arn
}
output "arc_cluster_endpoints" {
value = aws_route53recoverycontrol_cluster.main.cluster_endpoints
}
output "primary_rc_arn" {
value = aws_route53recoverycontrol_routing_control.primary.arn
}
output "secondary_rc_arn" {
value = aws_route53recoverycontrol_routing_control.secondary.arn
}
output "health_check_id" {
value = aws_route53_health_check.primary_alb.id
}
output "cloudwatch_alarm_name" {
value = aws_cloudwatch_metric_alarm.primary_unhealthy.alarm_name
}
output "app_url" {
value = "http://${var.app_subdomain}.${var.domain_name}"
}
EOFPaso 3: Desplegar
terraform init
# Preparar variables
if [ "$USE_DOMAIN" = "true" ]; then
DOMAIN_VARS="-var=use_domain=true -var=domain_name=$DOMAIN_NAME -var=app_subdomain=$APP_SUBDOMAIN -var=hosted_zone_id=$HOSTED_ZONE_ID"
else
DOMAIN_VARS="-var=use_domain=false"
fi
terraform plan $DOMAIN_VARS
terraform apply $DOMAIN_VARS -auto-approveAviso
El ARC Cluster tarda 3-5 minutos en crearse. Es normal.
Paso 4: Activar Routing Controls
export PRIMARY_RC_ARN=$(terraform output -raw primary_rc_arn)
export SECONDARY_RC_ARN=$(terraform output -raw secondary_rc_arn)
# Obtener el endpoint del cluster
export CLUSTER_ENDPOINT=$(terraform output -json arc_cluster_endpoints \
| python3 -c "import json,sys; eps=json.load(sys.stdin); print(eps[0]['Endpoint'])")
echo "Cluster Endpoint: $CLUSTER_ENDPOINT"
# Estado normal: primario ON, secundario OFF
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 "✅ Primario=ON | Secundario=OFF"✅ Verificación del Lab 3
echo "=== Verificación Lab 3 ==="
terraform show | grep -q "aws_route53recoverycontrol_cluster.main" \
&& echo "✅ ARC Cluster en state" || echo "❌ No en state"
RC_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)
[ "$RC_STATE" = "On" ] \
&& echo "✅ Routing Control Primario: $RC_STATE" \
|| echo "⚠️ RC Primario: $RC_STATE"
echo "📌 PRIMARY_RC_ARN = $PRIMARY_RC_ARN"
echo "📌 CLUSTER_ENDPOINT = $CLUSTER_ENDPOINT"Siguiente paso → Lab 4: SNS + Lambda