Announcing Tetrate Agent Operations Director for GenAI Runtime Visibility and Governance

Learn more
< Back

Header-Based Routing in Istio without Header Propagation

Istio uses Envoy proxy as a Pod sidecar to which the application delegates networking responsibilities like the inbound and outbound traffic

Header-Based%20Routing%20in%20Istio%20without%20Header%20Propagation

Istio uses Envoy proxy as a Pod sidecar to which the application delegates networking responsibilities like the inbound and outbound traffic, but there’s one responsibility that still belongs to the app container: header propagation.

The Envoy proxy cannot correlate the requests it sends to the app to those the app is responding to, so the headers cannot be automatically propagated by Istio.

Figure 1: Sidecar can’t correlate requests with responses if the app container does not forward back headers.

In most cases, headers-based routing would require app developers to implement header forwarding. For example, in Istio’s flagship Bookinfo app, the productpage microservice implements it like this. This drives us to a question:

How can platform admins use headers-based routing without modifying the application’s internals?

## A Swim-Lane Approach

Using the Bookinfo app, we’ll segment request paths based on an x-version header as in Figure 2 below:

Figure 2: Segmenting request paths based on x-version header.

Requests with no x-version header may be routed to an arbitrary backend.

Deploy Workloads

We will use Istio’s Bookinfo example with some minor changes regarding versioning apps as a sample implementation.

First, we create three productpage deployments, only changing the version labels.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: productpage-v{1,2,3}
  labels:
    app: productpage
    version: v{1,2,3}
spec:
  replicas: 1
  selector:
    matchLabels:
      app: productpage
      version: v{1,2,3}
  template:
    metadata:
      labels:
        app: productpage
        version: v{1,2,3}
...

And one service for them all:

apiVersion: v1
kind: Service
metadata:
  name: productpage
  labels:
    app: productpage
    service: productpage
spec:
  ports:
  - port: 9080
    name: http
  selector:
    app: productpage

Then, create three deployments for the reviews app:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: reviews-v{1,2,3}
  labels:
    app: reviews
    version: v{1,2,3}
spec:
  replicas: 1
  selector:
    matchLabels:
      app: reviews
      version: v{1,2,3}
  template:
    metadata:
      labels:
        app: reviews
        version: v{1,2,3}
...

Ratings and details apps just keep the same as in the original example.

Deploy Istio Config

Here’s where Istio’s routing capabilities come into play. A DestinationRule subsetsfor each productpage version is defined:

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: productpage
spec:
  host: productpage
  trafficPolicy:
    loadBalancer:
      simple: RANDOM
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
  - name: v3
    labels:
      version: v3

And a couple of VirtualService that implement the first half of the swim-lane headers logic. The following takes charge of the prefix matches and uses the delegate functionality to use a second VirtualService, so configs are atomic and declaring a mesh gateway selector is avoided (see quote below):

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: bookinfo
spec:
  hosts:
  - "*"
  gateways:
  - bookinfo-gateway
  http:
  - match:
    - uri:
        exact: /productpage
    - uri:
        prefix: /static
    - uri:
        exact: /login
    - uri:
        exact: /logout
    - uri:
        prefix: /api/v1/products
    delegate:
      name: productpage-route

Now the delegated productpage-route:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: productpage-route
spec:
  http:
  - name: "productpage-v1-route"
    match:
    - headers:
        x-version:
          exact: v1
    route:
    - destination:
        host: productpage
        subset: v1
  - name: "productpage-v2-route"
    match:
    - headers:
        x-version:
          exact: v2
    route:
    - destination:
        host: productpage
        subset: v2
  - name: "productpage-v3-route"
    match:
    - headers:
        x-version:
          exact: v3
    route:
    - destination:
        host: productpage
        subset: v3
  - name: "productpage-default-route"
    match:
    - withoutHeaders:
        x-version: {}
    route:
    - destination:
        host: productpage

Then, at the Reviews level, craft the second half of the swim-lane using the sourceLabels config at the httpMatchRequest:

One or more labels that constrain the applicability of a rule to source (client) workloads with the given labels. If the VirtualService has a list of gateways specified in the top-level gateways field, it must include the reserved gateway mesh for this field to be applicable.

Source: Istio Virtual Service Documentation

This is the VirtualService using the sourceLabels feature:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews-route
spec:
  hosts:
  - reviews
  http:
  - name: "reviews-v1-route"
    match:
    - sourceLabels:
        version: v1
    route:
    - destination:
        host: reviews
        subset: v1
  - name: "reviews-v2-route"
    match:
    - sourceLabels:
        version: v2
    route:
    - destination:
        host: reviews
        subset: v2
  - name: "reviews-v3-route"
    match:
    - sourceLabels:
        version: v3
    route:
    - destination:
        host: reviews
        subset: v3

Testing Header Routing without Header Propagation

First, start with the no header scenario, where you get responses from all lanes:

—————»  ns:bookinfo ❯ for i in {1..5}; do curl -s localhost:8080/productpage | grep -A1 "Reviews served by"; done
        <dt>Reviews served by:</dt>
        <u>reviews-v2-955b74755-t4jkb</u>
        <dt>Reviews served by:</dt>
        <u>reviews-v3-797fc48bc9-wsg26</u>
        <dt>Reviews served by:</dt>
        <u>reviews-v1-5cf854487-hjtrg</u>
        <dt>Reviews served by:</dt>
        <u>reviews-v3-797fc48bc9-wsg26</u>
        <dt>Reviews served by:</dt>
        <u>reviews-v2-955b74755-t4jkb</u>

Then check if the x-version: v1 header has any effect. You can see all the calls productpage-v1 workload make are being served exclusively by reviews-v1.

—————»  ns:bookinfo ❯ for i in {1..10}; \
do curl -s localhost:8080/productpage -H "x-version: v1" \
| grep -A1 "Reviews served by"; done
        <dt>Reviews served by:</dt>
        <u>reviews-v1-5cf854487-hjtrg</u>
        <dt>Reviews served by:</dt>
        <u>reviews-v1-5cf854487-hjtrg</u>
        <dt>Reviews served by:</dt>
        <u>reviews-v1-5cf854487-hjtrg</u>
        <dt>Reviews served by:</dt>
        <u>reviews-v1-5cf854487-hjtrg</u>
        <dt>Reviews served by:</dt>
        <u>reviews-v1-5cf854487-hjtrg</u>
        <dt>Reviews served by:</dt>
        <u>reviews-v1-5cf854487-hjtrg</u>
        <dt>Reviews served by:</dt>
        <u>reviews-v1-5cf854487-hjtrg</u>
        <dt>Reviews served by:</dt>
        <u>reviews-v1-5cf854487-hjtrg</u>
        <dt>Reviews served by:</dt>
        <u>reviews-v1-5cf854487-hjtrg</u>
        <dt>Reviews served by:</dt>
        <u>reviews-v1-5cf854487-hjtrg</u>

And finish testing with v3 header value:

—————»  ns:bookinfo ❯ for i in {1..10}; \
do curl -s localhost:8080/productpage -H "x-version: v3" \
| grep -A1 "Reviews served by"; done
        <dt>Reviews served by:</dt>
        <u>reviews-v3-797fc48bc9-wsg26</u>
        <dt>Reviews served by:</dt>
        <u>reviews-v3-797fc48bc9-wsg26</u>
        <dt>Reviews served by:</dt>
        <u>reviews-v3-797fc48bc9-wsg26</u>
        <dt>Reviews served by:</dt>
        <u>reviews-v3-797fc48bc9-wsg26</u>
        <dt>Reviews served by:</dt>
        <u>reviews-v3-797fc48bc9-wsg26</u>
        <dt>Reviews served by:</dt>
        <u>reviews-v3-797fc48bc9-wsg26</u>
        <dt>Reviews served by:</dt>
        <u>reviews-v3-797fc48bc9-wsg26</u>
        <dt>Reviews served by:</dt>
        <u>reviews-v3-797fc48bc9-wsg26</u>
        <dt>Reviews served by:</dt>
        <u>reviews-v3-797fc48bc9-wsg26</u>
        <dt>Reviews served by:</dt>
        <u>reviews-v3-797fc48bc9-wsg26</u>

Conclusion

In this article we used match on headers, subsets and sourceLabels in Istio to route based on headers with no header propagation. You could also see the usage of delegate functionality as well as withoutHeaders matching.

Product background Product background for tablets
New to service mesh?

Get up to speed with free online courses at Tetrate Academy and quickly learn Istio and Envoy.

Learn more
Using Kubernetes?

Tetrate Enterprise Gateway for Envoy (TEG) is the easiest way to get started with Envoy Gateway for production use cases. Get the power of Envoy Proxy in an easy-to-consume package managed via the Kubernetes Gateway API.

Learn more
Getting started with Istio?

Tetrate Istio Subscription (TIS) is the most reliable path to production, providing a complete solution for running Istio and Envoy securely in mission-critical environments. It includes:

  • Tetrate Istio Distro – A 100% upstream distribution of Istio and Envoy.
  • Compliance-ready – FIPS-verified and FedRAMP-ready for high-security needs.
  • Enterprise-grade support – The ONLY enterprise support for 100% upstream Istio, ensuring no vendor lock-in.
  • Learn more
    Need global visibility for Istio?

    TIS+ is a hosted Day 2 operations solution for Istio designed to streamline workflows for platform and support teams. It offers:

  • A global service dashboard
  • Multi-cluster visibility
  • Service topology visualization
  • Workspace-based access control
  • Learn more
    Decorative CTA background pattern background background
    Tetrate logo in the CTA section Tetrate logo in the CTA section for mobile

    Ready to enhance your
    network

    with more
    intelligence?