Subset based traffic routing using Gateway and ServiceRoute
In this how-to, you’ll learn how to setup subset based traffic routing by matching traffic on uri endpoint, header and port and routing it to destination service's host:port.
The example used here demonstrates matching external traffic on hostname helloworld.tetrate.io
, end-user: jason
header
and port 443
, and routing it to service versions v1:v2
in the ratio 80:20
.
Prerequisites
Before you get started, make sure you:
✓ Familiarize yourself with TSB concepts
✓ Install the TSB demo environment
✓ Create a Tenant
Create workspace and config groups
apiversion: api.tsb.tetrate.io/v2
kind: Workspace
metadata:
organization: tetrate
tenant: tetrate
name: helloworld-ws
spec:
namespaceSelector:
names:
- '*/helloworld'
---
apiVersion: gateway.tsb.tetrate.io/v2
kind: Group
metadata:
organization: tetrate
tenant: tetrate
workspace: helloworld-ws
name: helloworld-gw
spec:
namespaceSelector:
names:
- '*/helloworld'
configMode: BRIDGED
---
apiVersion: traffic.tsb.tetrate.io/v2
kind: Group
metadata:
organization: tetrate
tenant: tetrate
workspace: helloworld-ws
name: helloworld-trf
spec:
namespaceSelector:
names:
- '*/helloworld'
configMode: BRIDGED
Store the file as helloworld-ws-groups.yaml
, and apply with tctl
:
tctl apply -f helloworld-ws-groups.yaml
Deploy your application
To deploy your application, start by creating the namespace and enable the Istio sidecar injection.
kubectl create namespace helloworld
kubectl label namespace helloworld istio-injection=enabled
apiVersion: v1
kind: Service
metadata:
name: helloworld
labels:
app: helloworld
service: helloworld
spec:
ports:
- port: 5000
name: http
selector:
app: helloworld
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: helloworld-v1
labels:
app: helloworld
version: v1
spec:
replicas: 1
selector:
matchLabels:
app: helloworld
version: v1
template:
metadata:
labels:
app: helloworld
version: v1
spec:
containers:
- name: helloworld
image: docker.io/istio/examples-helloworld-v1
resources:
requests:
cpu: '100m'
imagePullPolicy: IfNotPresent #Always
ports:
- containerPort: 5000
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: helloworld-v2
labels:
app: helloworld
version: v2
spec:
replicas: 1
selector:
matchLabels:
app: helloworld
version: v2
template:
metadata:
labels:
app: helloworld
version: v2
spec:
containers:
- name: helloworld
image: docker.io/istio/examples-helloworld-v2
resources:
requests:
cpu: '100m'
imagePullPolicy: IfNotPresent #Always
ports:
- containerPort: 5000
Store as helloworld-2-subsets.yaml
, and apply with kubectl
:
kubectl apply -f helloworld-2-subsets.yaml -n helloworld
Deploy IngressGateway
apiVersion: install.tetrate.io/v1alpha1
kind: IngressGateway
metadata:
name: tsb-helloworld-gateway
namespace: helloworld
spec:
kubeSpec:
service:
type: LoadBalancer
Save as helloworld-ingress.yaml
, and apply with kubectl
:
kubectl apply -f helloworld-ingress.yaml
The TSB data plane operator in the cluster will pick up this configuration and deploy the gateway’s resources in your application namespace.
Get the Gateway IP. The following command will set the environment variable GATEWAY_IP
in your current shell.
You will use this environment variable in the next scenarios.
export GATEWAY_IP=$(kubectl -n helloworld get service tsb-helloworld-gateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
To confirm that you have a valid Gateway IP, you can use the following command to display the IP address.
echo $GATEWAY_IP
Finally, configure the gateway so that it routes traffic to your application.
Certificate for gateway
In this example, you’re going to expose the application using simple TLS at the gateway. You’ll need to provide it with a TLS certificate stored in a Kubernetes secret.
kubectl create secret tls -n helloworld helloworld-cert \
--cert /path/to/some/helloworld-cert.pem \
--key /path/to/some/helloworld-key.pem
Deploy Gateway
and ServiceRoute
First, create a Gateway.
- Gateway
- Legacy
apiVersion: gateway.tsb.tetrate.io/v2
kind: Gateway
metadata:
name: helloworld-gateway
group: helloworld-gw
workspace: helloworld-ws
tenant: tetrate
organization: tetrate
spec:
workloadSelector:
namespace: helloworld
labels:
app: tsb-helloworld-gateway
http:
- name: helloworld
port: 443
hostname: helloworld.tetrate.com
tls:
mode: SIMPLE
secretName: helloworld-cert
routing:
rules:
- route:
serviceDestination:
host: helloworld/helloworld.helloworld.svc.cluster.local
port: 5000
From TSB 1.7.0 onwards, we have merged both Tier1Gateway and IngressGateway capabilities under a common resource Gateway
apiVersion: gateway.tsb.tetrate.io/v2
kind: IngressGateway
metadata:
name: helloworld-gateway
group: helloworld-gw
workspace: helloworld-ws
tenant: tetrate
organization: tetrate
spec:
workloadSelector:
namespace: helloworld
labels:
app: tsb-helloworld-gateway
http:
- name: helloworld
port: 443
hostname: helloworld.tetrate.com
tls:
mode: SIMPLE
secretName: helloworld-cert
routing:
rules:
- route:
host: helloworld/helloworld.helloworld.svc.cluster.local
port: 5000
Save as helloworld-gw.yaml
, and apply with tctl
:
tctl apply -f helloworld-gw.yaml
You can check that your application is reachable by opening your web browser and directing it to the gateway service IP or domain name (depending on your configuration).
Next, create a ServiceRoute. This service route will match traffic on header end-user: jason
, and
route traffic in the ratio 80:20
between service versions v1:v2
. If no header is provided, then it routes entire
traffic to service version v1
.
The spec.service
in ServiceRoute
should match with some spec.http[*].routing.rules.route[*].host
of the
IngressGateway
created above. Otherwise, the routing rules mentioned in the ServiceRoute
will never take effect.
Both spec.service
in ServiceRoute
and spec.http[*].routing.rules.route[*].host
in IngressGateway
should be in namespace/fqdn
format.
apiVersion: traffic.tsb.tetrate.io/v2
kind: ServiceRoute
metadata:
name: helloworld-service-route
group: helloworld-trf
workspace: helloworld-ws
tenant: tetrate
organization: tetrate
spec:
service: helloworld/helloworld.helloworld.svc.cluster.local
portLevelSettings:
- port: 5000
trafficType: HTTP
subsets:
- name: v1
labels:
version: v1
weight: 50
- name: v2
labels:
version: v2
weight: 50
httpRoutes:
- name: http-route-match-header-and-port
match:
- name: match-header-and-port
headers:
end-user:
exact: jason
port: 5000
destination:
- subset: v1
weight: 80
port: 5000
- subset: v2
weight: 20
port: 5000
- name: http-route-match-port
match:
- name: match-port
port: 5000
destination:
- subset: v1
weight: 100
port: 5000
Save as helloworld-header-based-routing-service-route.yaml
,
and apply with tctl
:
tctl apply -f helloworld-header-based-routing-service-route.yaml
Verify result
Request with header
Send consecutive curl requests with header end-user: jason
. In this case, first route http-route-match-header-and-port
should be selected and traffic will get routed in the ratio 80:20
between v1:v2
.
for i in {1..20}; do curl -k -H "X-B3-Sampled: 1" "https://helloworld.tetrate.com/hello" \
--resolve "helloworld.tetrate.com:443:$GATEWAY_IP" \
-H "end-user: jason" 2>&1; done
Hello version: v2, instance: helloworld-v2-5b46bc9f84-wlsvh
Hello version: v2, instance: helloworld-v2-5b46bc9f84-wlsvh
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v2, instance: helloworld-v2-5b46bc9f84-wlsvh
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
You can see that v1
was returned majority of the times, which means traffic got routed according to first route.
Request without header
Send consecutive curl requests without any header. In this case, second route http-route-match-port
will be
selected and entire traffic will be routed to service version Hello version: v1
.
for i in {1..20}; do curl -k -H "X-B3-Sampled: 1" "https://helloworld.tetrate.com/hello" \
--resolve "helloworld.tetrate.com:443:$GATEWAY_IP" 2>&1; done
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
Hello version: v1, instance: helloworld-v1-fdb8c8c58-b2p2l
You can see that all responses were from Hello version: v1
.