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 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.