You are viewing docs for an older version of Linkerd. View the latest docs.
  • GitHub
  • Slack
  • Linkerd Forum

Configuring Dynamic Request Routing

Prerequisites

To use this guide, you’ll need to have Linkerd installed on your cluster and the Helm CLI installed.

Dynamic request routing

With dynamic request routing, you can route HTTP traffic based on the content of request headers. This can be useful for performing things like A/B testing and other strategies for traffic management.

The core configuration mechanism are the Gateway API types, HTTPRoute and GRPCRoute. In this example we’ll look at the common use case using the HTTPRoute type.

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

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).

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: alternative 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

Implementation notes

In the example above, we used the x-request-id header, which is a common header that is forwarded by podinfo. However, the same technique will work with arbitrary headers, as long as the application forwards them.

Note also that dyanmic request routing is client-side behavior, so while the traffic source (in this case, the frontend pod) needs to be meshed, strictly speaking, the destination does not need to be meshed.