Advanced routing
This guide will walk you through setting up your VirtualGateway
with multiple external RouteTable
s 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 (cluster1
) and a remote cluster (cluster2
) meets all of the following prerequisites:
- Istio:
- Istio is installed in the
istio-system
namespace on both clusters istio-ingressgateway
is deployed in theistio-system
namespace ofcluster1
, which is the default installation namespace- The
bookinfo
app is installed in thebookinfo
namespace of both clusters
- Istio is installed in the
- Gloo Mesh:
- Gloo Mesh Enterprise is installed in relay mode in the
gloo-mesh
namespace ofcluster1
enterprise-networking
is deployed in thegloo-mesh
namespace ofcluster1
and exposes its gRPC server on port 9900enterprise-agent
is deployed on both clusters and exposes its gRPC server on port 9977- Both
cluster1
andcluster2
are registered with Gloo Mesh - The following environment variables are set:
REMOTE_CONTEXT1=cluster_1_context REMOTE_CONTEXT2=cluster_2_context
- Gloo Mesh Enterprise is installed in relay mode in the
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 VirtualHost
s 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: cluster1
name: ratings
namespace: bookinfo
- matchers:
- uri:
prefix: /reviews
name: reviews
routeAction:
destinations:
- kubeService:
clusterName: cluster1
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:
- cluster1
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: cluster1
name: ratings
namespace: bookinfo
- matchers:
- uri:
prefix: /reviews
name: reviews
routeAction:
destinations:
- kubeService:
clusterName: cluster1
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:
- cluster1
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: cluster1
name: ratings
namespace: bookinfo
- matchers:
- uri:
prefix: /reviews/v1
name: reviews-v1
routeAction:
destinations:
- kubeService:
clusterName: cluster1
name: reviews
namespace: bookinfo
subset:
version: v1
pathRewrite: /reviews
- matchers:
- uri:
prefix: /reviews/v2
name: reviews-v2
routeAction:
destinations:
- kubeService:
clusterName: cluster1
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: cluster1
name: ratings
namespace: bookinfo
- matchers:
- uri:
prefix: /reviews/v1
name: reviews-v1
routeAction:
destinations:
- kubeService:
clusterName: cluster1
name: reviews
namespace: bookinfo
subset:
version: v1
pathRewrite: /reviews
- matchers:
- uri:
prefix: /reviews/v2
name: reviews-v2
routeAction:
destinations:
- kubeService:
clusterName: cluster1
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: cluster1
name: reviews
namespace: bookinfo
subset:
version: v1
pathRewrite: /reviews
- matchers:
- uri:
prefix: /v2
name: v2
routeAction:
destinations:
- kubeService:
clusterName: cluster1
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: cluster1
name: reviews
namespace: bookinfo
subset:
version: v1
pathRewrite: /reviews
- matchers:
- uri:
prefix: /v2
name: v2
routeAction:
destinations:
- kubeService:
clusterName: cluster1
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: cluster1
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: cluster1
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: cluster1
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: cluster1
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: cluster1
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: cluster1
name: ratings
namespace: bookinfo
- matchers:
- uri:
prefix: /reviews
name: reviews
delegateAction:
selector:
namespaces:
- gloo-mesh
EOF
Requests are now automatically delegated to all RouteTable
s 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 RouteTable
s 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: cluster1
name: reviews
namespace: bookinfo
subset:
version: v1
pathRewrite: /reviews
- matchers:
- uri:
prefix: /v2
name: v2
routeAction:
destinations:
- kubeService:
clusterName: cluster1
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: cluster1
name: reviews
namespace: bookinfo
subset:
version: v1
pathRewrite: /reviews
- matchers:
- uri:
prefix: /v2
name: v2
routeAction:
destinations:
- kubeService:
clusterName: cluster1
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 delegateAction
s of child
RouteTable
s. 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: cluster1
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: cluster1
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.