Skip to content

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:

APIDiscovery ServiceMô tả
CDSCluster Discovery ServiceUpstream service definitions (load balancing, outlier detection)
EDSEndpoint Discovery ServiceIP:port của actual service endpoints
LDSListener Discovery ServicePorts và protocols Envoy lắng nghe
RDSRoute Discovery ServiceHTTP routing rules (VirtualService rules)
SDSSecret Discovery ServiceTLS 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 again

Vấ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:

json
// 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ày

EDS (Endpoint Discovery Service)

EDS cung cấp actual IP:port của service instances:

json
{
  "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:

json
// 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):

json
{
  "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:

json
// 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:

  1. Envoy request certificate qua SDS khi khởi động
  2. Istiod push certificate (validity 24h)
  3. Envoy set timer tại 50% lifetime (12h)
  4. Tại 12h, Envoy gửi SDS request mới
  5. Istiod issue certificate mới và push
  6. 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ới

Configurable:

bash
# Envoy variables trong Istiod
PILOT_DEBOUNCE_AFTER=100ms    # Start timer after first change
PILOT_DEBOUNCE_MAX=10s        # Force push after max wait

Config Version Tracking

bash
# 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 push

Trạ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

bash
# 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

bash
# 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 production

3. xDS troubleshooting scenarios

Scenario 1: New Service không được discover

bash
# 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_pushes

Scenario 2: Route rule không apply

bash
# 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 payment

Scenario 3: Certificate không được push

bash
# 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-system

4. Envoy Admin Stats

bash
# 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 streams

xDS 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:

  1. Delta xDS được ưu tiên: Chỉ push diffs
  2. Debounce quan trọng: Batch nhiều changes thành 1 push
  3. Istiod memory: Mỗi proxy cần cache, 500 proxies × 6.5MB = 3.25GB cache
  4. Selective resource distribution: Pilot chỉ push resources mà Envoy cần

Envoy xDS Scaling Tips

bash
# 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:

  1. Debug routing issues: Route không apply → check RDS trong proxy-config
  2. Debug endpoint issues: Service unreachable → check EDS endpoints
  3. Debug mTLS issues: Cert not found → check SDS secrets
  4. 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.

References