Consul Service Mesh, when using Envoy as its sidecar proxy, doesn’t actually configure Envoy directly. Instead, Envoy watches Consul’s catalog and configuration endpoints to dynamically discover the network topology and apply policies itself.
Let’s see it in action. Imagine you have two services, frontend and backend, registered in Consul.
// Consul API response for frontend service
{
"ID": "frontend-1",
"Name": "frontend",
"Address": "10.0.0.1",
"Port": 8080,
"Meta": {
"servicePort": "80"
},
"Datacenter": "dc1"
}
// Consul API response for backend service
{
"ID": "backend-1",
"Name": "backend",
"Address": "10.0.0.2",
"Port": 9090,
"Meta": {
"servicePort": "80"
},
"Datacenter": "dc1"
}
When Envoy starts as a sidecar for frontend, it connects to Consul. It queries the /v1/catalog/service/backend endpoint. Upon receiving the backend service’s address (10.0.0.2) and port (9090), Envoy automatically configures itself to route traffic intended for backend to that specific IP and port. It does this by fetching the service discovery information and translating it into Envoy’s internal configuration format.
The core problem Envoy solves here is decoupling service discovery and policy enforcement from application code. Traditionally, applications had to know how to find other services, handle network failures, and implement security. With Envoy as a sidecar, these concerns are offloaded. The application frontend simply sends traffic to localhost:9901 (Envoy’s listener port), and Envoy handles the rest: discovering backend, making the connection, retrying if necessary, and encrypting the traffic if mTLS is enabled.
Internally, Envoy watches Consul using its xDS (Discovery Service) APIs. Specifically, it uses the LDS (Listener Discovery Service) to get its own listening ports and configurations, the CDS (Cluster Discovery Service) to learn about upstream services (like backend), and the RDS (Route Discovery Service) to determine how to route requests to those upstream clusters. Consul acts as the control plane, pushing these configurations to Envoy.
You control this behavior primarily through Consul’s service registration and configuration. When you register a service in Consul, you can add metadata that Envoy can interpret. For instance, to enable mTLS between frontend and backend, you’d configure a mesh stanza in Consul’s configuration or when registering services.
# Consul configuration for enabling mesh
service {
name = "frontend"
port = 8080
// ... other service config
mesh {
// This tells Consul to manage this service within the mesh
}
}
service {
name = "backend"
port = 9090
// ... other service config
mesh {
// This tells Consul to manage this service within the mesh
}
}
This tells Consul to manage these services within the mesh, which then instructs the Envoy sidecars to enable mTLS for their communication. Envoy will automatically provision and rotate TLS certificates for inter-service communication based on Consul’s CA.
The most surprising thing is how little explicit Envoy configuration you actually touch. You don’t write envoy.yaml files for the sidecar. Instead, you rely on Consul’s API and configuration to define the desired state. Envoy then pulls its configuration dynamically. This makes managing a large mesh much more scalable, as Envoy instances independently fetch their configurations from Consul rather than having a central entity distribute static files.
The next step in understanding Consul Service Mesh is exploring how to define advanced traffic management policies, like circuit breakers and retries, using Consul’s configuration.