DNS & Load Balancing

DNS architecture, record management, routing policies, and multi-layer load balancing strategies on AWS and GCP — from Route 53 health checks to GCP NEGs and Kubernetes Gateway API.

DNS Fundamentals

DNS Resolution Flow

# Full recursive resolution for api.example.com

1. Client queries local stub resolver (OS)
   → checks /etc/hosts, then local DNS cache

2. Local stub resolver queries Recursive Resolver (e.g. 8.8.8.8 or VPC DNS at .2)

3. Recursive resolver (if not cached):
   → queries Root nameservers (a.root-servers.net … m.root-servers.net, 13 anycast clusters)
   → Root returns referral: "ask .com TLD servers"

4. → queries .com TLD nameservers (managed by Verisign)
   → TLD returns: "NS records for example.com are at ns-xxx.awsdns-xx.com"

5. → queries authoritative NS for example.com (Route 53 / Cloud DNS)
   → Returns: api.example.com A 203.0.113.45 TTL 300

6. Recursive resolver caches result (for TTL duration), returns to client

# Negative caching: NXDOMAIN responses are also cached for the SOA MINIMUM TTL
# Verify with:
dig api.example.com +trace
dig @8.8.8.8 api.example.com +stats   # shows query time and when answer was cached

DNS Record Types

RecordPurposeExample
AIPv4 address mappingapi.example.com. 300 IN A 203.0.113.45
AAAAIPv6 address mappingapi.example.com. 300 IN AAAA 2001:db8::1
CNAMEAlias to another hostname (cannot coexist with other records at same name)www.example.com. CNAME example.com.
MXMail exchange with priorityexample.com. MX 10 mail.example.com.
TXTText (SPF, DKIM, domain verification)example.com. TXT "v=spf1 include:_spf.google.com ~all"
NSAuthoritative nameserver delegationexample.com. NS ns-1234.awsdns-45.com.
SOAZone authority, serial, refresh, retry, expire, minimum TTLOne per zone, defines zone properties
SRVService location (host + port + priority)_https._tcp.example.com. SRV 10 5 443 api.example.com.
PTRReverse DNS lookup (IP → hostname)45.113.0.203.in-addr.arpa. PTR api.example.com.
CAACertificate Authority Authorizationexample.com. CAA 0 issue "letsencrypt.org"
# Common DNS debugging commands
dig api.example.com A                    # A record lookup
dig api.example.com AAAA                 # IPv6
dig api.example.com MX                   # mail records
dig api.example.com TXT                  # TXT records (SPF, DKIM)
dig api.example.com ANY                  # all record types (may be blocked)
dig -x 203.0.113.45                      # reverse PTR lookup
dig api.example.com +short               # IP only output
dig @ns-1234.awsdns-45.com api.example.com  # query specific nameserver
dig api.example.com +dnssec              # check DNSSEC signatures

TTL Strategy

TTL Decision Framework

  • High TTL (3600–86400s): Stable records (MX, NS, SPF, static IPs). Reduces DNS query load, lower Route 53/Cloud DNS costs.
  • Medium TTL (300s): Good default for A/AAAA records on dynamic infrastructure.
  • Low TTL (60s or less): Active traffic shifting (blue/green, failover). Lower 24–48h before planned changes to drain caches.
  • Negative TTL: Controlled by SOA MINIMUM field. Keep low (300s) to recover quickly from NXDOMAIN errors.

AWS Route 53

Hosted Zones

# Public Hosted Zone: resolves from the internet
# Private Hosted Zone: resolves only within associated VPCs

# Create private hosted zone (Terraform)
resource "aws_route53_zone" "private" {
  name = "internal.example.com"
  vpc {
    vpc_id = aws_vpc.prod.id
  }
  # Can associate multiple VPCs (cross-account with aws_route53_vpc_association_authorization)
}

# Associate private zone with additional VPCs
resource "aws_route53_zone_association" "staging" {
  zone_id = aws_route53_zone.private.zone_id
  vpc_id  = aws_vpc.staging.id
}

# Split-horizon DNS: same domain name, different records for internal vs external
# public zone:  api.example.com → 203.0.113.45 (public ALB)
# private zone: api.example.com → 10.0.1.10 (internal service)

Routing Policies

PolicyUse CaseHow it works
SimpleSingle resource, no health checksReturns one or multiple values (random selection for multiple)
WeightedCanary deployments, A/B testingDistribute traffic by percentage weight (e.g. 90/10)
Latency-basedMulti-region active-activeRoutes to region with lowest measured latency for client
FailoverActive-passive DRPrimary record served; if health check fails, switch to secondary
GeolocationData residency, content localizationRoutes based on client continent/country/state
GeoproximityFine-grained geographic routingRoutes based on client-to-resource geographic distance with bias
Multivalue AnswerSimple load distribution with healthReturns up to 8 healthy IPs; client picks randomly
IP-basedRoute specific ISPs/prefixesRoutes based on client IP CIDR ranges you define
# Route 53 — Failover routing with health check (Terraform)
resource "aws_route53_health_check" "primary" {
  fqdn              = "api-us-east-1.example.com"
  port              = 443
  type              = "HTTPS"
  resource_path     = "/health"
  failure_threshold = 3
  request_interval  = 30   # seconds
  tags = { Name = "primary-health-check" }
}

resource "aws_route53_record" "api_primary" {
  zone_id        = aws_route53_zone.public.zone_id
  name           = "api.example.com"
  type           = "A"
  set_identifier = "primary"
  failover_routing_policy { type = "PRIMARY" }
  health_check_id = aws_route53_health_check.primary.id
  alias {
    name                   = aws_lb.primary.dns_name
    zone_id                = aws_lb.primary.zone_id
    evaluate_target_health = true
  }
}

resource "aws_route53_record" "api_secondary" {
  zone_id        = aws_route53_zone.public.zone_id
  name           = "api.example.com"
  type           = "A"
  set_identifier = "secondary"
  failover_routing_policy { type = "SECONDARY" }
  alias {
    name                   = aws_lb.secondary.dns_name   # us-west-2 ALB
    zone_id                = aws_lb.secondary.zone_id
    evaluate_target_health = true
  }
}

# Weighted routing for canary deployment (10% to new version)
resource "aws_route53_record" "api_v2_canary" {
  zone_id        = aws_route53_zone.public.zone_id
  name           = "api.example.com"
  type           = "A"
  set_identifier = "canary-v2"
  weighted_routing_policy { weight = 10 }
  alias {
    name    = aws_lb.canary.dns_name
    zone_id = aws_lb.canary.zone_id
    evaluate_target_health = true
  }
}

GCP Cloud DNS

# Create public managed zone
gcloud dns managed-zones create example-com \
  --dns-name="example.com." \
  --description="Production public zone" \
  --visibility=public \
  --dnssec-state=on

# Create private managed zone
gcloud dns managed-zones create internal-example \
  --dns-name="internal.example.com." \
  --visibility=private \
  --networks=prod-vpc

# Add records
gcloud dns record-sets create api.example.com. \
  --zone=example-com \
  --type=A \
  --ttl=300 \
  --rrdatas=203.0.113.45

gcloud dns record-sets create api.example.com. \
  --zone=example-com \
  --type=AAAA \
  --ttl=300 \
  --rrdatas=2001:db8::1

# DNS Server Policy — override resolver for VPC (e.g. forward to on-prem DNS)
gcloud dns policies create on-prem-forward \
  --networks=prod-vpc \
  --alternative-name-servers=192.168.1.53,192.168.1.54 \
  --description="Forward to on-prem DNS"

# DNS Response Policy (local override for specific names — split-horizon)
gcloud dns response-policies create corp-overrides \
  --networks=prod-vpc \
  --description="Internal overrides"

gcloud dns response-policies rules create api-override \
  --response-policy=corp-overrides \
  --dns-name="api.example.com." \
  --local-data=name="api.example.com.",type="A",ttl=60,rrdatas="10.0.1.10"

Load Balancing Fundamentals

Load Balancing Algorithms

AlgorithmHow it worksBest for
Round RobinRequests sent to backends sequentially in rotationHomogeneous backends, stateless services
Weighted Round RobinRound robin with weight factors (higher weight = more requests)Canary deployments, heterogeneous instance sizes
Least ConnectionsNew request sent to backend with fewest active connectionsLong-lived connections (WebSocket, gRPC streaming)
IP HashClient IP hashed to consistently pick same backendStateful apps needing session affinity (sticky sessions)
Random with Two ChoicesPick 2 backends randomly, send to one with fewer connectionsLow-overhead approximation of least connections at scale
Least Response TimeCombines least connections + fastest response timeHeterogeneous backends with variable latency

Layer 4 vs Layer 7 Load Balancing

FeatureLayer 4 (Transport)Layer 7 (Application)
Operates onTCP/UDP connectionsHTTP/HTTPS requests
Routing decisionsSource/dest IP + portURL path, hostname, headers, cookies
TLS terminationPass-through (backend terminates) or terminateAlways terminates TLS, then re-encrypts or HTTP to backend
PerformanceUltra-low latency, high throughputSlightly higher latency (HTTP parsing overhead)
AWS equivalentNLBALB
GCP equivalentNetwork LB / TCP ProxyGlobal HTTP(S) LB
Source IPPreserved (with proxy protocol or direct)Via X-Forwarded-For header

AWS Load Balancers

Application Load Balancer (ALB)

ALB Architecture

ALB operates at L7. Listeners define protocol/port. Rules evaluate conditions (path, host, header, query string, source IP, method) in priority order and forward to target groups. Target groups contain EC2 instances, ECS tasks, IPs, or Lambda functions.

# ALB Terraform — multi-rule listener

resource "aws_lb" "prod" {
  name               = "prod-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.alb.id]
  subnets            = aws_subnet.public[*].id
  enable_deletion_protection = true

  access_logs {
    bucket  = aws_s3_bucket.alb_logs.bucket
    prefix  = "prod-alb"
    enabled = true
  }
}

resource "aws_lb_listener" "https" {
  load_balancer_arn = aws_lb.prod.arn
  port              = 443
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
  certificate_arn   = aws_acm_certificate.wildcard.arn

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.api_v1.arn
  }
}

# HTTP → HTTPS redirect
resource "aws_lb_listener" "http" {
  load_balancer_arn = aws_lb.prod.arn
  port              = 80
  protocol          = "HTTP"
  default_action {
    type = "redirect"
    redirect {
      port        = "443"
      protocol    = "HTTPS"
      status_code = "HTTP_301"
    }
  }
}

# Listener rules — path-based routing
resource "aws_lb_listener_rule" "api_v2" {
  listener_arn = aws_lb_listener.https.arn
  priority     = 100

  condition {
    path_pattern { values = ["/api/v2/*"] }
  }
  action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.api_v2.arn
  }
}

resource "aws_lb_listener_rule" "admin" {
  listener_arn = aws_lb_listener.https.arn
  priority     = 200

  condition {
    host_header { values = ["admin.example.com"] }
  }
  condition {
    source_ip { values = ["10.100.0.0/16"] }   # restrict to corporate network
  }
  action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.admin.arn
  }
}

resource "aws_lb_listener_rule" "maintenance" {
  listener_arn = aws_lb_listener.https.arn
  priority     = 999
  condition {
    http_header {
      http_header_name = "X-Maintenance-Bypass"
      values           = ["secret-bypass-token"]
    }
  }
  action { type = "forward"; target_group_arn = aws_lb_target_group.api_v1.arn }
}

# Target group with health check
resource "aws_lb_target_group" "api_v1" {
  name        = "api-v1"
  port        = 8080
  protocol    = "HTTP"
  vpc_id      = aws_vpc.prod.id
  target_type = "ip"   # for ECS Fargate / direct IP

  health_check {
    path                = "/health"
    protocol            = "HTTP"
    matcher             = "200"
    interval            = 30
    timeout             = 5
    healthy_threshold   = 2
    unhealthy_threshold = 3
  }

  stickiness {
    type            = "lb_cookie"
    cookie_duration = 86400
    enabled         = false   # enable only if stateful
  }
}

Network Load Balancer (NLB)

# NLB key characteristics:
# - Static Elastic IPs per AZ (whitelistable)
# - Preserves source IP (unlike ALB which uses X-Forwarded-For)
# - TLS termination at NLB, or TCP pass-through
# - Ultra-low latency (~100 microseconds vs ALB ~1ms)
# - Supports UDP (for DNS, syslog, game servers, QUIC)
# - PrivateLink endpoint service requires NLB

resource "aws_lb" "nlb" {
  name               = "prod-nlb"
  internal           = false
  load_balancer_type = "network"
  enable_cross_zone_load_balancing = true

  subnet_mapping {
    subnet_id     = aws_subnet.public[0].id
    allocation_id = aws_eip.nlb[0].id   # static Elastic IP per AZ
  }
  subnet_mapping {
    subnet_id     = aws_subnet.public[1].id
    allocation_id = aws_eip.nlb[1].id
  }
}

resource "aws_lb_listener" "nlb_tls" {
  load_balancer_arn = aws_lb.nlb.arn
  port              = 443
  protocol          = "TLS"
  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
  certificate_arn   = aws_acm_certificate.wildcard.arn

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.nlb_tcp.arn
  }
}

resource "aws_lb_listener" "nlb_udp" {
  load_balancer_arn = aws_lb.nlb.arn
  port              = 53
  protocol          = "UDP"
  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.dns.arn
  }
}

Gateway Load Balancer (GWLB)

Inline Traffic Inspection

GWLB uses Geneve encapsulation (port 6081) to transparently insert security appliances (IDS/IPS, firewall, DLP) into traffic path. Traffic flows: Internet → GWLB → Appliance (inspect) → GWLB → Destination. Appliance is either a commercial NGFW (Palo Alto, Check Point) or custom.

GCP Load Balancers

Global HTTP(S) Load Balancer

# GCP Global HTTPS LB: anycast IP, Google's edge PoPs worldwide
# Terminates SSL at PoP (closest to user), then proxies to backend over Google backbone

resource "google_compute_global_address" "lb_ip" {
  name = "prod-lb-ip"
}

resource "google_compute_managed_ssl_certificate" "default" {
  name = "prod-cert"
  managed {
    domains = ["api.example.com", "www.example.com"]
  }
}

resource "google_compute_backend_service" "api" {
  name                  = "api-backend"
  protocol              = "HTTP"
  port_name             = "http"
  load_balancing_scheme = "EXTERNAL_MANAGED"
  timeout_sec           = 30

  backend {
    group           = google_compute_region_instance_group_manager.api.instance_group
    balancing_mode  = "UTILIZATION"
    max_utilization = 0.8
  }

  health_checks = [google_compute_health_check.api.id]

  # Cloud CDN
  enable_cdn = true
  cdn_policy {
    cache_mode = "CACHE_ALL_STATIC"
    default_ttl = 3600
  }

  # Cloud Armor security policy
  security_policy = google_compute_security_policy.waf.id
}

# URL Map — path-based routing
resource "google_compute_url_map" "prod" {
  name            = "prod-url-map"
  default_service = google_compute_backend_service.api.id

  host_rule {
    hosts        = ["api.example.com"]
    path_matcher = "api-paths"
  }

  path_matcher {
    name            = "api-paths"
    default_service = google_compute_backend_service.api.id

    path_rule {
      paths   = ["/v2/*"]
      service = google_compute_backend_service.api_v2.id
    }
    path_rule {
      paths   = ["/static/*"]
      service = google_compute_backend_service.cdn.id
    }
  }
}

resource "google_compute_target_https_proxy" "prod" {
  name             = "prod-https-proxy"
  url_map          = google_compute_url_map.prod.id
  ssl_certificates = [google_compute_managed_ssl_certificate.default.id]
  ssl_policy       = google_compute_ssl_policy.modern.id
}

resource "google_compute_global_forwarding_rule" "https" {
  name                  = "prod-https-rule"
  target                = google_compute_target_https_proxy.prod.id
  port_range            = "443"
  ip_address            = google_compute_global_address.lb_ip.address
  load_balancing_scheme = "EXTERNAL_MANAGED"
}

Network Endpoint Groups (NEGs)

NEG TypeUse CaseBackend
Zonal (GCE)VM instances or containers in a zoneGCE VMs, GKE pods (directly)
Internet NEGExternal endpoint (FQDN or IP)Third-party APIs, on-prem, partner services
Serverless NEGCloud Run, App Engine, Cloud FunctionsServerless workloads behind GCP LB
Private Service Connect NEGConsume PSC services via LBPublished services in other VPCs
# Serverless NEG for Cloud Run (expose via Global HTTPS LB)
resource "google_compute_region_network_endpoint_group" "cloudrun" {
  name                  = "cloudrun-neg"
  network_endpoint_type = "SERVERLESS"
  region                = "us-central1"
  cloud_run {
    service = google_cloud_run_service.api.name
  }
}

resource "google_compute_backend_service" "cloudrun" {
  name                  = "cloudrun-backend"
  load_balancing_scheme = "EXTERNAL_MANAGED"
  protocol              = "HTTPS"
  backend {
    group = google_compute_region_network_endpoint_group.cloudrun.id
  }
}

# Internet NEG — route to external endpoint through GCP LB (for Cloud Armor protection)
resource "google_compute_global_network_endpoint_group" "external_api" {
  name                  = "partner-api-neg"
  network_endpoint_type = "INTERNET_FQDN_PORT"
  default_port          = 443
}

resource "google_compute_global_network_endpoint" "partner" {
  global_network_endpoint_group = google_compute_global_network_endpoint_group.external_api.name
  fqdn                          = "api.partner.com"
  port                          = 443
}

Kubernetes Ingress vs Gateway API

FeatureIngressGateway API
MaturityStable, widely adoptedGA since Kubernetes 1.28
Role separationSingle object (admin + developer mixed)GatewayClass (infra), Gateway (ops), HTTPRoute (dev)
Protocol supportHTTP/HTTPS onlyHTTP, TCP, UDP, gRPC, TLS
Cross-namespace routingLimitedFirst-class support via ReferenceGrant
Traffic splittingController-specific annotationsNative via weight in HTTPRoute
# NGINX Ingress — path and host routing
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: prod-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  ingressClassName: nginx
  tls:
  - hosts: [api.example.com]
    secretName: api-tls-cert
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /api/v1
        pathType: Prefix
        backend:
          service: { name: api-v1, port: { number: 80 } }
      - path: /api/v2
        pathType: Prefix
        backend:
          service: { name: api-v2, port: { number: 80 } }

---
# Gateway API — HTTPRoute with traffic splitting
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api-canary
spec:
  parentRefs:
  - name: prod-gateway
    namespace: infra
  hostnames: [api.example.com]
  rules:
  - matches:
    - path: { type: PathPrefix, value: /api }
    backendRefs:
    - name: api-v1
      port: 80
      weight: 90
    - name: api-v2
      port: 80
      weight: 10   # 10% canary traffic

---
# cert-manager ClusterIssuer (Let's Encrypt)
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef: { name: letsencrypt-prod }
    solvers:
    - http01:
        ingress: { class: nginx }

Health Checks

# AWS ALB health check (Terraform)
health_check {
  enabled             = true
  path                = "/health"
  protocol            = "HTTP"
  matcher             = "200-299"
  port                = "traffic-port"
  interval            = 30        # seconds between checks
  timeout             = 5         # fail if no response in 5s
  healthy_threshold   = 2         # 2 consecutive successes = healthy
  unhealthy_threshold = 3         # 3 consecutive failures = unhealthy
}
# Grace period: set deregistration_delay.timeout_seconds = 30 (drain existing connections)

# GCP health check
resource "google_compute_health_check" "api" {
  name                = "api-health-check"
  check_interval_sec  = 10
  timeout_sec         = 5
  healthy_threshold   = 2
  unhealthy_threshold = 3

  https_health_check {
    port         = 8443
    request_path = "/healthz"
  }
}

# gRPC health check (for gRPC services on GCP)
resource "google_compute_health_check" "grpc" {
  name = "grpc-health"
  grpc_health_check {
    port         = 50051
    grpc_service_name = "my.service.Health"
  }
}

# Kubernetes Probes
livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 10   # wait before first check (app startup time)
  periodSeconds: 10
  failureThreshold: 3        # restart pod after 3 failures

readinessProbe:
  httpGet:
    path: /ready
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 5
  failureThreshold: 3        # remove from LB endpoints after 3 failures
DNS & LB Best Practices Summary:
  • Lower DNS TTL 24–48h before any migration or failover test
  • Use Route 53 alias records (not CNAME) for AWS resources — no extra query, works at zone apex
  • Enable ALB access logs → S3 + Athena for query analysis
  • Set ALB idle timeout greater than application keep-alive to avoid 504s
  • Use NLB for PrivateLink services, static IPs, UDP, or TLS pass-through
  • GCP: prefer Serverless NEG + Global HTTPS LB over direct Cloud Run URLs for WAF protection
  • Enable Cloud Armor / AWS WAF on public-facing load balancers