Imagen destacada del artículo: Amazon Route 53: mucho más que DNS

Amazon Route 53: mucho más que DNS

Durante años, el DNS se ha tratado como un trámite: compras un dominio, apuntas un par de registros A y sigues con lo “importante”.

Ese enfoque funciona… hasta que deja de hacerlo.

En entornos cloud modernos, el DNS no es un detalle administrativo: es la primera decisión de arquitectura que impacta en latencia, disponibilidad y experiencia de usuario.

Y aquí es donde Amazon Route 53 juega un papel que muchos equipos siguen infravalorando.

El DNS no es pasivo (aunque lo parezca)

Cuando un usuario accede a tu aplicación, el código todavía no ha hecho nada. No hay microservicios, no hay cachés, no hay base de datos.

Todo empieza antes, en una pregunta simple:

¿A qué IP tengo que ir?

Esa respuesta la da el DNS, y si es lenta, incorrecta o apunta al sitio equivocado, da igual lo bien que esté escrito tu backend.

La ilusión de la instantaneidad:

Flujo real de una petición (antes de llegar a tu app):

1. Resolución DNS (Route 53):
   - Consulta recursiva desde el cliente
   - Respuesta desde nameservers autoritativos
   - TTL cacheado localmente
   - Tiempo: 50-200ms primera vez, ~0ms si cacheado
   
2. Handshake TCP/TLS:
   - SYN → SYN-ACK → ACK
   - Negociación TLS 1.3
   - Tiempo: 1-2 RTTs (20-100ms según distancia)
   
3. Petición HTTP/2 o HTTP/3:
   - Request headers + body
   - Tiempo: depende de payload

El DNS es el paso 0. Si falla o es lento, todo lo demás es irrelevante.

Route 53 no es solo un DNS autoritativo. Es un servicio global, altamente disponible y diseñado para tomar decisiones de enrutamiento en función de criterios reales:

  • ✅ Salud del endpoint (health checks)
  • ✅ Latencia medida (no teórica)
  • ✅ Peso configurado (traffic shaping)
  • ✅ Localización geográfica (compliance)

No se limita a resolver nombres: decide por dónde entra el tráfico.

Qué es realmente Amazon Route 53

A nivel funcional, Route 53 combina tres cosas clave en un único servicio:

Route 53 como sistema:

1. DNS autoritativo gestionado:
   - Nameservers distribuidos globalmente
   - 100% SLA de disponibilidad
   - Anycast para baja latencia
   - DNSSEC opcional
   
2. Motor de routing inteligente:
   - Políticas de enrutamiento dinámicas
   - Decisiones basadas en métricas reales
   - Sin necesidad de código en la app
   
3. Sistema de health checks integrado:
   - Monitoreo desde múltiples ubicaciones
   - Alertas a CloudWatch
   - Failover automático

Pero lo interesante no es la definición, sino lo que permite hacer en arquitecturas reales.

Route 53 vive:

  • Fuera de tu VPC
  • Fuera de tus clusters
  • Fuera de tus balanceadores

Eso lo convierte en una capa de control independiente, ideal para tomar decisiones cuando otras piezas están fallando.

El error más común: pensar que el DNS “no escala”

Es habitual ver arquitecturas muy sofisticadas detrás de un DNS configurado con prisas.

El razonamiento suele ser:

“Esto solo apunta a un ALB, ya escalará solo.”

El problema es que el DNS también es parte del camino crítico.

Ejemplo realista que he visto fallar:

Escenario:
  - Aplicación multi-región (us-east-1 + eu-west-1)
  - ALBs en ambas regiones
  - Auto Scaling Groups configurados
  - CloudFront delante (opcional)
  
DNS configurado mal:
  api.empresa.com → ALB us-east-1 (siempre)
  
¿Qué pasa cuando us-east-1 cae?
  ❌ El tráfico sigue entrando a us-east-1
  ❌ Usuarios ven timeouts
  ❌ eu-west-1 está funcionando perfectamente (pero nadie llega)
  ❌ Incidente P0 declarado
  ❌ On-call Engineer cambia DNS manualmente
  ❌ Propagación: 5-60 minutos según TTL
  ❌ Revenue loss: $$$$$

Con Route 53 failover routing:
  ✅ Health check detecta us-east-1 caído
  ✅ Failover automático a eu-west-1
  ✅ Tiempo de detección: 30-60 segundos
  ✅ Sin intervención manual
  ✅ Incident resolved automáticamente

Route 53 está diseñado justo para eso: absorber decisiones que no quieres meter en el código ni en el load balancer.

Routing policies: el verdadero valor de Route 53

Aquí es donde Route 53 deja de ser “otro DNS”.

1. Simple routing: lo justo para empezar

Es el caso básico: un nombre apunta a un destino.

# Terraform: Simple routing
resource "aws_route53_record" "simple" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "blog.example.com"
  type    = "A"
  ttl     = 300
  records = ["192.0.2.1"]
}

# O apuntando a ALB
resource "aws_route53_record" "simple_alb" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "app.example.com"
  type    = "A"
  
  alias {
    name                   = aws_lb.main.dns_name
    zone_id                = aws_lb.main.zone_id
    evaluate_target_health = true
  }
}

Cuándo usarlo:

  • ✅ Entornos pequeños o internos
  • ✅ Servicios de prueba/desarrollo
  • ✅ Cuando solo hay un endpoint
  • ✅ No necesitas failover ni distribución

Cuándo NO usarlo:

  • ❌ Producción con alta disponibilidad
  • ❌ Multi-región
  • ❌ Necesitas control de tráfico

2. Weighted routing: despliegues sin tocar infraestructura

Imagina que tienes dos versiones de una API:

  • v1: estable, conocida, en producción
  • v2: recién desplegada, necesitas probar

Con weighted routing puedes enviar:

  • 90% del tráfico a v1
  • 10% del tráfico a v2

Sin modificar el ALB, sin feature flags y sin lógica adicional.

# Terraform: Weighted routing para canary deployment

# 90% tráfico a versión estable
resource "aws_route53_record" "api_v1" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "api.example.com"
  type    = "A"
  
  set_identifier = "api-v1-stable"
  
  weighted_routing_policy {
    weight = 90
  }
  
  alias {
    name                   = aws_lb.v1.dns_name
    zone_id                = aws_lb.v1.zone_id
    evaluate_target_health = true
  }
}

# 10% tráfico a nueva versión (canary)
resource "aws_route53_record" "api_v2" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "api.example.com"
  type    = "A"
  
  set_identifier = "api-v2-canary"
  
  weighted_routing_policy {
    weight = 10
  }
  
  alias {
    name                   = aws_lb.v2.dns_name
    zone_id                = aws_lb.v2.zone_id
    evaluate_target_health = true
  }
}

Proceso de despliegue canary con Route 53:

Fase 1 - Despliegue inicial:
  v1: 100% (weight = 100)
  v2: 0%   (no existe todavía)
  
Fase 2 - Canary (5%):
  v1: 95%  (weight = 95)
  v2: 5%   (weight = 5)
  Monitoreo: error rates, latencia, métricas negocio
  
Fase 3 - Expansión (25%):
  v1: 75%  (weight = 75)
  v2: 25%  (weight = 25)
  Validación: más usuarios, más carga
  
Fase 4 - Mayoría (50%):
  v1: 50%  (weight = 50)
  v2: 50%  (weight = 50)
  Decisión: continuar o rollback
  
Fase 5 - Finalización (100%):
  v1: 0%   (eliminado)
  v2: 100% (weight = 100)
  
Si algo falla en cualquier fase:
  ✅ Cambias pesos en Terraform
  ✅ Apply rápido (segundos)
  ✅ Rollback inmediato
  ✅ Sin downtime

Esto es oro puro para despliegues progresivos y rollbacks rápidos.

3. Latency-based routing: experiencia de usuario real

Aquí Route 53 responde con el endpoint que ofrece menor latencia desde la ubicación del cliente.

No por cercanía geográfica teórica, sino por medición real.

# Terraform: Latency-based routing multi-región

# ALB en us-east-1
resource "aws_route53_record" "api_us_east" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "api.example.com"
  type    = "A"
  
  set_identifier = "api-us-east-1"
  
  latency_routing_policy {
    region = "us-east-1"
  }
  
  alias {
    name                   = aws_lb.us_east.dns_name
    zone_id                = aws_lb.us_east.zone_id
    evaluate_target_health = true
  }
}

# ALB en eu-west-1
resource "aws_route53_record" "api_eu_west" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "api.example.com"
  type    = "A"
  
  set_identifier = "api-eu-west-1"
  
  latency_routing_policy {
    region = "eu-west-1"
  }
  
  alias {
    name                   = aws_lb.eu_west.dns_name
    zone_id                = aws_lb.eu_west.zone_id
    evaluate_target_health = true
  }
}

# ALB en ap-southeast-1
resource "aws_route53_record" "api_ap_southeast" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "api.example.com"
  type    = "A"
  
  set_identifier = "api-ap-southeast-1"
  
  latency_routing_policy {
    region = "ap-southeast-1"
  }
  
  alias {
    name                   = aws_lb.ap_southeast.dns_name
    zone_id                = aws_lb.ap_southeast.zone_id
    evaluate_target_health = true
  }
}

Resultado:

Usuario desde Madrid:
  Consulta DNS → Route 53
  Route 53 mide latencia:
    - us-east-1: 80ms
    - eu-west-1: 15ms  ← MEJOR
    - ap-southeast-1: 180ms
  Responde: ALB eu-west-1
  
Usuario desde San Francisco:
  Consulta DNS → Route 53
  Route 53 mide latencia:
    - us-east-1: 10ms  ← MEJOR
    - eu-west-1: 140ms
    - ap-southeast-1: 120ms
  Responde: ALB us-east-1
  
Usuario desde Singapur:
  Consulta DNS → Route 53
  Route 53 mide latencia:
    - us-east-1: 210ms
    - eu-west-1: 180ms
    - ap-southeast-1: 8ms  ← MEJOR
  Responde: ALB ap-southeast-1

Sin reglas manuales. Sin redirecciones. Sin lógica en el código.

Usuarios europeos entran por Europa, americanos por EE. UU., asiáticos por Asia. Experiencia óptima automática.

4. Failover routing: resiliencia de verdad

Esta política permite definir un endpoint principal y uno secundario.

Si el health check falla, Route 53 deja de responder con el endpoint caído.

# Terraform: Failover routing activo-pasivo

# Endpoint PRIMARY
resource "aws_route53_record" "api_primary" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "api.example.com"
  type    = "A"
  
  set_identifier = "api-primary-us-east-1"
  
  failover_routing_policy {
    type = "PRIMARY"
  }
  
  alias {
    name                   = aws_lb.primary.dns_name
    zone_id                = aws_lb.primary.zone_id
    evaluate_target_health = true
  }
  
  health_check_id = aws_route53_health_check.primary.id
}

# Endpoint SECONDARY
resource "aws_route53_record" "api_secondary" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "api.example.com"
  type    = "A"
  
  set_identifier = "api-secondary-eu-west-1"
  
  failover_routing_policy {
    type = "SECONDARY"
  }
  
  alias {
    name                   = aws_lb.secondary.dns_name
    zone_id                = aws_lb.secondary.zone_id
    evaluate_target_health = true
  }
}

# Health check para PRIMARY
resource "aws_route53_health_check" "primary" {
  type              = "HTTPS"
  resource_path     = "/health"
  fqdn              = aws_lb.primary.dns_name
  port              = 443
  request_interval  = 30
  failure_threshold = 3
  measure_latency   = true
  
  tags = {
    Name = "api-primary-health-check"
  }
}

# CloudWatch alarm para notificaciones
resource "aws_cloudwatch_metric_alarm" "primary_unhealthy" {
  alarm_name          = "route53-primary-endpoint-unhealthy"
  comparison_operator = "LessThanThreshold"
  evaluation_periods  = 2
  metric_name         = "HealthCheckStatus"
  namespace           = "AWS/Route53"
  period              = 60
  statistic           = "Minimum"
  threshold           = 1
  alarm_description   = "Primary endpoint failing health checks"
  alarm_actions       = [aws_sns_topic.incidents.arn]
  
  dimensions = {
    HealthCheckId = aws_route53_health_check.primary.id
  }
}

Comportamiento en failover:

Estado normal:
  Health check PRIMARY: ✅ HEALTHY
  Tráfico: 100% a PRIMARY
  SECONDARY: standby (idle pero vivo)
  
Fallo detectado:
  t=0s:  PRIMARY empieza a fallar
  t=30s: Primer health check falla
  t=60s: Segundo health check falla
  t=90s: Tercer health check falla (threshold = 3)
  t=90s: Route 53 marca PRIMARY como UNHEALTHY
  t=90s: Tráfico nuevo va a SECONDARY automáticamente
  t=90s: CloudWatch alarm dispara → SNS → PagerDuty
  
Recuperación:
  PRIMARY vuelve a estar sano
  3 health checks consecutivos exitosos
  Route 53 marca PRIMARY como HEALTHY
  Tráfico vuelve a PRIMARY gradualmente (según TTL)

Ojo: no es instantáneo ni mágico. Depende del:

  • ⏱️ Request interval (30s recomendado)
  • ⏱️ Failure threshold (3 checks = 90s)
  • ⏱️ TTL del registro (60-300s)

Pero bien configurado es una red de seguridad brutal cuando todo lo demás falla.

5. Geolocation routing: control y cumplimiento

No siempre quieres el endpoint “más rápido”. A veces necesitas el correcto.

Casos de uso reales:

Compliance legal:
  - GDPR: datos europeos no salen de Europa
  - China: requisitos de soberanía de datos
  - Rusia: ley de localización de datos
  
Experiencia regionalizada:
  - Contenido en idioma local
  - Catálogos de productos diferentes
  - Precios en moneda local
  
Restricciones de licencia:
  - Contenido de streaming por país
  - Aplicaciones bancarias reguladas
  - Farmacéuticas con regulación regional
# Terraform: Geolocation routing por región

# Europa → data center europeo
resource "aws_route53_record" "app_europe" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "app.example.com"
  type    = "A"
  
  set_identifier = "app-europe"
  
  geolocation_routing_policy {
    continent = "EU"
  }
  
  alias {
    name                   = aws_lb.eu_west.dns_name
    zone_id                = aws_lb.eu_west.zone_id
    evaluate_target_health = true
  }
}

# América del Norte → data center US
resource "aws_route53_record" "app_north_america" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "app.example.com"
  type    = "A"
  
  set_identifier = "app-north-america"
  
  geolocation_routing_policy {
    continent = "NA"
  }
  
  alias {
    name                   = aws_lb.us_east.dns_name
    zone_id                = aws_lb.us_east.zone_id
    evaluate_target_health = true
  }
}

# China específicamente
resource "aws_route53_record" "app_china" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "app.example.com"
  type    = "A"
  
  set_identifier = "app-china"
  
  geolocation_routing_policy {
    country = "CN"
  }
  
  alias {
    name                   = aws_lb.cn_north.dns_name
    zone_id                = aws_lb.cn_north.zone_id
    evaluate_target_health = true
  }
}

# Default para resto del mundo
resource "aws_route53_record" "app_default" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "app.example.com"
  type    = "A"
  
  set_identifier = "app-default"
  
  geolocation_routing_policy {
    continent = "*"  # wildcard
  }
  
  alias {
    name                   = aws_lb.us_east.dns_name
    zone_id                = aws_lb.us_east.zone_id
    evaluate_target_health = true
  }
}

Route 53 te permite decidir por origen, no por infraestructura.

6. Multivalue answer routing: pobre man’s load balancing

Devuelve múltiples valores (hasta 8) y el cliente elige uno aleatoriamente.

# Terraform: Multivalue answer routing
resource "aws_route53_record" "api_multivalue_1" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "api.example.com"
  type    = "A"
  ttl     = 60
  
  set_identifier = "api-instance-1"
  
  multivalue_answer_routing_policy = true
  
  records = [aws_instance.api_1.public_ip]
  
  health_check_id = aws_route53_health_check.instance_1.id
}

resource "aws_route53_record" "api_multivalue_2" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "api.example.com"
  type    = "A"
  ttl     = 60
  
  set_identifier = "api-instance-2"
  
  multivalue_answer_routing_policy = true
  
  records = [aws_instance.api_2.public_ip]
  
  health_check_id = aws_route53_health_check.instance_2.id
}

Útil para:

  • ✅ Distribución simple sin ALB
  • ✅ Reducir costes (sin balanceador)
  • ✅ Servicios internos de baja criticidad

NO usar para:

  • ❌ Producción crítica
  • ❌ Cuando necesitas control real de tráfico
  • ❌ Sticky sessions

Health checks: simples, pero estratégicos

Los health checks de Route 53 no sustituyen a Prometheus ni a Datadog, y no están pensados para eso.

Su función es otra: decidir si un endpoint debe recibir tráfico o no.

Tipos de health checks:

1. Endpoint health checks:
   - HTTP/HTTPS/TCP
   - IP pública o DNS name
   - Request interval: 10s o 30s
   - String matching (opcional)
   - Latency measurement
   
2. Calculated health checks:
   - Combina múltiples checks
   - Lógica AND/OR/NOT
   - Útil para dependencias
   
3. CloudWatch alarm health checks:
   - Basado en métricas
   - Integración profunda con AWS
   - Custom metrics soportados

Ejemplo avanzado - Health check que realmente verifica salud:

# Terraform: Health check realista

# Health check básico (solo conectividad)
resource "aws_route53_health_check" "basic" {
  type              = "HTTPS"
  resource_path     = "/ping"
  fqdn              = "api.example.com"
  port              = 443
  request_interval  = 30
  failure_threshold = 3
  
  tags = {
    Name = "basic-connectivity-check"
  }
}

# Health check avanzado (verifica contenido)
resource "aws_route53_health_check" "advanced" {
  type                            = "HTTPS"
  resource_path                   = "/health"
  fqdn                            = "api.example.com"
  port                            = 443
  request_interval                = 30
  failure_threshold               = 3
  measure_latency                 = true
  enable_sni                      = true
  search_string                   = "\"status\":\"healthy\""
  
  tags = {
    Name = "advanced-content-check"
  }
}

# Health check basado en CloudWatch (métricas reales)
resource "aws_cloudwatch_metric_alarm" "api_error_rate" {
  alarm_name          = "api-high-error-rate"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = 2
  metric_name         = "5XXError"
  namespace           = "AWS/ApplicationELB"
  period              = 60
  statistic           = "Average"
  threshold           = 5  # > 5% error rate
  alarm_description   = "API error rate too high"
  
  dimensions = {
    LoadBalancer = aws_lb.main.arn_suffix
  }
}

resource "aws_route53_health_check" "cloudwatch_based" {
  type                   = "CLOUDWATCH_METRIC"
  cloudwatch_alarm_name  = aws_cloudwatch_metric_alarm.api_error_rate.alarm_name
  cloudwatch_alarm_region = "us-east-1"
  insufficient_data_health_status = "Healthy"
  
  tags = {
    Name = "cloudwatch-error-rate-check"
  }
}

# Health check calculado (combina múltiples)
resource "aws_route53_health_check" "calculated" {
  type                   = "CALCULATED"
  child_health_threshold = 2  # Al menos 2 de 3 deben estar sanos
  child_healthchecks = [
    aws_route53_health_check.basic.id,
    aws_route53_health_check.advanced.id,
    aws_route53_health_check.cloudwatch_based.id
  ]
  
  tags = {
    Name = "combined-health-check"
  }
}

Endpoint que responde vs Endpoint que funciona:

Escenario común (MAL):
  Health check: GET /ping
  Respuesta: 200 OK
  
  Pero la aplicación:
    ❌ Database connection pool agotado
    ❌ Requests reales fallan con 500
    ❌ Latencia p99 = 10 segundos
  
  Resultado:
    Health check: ✅ HEALTHY
    Usuarios: 😡 errores constantes
    Route 53: sigue enviando tráfico

Escenario correcto (BIEN):
  Health check: GET /health/deep
  Endpoint verifica:
    ✅ Database connectivity
    ✅ Redis cache
    ✅ S3 access
    ✅ Downstream APIs
    ✅ Memory/CPU usage
  
  Respuesta: 200 solo si TODO está OK
  
  Resultado:
    Health check refleja salud real
    Failover cuando corresponde
    Experiencia de usuario protegida

Un endpoint que responde pero devuelve errores 500 puede estar “vivo” para un monitor, pero no debería recibir usuarios.

Diseñar bien estos checks marca la diferencia entre un failover útil y uno inútil.

Route 53 como capa de arquitectura (no como configuración)

Cuando entiendes Route 53, empiezas a usarlo como pieza de diseño, no como formulario de AWS.

Ejemplos muy reales:

1. Separar entornos sin duplicar infraestructura:

# Terraform: Entornos con hosted zones

# Zona principal corporativa
resource "aws_route53_zone" "corporate" {
  name = "example.com"
  
  tags = {
    Environment = "shared"
    ManagedBy   = "platform-team"
  }
}

# Subzona Development
resource "aws_route53_zone" "development" {
  name = "dev.example.com"
  
  tags = {
    Environment = "development"
  }
}

resource "aws_route53_record" "dev_ns" {
  zone_id = aws_route53_zone.corporate.zone_id
  name    = "dev.example.com"
  type    = "NS"
  ttl     = 300
  records = aws_route53_zone.development.name_servers
}

# Subzona Staging
resource "aws_route53_zone" "staging" {
  name = "staging.example.com"
  
  tags = {
    Environment = "staging"
  }
}

resource "aws_route53_record" "staging_ns" {
  zone_id = aws_route53_zone.corporate.zone_id
  name    = "staging.example.com"
  type    = "NS"
  ttl     = 300
  records = aws_route53_zone.staging.name_servers
}

# Zona Production (separada por seguridad)
resource "aws_route53_zone" "production" {
  name = "prod.example.com"
  
  tags = {
    Environment = "production"
    Critical    = "true"
  }
}

Beneficios:

  • ✅ Aislamiento total entre entornos
  • ✅ Permisos IAM granulares por zona
  • ✅ Diferentes equipos gestionan diferentes zonas
  • ✅ Blast radius contenido

2. Migración entre regiones sin tocar aplicaciones:

Escenario: migrar de us-east-1 a eu-west-1

Paso 1 - Estado inicial:
  api.example.com → us-east-1 (100%)
  
Paso 2 - Desplegar en nueva región:
  eu-west-1: infraestructura lista
  Datos replicados
  Sin tráfico aún
  
Paso 3 - Weighted routing (canary):
  api.example.com:
    - us-east-1: 95%
    - eu-west-1: 5%
  Monitoreo intensivo
  
Paso 4 - Incrementar gradualmente:
  Día 1: 95% / 5%
  Día 2: 80% / 20%
  Día 3: 50% / 50%
  Día 4: 20% / 80%
  Día 5: 0% / 100%
  
Paso 5 - Cambio a latency-based:
  Ambas regiones activas
  Route 53 decide por latencia
  
Paso 6 - Decomisionar us-east-1:
  Solo cuando estés seguro
  Rollback siempre posible

Tiempo total: 1-2 semanas
Downtime: 0 segundos
Cambios en código: 0 líneas

3. Aislar incidentes graves redirigiendo tráfico:

Incident: RDS primary en us-east-1 corrupto

Opción A (sin Route 53):
  1. Fix database (horas/días)
  2. Downtime completo
  3. Revenue loss
  4. Reputation damage
  
Opción B (con Route 53 + multi-región):
  1. Identificar incidente (minutos)
  2. Cambiar weight: us-east-1 = 0%, eu-west-1 = 100%
  3. Terraform apply
  4. Tráfico redirigido (60-90 segundos)
  5. Arreglar us-east-1 sin presión
  6. Revertir cuando esté listo
  
Downtime: <2 minutos
Revenue loss: mínimo
Reputation: "tuvimos un problema breve, ya resuelto"

Todo esto ocurre antes de que el tráfico llegue a tu cloud. Y eso, para un perfil Cloud Engineer o SRE, es una ventaja enorme.

Private Hosted Zones: el DNS interno que nadie usa (pero debería)

Route 53 no solo es para Internet. También gestiona DNS interno dentro de tu VPC.

Problema clásico sin Private Hosted Zones:

Arquitectura típica:
  - EC2 instances se comunican por IP privada
  - RDS endpoint: db-prod-cluster.xxxx.us-east-1.rds.amazonaws.com
  - ElastiCache: redis-prod.xxxx.cache.amazonaws.com
  
Problemas:
  ❌ IPs hardcodeadas en config files
  ❌ Endpoints AWS largos e incómodos
  ❌ Cambios requieren redeploy
  ❌ No hay consistencia entre entornos
  ❌ Service discovery manual

Solución con Private Hosted Zone:

# Terraform: Private Hosted Zone

resource "aws_route53_zone" "internal" {
  name = "internal.example.com"
  
  vpc {
    vpc_id = aws_vpc.main.id
  }
  
  tags = {
    Name        = "internal-dns"
    Environment = "production"
    Visibility  = "private"
  }
}

# Database endpoint legible
resource "aws_route53_record" "database" {
  zone_id = aws_route53_zone.internal.zone_id
  name    = "db.internal.example.com"
  type    = "CNAME"
  ttl     = 300
  records = [aws_db_instance.main.address]
}

# Cache endpoint
resource "aws_route53_record" "cache" {
  zone_id = aws_route53_zone.internal.zone_id
  name    = "cache.internal.example.com"
  type    = "CNAME"
  ttl     = 300
  records = [aws_elasticache_cluster.main.cache_nodes[0].address]
}

# API interna entre servicios
resource "aws_route53_record" "api_internal" {
  zone_id = aws_route53_zone.internal.zone_id
  name    = "api.internal.example.com"
  type    = "A"
  
  alias {
    name                   = aws_lb.internal.dns_name
    zone_id                = aws_lb.internal.zone_id
    evaluate_target_health = true
  }
}

# Service discovery pattern
resource "aws_route53_record" "service_discovery" {
  zone_id = aws_route53_zone.internal.zone_id
  name    = "service-a.internal.example.com"
  type    = "A"
  ttl     = 60
  records = aws_instance.service_a[*].private_ip
}

Ventajas:

Configuración de aplicación:
  Antes:
    DB_HOST=db-prod-cluster.cg8h3j2k1l9m.us-east-1.rds.amazonaws.com
    CACHE_HOST=redis-prod.abc123.0001.use1.cache.amazonaws.com
    
  Después:
    DB_HOST=db.internal.example.com
    CACHE_HOST=cache.internal.example.com
    
Beneficios:
  ✅ Legible y memorable
  ✅ Consistente entre entornos
  ✅ Cambios sin redeploy (solo DNS)
  ✅ Multi-VPC con VPC peering
  ✅ Logging y auditoría

El DNS también es responsabilidad del equipo

Uno de los problemas más habituales es que nadie “posee” el DNS.

Está ahí, funciona… hasta que alguien cambia algo sin entender TTLs, caché o propagación.

Anti-patterns comunes:

❌ DNS gestionado manualmente en consola:
  - Cambios sin version control
  - No hay audit trail
  - Imposible hacer rollback
  - Nadie sabe qué hace cada registro
  
❌ TTLs mal configurados:
  - TTL = 86400 (24 horas) en producción
  - Cambio urgente requiere esperar 24h
  - Incident extended innecesariamente
  
❌ Sin health checks:
  - Failover manual
  - Depende de humanos despiertos
  - Downtime prolongado
  
❌ Sin monitoreo:
  - No sabes cuándo un health check falla
  - Descubres problemas por usuarios
  - Mean Time To Detection altísimo
  
❌ Mixing DNS providers:
  - Route 53 + Cloudflare + GoDaddy
  - Nadie sabe dónde está cada registro
  - Debugging imposible

Best practices para equipos maduros:

✅ DNS como código (IaC):
  - Terraform/CloudFormation
  - Version control (Git)
  - Peer review obligatorio
  - CI/CD pipeline
  - Rollback fácil

✅ TTLs estratégicos:
  Producción:
    - Normal: 300s (5 minutos)
    - Pre-cambio: 60s (1 minuto)
    - Post-cambio: volver a 300s
  
  Desarrollo:
    - 60s siempre (cambios frecuentes)

✅ Health checks + alarmas:
  - CloudWatch alarms
  - SNS → PagerDuty/Slack
  - Runbooks documentados
  - On-call informado

✅ Documentación viva:
  - Diagrama de arquitectura DNS
  - Matriz de routing policies
  - Procedimientos de cambio
  - Disaster recovery plan

✅ Ownership claro:
  - RACI matrix
  - Platform team como owner
  - Application teams como consumers
  - Proceso de request/approval

Ejemplo: proceso de cambio de DNS maduro:

Proceso de cambio DNS en equipo maduro:

1. Request:
   - Ticket en Jira/ServiceNow
   - Justificación de negocio
   - Impacto estimado
   - Rollback plan

2. Review:
   - Tech Lead revisa
   - Platform Team aprueba
   - Security revisa (si aplica)

3. Pre-change:
   - Reducir TTL a 60s
   - Esperar 2x TTL viejo (10 min)
   - Notificar stakeholders

4. Implementation:
   - Pull request en Terraform
   - Peer review
   - terraform plan (preview)
   - terraform apply (producción)
   - Merge a main

5. Validation:
   - dig/nslookup desde múltiples ubicaciones
   - Health checks monitoreados
   - Métricas de negocio
   - User feedback

6. Post-change:
   - TTL de vuelta a 300s
   - Documentación actualizada
   - Postmortem (si hubo issues)
   - Lessons learned

Tiempo total: 30-60 minutos
Downtime: 0 segundos
Risk: minimizado

Route 53 facilita mucho la gestión, pero no elimina la necesidad de criterio técnico.

Un buen equipo trata el DNS como:

  1. Código (infraestructura versionada)
  2. Parte del diseño de resiliencia
  3. Elemento crítico en incidentes

Casos de uso avanzados

1. Blue/Green Deployment a nivel DNS:

# Terraform: Blue/Green con Route 53

# Blue environment (actual producción)
resource "aws_route53_record" "blue_green_blue" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "app.example.com"
  type    = "A"
  
  set_identifier = "blue-environment"
  
  weighted_routing_policy {
    weight = 100  # 100% tráfico a blue
  }
  
  alias {
    name                   = aws_lb.blue.dns_name
    zone_id                = aws_lb.blue.zone_id
    evaluate_target_health = true
  }
}

# Green environment (nueva versión)
resource "aws_route53_record" "blue_green_green" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "app.example.com"
  type    = "A"
  
  set_identifier = "green-environment"
  
  weighted_routing_policy {
    weight = 0  # 0% tráfico a green (standby)
  }
  
  alias {
    name                   = aws_lb.green.dns_name
    zone_id                = aws_lb.green.zone_id
    evaluate_target_health = true
  }
}

# Cutover: cambiar pesos
# Blue: 100 → 0
# Green: 0 → 100
# Rollback: invertir pesos

2. Disaster Recovery automatizado:

# Terraform: DR multi-región

# Primary region (activa)
resource "aws_route53_record" "dr_primary" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "app.example.com"
  type    = "A"
  
  set_identifier = "primary-us-east-1"
  
  failover_routing_policy {
    type = "PRIMARY"
  }
  
  alias {
    name                   = aws_lb.primary.dns_name
    zone_id                = aws_lb.primary.zone_id
    evaluate_target_health = true
  }
  
  health_check_id = aws_route53_health_check.primary_comprehensive.id
}

# DR region (standby)
resource "aws_route53_record" "dr_secondary" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "app.example.com"
  type    = "A"
  
  set_identifier = "dr-eu-west-1"
  
  failover_routing_policy {
    type = "SECONDARY"
  }
  
  alias {
    name                   = aws_lb.dr.dns_name
    zone_id                = aws_lb.dr.zone_id
    evaluate_target_health = true
  }
}

# Health check complejo
resource "aws_route53_health_check" "primary_comprehensive" {
  type = "CALCULATED"
  child_health_threshold = 3  # 3 de 4 deben estar OK
  
  child_healthchecks = [
    aws_route53_health_check.primary_alb.id,
    aws_route53_health_check.primary_rds.id,
    aws_route53_health_check.primary_api.id,
    aws_route53_health_check.primary_metrics.id
  ]
}

3. Geo-proximity routing (distancia física):

# Terraform: Geo-proximity routing

resource "aws_route53_record" "geo_prox_us_east" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "cdn.example.com"
  type    = "A"
  
  set_identifier = "us-east-datacenter"
  
  geoproximity_routing_policy {
    aws_region = "us-east-1"
    bias       = 0  # neutral
  }
  
  alias {
    name                   = aws_cloudfront_distribution.us_east.domain_name
    zone_id                = aws_cloudfront_distribution.us_east.hosted_zone_id
    evaluate_target_health = false
  }
}

resource "aws_route53_record" "geo_prox_eu_west" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "cdn.example.com"
  type    = "A"
  
  set_identifier = "eu-west-datacenter"
  
  geoproximity_routing_policy {
    aws_region = "eu-west-1"
    bias       = 10  # atrae más tráfico (10% más de "área")
  }
  
  alias {
    name                   = aws_cloudfront_distribution.eu_west.domain_name
    zone_id                = aws_cloudfront_distribution.eu_west.hosted_zone_id
    evaluate_target_health = false
  }
}

Monitoreo y observabilidad

CloudWatch Metrics para Route 53:

# Terraform: Monitoring completo

# Dashboard CloudWatch
resource "aws_cloudwatch_dashboard" "route53" {
  dashboard_name = "route53-health-monitoring"
  
  dashboard_body = jsonencode({
    widgets = [
      {
        type = "metric"
        properties = {
          metrics = [
            ["AWS/Route53", "HealthCheckStatus", { stat = "Average" }],
            [".", "HealthCheckPercentageHealthy", { stat = "Average" }],
            [".", "ConnectionTime", { stat = "Average" }]
          ]
          period = 60
          stat   = "Average"
          region = "us-east-1"
          title  = "Health Check Status"
        }
      }
    ]
  })
}

# Alarm: health check unhealthy
resource "aws_cloudwatch_metric_alarm" "health_check_failed" {
  alarm_name          = "route53-health-check-failed"
  comparison_operator = "LessThanThreshold"
  evaluation_periods  = 2
  metric_name         = "HealthCheckStatus"
  namespace           = "AWS/Route53"
  period              = 60
  statistic           = "Minimum"
  threshold           = 1
  alarm_description   = "Route 53 health check failing"
  alarm_actions       = [aws_sns_topic.critical.arn]
  
  dimensions = {
    HealthCheckId = aws_route53_health_check.primary.id
  }
}

# Query Logging (CloudWatch Logs)
resource "aws_route53_query_log" "main" {
  cloudwatch_log_group_arn = aws_cloudwatch_log_group.route53_queries.arn
  zone_id                  = aws_route53_zone.main.zone_id
}

resource "aws_cloudwatch_log_group" "route53_queries" {
  name              = "/aws/route53/${aws_route53_zone.main.name}"
  retention_in_days = 7
  
  tags = {
    Purpose = "DNS query logging for debugging"
  }
}

Logs de queries DNS (debugging):

// Ejemplo de log de query Route 53
{
  "version": "1.100000",
  "account_id": "123456789012",
  "region": "us-east-1",
  "vpc_id": "vpc-1a2b3c4d",
  "query_timestamp": "2026-01-04T10:15:30.123Z",
  "query_name": "api.example.com.",
  "query_type": "A",
  "query_class": "IN",
  "rcode": "NOERROR",
  "answers": [
    {
      "Rdata": "192.0.2.1",
      "Type": "A",
      "Class": "IN"
    }
  ],
  "srcaddr": "10.0.1.25",
  "srcport": "54321",
  "transport": "UDP",
  "srcids": {
    "instance": "i-0abcd1234efgh5678"
  }
}

Costes: Route 53 no es caro (pero tampoco gratis)

Pricing actual (2025):

Hosted Zones:
  - $0.50/mes por hosted zone
  - Primeras 25 zonas
  - Descuentos por volumen después
  
Queries:
  - Primeras 1B queries/mes: $0.40 por millón
  - Siguiente tramo: $0.20 por millón
  - Alias queries a recursos AWS: GRATIS
  
Health Checks:
  - Basic (Fast interval): $0.50/mes por check
  - HTTPS con string matching: $0.75/mes
  - Calculated: $1.00/mes
  
Query Logging:
  - CloudWatch Logs estándar
  - $0.50 por GB ingested
  
Traffic Flow (visual policy editor):
  - $50/mes por policy record
  - (Raramente usado, IaC es mejor)

Ejemplo realista:
  3 hosted zones: $1.50/mes
  100M queries: $40/mes
  10 health checks: $5-7.50/mes
  Query logging: $5-10/mes
  
  Total: ~$52-59/mes para app mediana

Es barato comparado con el coste de downtime.

Conclusión: el DNS como decisión arquitectónica

Route 53 no es “el DNS de AWS”. Es una capa de control de tráfico que vive fuera de tu infraestructura y puede salvarte cuando todo lo demás falla.

Evolución de madurez:

Nivel 1 - DNS básico:
  - Simple routing
  - Registros manuales
  - Sin health checks
  - Sin monitoreo
  Estado: funciona hasta que no funciona

Nivel 2 - DNS gestionado:
  - Terraform/IaC
  - Alias records a ALB
  - Health checks básicos
  - CloudWatch alarms
  Estado: profesional pero reactivo

Nivel 3 - DNS como arquitectura:
  - Routing policies estratégicas
  - Failover automático
  - Multi-región activo-activo
  - Disaster recovery probado
  - Canary deployments
  - Private hosted zones
  Estado: resiliente y proactivo

Nivel 4 - DNS como ventaja competitiva:
  - Latency optimization global
  - Geo-compliance automatizado
  - Cost optimization por región
  - A/B testing a nivel DNS
  - Incident mitigation en <60s
  Estado: excelencia operacional

La realidad que nadie te cuenta:

El DNS es la primera impresión que da tu infraestructura. Si falla, nada más importa.

Un DNS bien diseñado:

  • Reduce MTTR (Mean Time To Recovery)
  • Permite experimentos seguros (canary, blue/green)
  • Actúa como última línea de defensa (failover)
  • Simplifica compliance (geolocation)
  • Mejora experiencia de usuario (latency-based)

Route 53 no es caro. El downtime sí.