Istio routing based on headers without header propagation
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 holds the App container, which is the headers propagation.
The Envoy proxy cannot correlate the packets that it sends to the App to those that the App is responding to, so the headers cannot be automatically propagated by Istio.
In most cases, a headers-based routing would need the App developers to implement a library. For example, in Istio’s flagship Bookinfo app, the productpage
microservice implements it like this. This drive us to a question:
How can a Platform Admin have headers-based routing without modifying the application’s internals?
A swim-lane approach
Using Bookinfo app, we’re segmenting different request paths based on a x-version
header like:
The implementation will randomly select backends paths if no x-version
header is present.
Deploy workloads
We will use Istio’s Bookinfo example with some minor changes regarding versioning apps to show an implementation.
First of all, create 3 different 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 3 deployments for 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 configs
Here’s where Istio’s routing capabilities come into play. A DestinationRule subsets
for each productpage
version needs to be 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 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
See how the last match called productpage-default-route
takes care of request with no x-version
header. In this case, a random response from any of the lanes will be given. Note the random case is not in the diagram at the beginning of this blog post.
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 gatewaymesh
for this field to be applicable.Source: https://istio.io/latest/docs/reference/config/networking/virtual-service/#HTTPMatchRequest
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>
In this article you could see how using match
on headers, subsets
and sourceLabels
in Istio it is possible to route based on headers with no header propagation. You could also see the usage of delegate
functionality as well as withoutHeaders
matching.