Envoy xDS API — Cách Istiod Push Config Xuống Envoy
xDS là gì và tại sao cần?
Envoy là dynamic proxy. Không như Nginx hay HAProxy cần file config tĩnh và restart để apply changes, Envoy nhận config updates real-time qua gRPC streaming mà không cần restart. Đây là cốt lõi cho service mesh: khi một Pod mới được tạo, Envoy trong các Pods khác cần biết ngay về endpoint mới để route traffic đến.
xDS (x Discovery Service) là protocol gRPC để Istiod push config xuống Envoy. "x" trong xDS là wildcard — đại diện cho nhiều loại discovery services:
| API | Discovery Service | Mô tả |
|---|---|---|
| CDS | Cluster Discovery Service | Upstream service definitions (load balancing, outlier detection) |
| EDS | Endpoint Discovery Service | IP:port của actual service endpoints |
| LDS | Listener Discovery Service | Ports và protocols Envoy lắng nghe |
| RDS | Route Discovery Service | HTTP routing rules (VirtualService rules) |
| SDS | Secret Discovery Service | TLS certificates và keys (SPIFFE/SVID) |
Ngoài ra còn có:
- ADS (Aggregated Discovery Service): Kết hợp nhiều xDS APIs trên một gRPC stream
- ECDS (Extension Config Discovery Service): Dynamic Envoy filter config
- RTDS (Runtime Discovery Service): Envoy runtime flags
xDS Protocol: Push vs Pull
SotW (State of the World) và Delta xDS
Có hai variants của xDS protocol:
1. SotW (State of the World) — Original
Envoy → Istiod: "Subscribe to clusters"
Istiod → Envoy: [cluster-A, cluster-B, cluster-C] # Full list
...
Pod C starts
Istiod → Envoy: [cluster-A, cluster-B, cluster-C, cluster-D] # Full list againVấn đề: Mỗi update phải gửi toàn bộ state. Với 1000 clusters, mỗi Pod update = 1000 cluster definitions gửi đến mọi Envoy.
2. Delta xDS — Incremental (Istio 1.6+)
Envoy → Istiod: "Subscribe to clusters (I have: A, B, C)"
...
Pod C starts
Istiod → Envoy: "Added: cluster-D" (chỉ gửi diff)
...
Pod B dies
Istiod → Envoy: "Removed: cluster-B"Istiod ưu tiên dùng Delta xDS khi Envoy hỗ trợ. Điều này giảm bandwidth đáng kể trong large clusters.
gRPC Streaming Architecture
┌──────────────────────────────────────────────────────────────┐
│ Istiod │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Pilot │ │ Citadel │ │ Galley │ │ Discovery│ │
│ │(route │ │ (certs) │ │ (config │ │ Server │ │
│ │ rules) │ │ │ │ valid.) │ │ (xDS) │ │
│ └────┬─────┘ └────┬─────┘ └──────────┘ └────┬─────┘ │
│ └─────────────┴──────────────────────────┘ │ │
│ xDS Server ◄┘ │
└──────────────────────────────────────────────────┬───────────┘
│ gRPC ADS stream
┌──────────────────────────────┼────────────────────┐
│ ▼ │
┌─────┴──────┐ ┌──────────────────────────┐ ┌────────┐│
│ Envoy Pod 1 │ │ Envoy Pod 2 │ │ ... ││
│ CDS/EDS/ │ │ CDS/EDS/LDS/RDS/SDS │ │ ││
│ LDS/RDS/ │ │ │ │ ││
│ SDS cache │ │ │ │ ││
└────────────┘ └──────────────────────────┘ └────────┘│
└───────────────────────────────────────────────────────┘Envoy xDS Client Process
1. Envoy start → connect đến Istiod via gRPC (xds-grpc cluster)
Address: istiod.istio-system.svc:15010 (plain) hoặc :15012 (mTLS)
2. Envoy gửi DiscoveryRequest cho từng API:
{
"version_info": "", # Empty = first request
"node": {
"id": "sidecar~10.0.0.5~my-pod.production~production.svc.cluster.local",
"cluster": "my-deployment",
"metadata": { "NAMESPACE": "production", ... }
},
"type_url": "type.googleapis.com/envoy.config.cluster.v3.Cluster"
}
3. Istiod trả DiscoveryResponse:
{
"version_info": "2026-06-25T10:00:00Z/3",
"resources": [...cluster definitions...],
"type_url": "...",
"nonce": "abc123"
}
4. Envoy ACK (apply config):
{
"version_info": "2026-06-25T10:00:00Z/3",
"response_nonce": "abc123", # Confirm nhận được
"type_url": "..."
}
5. Nếu Envoy NACK (config invalid):
{
"version_info": "previous-version", # Giữ version cũ
"error_detail": { "message": "Invalid cluster config: ..." },
"response_nonce": "abc123"
}Các xDS APIs chi tiết
CDS (Cluster Discovery Service)
CDS define upstream services — cách Envoy kết nối đến service instances:
// Envoy cluster config (xDS payload)
{
"name": "outbound|8080|v1|payment-service.production.svc.cluster.local",
"type": "EDS", // Endpoints từ EDS
"eds_cluster_config": {
"eds_config": { "ads": {} },
"service_name": "outbound|8080|v1|payment-service.production.svc.cluster.local"
},
"connect_timeout": "3s",
"lb_policy": "ROUND_ROBIN",
"circuit_breakers": {
"thresholds": [{
"priority": "DEFAULT",
"max_connections": 1024,
"max_pending_requests": 1024,
"max_requests": 1024,
"max_retries": 3
}]
},
"outlier_detection": {
"consecutive_5xx": 5,
"interval": "30s",
"base_ejection_time": "30s",
"max_ejection_percent": 50
},
"transport_socket": {
// mTLS config từ SDS
}
}Naming convention của Istio clusters:
outbound|<port>|<subset>|<hostname>
inbound|<port>||<hostname>
Ví dụ:
outbound|8080|v1|payment-service.production.svc.cluster.local
outbound|8080||payment-service.production.svc.cluster.local # No subset
inbound|8080|| # Inbound đến chính pod nàyEDS (Endpoint Discovery Service)
EDS cung cấp actual IP:port của service instances:
{
"cluster_name": "outbound|8080|v1|payment-service.production.svc.cluster.local",
"endpoints": [{
"locality": {
"region": "us-central1",
"zone": "us-central1-a"
},
"load_balancing_weight": 1,
"lb_endpoints": [
{
"endpoint": {
"address": {
"socket_address": {
"address": "10.0.0.5",
"port_value": 8080
}
}
},
"health_status": "HEALTHY",
"metadata": {
"filter_metadata": {
"istio": {
"uid": "kubernetes://pod/production/payment-pod-xyz"
}
}
}
},
{
"endpoint": {
"address": { "socket_address": { "address": "10.0.0.6", "port_value": 8080 } }
},
"health_status": "HEALTHY"
}
]
}]
}LDS (Listener Discovery Service)
LDS define ports Envoy lắng nghe và filter chains xử lý traffic:
// Outbound listener (15001)
{
"name": "0.0.0.0_15001",
"address": {
"socket_address": { "address": "0.0.0.0", "port_value": 15001 }
},
"use_original_dst": true, // Dùng original destination từ SO_ORIGINAL_DST
"filter_chains": [...] // Default filter chain
}
// Service-specific listener (per service port)
{
"name": "0.0.0.0_8080",
"address": {
"socket_address": { "address": "0.0.0.0", "port_value": 8080 }
},
"filter_chains": [{
"filters": [{
"name": "http_connection_manager",
"typed_config": {
"route_config_name": "8080", // Reference RDS route
// ...
"http_filters": [
{ "name": "istio_authn" },
{ "name": "cors" },
{ "name": "fault" },
{ "name": "router" }
]
}
}]
}]
}RDS (Route Discovery Service)
RDS define HTTP routing rules (từ VirtualService):
{
"name": "8080",
"virtual_hosts": [
{
"name": "payment-service.production.svc.cluster.local:8080",
"domains": [
"payment-service",
"payment-service.production",
"payment-service.production.svc",
"payment-service.production.svc.cluster.local",
"10.96.100.1" // ClusterIP
],
"routes": [
{
"match": { "prefix": "/api/v2" },
"route": {
"cluster": "outbound|8080|v2|payment-service...",
"timeout": "10s",
"retry_policy": {
"retry_on": "gateway-error,connect-failure",
"num_retries": 3,
"per_try_timeout": "3s"
}
}
},
{
"match": { "prefix": "/" },
"route": {
"weighted_clusters": {
"clusters": [
{
"name": "outbound|8080|v1|payment-service...",
"weight": 90
},
{
"name": "outbound|8080|v2|payment-service...",
"weight": 10
}
]
}
}
}
]
}
]
}SDS (Secret Discovery Service)
SDS cung cấp TLS certificates cho Envoy, không cần file trên disk:
// SDS request từ Envoy
{
"type_url": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
"resource_names": [
"default", // Workload certificate (SVID)
"ROOTCA" // Trusted CA bundle
]
}
// SDS response từ Istiod
{
"resources": [
{
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
"name": "default",
"tls_certificate": {
"certificate_chain": { "inline_bytes": "<base64-encoded-cert-chain>" },
"private_key": { "inline_bytes": "<base64-encoded-private-key>" }
// Private key không được persist ra disk!
}
},
{
"name": "ROOTCA",
"validation_context": {
"trusted_ca": { "inline_bytes": "<base64-encoded-root-ca>" }
}
}
]
}SDS flow với certificate rotation:
- Envoy request certificate qua SDS khi khởi động
- Istiod push certificate (validity 24h)
- Envoy set timer tại 50% lifetime (12h)
- Tại 12h, Envoy gửi SDS request mới
- Istiod issue certificate mới và push
- Envoy swap certificate in-memory, zero downtime
Config Push và Versioning
Debounce và Batch Updates
Khi nhiều thay đổi xảy ra cùng lúc (ví dụ: deploy mới tạo 10 Pods), Istiod không push ngay sau mỗi thay đổi. Thay vào đó:
t=0: Pod 1 start → Istiod nhận EndpointSlice update
t=0.05s: Pod 2 start → more updates
t=0.1s: Pod 3-10 start → more updates
...
t=0.1s (debounce window expires): Istiod compute consolidated xDS update
Push một lần với tất cả endpoints mớiConfigurable:
# Envoy variables trong Istiod
PILOT_DEBOUNCE_AFTER=100ms # Start timer after first change
PILOT_DEBOUNCE_MAX=10s # Force push after max waitConfig Version Tracking
# Xem version info của xDS config trên Envoy
istioctl proxy-status
# Output:
# NAME CDS LDS EDS RDS ISTIOD
# payment-pod-xyz SYNCED SYNCED SYNCED SYNCED istiod-xxx
# order-pod-abc STALE SYNCED SYNCED SYNCED istiod-xxx
# ↑
# STALE = Envoy chưa ACK version mới nhất
# Detail về version
istioctl proxy-status my-pod -n production
# Shows version hash của config đang dùng vs version Istiod đang pushTrạng thái STALE không phải lúc nào cũng alarm:
- Istiod push config và chờ ACK
- Nếu Envoy đang busy process, ACK có thể delay vài giây
- STALE kéo dài > 1 phút → investigate
Debugging xDS
1. Xem raw Envoy config dump
# Full config dump (rất lớn, ~MB)
kubectl exec my-pod -c istio-proxy -- \
curl -s localhost:15000/config_dump > /tmp/config-dump.json
# Filter specific parts
kubectl exec my-pod -c istio-proxy -- \
curl -s localhost:15000/config_dump | \
jq '.configs[] | select(."@type" | contains("ClustersConfigDump"))'
# Xem listeners
kubectl exec my-pod -c istio-proxy -- \
curl -s localhost:15000/config_dump | \
jq '.configs[] | select(."@type" | contains("ListenersConfigDump")) |
.dynamic_listeners[].active_state.listener | {name, address}'2. istioctl proxy-config commands
# Clusters: xem tất cả upstream services
istioctl proxy-config clusters my-pod -n production
# Filter specific cluster
istioctl proxy-config clusters my-pod -n production --fqdn payment-service
# Output:
# SERVICE FQDN PORT SUBSET VERSION DIRECTION TYPE
# payment-service.production.svc.cluster.local 8080 v1 - outbound EDS
# payment-service.production.svc.cluster.local 8080 v2 - outbound EDS
# Endpoints: xem actual pod IPs
istioctl proxy-config endpoints my-pod -n production --cluster "outbound|8080|v1|payment-service..."
# Output:
# ENDPOINT STATUS OUTLIER CHECK CLUSTER
# 10.0.0.5:8080 HEALTHY OK outbound|8080|v1|payment-service...
# 10.0.0.6:8080 HEALTHY OK outbound|8080|v1|payment-service...
# 10.0.0.7:8080 EJECTED FAILED outbound|8080|v1|payment-service...
# ↑ Circuit breaker đang active cho endpoint này!
# Routes
istioctl proxy-config routes my-pod -n production --name "8080"
# Listeners
istioctl proxy-config listeners my-pod -n production
# Bootstrap config
istioctl proxy-config bootstrap my-pod -n production3. xDS troubleshooting scenarios
Scenario 1: New Service không được discover
# Check cluster tồn tại trong Envoy
istioctl proxy-config clusters my-pod | grep new-service
# Nếu không có:
# 1. Check Service K8s object tồn tại
kubectl get service new-service -n production
# 2. Check Istiod đang watch namespace
kubectl logs -n istio-system deployment/istiod | grep "new-service"
# 3. Check proxy-status SYNCED
istioctl proxy-status my-pod
# 4. Check Istiod push stats
kubectl exec -n istio-system deployment/istiod -- \
curl -s localhost:15014/metrics | grep pilot_xds_pushesScenario 2: Route rule không apply
# Verify VirtualService syntax valid
istioctl analyze -n production
# Check route trong Envoy
istioctl proxy-config routes my-pod -n production --name 8080 -o json | \
jq '.routes[].match'
# Nếu route không xuất hiện:
# 1. VirtualService hosts match hostname không?
# 2. VirtualService gateway field đúng không?
# 3. DestinationRule subset labels match Pod labels không?
kubectl get destinationrule -n production -o yaml | grep -A5 subsets
kubectl get pod -n production --show-labels | grep paymentScenario 3: Certificate không được push
# Check SDS status
istioctl proxy-config secret my-pod -n production
# Nếu không có secret:
kubectl logs my-pod -c istio-proxy | grep -i "secret\|sds\|cert"
# Check Istiod CA health
kubectl logs -n istio-system deployment/istiod | grep -i "signing\|cert"
# Verify cacerts secret
kubectl get secret cacerts -n istio-system4. Envoy Admin Stats
# Access Envoy admin
kubectl port-forward my-pod 15000:15000 &
# xDS connection stats
curl localhost:15000/stats | grep "grpc.xds-grpc"
# Config push stats
curl localhost:15000/stats | grep "control_plane"
# Key metrics:
# control_plane.connected_state: 1 = connected to Istiod
# grpc.xds-grpc.streams_active: N active xDS streamsxDS và Scale
Performance với large clusters
Với 1000+ services, 10000+ endpoints:
Istiod xDS push size estimate:
- CDS: ~1000 clusters × 1KB = 1MB per push
- EDS: ~10000 endpoints × 200B = 2MB per push
- LDS: ~5000 listeners × 500B = 2.5MB per push
- RDS: ~2000 routes × 500B = 1MB per push
Total: ~6.5MB per Envoy proxy per full push cycle
With 500 proxies: 3.25GB bandwidth per push event!Đây là lý do tại sao:
- Delta xDS được ưu tiên: Chỉ push diffs
- Debounce quan trọng: Batch nhiều changes thành 1 push
- Istiod memory: Mỗi proxy cần cache, 500 proxies × 6.5MB = 3.25GB cache
- Selective resource distribution: Pilot chỉ push resources mà Envoy cần
Envoy xDS Scaling Tips
# Increase Istiod memory cho large clusters
resources:
requests:
memory: 4Gi
limits:
memory: 8Gi
# Enable Delta xDS (default trong Istio 1.16+)
PILOT_ENABLE_DELTA_XDS: "true"
# Reduce push frequency cho stable clusters
PILOT_DEBOUNCE_AFTER: "500ms"
PILOT_DEBOUNCE_MAX: "30s"
# Enable lazy xDS (only push what proxy needs)
PILOT_FILTER_GATEWAY_CLUSTER_CONFIG: "true"Kết luận
xDS là "ngôn ngữ" mà Istiod và Envoy nói chuyện. Hiểu xDS giúp bạn:
- Debug routing issues: Route không apply → check RDS trong proxy-config
- Debug endpoint issues: Service unreachable → check EDS endpoints
- Debug mTLS issues: Cert not found → check SDS secrets
- Understand performance: Large clusters → Delta xDS, debounce tuning
Key mental model: Istiod là source of truth, Envoy là consumer. Bất kỳ policy nào bạn define (VirtualService, DestinationRule, PeerAuthentication) đều được Istiod compile thành xDS config và push xuống Envoy. Nếu traffic không behave như expected, trace từ Istio object → xDS → Envoy config.