Enabling GZIP Response Compression with EnvoyFilter
We recently decided to centralise all gzip compression for our 500 or so microservices into an EnvoyFilter. Why? You ask. Because:
- Some people added compression response filters to their apps, others didn't.
- Those who did implement compression response filters configured them differently (for example; min-compression-bytes, compression levels etc).
- We have some internal
content-type
responses that are not compressed by default, but we want them to be, globally. - Different languages, and frameworks, have different performance profiles when it comes to compression.
- We have other EnvoyFilters that inspect the response body from a service, that would not work if the service returned a compressed response.
So we strived for consistency. We run Envoy/Istio sidecars next to all of our application containers, so we decided to move this responsibility to there.
Implementation
We opt'd to create an EnvoyFilter per-service, so that it was tied to their release pipeline for a more controlled rollout. However here i'm demonstrating applying it globally, without a workload selector and in the istio-system
namespace.
We're using type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor type in order to do this, with the type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip compressor library.
The applyTo
section effetively injects this into the SIDECAR_INBOUND
filterChain for every sidecar on our mesh. It feels a bit backwards; because the compression actually happens on the response
of an inbound request. The configuration of that response can be seen under the response_direction_config
block.
The other keys to draw attentiont to here are:
min_content_length
. You'll need to experiment to find the optimal value for your configuration, and network. Remember that gzipping is a trade, less network for more CPU and potentially more latency.remove_accept_encoding_header
. This will ensure theAccept-Encoding
header from the incoming request is removed. Otherwise your app's compression filter will handle it, rather than Envoy.
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: inject-gzip
namespace: istio-system
spec:
priority: -10
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
listener:
filterChain:
filter:
name: envoy.filters.network.http_connection_manager
subFilter:
name: envoy.filters.http.router
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.compressor
typed_config:
'@type': type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor
compressor_library:
name: text_optimized
typed_config:
'@type': type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip
compression_level: BEST_SPEED
compression_strategy: DEFAULT_STRATEGY
memory_level: 8
window_bits: 15
chunk_size: 4096
response_direction_config:
remove_accept_encoding_header: true
common_config:
enabled:
default_value: true
runtime_key: response_direction_config_enabled
content_type:
- application/hal+json
- application/atom+xml
- application/javascript
- application/x-javascript
- application/json
- application/rss+xml
- application/vnd.ms-fontobject
- application/x-font-ttf
- application/x-web-app-manifest+json
- application/xhtml+xml
- application/xml
- font/opentype
- image/svg+xml
- image/x-icon
- text/css
- text/html
- text/plain
- text/xml
- text/x-component
min_content_length: 860
request_direction_config:
common_config:
enabled:
default_value: false
runtime_key: request_direction_config_enabled
Conclusion
Not much to say, enjoy consistent compression implementation across your mesh! Any client that sends Accept-Encoding: gzip
will get a gzip response.