A Kubernetes-native control plane for Envoy Proxy that implements the xDS (discovery service) protocol. It enables dynamic configuration of Envoy proxies through Kubernetes Custom Resources, with built-in support for automatic TLS certificate management via Let's Encrypt.
The Envoy xDS Controller is an xDS control plane that translates Kubernetes Custom Resources into Envoy configurations. Instead of manually managing Envoy's complex YAML configurations, you define simple Kubernetes resources, and the controller automatically:
- Generates and pushes configurations to Envoy proxies via gRPC
- Manages TLS certificates automatically using Let's Encrypt (ACME protocol)
- Supports multiple Envoy instances with different configurations using cluster/node annotations
- Provides hot-reload capability - Envoy picks up changes without restarts
Managing Envoy at scale presents several challenges:
| Challenge | How xDS Controller Solves It |
|---|---|
| Manual configuration | Define resources in Kubernetes CRDs, not complex Envoy YAML |
| Certificate management | Automatic Let's Encrypt integration with DNS-01/HTTP-01 challenges |
| Configuration sprawl | Single source of truth in Kubernetes |
| Zero-downtime updates | Hot configuration reload via xDS protocol |
| Multi-environment support | Cluster/node annotations for environment-specific configs |
| Secret storage | Integration with HashiCorp Vault or local storage |
-
Full xDS Protocol Support
- LDS (Listener Discovery Service) - Dynamic listener configuration
- RDS (Route Discovery Service) - Dynamic routing configuration
- CDS (Cluster Discovery Service) - Dynamic upstream cluster configuration
- SDS (Secret Discovery Service) - Dynamic TLS certificate management
- EDS (Endpoint Discovery Service) - Dynamic endpoint configuration
-
Automatic TLS Management
- Let's Encrypt integration (Staging & Production)
- DNS-01 and HTTP-01 ACME challenges
- Multiple DNS provider support (Cloudflare, Google Cloud DNS, etc.)
- Automatic certificate renewal
- Certificate expiry monitoring with Prometheus metrics
-
Advanced Capabilities
- Full HTTP Connection Manager (HCM) configuration support
- QUIC/HTTP3 support
- TCP proxy support
- Leader election for HA deployments
- Prometheus metrics for monitoring
- Vault integration for secret storage
┌─────────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌─────────────┐ ┌──────────────────────────────────────┐ │
│ │ kubectl │──→ │ Custom Resources (CRDs) │ │
│ │ / Helm │ │ • Listener • Route • Cluster │ │
│ └─────────────┘ │ • TLSSecret • Endpoint │ │
│ └──────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────┐ │
│ │ Envoy xDS Controller │ │
│ │ ┌────────────────────────────────┐ │ │
│ │ │ • LDS Controller │ │ │
│ │ │ • RDS Controller │ │ │
│ │ │ • CDS Controller │ │ │
│ │ │ • SDS Controller (ACME) │ │ │
│ │ │ • EDS Controller │ │ │
│ │ └────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌────────────────────────────────┐ │ │
│ │ │ xDS gRPC Server │ │ │
│ │ │ (Aggregated Discovery) │ │ │
│ │ └────────────────────────────────┘ │ │
│ └──────────────────────────────────────┘ │
│ │ │
│ gRPC (xDS Protocol) │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────┐ │
│ │ Envoy Proxy Fleet │ │
│ │ (Configuration hot-reloaded) │ │
│ └──────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
External Dependencies:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Let's Encrypt │ │ DNS Provider │ │ HashiCorp Vault │
│ (ACME) │ │ (Cloudflare, │ │ (Optional) │
│ │ │ GCloud, etc) │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Try the full xDS Controller locally with KIND:
# Prerequisites: docker, kind, kubectl
./kind/setup.sh
# Test
curl http://localhost:8080
# Cleanup
kind delete cluster --name xds-demoSee kind/README.md for details.
# 1. Install CRDs
kubectl apply -f https://raw.githubusercontent.com/tentens-tech/xds-controller/main/config/crd/bases/envoyxds.io_listeners.yaml
kubectl apply -f https://raw.githubusercontent.com/tentens-tech/xds-controller/main/config/crd/bases/envoyxds.io_routes.yaml
kubectl apply -f https://raw.githubusercontent.com/tentens-tech/xds-controller/main/config/crd/bases/envoyxds.io_clusters.yaml
kubectl apply -f https://raw.githubusercontent.com/tentens-tech/xds-controller/main/config/crd/bases/envoyxds.io_endpoints.yaml
kubectl apply -f https://raw.githubusercontent.com/tentens-tech/xds-controller/main/config/crd/bases/envoyxds.io_tlssecrets.yaml
# 2. Deploy xDS Controller
kubectl apply -f https://github.com/tentens-tech/xds-controller/releases/latest/download/xds-controller.yaml
# 3. Deploy Envoy (connected to xDS Controller)
kubectl apply -f https://github.com/tentens-tech/xds-controller/releases/latest/download/envoy.yaml
# 4. Run demo (nginx + Listener + Cluster + Route)
kubectl apply -f https://raw.githubusercontent.com/tentens-tech/xds-controller/main/config/demo/demo.yaml
# 5. Test
kubectl port-forward -n xds-system svc/envoy 8080:8080
curl http://localhost:8080/You should see the nginx welcome page served through Envoy! 🎉
-
Apply the Custom Resource Definitions (CRDs):
kubectl apply -f config/crd/bases/
-
Deploy the controller:
kubectl apply -f config/controller/
-
Apply sample configuration:
kubectl apply -f config/samples/
-
Run the controller locally:
go run ./cmd/xds
-
Deploy Envoy:
kubectl apply -f config/envoy/envoy-deployment.yaml
| Component | Description |
|---|---|
| LDS (Listener Discovery Service) | Configure how Envoy listens for incoming connections |
| RDS (Route Discovery Service) | Configure routing rules and virtual hosts |
| CDS (Cluster Discovery Service) | Configure upstream clusters and load balancing |
| EDS (Endpoint Discovery Service) | Configure dynamic endpoints for clusters |
| SDS (Secret Discovery Service) | Manage TLS certificates with Let's Encrypt |
| Parameter | Description | Required |
|---|---|---|
XDS_NAMESPACE |
Kubernetes namespace to watch | No (all namespaces) |
XDS_LOG_LEVEL |
Log level (debug, info, warn, error) | No (info) |
| Parameter | Description | Required |
|---|---|---|
XDS_LETS_ENCRYPT_EMAIL |
Email for Let's Encrypt notifications | Yes (for TLS) |
XDS_LETS_ENCRYPT_PRIVATEKEYB64 |
Base64 RSA key for Let's Encrypt account | No (auto-generated) |
| Parameter | Description | Required |
|---|---|---|
XDS_VAULT_URL |
HashiCorp Vault URL | No |
XDS_VAULT_TOKEN |
Vault authentication token | No |
XDS_VAULT_PATH |
Vault KV2 secret path (e.g., "secret/envoy") | No |
SDS uses lego for ACME certificate management. Set the appropriate environment variables for your DNS provider:
| Provider | Environment Variables |
|---|---|
| Cloudflare | CLOUDFLARE_DNS_API_TOKEN |
| Google Cloud DNS | GCE_PROJECT, GCE_SERVICE_ACCOUNT_FILE |
| AWS Route53 | AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION |
| DigitalOcean | DO_AUTH_TOKEN |
📚 Full list: lego DNS Providers
The xDS Controller supports targeting specific Envoy instances using node and cluster annotations. This maps directly to Envoy's node.id and node.cluster configuration.
┌─────────────────────────────────────────────────────────────────────┐
│ Envoy Configuration │
│ │
│ node: │
│ cluster: production ←── matches "clusters" annotation │
│ id: envoy-01 ←── matches "nodes" annotation │
└─────────────────────────────────────────────────────────────────────┘
If no annotations are specified, resources are sent to the default node (global/global):
# No annotations = sent to all Envoy instances with node.cluster=global, node.id=global
apiVersion: envoyxds.io/v1alpha1
kind: Listener
metadata:
name: http
spec:
address:
socket_address:
address: 0.0.0.0
port_value: 8080Default Envoy config to receive these resources:
node:
cluster: global
id: globalUse annotations to send resources to specific Envoy instances:
apiVersion: envoyxds.io/v1alpha1
kind: Listener
metadata:
name: https
annotations:
clusters: "production" # Target Envoys with node.cluster=production
nodes: "envoy-01,envoy-02" # Target specific node IDs
spec:
address:
socket_address:
address: 0.0.0.0
port_value: 443| Annotation | Description | Example |
|---|---|---|
clusters |
Comma-separated list of Envoy clusters to target | "production,staging" |
nodes |
Comma-separated list of Envoy node IDs to target | "01,02,03" |
# Production listener - only sent to production Envoys
apiVersion: envoyxds.io/v1alpha1
kind: Listener
metadata:
name: https-prod
annotations:
clusters: "production"
spec:
address:
socket_address:
address: 0.0.0.0
port_value: 443
---
# Staging listener - only sent to staging Envoys
apiVersion: envoyxds.io/v1alpha1
kind: Listener
metadata:
name: https-staging
annotations:
clusters: "staging"
spec:
address:
socket_address:
address: 0.0.0.0
port_value: 443Production Envoy config:
node:
cluster: production
id: envoy-prod-01Staging Envoy config:
node:
cluster: staging
id: envoy-staging-01Routes use spec fields to reference listeners and TLS certificates:
spec:
listener_refs:
- "https" # Attach route to this listener
tlssecret_ref: "my-cert" # TLS certificate name (optional)| Metric | Description |
|---|---|
xds_cert_expiry_countdown |
Minutes until certificate expiry |
xds_snapshot_version_match |
Config sync status (1=synced, 0=mismatch) |
xds_snapshot_update_total |
Total configuration updates |
xds_envoy_stream_active |
Active Envoy connections |
xds_resource_count |
Resource count by type |
xds_error_total |
Error counter by type |
xds_config_error_count |
Configuration errors |
For comprehensive examples with production-ready configurations, see the individual controller documentation:
| Resource | Documentation | Description |
|---|---|---|
| Listener | LDS Examples | HTTP, HTTPS, QUIC/HTTP3, TCP proxy |
| Route | RDS Examples | Routing, CORS, Lua, gRPC, compression |
| Cluster | CDS Examples | Load balancing, health checks, circuit breakers |
| Endpoint | EDS Examples | Locality-aware routing, failover |
| TLSSecret | SDS Examples | Let's Encrypt, Vault, self-signed |
# 1. Listener - accepts traffic on port 8080
apiVersion: envoyxds.io/v1alpha1
kind: Listener
metadata:
name: http
spec:
address:
socket_address:
address: 0.0.0.0
port_value: 8080
---
# 2. Cluster - defines the backend
apiVersion: envoyxds.io/v1alpha1
kind: Cluster
metadata:
name: backend
spec:
name: backend
connect_timeout: 5s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: backend
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: my-service.default.svc.cluster.local
port_value: 80
---
# 3. Route - connects listener to cluster
apiVersion: envoyxds.io/v1alpha1
kind: Route
metadata:
name: default-route
spec:
listener_refs:
- http
stat_prefix: default
route_config:
virtual_hosts:
- name: default
domains: ["*"]
routes:
- match:
prefix: /
route:
cluster: backend
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router# TLS Certificate (auto-managed via Let's Encrypt)
apiVersion: envoyxds.io/v1alpha1
kind: TLSSecret
metadata:
name: my-cert
spec:
domains:
- "example.com"
- "*.example.com"
challenge:
challenge_type: DNS01
dns01_provider: cloudflare
acme_env: Production
---
# HTTPS Listener
apiVersion: envoyxds.io/v1alpha1
kind: Listener
metadata:
name: https
spec:
address:
socket_address:
address: 0.0.0.0
port_value: 443
listener_filters:
- name: envoy.filters.listener.tls_inspector
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector
---
# HTTPS Route with TLS
apiVersion: envoyxds.io/v1alpha1
kind: Route
metadata:
name: secure-route
spec:
listener_refs:
- https
tlssecret_ref: my-cert
filter_chain_match:
server_names:
- example.com
stat_prefix: secure
route_config:
virtual_hosts:
- name: secure
domains: ["*"]
routes:
- match:
prefix: /
route:
cluster: backend
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Routermake manifestsmake testmake build- Kubebuilder - Kubernetes controller framework
- go-control-plane - Envoy xDS implementation
- lego - ACME client for Let's Encrypt
- controller-runtime - Controller utilities
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.