Dynamic routing with Gloo Edge

Photo by Jin xc on Unsplash

In the race for APIs, it’s best to think carefully about your API management solution. With Kubernetes gaining popularity, whether on-premises or on public clouds, we strongly suggest you adopt a Kubernetes-native solution like Gloo Edge.

An emerging need of our customers, especially those who operate multi-tenant platforms, is the ability to do dynamic routing. This means being able to select the appropriate upstream API in the moment, depending on the context of the request. This could be targeting a tenant’s workloads, or targeting a set of more or less powerful machines based on the user’s subscription, or targeting a shard of your customer database based on specific headers. Most API gateway solutions allow for path rewriting, but not all have dynamic routing.

Get ready for some dynamic routing configuration, GitOps-flavored!

Initial route selection

When an HTTP request enters the Envoy Proxy, the HTTP Connection Manager (HCM) first selects a route depending on Matchers.

  routes:
    - matchers:
      - prefix: /
        headers:
          - name: choice
            value: first

These matchers rely on headers (and also HTTP/2 pseudo-headers). Throughout the filter flow, headers can be modified, added, or deleted, and you might want to reconsider the route to be used, at runtime. To do this, you need dynamic routing.

So let’s explore three capabilities offered by Gloo Edge.

Dynamic route selection

Fortunately, Envoy has an option called clearRouteCache which resets the route selected by the HCM. Gloo Edge exposes this option in three different places:

  • Transformations – where you can build new headers
  • JWT – where you can extract claims and set them to new headers
  • ExtAuth authorization policies and custom plugins – where you can add or modify headers too

The HCM will then finalize its route selection based upon these new headers.

NOTE: if you want to know more about the Envoy filter flow with Gloo Edge, watch my video on YouTube.

Since you can modify some headers on the fly, routing possibilities are endless! The most common business use case I’ve seen is for tiering purposes. Another use case I see is extracting path segments, or JWT claims to new headers, and re-route to some other upstream service.

Dynamic RouteTable selection

You can take advantage of our delegation model, where developers and tenants can design their routes in their own namespace. You can delegate actions to any RouteTable, living in any namespace, using the special namespace: "*" selector.

apiVersion: gateway.solo.io/v1
kind: RouteTable
metadata:
  name: 'tenant-hub'
  namespace: 'gloo-system'
spec:
  routes:
    - matchers:
        prefix: "/api"
  delegateAction:
    selector:
      namespaces: "*"

 

Dynamic Upstream selection

We can even go one step further by using a special attribute from Envoy, also exposed by Gloo Edge: clusterHeader By using this attribute, you can tell Envoy which “upstream cluster” (~Upstream) you want the request to be sent.

That’s a bit tricky because you need to be sure that this Upstream actually exists and is known to Envoy. For Gloo, it means that the Upstream resource must exist (it can be automatically discovered or created manually).

Let’s see it in action! Say you have one Upstream per tenant, with the tenant ID in its name. You want to automatically route requests to the correct Upstream, depending on this ID, which is available in a JWT.

Here’s a diagram to illustrate this concept:

 

Say we have a JWT with the following payload:

{
  "sub": "abc",
  "iss": "my-jwt-issuer"
  "iat": 1516239022
}

Here is a way of doing this JWT claim-based dynamic routing:

  • First, extract the JWT sub claim to a new header, called x-gloo-tenant
  ...
  jwtStaged:
    afterExtAuth:
      providers:
        custom:
          claimsToHeaders:
            - claim: sub
              header: x-gloo-tenant
          jwks:
            ...
          tokenSource:
            headers:
              - header: Authorization
                prefix: Bearer
  • Then, create a new header, here named tenant-upstream, using a transformation and the following pattern: <upstream-name>_<upstream-namespace>:
  ...  
  stagedTransformations:
    regular:
      requestTransforms:
        - requestTransformation:
            transformationTemplate:
              headers:
               tenant-upstream:
                 text: "tenant-{{ header(\"x-gloo-tenant\") }}_analytics" # analytics being the namespace of the upstream service
          clearRouteCache: true
  • Note the clearRouteCache: true option in this transformation above.
  • Finally, use this new header in your RouteAction:
  ...
  routes:
    - matchers:
        - prefix: /analytics
      routeAction:
        clusterHeader: tenant-upstream

That will dynamically target the so-called tenant-abc_analytics Upstream and make your config much less verbose.

Acknowledgments

Thanks to our customers who help us define these use cases.

Credit to WayLay for successfully experimenting with these approaches and for their talk including these dynamic routing capabilities: