Cloud Native 10 min read

Mastering Gray Release with Envoy: Weight, Header, and Cookie Routing

This article explains how to implement safe, incremental gray releases in a microservice architecture using Envoy Proxy, covering weight‑based traffic splitting, header‑based canary routing, and cookie‑based routing with full configuration examples and testing commands.

Linux Ops Smart Journey
Linux Ops Smart Journey
Linux Ops Smart Journey
Mastering Gray Release with Envoy: Weight, Header, and Cookie Routing

Why Gray Release Matters

In today’s microservice world, safely rolling out new service versions is critical. Gray release (canary deployment) lets you expose a new version to a small user subset, verify stability, and gradually increase traffic, reducing the impact of potential failures.

Envoy as the Ideal Proxy

Envoy Proxy’s high performance, extensibility, and powerful routing capabilities make it a perfect choice for implementing gray releases. The following sections show how to achieve flexible gray releases using weight‑based, header‑based, and cookie‑based routing rules.

1. Weight‑Based Gray Release

Route 80% of traffic to the old service and 20% to the new service.

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 10000
    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_http
          http_filters:
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match:
                  prefix: "/"
                route:
                  weighted_clusters:
                    clusters:
                    - name: simple_cluster_old
                      weight: 80
                    - name: simple_cluster_new
                      weight: 20
  clusters:
  - name: simple_cluster_new
    lb_policy: ROUND_ROBIN
    type: STATIC
    load_assignment:
      cluster_name: simple_cluster
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address: { address: 172.139.20.170, port_value: 8090 }
  - name: simple_cluster_old
    lb_policy: ROUND_ROBIN
    type: STATIC
    load_assignment:
      cluster_name: simple_cluster
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address: { address: 172.139.20.3, port_value: 8090 }
        - endpoint:
            address:
              socket_address: { address: 172.139.20.92, port_value: 8090 }

Test the configuration by repeatedly calling the service endpoint and observing the returned version information.

$ for i in {1..5}; do curl http://localhost:10000/version; echo; done
{ "BuildTime": "2025-09-09", "CommitID": "09ee5a64f9a2ebdded0fafbbae5e7dcfb3bf424c", "Version": "v1.5.1" }
# Adjust traffic split to 80/20
$ curl -XPOST http://localhost:9901/runtime_modify?routing.traffic_shift.simple=80
OK

2. Header‑Based Gray Release

Requests containing the header x-canary=true are routed to the new version; all others go to the old version.

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 10000
    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_http
          http_filters:
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match:
                  prefix: "/"
                  headers:
                  - name: "x-canary"
                    exact_match: "true"
                route:
                  cluster: simple_cluster_new
              - match:
                  prefix: "/"
                route:
                  cluster: simple_cluster_old
  clusters:
  - name: simple_cluster_new
    lb_policy: ROUND_ROBIN
    type: STATIC
    load_assignment:
      cluster_name: simple_cluster
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address: { address: 172.139.20.170, port_value: 8090 }
  - name: simple_cluster_old
    lb_policy: ROUND_ROBIN
    type: STATIC
    load_assignment:
      cluster_name: simple_cluster
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address: { address: 172.139.20.3, port_value: 8090 }
        - endpoint:
            address:
              socket_address: { address: 172.139.20.92, port_value: 8090 }

3. Cookie‑Based Gray Release

Requests with a cookie canary=always are sent to the new version; others go to the old version.

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 10000
    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_http
          http_filters:
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match:
                  prefix: "/"
                  headers:
                  - name: "Cookie"
                    exact_match: "canary=always"
                route:
                  cluster: simple_cluster_new
              - match:
                  prefix: "/"
                route:
                  cluster: simple_cluster_old
  clusters:
  - name: simple_cluster_new
    lb_policy: ROUND_ROBIN
    type: STATIC
    load_assignment:
      cluster_name: simple_cluster
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address: { address: 172.139.20.170, port_value: 8090 }
  - name: simple_cluster_old
    lb_policy: ROUND_ROBIN
    type: STATIC
    load_assignment:
      cluster_name: simple_cluster
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address: { address: 172.139.20.3, port_value: 8090 }
        - endpoint:
            address:
              socket_address: { address: 172.139.20.92, port_value: 8090 }

Tip: Larger sample sizes make traffic‑split percentages more accurate.

Conclusion

Gray release is not a one‑off task but an ongoing safety mechanism for continuous delivery. Envoy’s powerful routing and flexible configuration model provide a solid foundation for implementing reliable canary deployments.

gray-releaseroutingservice meshEnvoycanary deployment
Linux Ops Smart Journey
Written by

Linux Ops Smart Journey

The operations journey never stops—pursuing excellence endlessly.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.