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.
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:
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 subsets
for 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
Source: Istio Virtual Service Documentationgateways
field, it must include the reserved gatewaymesh
for this field to be applicable.
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.
###
If you’re new to service mesh, Tetrate has a bunch of free online courses available at Tetrate Academy that will quickly get you up to speed with Istio and Envoy.
Are you 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 by the Kubernetes Gateway API. Learn more ›
Getting started with Istio? If you’re looking for the surest way to get to production with Istio, check out Tetrate Istio Subscription. Tetrate Istio Subscription has everything you need to run Istio and Envoy in highly regulated and mission-critical production environments. It includes Tetrate Istio Distro, a 100% upstream distribution of Istio and Envoy that is FIPS-verified and FedRAMP ready. For teams requiring open source Istio and Envoy without proprietary vendor dependencies, Tetrate offers the ONLY 100% upstream Istio enterprise support offering.
Get a Demo