Advanced routing

This guide will walk you through setting up your VirtualGateway with multiple external RouteTables in order to get more fine-grained control over how routes are organized.

Before You Begin

Before you begin, ensure that your setup for the management cluster (cluster-1) and a remote cluster (cluster-2) meets all of the following prerequisites:

To understand the custom resources that are used in this guide, see the Gateway Concepts Overview.

Setting up a Basic VirtualGateway and VirtualHosts

As a starting point, create the following VirtualGateway and VirtualHosts to route to the reviews and ratings services.


apiVersion: networking.enterprise.mesh.gloo.solo.io/v1beta1
kind: VirtualHost
metadata:
  name: demo-virtualhost
  namespace: gloo-mesh
  labels:
    app: bookinfo
spec:
  domains:
  - www.example.com
  routes:
  - matchers:
    - uri:
        prefix: /ratings
    name: ratings
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: ratings
          namespace: bookinfo
  - matchers:
    - uri:
        prefix: /reviews
    name: reviews
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: reviews
          namespace: bookinfo
---
apiVersion: networking.enterprise.mesh.gloo.solo.io/v1beta1
kind: VirtualGateway
metadata:
  name: demo-gateway
  namespace: gloo-mesh
spec:
  ingressGatewaySelectors:
  - portName: http2
    destinationSelectors:
    - kubeServiceMatcher:
        clusters:
        - cluster-1
        labels:
          istio: ingressgateway
        namespaces:
        - istio-system
  connectionHandlers:
  - http:
      routeConfig:
      - virtualHostSelector:
          namespaces:
          - gloo-mesh
          labels:
            app: bookinfo

cat << EOF | kubectl apply --context $REMOTE_CONTEXT1 -f -
apiVersion: networking.enterprise.mesh.gloo.solo.io/v1beta1
kind: VirtualHost
metadata:
  name: demo-virtualhost
  namespace: gloo-mesh
  labels:
    app: bookinfo
spec:
  domains:
  - www.example.com
  routes:
  - matchers:
    - uri:
        prefix: /ratings
    name: ratings
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: ratings
          namespace: bookinfo
  - matchers:
    - uri:
        prefix: /reviews
    name: reviews
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: reviews
          namespace: bookinfo
---
apiVersion: networking.enterprise.mesh.gloo.solo.io/v1beta1
kind: VirtualGateway
metadata:
  name: demo-gateway
  namespace: gloo-mesh
spec:
  ingressGatewaySelectors:
  - portName: http2
    destinationSelectors:
    - kubeServiceMatcher:
        clusters:
        - cluster-1
        labels:
          istio: ingressgateway
        namespaces:
        - istio-system
  connectionHandlers:
  - http:
      routeConfig:
      - virtualHostSelector:
          namespaces:
          - gloo-mesh
EOF

Verify that the istio-ingressgateway is exposed as a LoadBalancer service with an external IP address.

kubectl get service -n istio-system

In this example output, the EXTERNAL_IP address is 32.12.34.555:

NAME                   TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)                                                                      AGE
istio-ingressgateway   LoadBalancer   10.96.229.177   32.12.34.555   15021:31911/TCP,80:30166/TCP,443:32302/TCP,15012:30471/TCP,15443:31931/TCP   10m
istiod                 ClusterIP      10.96.180.254   <none>         15010/TCP,15012/TCP,443/TCP,15014/TCP                                        10m

If using an alternative environment such as KinD that does not support LoadBalancer services, you can forward the http2 port of the istio-ingressgateway deployment.

kubectl --context $REMOTE_CONTEXT1 -n istio-system port-forward deploy/istio-ingressgateway 8080

Now the ratings service should be available, which can be confirmed via an HTTP call:

curl -H "Host: www.example.com" localhost:8081/ratings/1
{"id":1,"ratings":{"Reviewer1":5,"Reviewer2":4}}

Adding Routes

Next, we will be updating the VirtualHost to route to both versions of the reviews service via subset routing. This will let you request a specific version of the reviews service via the route.


apiVersion: networking.enterprise.mesh.gloo.solo.io/v1beta1
kind: VirtualHost
metadata:
  name: demo-virtualhost
  namespace: gloo-mesh
spec:
  domains:
  - www.example.com
  routes:
  - matchers:
    - uri:
        prefix: /ratings
    name: ratings
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: ratings
          namespace: bookinfo
  - matchers:
    - uri:
        prefix: /reviews/v1
    name: reviews-v1
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: reviews
          namespace: bookinfo
          subset:
            version: v1
      pathRewrite: /reviews
  - matchers:
    - uri:
        prefix: /reviews/v2
    name: reviews-v2
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: reviews
          namespace: bookinfo
          subset:
            version: v2
      pathRewrite: /reviews

cat << EOF | kubectl apply --context $REMOTE_CONTEXT1 -f -
apiVersion: networking.enterprise.mesh.gloo.solo.io/v1beta1
kind: VirtualHost
metadata:
  name: demo-virtualhost
  namespace: gloo-mesh
spec:
  domains:
  - www.example.com
  routes:
  - matchers:
    - uri:
        prefix: /ratings
    name: ratings
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: ratings
          namespace: bookinfo
  - matchers:
    - uri:
        prefix: /reviews/v1
    name: reviews-v1
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: reviews
          namespace: bookinfo
          subset:
            version: v1
      pathRewrite: /reviews
  - matchers:
    - uri:
        prefix: /reviews/v2
    name: reviews-v2
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: reviews
          namespace: bookinfo
          subset:
            version: v2
      pathRewrite: /reviews
EOF

Now, you can test that both versions are accessible by sending requests to the new versioned endpoints:

curl -H "Host: www.example.com" localhost:8081/reviews/v1/1
curl -H "Host: www.example.com" localhost:8081/reviews/v2/1

Both endpoints should return the same review JSON, but the v2 endpoint will also have a rating.

Splitting Out the Routes

Now that multiple routes are set up for the /reviews prefix, you can create one RouteTable resource to split the routes into respective route tables for better organization. Route tables also provide more fine-grained access control because authorized users can have permission to edit specific route tables without having permission to edit the VirtualHost or other route tables. Start by creating a RouteTable resource with the v1 and v2 routes. Note that the /reviews prefix is omitted.


apiVersion: networking.enterprise.mesh.gloo.solo.io/v1beta1
kind: RouteTable
metadata:
  name: demo-routetable
  namespace: gloo-mesh
spec:
  routes:
  - matchers:
    - uri:
        prefix: /v1
    name: v1
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: reviews
          namespace: bookinfo
          subset:
            version: v1
      pathRewrite: /reviews
  - matchers:
    - uri:
        prefix: /v2
    name: v2
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: reviews
          namespace: bookinfo
          subset:
            version: v2
      pathRewrite: /reviews

cat << EOF | kubectl apply --context $REMOTE_CONTEXT1 -f -
apiVersion: networking.enterprise.mesh.gloo.solo.io/v1beta1
kind: RouteTable
metadata:
  name: demo-routetable
  namespace: gloo-mesh
spec:
  routes:
  - matchers:
    - uri:
        prefix: /v1
    name: v1
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: reviews
          namespace: bookinfo
          subset:
            version: v1
      pathRewrite: /reviews
  - matchers:
    - uri:
        prefix: /v2
    name: v2
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: reviews
          namespace: bookinfo
          subset:
            version: v2
      pathRewrite: /reviews
EOF

Now, specify a delegateAction field in the VirtualGateway to send all /reviews requests to the reviews route table.


apiVersion: networking.enterprise.mesh.gloo.solo.io/v1beta1
kind: VirtualHost
metadata:
  name: demo-virtualhost
  namespace: gloo-mesh
spec:
  domains:
  - www.example.com
  routes:
  - matchers:
    - uri:
        prefix: /ratings
    name: ratings
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: ratings
          namespace: bookinfo
  - matchers:
    - uri:
        prefix: /reviews
    name: reviews
    delegateAction:
      refs:
      - name: demo-routetable
        namespace: gloo-mesh

cat << EOF | kubectl apply --context $REMOTE_CONTEXT1 -f -
apiVersion: networking.enterprise.mesh.gloo.solo.io/v1beta1
kind: VirtualHost
metadata:
  name: demo-virtualhost
  namespace: gloo-mesh
spec:
  domains:
  - www.example.com
  routes:
  - matchers:
    - uri:
        prefix: /ratings
    name: ratings
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: ratings
          namespace: bookinfo
  - matchers:
    - uri:
        prefix: /reviews
    name: reviews
    delegateAction:
      refs:
      - name: demo-routetable
        namespace: gloo-mesh
EOF

Route behavior is unchanged, but the subroutes are now defined in a separate Kubernetes resource.

Next, say you want to create a catch-all 404 route for reviews on a different RouteTable:


apiVersion: networking.enterprise.mesh.gloo.solo.io/v1beta1
kind: RouteTable
metadata:
  name: demo-routetable2
  namespace: gloo-mesh
spec:
  routes:
  - matchers:
    - uri:
        prefix: /
    name: not-found
    directResponseAction:
      status: 404
      body: "'custom not found'"

cat << EOF | kubectl apply --context $REMOTE_CONTEXT1 -f -
apiVersion: networking.enterprise.mesh.gloo.solo.io/v1beta1
kind: RouteTable
metadata:
  name: demo-routetable2
  namespace: gloo-mesh
  labels:
    service: reviews
spec:
  routes:
  - matchers:
    - uri:
        prefix: /
    name: not-found
    directResponseAction:
      status: 404
      body: "'custom not found'"
EOF

Next, add the table to the VirtualHost.


apiVersion: networking.enterprise.mesh.gloo.solo.io/v1beta1
kind: VirtualHost
metadata:
  name: demo-virtualhost
  namespace: gloo-mesh
spec:
  domains:
  - www.example.com
  routes:
  - matchers:
    - uri:
        prefix: /ratings
    name: ratings
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: ratings
          namespace: bookinfo
  - matchers:
    - uri:
        prefix: /reviews
    name: reviews
    delegateAction:
      refs:
      - name: demo-routetable
        namespace: gloo-mesh
      - name: demo-routetable2
        namespace: gloo-mesh

cat << EOF | kubectl apply --context $REMOTE_CONTEXT1 -f -
apiVersion: networking.enterprise.mesh.gloo.solo.io/v1beta1
kind: VirtualHost
metadata:
  name: demo-virtualhost
  namespace: gloo-mesh
spec:
  domains:
  - www.example.com
  routes:
  - matchers:
    - uri:
        prefix: /ratings
    name: ratings
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: ratings
          namespace: bookinfo
  - matchers:
    - uri:
        prefix: /reviews
    name: reviews
    delegateAction:
      refs:
      - name: demo-routetable
        namespace: gloo-mesh
      - name: demo-routetable2
        namespace: gloo-mesh
EOF

It is important that the second route table is listed after the first route table in the list of references to prevent the second route table from short-circuiting all other routes. In this context, short-circuiting is when a more general route matches a request before another route with a more specific match has an opportunity. For example, if there is a route that has a prefix match / followed by a exact route match /health, a request to /health will get matched by the prefix / matcher first. You can now send a request to the reviews services endpoints to get the reviews, and all other requests to routes return the custom 404 message.

Improving the Sorting Logic

In the previous section, you added two RouteTables to handle routing logic for the reviews service and used a delegate action to route to the route tables. This process included adding every RouteTable to the VirtualHost delegate action. However, depending on your requirements, manually adding individual route table references might not be viable. Instead, you can specify a selector to the delegate action.


apiVersion: networking.enterprise.mesh.gloo.solo.io/v1beta1
kind: VirtualHost
metadata:
  name: demo-virtualhost
  namespace: gloo-mesh
spec:
  domains:
  - www.example.com
  routes:
  - matchers:
    - uri:
        prefix: /ratings
    name: ratings
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: ratings
          namespace: bookinfo
  - matchers:
    - uri:
        prefix: /reviews
    name: reviews
    delegateAction:
      selector:
        namespaces:
        - gloo-mesh

cat << EOF | kubectl apply --context $REMOTE_CONTEXT1 -f -
apiVersion: networking.enterprise.mesh.gloo.solo.io/v1beta1
kind: VirtualHost
metadata:
  name: demo-virtualhost
  namespace: gloo-mesh
spec:
  domains:
  - www.example.com
  routes:
  - matchers:
    - uri:
        prefix: /ratings
    name: ratings
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: ratings
          namespace: bookinfo
  - matchers:
    - uri:
        prefix: /reviews
    name: reviews
    delegateAction:
      selector:
        namespaces:
        - gloo-mesh
EOF

Requests are now automatically delegated to all RouteTables in the gloo-mesh namespace label without any changes to the VirtualHost. You can use a combination of specific RouteTable references and label selectors. However, RouteTable references take precedence over RouteTable selectors, which are sorted alphabetically by namespace and then name because Kubernetes does not guarantee a deterministic order when selecting multiple objects. Because the 404 route matches all requests to /reviews, the 404 route must come after the service routes to prevent it from short-circuiting them. In situations like this, you can add weights to the RouteTables in order to guarantee a sort order.


apiVersion: networking.enterprise.mesh.gloo.solo.io/v1beta1
kind: RouteTable
metadata:
  name: demo-routetable
  namespace: gloo-mesh
spec:
  routes:
  - matchers:
    - uri:
        prefix: /v1
    name: v1
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: reviews
          namespace: bookinfo
          subset:
            version: v1
      pathRewrite: /reviews
  - matchers:
    - uri:
        prefix: /v2
    name: v2
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: reviews
          namespace: bookinfo
          subset:
            version: v2
      pathRewrite: /reviews
  weight: 10

cat << EOF | kubectl apply --context $REMOTE_CONTEXT1 -f -
apiVersion: networking.enterprise.mesh.gloo.solo.io/v1beta1
kind: RouteTable
metadata:
  name: demo-routetable
  namespace: gloo-mesh
spec:
  routes:
  - matchers:
    - uri:
        prefix: /v1
    name: v1
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: reviews
          namespace: bookinfo
          subset:
            version: v1
      pathRewrite: /reviews
  - matchers:
    - uri:
        prefix: /v2
    name: v2
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: reviews
          namespace: bookinfo
          subset:
            version: v2
      pathRewrite: /reviews
  weight: 10
EOF

The routes of demo-routetable will now always be first because it has a higher weight than demo-routetable, which has the default weight of 0.

Now, say you want to add a version endpoint to each review service. You can add them to the new RouteTable.


apiVersion: networking.enterprise.mesh.gloo.solo.io/v1beta1
kind: RouteTable
metadata:
  name: demo-routetable2
  namespace: gloo-mesh
spec:
  routes:
  - matchers:
    - uri:
        exact: /v1/version
    name: v1-version
    directResponseAction:
      status: 200
      body: "'v1'"
  - matchers:
    - uri:
        exact: /v2/version
    name: v2-version
    directResponseAction:
      status: 200
      body: "'v1'"
  - matchers:
    - uri:
        prefix: /
    name: not-found
    directResponseAction:
      status: 404
      body: "'custom not found'"

cat << EOF | kubectl apply --context $REMOTE_CONTEXT1 -f -
apiVersion: networking.enterprise.mesh.gloo.solo.io/v1beta1
kind: RouteTable
metadata:
  name: demo-routetable2
  namespace: gloo-mesh
  labels:
    service: reviews
spec:
  routes:
  - matchers:
    - uri:
        exact: /v1/version
    name: v1-version
    directResponseAction:
      status: 200
      body: "'v1'"
  - matchers:
    - uri:
        exact: /v2/version
    name: v2-version
    directResponseAction:
      status: 200
      body: "'v2'"
  - matchers:
    - uri:
        prefix: /
    name: not-found
    directResponseAction:
      status: 404
      body: "'custom not found'"
EOF

Now if you try to send a request to the new version endpoints, you get a 404 response from the reviews service. The first route table that you created takes precedence due to its weight of 10, which means that the prefix route /reviews/v1 is being matched before the request reaches the new exact route /reviews/v1/version. You could adjust the weight of the second route table to come before the first, but the 404 route will then short-circuit the review service routes. One solution is to use a separate route table for the version endpoints and 404 endpoint. However, an easier method is to use the secondary type of Gloo Mesh Gateway routing.

Recall that the default type of sorting is by use of table weight, in which routes are kept in the same order that they appear on their table and relative to table order. The alternative type of sorting is by route specificity, in which routes are sorted according to a heuristic that estimates how specific a route is and orders routes from most to least specific in order to minimize short circuits. Exact match routes are considered the most specific, followed by regex match routes, followed by prefix match routes. Routes of the same match type are then compared based on length, in which longer matches are more specific.

The sorting action is set at the delegateAction level, and overwrites any sorting in delegateActions of child RouteTables. You can enable the ROUTE_SPECIFICITY sorting action on the VirtualHost to allow the routes of both tables to be sorted together.


apiVersion: networking.enterprise.mesh.gloo.solo.io/v1beta1
kind: VirtualHost
metadata:
  name: demo-virtualhost
  namespace: gloo-mesh
spec:
  domains:
  - www.example.com
  routes:
  - matchers:
    - uri:
        prefix: /ratings
    name: ratings
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: ratings
          namespace: bookinfo
  - matchers:
    - uri:
        prefix: /reviews
    name: reviews
    delegateAction:
      selector:
        namespaces:
        - gloo-mesh
      sortMethod: ROUTE_SPECIFICITY

cat << EOF | kubectl apply --context $REMOTE_CONTEXT1 -f -
apiVersion: networking.enterprise.mesh.gloo.solo.io/v1beta1
kind: VirtualHost
metadata:
  name: demo-virtualhost
  namespace: gloo-mesh
spec:
  domains:
  - www.example.com
  routes:
  - matchers:
    - uri:
        prefix: /ratings
    name: ratings
    routeAction:
      destinations:
      - kubeService:
          clusterName: cluster-1
          name: ratings
          namespace: bookinfo
  - matchers:
    - uri:
        prefix: /reviews
    name: reviews
    delegateAction:
      selector:
        namespaces:
        - gloo-mesh
      sortMethod: ROUTE_SPECIFICITY
EOF

The resulting routes are sorted such that both the version endpoints and the service endpoints can be matched against, and all other requests are matched to the 404 route.

You can learn more about the routing options by looking at the Helm values for routes.