Run `kubectl get pods` in a Kubernetes cluster with Istio installed, and every application pod shows `2/2` containers. You deployed one container. The second is Envoy, and it now handles every byte of network traffic your application sends or receives.
Envoy is an open-source, high-performance proxy for cloud-native systems. It handles networking tasks like routing, retries, timeouts, load balancing, encryption, and telemetry, replacing application-managed functions. By the end of this article, you'll understand why Envoy exists, how its configuration works, and why Kubernetes tools such as Istio, Envoy Gateway, Contour, and Emissary build on it.
## Why Envoy Exists
[Matt Klein and the team at Lyft built Envoy][envoy-announcement] around 2015, as Lyft broke a monolith into microservices written in several languages. That migration exposed a problem every polyglot microservices shop hits:
* Every language needs client libraries for retries, timeouts, circuit breaking, and service discovery. PHP, Python, and Go each reimplement the same logic with different bugs.
* Nobody can see network activity. When a request passes through five services and fails, identifying the broken hop requires correlating five different logs, if they exist.
* Upgrading network behavior requires redeploying all applications, as their logic runs inside them.
Envoy's answer: move all that out of the app into a separate process that runs beside it. The app talks to localhost. Envoy manages destination discovery, load balancing, retries, timeouts, encryption, and metrics for each hop.
Apply this to all services, and the network stops being a black box. Every request between services routes through Envoy on both ends, generating the same metrics, access logs, and tracing headers, regardless of language.
Envoy became the third project to [graduate from the Cloud Native Computing Foundation (CNCF)][cncf-graduation], after Kubernetes and Prometheus, demonstrating its importance.
## How Envoy Works
Envoy's request handling boils down to four concepts:
* **Listeners.** A listener is an address and port Envoy accepts connections on, such as `0.0.0.0:8080`.
* **Filters.** Each listener runs connections through a chain of filters. Filters can parse Hypertext Transfer Protocol (HTTP) traffic, terminate Transport Layer Security (TLS), enforce rate limits, or run custom logic. The HTTP router is itself a filter, and the chain gives Envoy its extensibility.
* **Routes.** For HTTP traffic, routes match requests (by path, header, or authority) and decide which cluster should receive them. Routes also carry per-route policy such as retries, timeouts, and traffic splitting.
* **Clusters and endpoints.** A cluster is a named upstream service, and endpoints are the addresses behind it (in Kubernetes, the pod Internet Protocol (IP) addresses). Envoy load-balances across them and monitors their health.
A request goes through a listener, filter chain, matches a route, and reaches an endpoint in a cluster. That's Envoy's entire data pipeline.
Here's a minimal static config wiring all four concepts together: one listener on port 8080, an HTTP filter chain, one route, and one cluster:
```yaml
static_resources:
listeners:
- address:
socket_address: { address: 0.0.0.0, port_value: 8080 }
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress
route_config:
virtual_hosts:
- name: app
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
clusters:
- name: backend
type: STRICT_DNS
load_assignment:
cluster_name: backend
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address: { address: backend.default.svc.cluster.local, port_value: 8080 }
```
Nearly thirty lines to say "listen on 8080 and forward everything to backend." That verbosity is a theme you'll meet again in the trade-offs.
### The xDS APIs: Configuration as a Conversation
The above pipeline describes nearly any proxy, but Envoy's unique aspect is its configuration.
Traditional proxies reload on config file change, but in Kubernetes with pods constantly appearing and disappearing, this isn't sufficient. Envoy uses a family of application programming interfaces (APIs) called xDS, short for "x Discovery Service," where the x stands in for whatever is being discovered: listeners, routes, clusters, or endpoints. The APIs run over gRPC, a high-performance Remote Procedure Call (RPC) framework, allowing live updates without restarts or dropped connections.
The control plane handles streaming, while Envoy (the data plane) moves the actual bytes.
```mermaid
graph TB
CP[Control Plane
Istio, Envoy Gateway, Contour] -->|watches| K8S[Kubernetes API
Services, Pods, Gateway resources]
CP -->|streams config via xDS| E1[Envoy: sidecar in pod A]
CP -->|streams config via xDS| E2[Envoy: sidecar in pod B]
CP -->|streams config via xDS| E3[Envoy: edge gateway]
E3 --> E1
E1 --> E2
style CP fill:#e1f5fe
style K8S fill:#eceff1
style E1 fill:#fff8e1
style E2 fill:#fff8e1
style E3 fill:#fff8e1
```
That split clarifies Envoy's role: it provides the machinery, while the control plane translates high-level intent like "route 10% of checkout traffic to v2" into listeners, routes, and clusters.
Envoy is to networking what a container runtime is to compute: rarely interacted with directly, but essential for Kubernetes. Similarly, configuring Istio or a Gateway resource results in Envoy executing underneath.
## Where Envoy Shows Up in Kubernetes
Envoy plays three roles in a typical cluster, and mixing them up causes most of the confusion around it.
**Sidecar proxy**
One Envoy per pod, injected by service mesh like Istio, manages east-west traffic: mutual TLS, retries, telemetry.
--card--
**Edge gateway**
Envoy at the cluster boundary, managed by Envoy Gateway, Contour, Emissary, or Istio's ingress. Handles inbound (north-south) traffic: TLS termination, routing, rate limiting.
--card--
**Standalone proxy**
Envoy runs directly, outside any mesh, as a load balancer or gRPC bridge. Common outside Kubernetes too.
A few relationships worth reinforcing:
* **Istio and Envoy.** Istio is a service mesh with a control plane and policy model, with Envoy as its data plane. When Istio injects Envoy into a pod, it configures it with an Istio bootstrap. Its newer ambient mode relocates Envoy to shared proxies, but the core engine remains Envoy.
* **kube-proxy and Envoy.** kube-proxy forwards connections; Envoy understands requests. kube-proxy programs the node to forward Service traffic at the connection level, while Envoy operates at the application layer, where it can retry a failed request, split traffic by header, and record request latency. Meshes typically leave kube-proxy in place and route around it.
* **The Gateway API.** Kubernetes' successor to Ingress defines routing intent (Gateway, HTTPRoute) without specifying an implementation. [Envoy Gateway][envoy-gateway] is the Envoy project's control plane, with other implementations running Envoy underneath.
* **The broader traffic picture.** Envoy is a key implementation of the sidecar and gateway patterns. [Fundamentals of Software Traffic Management][fundamentals-traffic] explains these patterns, while [Learn Kubernetes][learn-kubernetes] covers the platform assumptions behind this article.
## Trade-offs and Limitations
Envoy's power has real costs:
* **A sidecar per pod adds up.** Each Envoy consumes a modest amount of memory and central processing unit (CPU) capacity, incurring significant resource costs when applying it to thousands of pods. This overhead pushed Istio to build ambient mode and pushed some meshes (Linkerd) to write their own lighter proxy instead.
* **Every hop through a proxy adds latency.** Each hop usually costs a millisecond or less, but two sidecars sit on every service-to-service call. Measure latency on critical paths before you commit them to a mesh.
* **Raw Envoy configuration is verbose.** A minimal hand-written config runs to dozens of lines of YAML (YAML Ain't Markup Language), while a realistic one reaches hundreds. This verbosity is deliberate, as the config is a machine-oriented API surface, but it makes using Envoy without a control plane difficult. Almost everyone uses one.
* **Debugging gets a new layer.** When traffic misbehaves, the question is whether the bug resides in the application, Envoy config, or control plane that generated it. The telemetry helps, but the failure surface is broader.
## Common Misconceptions
* **"Envoy is a service mesh."** Envoy is the data plane building a mesh, which adds a control plane, identity, and policy. Running Envoy alone provides an excellent proxy, without a mesh.
* **"You need Istio to use Envoy."** Envoy Gateway, Contour, and Emissary all use Envoy without mesh, and many teams run Envoy standalone as an edge proxy or gRPC load balancer.
* **"Envoy replaces kube-proxy."** kube-proxy offers basic Service connectivity for the node, while Envoy manages application-layer traffic for workloads using it.
* **"Envoy is just NGINX with extra steps."** NGINX predates the control plane era and added dynamic APIs later; Envoy started API-first, with first-class HTTP/2 and gRPC support and hot configuration updates as the default. For static configs, the difference is small. For a Kubernetes cluster where endpoints churn constantly, the difference is the product.
## Conclusion
Envoy is a universal data plane: a proxy that moved networking concerns (routing, resilience, security, observability) out of application code and into infrastructure, and that expects to be programmed by machines rather than configured by hand. The xDS split explains everything else. Control planes like Istio and Envoy Gateway exist to speak xDS, Kubernetes integrations exist to feed those control planes, and your pods show `2/2` because the second container is the data plane doing the work.
## Next Steps
* Read [Fundamentals of Software Traffic Management][fundamentals-traffic] for the sidecar, gateway, and load balancing patterns Envoy implements.
* Read [Learn Kubernetes][learn-kubernetes] if you need to reinforce the platform side (pods, Services, kube-proxy) first.
* Read [Fundamentals of Containerization][fundamentals-containers] for the runtime layer underneath all of this.
* Read the [Envoy documentation][envoy-docs] for the authoritative description of listeners, filters, clusters, and xDS.
## References
* [Announcing Envoy][envoy-announcement], Matt Klein's original post introducing the proxy and the problems Lyft built it to solve.
* [Envoy documentation][envoy-docs], the project's official docs, including the architecture overview and xDS protocol specification.
* [Envoy graduates from CNCF][cncf-graduation], the Cloud Native Computing Foundation's graduation announcement.
* [Envoy Gateway][envoy-gateway], the Envoy project's control plane for the Kubernetes Gateway API.
[envoy-announcement]: https://eng.lyft.com/announcing-envoy-c-l7-proxy-and-communication-bus-92520b6c8191
[envoy-docs]: https://www.envoyproxy.io/docs/envoy/latest/
[cncf-graduation]: https://www.cncf.io/announcements/2018/11/28/cncf-announces-envoy-graduation/
[envoy-gateway]: https://gateway.envoyproxy.io/
[fundamentals-traffic]: https://jeffbailey.us/fundamentals-of-software-traffic-management/
[learn-kubernetes]: https://jeffbailey.us/learn-kubernetes/
[fundamentals-containers]: https://jeffbailey.us/fundamentals-of-containerization/