Enabling GZIP Response Compression with EnvoyFilter

How to enable GZIP compression of responses using 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 the Accept-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.