Configuring Dynamic Request Routing
Prerequisites
To use this guide, you’ll need to have Linkerd installed on your cluster. Follow the Installing Linkerd Guide if you haven’t already done this (make sure you have at least linkerd stable-2.13.0 or edge-23.3.2).
You also need to have the Helm CLI installed.
HTTPRoute for Dynamic Request Routing
With dynamic request routing, you can route HTTP traffic based on the contents of request headers. This can be useful for performing things like A/B testing and many other strategies for traffic management.
In this tutorial, we’ll make use of the podinfo project to showcase dynamic request routing, by deploying in the cluster two backend and one frontend podinfo pods. Traffic will flow to just one backend, and then we’ll switch traffic to the other one just by adding a header to the frontend requests.
Setup
First we create the test
namespace, annotated by linkerd so all pods that get
created there get injected with the linkerd proxy:
kubectl create ns test --dry-run=client -o yaml \
| linkerd inject - \
| kubectl apply -f -
Then we add podinfo’s Helm repo, and install two instances of it. The first one
will respond with the message “A backend
”, the second one with “B backend
”.
helm repo add podinfo https://stefanprodan.github.io/podinfo
helm install backend-a -n test \
--set ui.message='A backend' podinfo/podinfo
helm install backend-b -n test \
--set ui.message='B backend' podinfo/podinfo
We add another podinfo instance which will forward requests only to the first
backend instance backend-a
:
helm install frontend -n test \
--set backend=http://backend-a-podinfo:9898/env podinfo/podinfo
Once those three pods are up and running, we can port-forward requests from our local machine to the frontend:
kubectl -n test port-forward svc/frontend-podinfo 9898 &
Sending Requests
Requests to /echo
on port 9898 to the frontend pod will get forwarded the pod
pointed by the Service backend-a-podinfo
:
$ curl -sX POST localhost:9898/echo \
| grep -o 'PODINFO_UI_MESSAGE=. backend'
PODINFO_UI_MESSAGE=A backend
Introducing HTTPRoute
Let’s apply the following HTTPRoute
resource to enable header-based routing:
cat <<EOF | kubectl -n test apply -f -
apiVersion: policy.linkerd.io/v1beta2
kind: HTTPRoute
metadata:
name: backend-router
namespace: test
spec:
parentRefs:
- name: backend-a-podinfo
kind: Service
group: core
port: 9898
rules:
- matches:
- headers:
- name: "x-request-id"
value: "alternative"
backendRefs:
- name: "backend-b-podinfo"
port: 9898
- backendRefs:
- name: "backend-a-podinfo"
port: 9898
EOF
Note
Two versions of the HTTPRoute resource may be used with Linkerd:
- The upstream version provided by the Gateway API, with the
gateway.networking.k8s.io
API group - A Linkerd-specific CRD provided by Linkerd, with the
policy.linkerd.io
API group
The two HTTPRoute resource definitions are similar, but the Linkerd version implements experimental features not yet available with the upstream Gateway API resource definition. See the HTTPRoute reference documentation for details.
In parentRefs
we specify the resources we want this HTTPRoute
instance to
act on. So here we point to the backend-a-podinfo
Service on the HTTPRoute
’s
namespace (test
), and also specify the Service port number (not the Service’s
target port).
Warning
HTTPRoute
s and
ServiceProfile
s provide overlapping
configuration. For backwards-compatibility reasons, a ServiceProfile
will
take precedence over HTTPRoute
s which configure the same Service. If a
ServiceProfile
is defined for the parent Service of an HTTPRoute
,
proxies will use the ServiceProfile
configuration, rather than the
HTTPRoute
configuration, as long as the ServiceProfile
exists.Next, we give a list of rules that will act on the traffic hitting that Service.
The first rule contains two entries: matches
and backendRefs
.
In matches
we list the conditions that this particular rule has to match. One
matches suffices to trigger the rule (conditions are OR’ed). Inside, we use
headers
to specify a match for a particular header key and value. If multiple
headers are specified, they all need to match (matchers are AND’ed). Note we can
also specify a regex match on the value by adding a type: RegularExpression
field. By not specifying the type like we did here, we’re performing a match of
type Exact
.
In backendRefs
we specify the final destination for requests matching the
current rule, via the Service’s name
and port
.
Here we’re specifying we’d like to route to backend-b-podinfo
all the requests
having the x-request-id: alterrnative
header. If the header is not present,
the engine fall backs to the last rule which has no matches
entries and points
to the backend-a-podinfo
Service.
The previous requests should still reach backend-a-podinfo
only:
$ curl -sX POST localhost:9898/echo \
| grep -o 'PODINFO_UI_MESSAGE=. backend'
PODINFO_UI_MESSAGE=A backend
But if we add the “x-request-id: alternative
” header they get routed to
backend-b-podinfo
:
$ curl -sX POST \
-H 'x-request-id: alternative' \
localhost:9898/echo \
| grep -o 'PODINFO_UI_MESSAGE=. backend'
PODINFO_UI_MESSAGE=B backend
To Keep in Mind
Note that you can use any header you like, but for this to work the frontend has
to forward it. “x-request-id
” is a common header used in microservices, that is
explicitly forwarded by podinfo, and that’s why we chose it.
Also, keep in mind the linkerd proxy handles this on the client side of the request (the frontend pod in this case) and so that pod needs to be injected, whereas the destination pods don’t require to be injected. But of course the more workloads you have injected the better, to benefit from things like easy mTLS setup and all the other advantages that linkerd brings to the table!