Enrutamiento consciente de zona
El enrutamiento consciente de zona es una función que optimiza el tráfico al reconocer las Zonas de disponibilidad de Kubernetes. Reduce la latencia y los costos de transferencia de datos entre AZ al priorizar la comunicación dentro de la misma AZ.
Tabla de contenidos
- Descripción general
- Cómo funciona
- Configuración básica
- Configuración avanzada
- Configuración en AWS EKS
- Ejemplos prácticos
- Monitoreo
- Solución de problemas
Descripción general
El enrutamiento consciente de zona ofrece los siguientes beneficios:
Beneficios
- Latencia reducida: Minimice la latencia de red con la comunicación dentro de la misma AZ
- Ahorro de costos: Reduzca los costos de transferencia de datos entre AZ
- AWS: $0.01-0.02 por GB para transferencia entre AZ
- Disponibilidad mejorada: Failover automático a otras AZ durante fallos
- Optimización del rendimiento: Ancho de banda de red optimizado
Cómo funciona
Algoritmo de balanceo de carga por localidad
Jerarquía de localidad
Istio utiliza la siguiente jerarquía de localidad:
Region/Zone/SubZone
Example:
us-east-1/us-east-1a/*
us-east-1/us-east-1b/*
us-west-2/us-west-2a/*Prioridad:
- Misma zona: Misma Region, misma Zone
- Misma Region: Misma Region, Zone diferente
- Region diferente: Region diferente
Cómo funciona sin etiquetas de AZ en los Pod
Importante: Los Pod no necesitan etiquetas de AZ. Istio lee las etiquetas de topología de Node para determinar automáticamente la localidad del Pod.
Cómo funciona
Proceso paso a paso
Paso 1: Istiod recopila información de Pod
# Istiod queries Pod information via Kubernetes API
kubectl get pod <pod-name> -o json | jq '.spec.nodeName'
# Output: "ip-10-0-1-10.ec2.internal"Paso 2: consultar las etiquetas de topología del Node que ejecuta el Pod
# Query Node info using Pod's nodeName
kubectl get node ip-10-0-1-10.ec2.internal -o json | \
jq '.metadata.labels."topology.kubernetes.io/zone"'
# Output: "us-east-1a"Paso 3: generar EDS (Endpoint Discovery Service)
Istiod genera EDS con la IP del Pod y la información de localidad:
{
"cluster_name": "outbound|8080||myapp.default.svc.cluster.local",
"endpoints": [
{
"locality": {
"region": "us-east-1",
"zone": "us-east-1a"
},
"lb_endpoints": [
{
"endpoint": {
"address": {
"socket_address": {
"address": "10.0.1.10",
"port_value": 8080
}
}
}
}
]
},
{
"locality": {
"region": "us-east-1",
"zone": "us-east-1b"
},
"lb_endpoints": [
{
"endpoint": {
"address": {
"socket_address": {
"address": "10.0.2.20",
"port_value": 8080
}
}
}
}
]
}
]
}Paso 4: Envoy realiza el enrutamiento basado en localidad
Envoy compara su propia localidad con la información de EDS recibida para enrutar:
# Check Envoy's Locality (based on node it's running on)
kubectl exec <pod-name> -c istio-proxy -- \
curl -s localhost:15000/config_dump | \
jq '.configs[] | select(.["@type"] | contains("BootstrapConfigDump")) | .bootstrap.node.locality'
# Output:
# {
# "region": "us-east-1",
# "zone": "us-east-1a"
# }Método de verificación
# 1. Check which Node the Pod is running on
kubectl get pod <pod-name> -o wide
# NAME READY STATUS NODE
# myapp-abc 2/2 Running ip-10-0-1-10.ec2.internal
# 2. Check the Node's Zone label
kubectl get node ip-10-0-1-10.ec2.internal \
-o jsonpath='{.metadata.labels.topology\.kubernetes\.io/zone}'
# Output: us-east-1a
# 3. Check Endpoint Locality recognized by Envoy
istioctl proxy-config endpoints <pod-name> | grep myapp
# ENDPOINT STATUS OUTLIER CHECK CLUSTER LOCALITY
# 10.0.1.10:8080 HEALTHY OK myapp.default us-east-1/us-east-1a
# 10.0.2.20:8080 HEALTHY OK myapp.default us-east-1/us-east-1bPor qué no se necesitan etiquetas de Pod
Enfoque tradicional (innecesario):
# Not needed
apiVersion: v1
kind: Pod
metadata:
labels:
topology.kubernetes.io/zone: us-east-1a # Unnecessary!Enfoque de Istio (automático):
# Only Node labels needed
apiVersion: v1
kind: Node
metadata:
name: ip-10-0-1-10.ec2.internal
labels:
topology.kubernetes.io/zone: us-east-1a # This is all you need!
topology.kubernetes.io/region: us-east-1Motivos:
- Los Pod no se mueven: Los Pod no se trasladan a otros Node después de su creación
- El Node es la fuente de verdad: La ubicación física del Pod siempre la determina el Node
- Eliminación de redundancia: No es necesario agregar etiquetas a cada Pod; basta con administrar las etiquetas de Node
- Sincronización automática: Istiod siempre consulta la información más reciente de Node desde la API de Kubernetes
Configuración automática de AWS EKS
AWS EKS agrega automáticamente etiquetas de topología al crear Node:
# Check EKS nodes
kubectl get nodes -L topology.kubernetes.io/zone,topology.kubernetes.io/region
# Example output:
# NAME ZONE REGION
# ip-10-0-1-10.ec2.internal us-east-1a us-east-1
# ip-10-0-2-20.ec2.internal us-east-1b us-east-1
# ip-10-0-3-30.ec2.internal us-east-1c us-east-1Estas etiquetas se obtienen automáticamente de las siguientes fuentes:
- Metadatos de instancia de EC2:
http://169.254.169.254/latest/meta-data/placement/availability-zone - API de AWS: Consulta la información de EC2 mediante el
spec.providerIDdel Node
Configuración básica
1. Establecer etiquetas de topología en los Node de Kubernetes
AWS EKS agrega automáticamente las siguientes etiquetas:
topology.kubernetes.io/region: us-east-1
topology.kubernetes.io/zone: us-east-1aVerificación:
kubectl get nodes -L topology.kubernetes.io/zone -L topology.kubernetes.io/region
# Example output:
# NAME ZONE REGION
# ip-10-0-1-10.ec2.internal us-east-1a us-east-1
# ip-10-0-2-20.ec2.internal us-east-1b us-east-1
# ip-10-0-3-30.ec2.internal us-east-1c us-east-12. Habilitar el enrutamiento consciente de zona en DestinationRule
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: myapp
namespace: default
spec:
host: myapp
trafficPolicy:
loadBalancer:
localityLbSetting:
enabled: true # Enable Zone Aware Routing3. Configurar las proporciones de distribución
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: myapp
namespace: default
spec:
host: myapp
trafficPolicy:
loadBalancer:
localityLbSetting:
enabled: true
distribute:
# Traffic originating from us-east-1a
- from: us-east-1/us-east-1a/*
to:
"us-east-1/us-east-1a/*": 80 # 80% to same AZ
"us-east-1/us-east-1b/*": 10 # 10% to adjacent AZ
"us-east-1/us-east-1c/*": 10 # 10% to adjacent AZ
# Traffic originating from us-east-1b
- from: us-east-1/us-east-1b/*
to:
"us-east-1/us-east-1b/*": 80
"us-east-1/us-east-1a/*": 10
"us-east-1/us-east-1c/*": 10
# Traffic originating from us-east-1c
- from: us-east-1/us-east-1c/*
to:
"us-east-1/us-east-1c/*": 80
"us-east-1/us-east-1a/*": 10
"us-east-1/us-east-1b/*": 10Configuración avanzada
Configuración de failover
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: myapp-failover
namespace: default
spec:
host: myapp
trafficPolicy:
loadBalancer:
localityLbSetting:
enabled: true
failover:
# When us-east-1a fails, go to us-east-1b
- from: us-east-1/us-east-1a
to: us-east-1/us-east-1b
# When us-east-1b fails, go to us-east-1c
- from: us-east-1/us-east-1b
to: us-east-1/us-east-1c
# When us-east-1c fails, go to us-east-1a
- from: us-east-1/us-east-1c
to: us-east-1/us-east-1aUso con Outlier Detection
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: myapp-resilient
namespace: default
spec:
host: myapp
trafficPolicy:
# Zone Aware Routing
loadBalancer:
localityLbSetting:
enabled: true
distribute:
- from: us-east-1/us-east-1a/*
to:
"us-east-1/us-east-1a/*": 80
"us-east-1/us-east-1b/*": 20
# Outlier Detection
outlierDetection:
consecutiveErrors: 5
interval: 30s
baseEjectionTime: 30s
maxEjectionPercent: 50
# Maintain minimum healthy instances per zone
minHealthPercent: 50Configuración multi-Region
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: myapp-multi-region
namespace: default
spec:
host: myapp.global
trafficPolicy:
loadBalancer:
localityLbSetting:
enabled: true
# Cross-region distribution
distribute:
# Traffic originating from us-east-1
- from: us-east-1/*/*
to:
"us-east-1/*/*": 90 # 90% to same region
"us-west-2/*/*": 10 # 10% to other region
# Traffic originating from us-west-2
- from: us-west-2/*/*
to:
"us-west-2/*/*": 90
"us-east-1/*/*": 10
# Region failover
failover:
- from: us-east-1
to: us-west-2
- from: us-west-2
to: us-east-1Configuración en AWS EKS
1. Crear grupos de Node multi-AZ
# eksctl configuration
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: my-cluster
region: us-east-1
nodeGroups:
- name: ng-zone-a
instanceType: t3.medium
desiredCapacity: 2
availabilityZones:
- us-east-1a
labels:
zone: us-east-1a
- name: ng-zone-b
instanceType: t3.medium
desiredCapacity: 2
availabilityZones:
- us-east-1b
labels:
zone: us-east-1b
- name: ng-zone-c
instanceType: t3.medium
desiredCapacity: 2
availabilityZones:
- us-east-1c
labels:
zone: us-east-1c2. Distribuir Pod entre las zonas
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 9
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
# Even distribution across zones
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: myapp
containers:
- name: myapp
image: myapp:latest
ports:
- containerPort: 8080
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"3. Habilitar el enrutamiento consciente de zona en Istio
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: myapp
spec:
host: myapp
trafficPolicy:
loadBalancer:
localityLbSetting:
enabled: true
distribute:
- from: us-east-1/us-east-1a/*
to:
"us-east-1/us-east-1a/*": 80
"us-east-1/us-east-1b/*": 10
"us-east-1/us-east-1c/*": 10Ejemplos prácticos
Ejemplo 1: cadena de microservicios
# Frontend → Backend → Database
# Frontend (All AZs)
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 6
template:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: frontend
---
# Backend (All AZs)
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
spec:
replicas: 9
template:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: backend
---
# Database (Single AZ - StatefulSet)
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: database
spec:
replicas: 1
template:
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- us-east-1a
---
# Zone Aware Routing configuration
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: backend
spec:
host: backend
trafficPolicy:
loadBalancer:
localityLbSetting:
enabled: true
distribute:
- from: us-east-1/us-east-1a/*
to:
"us-east-1/us-east-1a/*": 90
"us-east-1/us-east-1b/*": 5
"us-east-1/us-east-1c/*": 5Ejemplo 2: optimización de costos
# Minimize cross-AZ costs
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: cost-optimized
spec:
host: myapp
trafficPolicy:
loadBalancer:
localityLbSetting:
enabled: true
distribute:
# Concentrate 95% in same AZ
- from: us-east-1/us-east-1a/*
to:
"us-east-1/us-east-1a/*": 95
"us-east-1/us-east-1b/*": 3
"us-east-1/us-east-1c/*": 2Ejemplo 3: alta disponibilidad
# Availability first (allow cross-AZ)
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: high-availability
spec:
host: myapp
trafficPolicy:
loadBalancer:
localityLbSetting:
enabled: true
distribute:
# Even distribution across all AZs
- from: us-east-1/us-east-1a/*
to:
"us-east-1/us-east-1a/*": 34
"us-east-1/us-east-1b/*": 33
"us-east-1/us-east-1c/*": 33
# Failover configuration
failover:
- from: us-east-1/us-east-1a
to: us-east-1/us-east-1bMonitoreo
Métricas de Prometheus
# Traffic distribution across zones
sum(rate(istio_requests_total[5m])) by (source_zone, destination_zone)
# Same zone traffic ratio
(
sum(rate(istio_requests_total{source_zone=destination_zone}[5m]))
/
sum(rate(istio_requests_total[5m]))
) * 100
# Error rate by zone
sum(rate(istio_requests_total{response_code=~"5.."}[5m])) by (destination_zone)
/
sum(rate(istio_requests_total[5m])) by (destination_zone)
# Locality information check
envoy_cluster_upstream_cx_active{envoy_cluster_name=~".*myapp.*"}Dashboard de Grafana
{
"dashboard": {
"title": "Istio Zone Aware Routing",
"panels": [
{
"title": "Traffic Distribution by Zone",
"targets": [
{
"expr": "sum(rate(istio_requests_total[5m])) by (source_zone, destination_zone)",
"legendFormat": "{{source_zone}} → {{destination_zone}}"
}
]
},
{
"title": "Same Zone Traffic Percentage",
"targets": [
{
"expr": "(sum(rate(istio_requests_total{source_zone=destination_zone}[5m])) / sum(rate(istio_requests_total[5m]))) * 100",
"legendFormat": "Same Zone %"
}
]
}
]
}
}Verificación en tiempo real
# Check Envoy endpoints
istioctl proxy-config endpoints <pod-name> -n <namespace>
# Check locality information
kubectl exec -n <namespace> <pod-name> -c istio-proxy -- \
curl localhost:15000/clusters | grep myapp
# Example output:
# myapp.default.svc.cluster.local::10.0.1.10:8080::region::us-east-1::zone::us-east-1a::
# myapp.default.svc.cluster.local::10.0.2.20:8080::region::us-east-1::zone::us-east-1b::Solución de problemas
El enrutamiento consciente de zona no funciona
# 1. Check node Topology labels
kubectl get nodes -L topology.kubernetes.io/zone -L topology.kubernetes.io/region
# If labels are missing, add manually:
kubectl label nodes <node-name> topology.kubernetes.io/zone=us-east-1a
kubectl label nodes <node-name> topology.kubernetes.io/region=us-east-1
# 2. Check DestinationRule
kubectl get destinationrule -n <namespace>
kubectl describe destinationrule <name> -n <namespace>
# 3. Check Envoy configuration
istioctl proxy-config clusters <pod-name> -n <namespace> -o json | \
jq '.[] | select(.name | contains("myapp")) | .loadAssignment.endpoints[].locality'
# 4. Check pod zone distribution
kubectl get pods -n <namespace> -o wide \
-L topology.kubernetes.io/zoneProporción alta de tráfico hacia otras zonas
# Root cause analysis:
# 1. Unbalanced pod count per zone
kubectl get pods -n <namespace> -o wide | \
awk '{print $7}' | sort | uniq -c
# 2. Some pods are unhealthy
kubectl get pods -n <namespace> -o wide | \
grep -v "Running"
# 3. Pods excluded by Outlier Detection
kubectl exec -n <namespace> <pod-name> -c istio-proxy -- \
curl localhost:15000/stats/prometheus | grep outlier_detectionFaltan etiquetas de topología en EKS
# Resolve by installing AWS Node Termination Handler
kubectl apply -f https://github.com/aws/aws-node-termination-handler/releases/download/v1.19.0/all-resources.yaml
# Or add labels manually
for node in $(kubectl get nodes -o name); do
ZONE=$(kubectl get $node -o jsonpath='{.metadata.labels.topology\.kubernetes\.io/zone}')
if [ -z "$ZONE" ]; then
# Get AZ from AWS EC2 metadata
ZONE=$(kubectl get $node -o jsonpath='{.spec.providerID}' | \
xargs -I {} aws ec2 describe-instances --instance-ids {} --query 'Reservations[0].Instances[0].Placement.AvailabilityZone' --output text)
kubectl label $node topology.kubernetes.io/zone=$ZONE
fi
donePrácticas recomendadas
1. Distribución uniforme de Pod entre zonas
# Use topologySpreadConstraints
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule2. Optimización de costos
# Prioritize same zone (80% or more)
distribute:
- from: us-east-1/us-east-1a/*
to:
"us-east-1/us-east-1a/*": 80
"us-east-1/us-east-1b/*": 10
"us-east-1/us-east-1c/*": 103. Garantizar alta disponibilidad
# Failover configuration is essential
failover:
- from: us-east-1/us-east-1a
to: us-east-1/us-east-1b4. Se recomienda una sola AZ para StatefulSet
# Deploy StatefulSet (Database, etc.) in single AZ
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- us-east-1a