Envoy’s versioning is designed for forward compatibility, meaning newer versions can generally run older configurations without issue.
Let’s see Envoy in action. Imagine a simple HTTP filter chain.
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 10000 }
filter_chains:
- filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
Here, we have a listener on port 10000 with a single HTTP filter: the router. This is the most basic setup, essential for directing traffic. If we wanted to add rate limiting, we’d insert another filter:
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 10000 }
filter_chains:
- filters:
- name: envoy.filters.http.rate_limit
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
stat_prefix: rate_limit
token_bucket:
max_tokens: 10
fill_interval: "60s"
# ... other rate limiting config
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
Notice how the router filter is still present. Envoy processes filters in the order they appear in the filters array. The typed_config field uses a @type field to specify the exact protobuf message type for that filter, which is key to how Envoy deserializes configurations. This allows Envoy to be highly extensible.
The core problem Envoy solves is providing a universal, high-performance edge and service proxy. It decouples application logic from network concerns like load balancing, TLS termination, and observability. By using a dynamic configuration system, it allows these network functions to be managed independently of the application code.
When upgrading Envoy, the primary concern is ensuring that the configuration you’re using is still valid for the new version of Envoy. Envoy’s API is versioned, typically using v2, v3, etc. For example, envoy.config.listener.v2.Listener might evolve into envoy.config.listener.v3.Listener. While Envoy aims for backward compatibility within the same API version (e.g., a v3 config should work with a v3 Envoy), it doesn’t guarantee forward compatibility across API versions. You can’t just point an Envoy v1.18 at a config written for v1.22 if that config uses features introduced in v1.22 that were not backported.
The mechanism that allows you to run older configurations with newer Envoy versions is primarily through Envoy’s API versioning and its internal mapping of configuration types. When Envoy starts, it parses its static configuration. It looks at the typed_config for each filter and listener. The @type field tells Envoy exactly which Go struct (or C++ class) to instantiate and deserialize the configuration into. If a filter or a configuration field has been deprecated or removed in the new Envoy binary, but your configuration still refers to it using its old @type or field name, Envoy will reject the configuration upon loading. The upgrade process is thus about ensuring your existing configuration uses API versions and fields that are still supported by the target Envoy binary.
A common pitfall during upgrades is relying on deprecated fields or API versions that have been removed in newer Envoy releases. For instance, if your configuration uses v2 API protos for listeners and clusters, and your target Envoy version only supports v3 APIs, the configuration load will fail. Envoy provides tools and documentation to help migrate between API versions. The protoc-gen-validate plugin, often used with Envoy’s protobuf definitions, can help identify invalid configurations during the build process before deployment. However, during a runtime upgrade, you’re more likely to encounter issues if your configuration references an older API version that has been fully sunsetted. For example, a configuration using envoy.extensions.filters.network.http_connection_manager.v2.HttpConnectionManager will fail if the Envoy binary you’re upgrading to only supports v3 and has removed the v2 type. The fix is to manually update the @type field in your configuration to the corresponding v3 type, e.g., envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager, and adjust any field changes mandated by the v3 API.
The most surprising thing about Envoy’s configuration is how deeply it’s structured around protobufs and their versioning, which is both its greatest strength and a potential source of complexity during upgrades.
When you update Envoy, you’ll often need to update your configuration’s transport_socket types, especially if you’re moving from older TLS settings to newer ones like envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext. If your configuration specifies common_tls_context (an older field) instead of the newer tls_context within the transport_socket, Envoy might complain about an unrecognized field or a deprecated structure when loading the configuration with a newer binary. The fix involves changing the structure to use the tls_context field within transport_socket, and ensuring the specific TLS configuration within it uses v3 API types. This ensures that Envoy is using the latest, most secure, and feature-rich TLS configurations available in the new binary.
The next thing you’ll likely encounter is a need to update your dynamic configuration discovery service (xDS) server to serve v3 API versions if it hasn’t already.