Knative Traffic Splitting “helloworld”

Sample getting started tutorial on how to do traffic splitting with Knative Serving using Istio.

This is just my “hello world” application demonstrating stuff here:

While you can easily follow those tutorials, this article specifically designed to deconstruct the basic knative serving objects (Services, Congigurations, Routes, Revisions) and how to use them separately to split traffic.

We will be showing traffic splitting using

(A) Services

  • Deploy Service (v1)
  • Creates Revision istioinit-gke-0001
  • 100% traffic goes to istioinit-gke-0001
  • Create Revision istioinit-gke-0002
  • Update Service and split traffic between Revisions

(B) Confiuration and Routes

  • Deploy Configuration for (v1) and a Route to that Revision
  • Creates Revision istioinit-gke-0001
  • 100% traffic goes to istioinit-gke-0001
  • Create Configuration for (v2) and Revision istioinit-gke-0002
  • Update Route and split traffic between Revisions

The application i’ll be using is the handy one i used for various other tutorials that just returns the ‘version’ number (a simple 1 or 2 denoting the app verion that it hit). We will use the version number as the signal to confirm the routing scheme we intended to specify got applied.

You can find more info about the app istio_helloworld…the endpoint we’ll use is just /version.

You can find the source here:

and the “istio helloworld” bit here:

Setup

  • Install Istio, Knative on GKE.

We will not be using GKE+Istio Addon or Cloud Run; the installation of Istio and Knative is done by hand as if its installed on k8s directly (Knative with any k8s)

Install GKE and Istio

At this point, you’ll have a GKE cluster with Istio running. You can verify everything is ok by running:

$ kubectl get no,po,rc,svc,ing,deployment -n istio-system

Acquire the GATEWAY_IP allocated to istio-ingress

$ export GATEWAY_IP=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')$ echo $GATEWAY_IP

Install Knative

We’ll instlal Knative on top of Istio now:

Follow the instructions detailed here Install on a Kubernetes cluster

A) Traffic Splitting with Services

First lets deploy a Service by itself.

$ kubectl apply -f service_1.yamlapiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: istioinit-gke
namespace: default
spec:
template:
metadata:
name: istioinit-gke-0001
spec:
containers:
- image: gcr.io/mineral-minutia-820/istioinit:1

This will automatically create a Configuration, Revision and a Route all for you:

$ kubectl get ksvc,configuration,routes,revisions
NAME URL LATESTCREATED LATESTREADY READY REASON
service.serving.knative.dev/istioinit-gke http://istioinit-gke.default.example.com istioinit-gke-0001 istioinit-gke-0001 True
NAME LATESTCREATED LATESTREADY READY REASON
configuration.serving.knative.dev/istioinit-gke istioinit-gke-0001 istioinit-gke-0001 True
NAME URL READY REASON
route.serving.knative.dev/istioinit-gke http://istioinit-gke.default.example.com True
NAME CONFIG NAME K8S SERVICE NAME GENERATION READY REASON
revision.serving.knative.dev/istioinit-gke-0001 istioinit-gke istioinit-gke-0001 1 True

We can now send it some traffic

$ for i in {1..1000}; do curl -s  -H "Host: istioinit-gke.default.example.com" http://${GATEWAY_IP}/version; sleep 1; done
11111111111

What the simple 1 there signifies is the version of the application we've deploy (which is version 1 since we deployed just gcr.io/mineral-minutia-820/istioinit:1).

A Service by itself needs to target traffic towards a revision...but so far we only have one revision. Lets deploy a revision for version 2 of our application. That is, a revision that has gcr.io/mineral-minutia-820/istioinit:2:

config_2.yaml:

apiVersion: serving.knative.dev/v1
kind: Configuration
metadata:
name: istioinit-gke
namespace: default
spec:
template:
metadata:
name: istioinit-gke-0002
spec:
containers:
- image: gcr.io/mineral-minutia-820/istioinit:2
imagePullPolicy: IfNotPresent

Deploy and check the revisions, routes, configurations that got created:

$ kubectl apply -f config_2.yaml$ kubectl get ksvc,configuration,routes,revisions
NAME URL LATESTCREATED LATESTREADY READY REASON
service.serving.knative.dev/istioinit-gke http://istioinit-gke.default.example.com istioinit-gke-0001 istioinit-gke-0001 True
NAME LATESTCREATED LATESTREADY READY REASON
configuration.serving.knative.dev/istioinit-gke istioinit-gke-0001 istioinit-gke-0001 True
NAME URL READY REASON
route.serving.knative.dev/istioinit-gke http://istioinit-gke.default.example.com True
NAME CONFIG NAME K8S SERVICE NAME GENERATION READY REASON
revision.serving.knative.dev/istioinit-gke-0001 istioinit-gke istioinit-gke-0001 1 True
revision.serving.knative.dev/istioinit-gke-0002 istioinit-gke istioinit-gke-0002 2 True

We’ve got two revisions but the traffic for the Service is still going version 1.

To apply a 50/50 split on the Service itself, we need to add in the traffic section describing the targets

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: istioinit-gke
namespace: default
spec:
template:
metadata:
name: istioinit-gke-0002
spec:
containers:
- image: gcr.io/mineral-minutia-820/istioinit:2
traffic:
- revisionName: istioinit-gke-0001
percent: 50
- revisionName: istioinit-gke-0002
percent: 50

Apply the config:

$ kubectl apply -f service_12.yaml

At this point, the frontend service traffic should be split evenly-ish:

$ for i in {1..1000}; do curl -s  -H "Host: istioinit-gke.default.example.com" http://${GATEWAY_IP}/version; sleep 1; done
1222122222212212221121112122111

Wonderful! we’ve used Services to split traffic. Lets rollback the config so we can use the Configuration and Revision directly:

kubectl delete -f config_2.yaml -f service_12.yaml

Verify:

$ kubectl get ksvc,configuration,routes,revisions
No resources found.
# you may have to wait a couple of mins for the pods to terminate; this is somthing up w/ the app i have$ kubectl get po
No resources found.

Traffic Splitting with Configurations and Revisions

Now Deploy a config and route to that config for version 1:

cd config_revision/$ kubectl apply -f config_1.yaml -f route_1.yamlapiVersion: serving.knative.dev/v1
kind: Configuration
metadata:
name: istioinit-gke
namespace: default
spec:
template:
metadata:
name: istioinit-gke-0001
spec:
containers:
- image: gcr.io/mineral-minutia-820/istioinit:1
imagePullPolicy: IfNotPresent
---
apiVersion: serving.knative.dev/v1
kind: Route
metadata:
name: istioinit-gke
namespace: default
spec:
traffic:
- revisionName: istioinit-gke-0001
percent: 100

We didn’t deploy a service but an endpoite-route does get setup for you:

$ kubectl get ksvc,configuration,routes,revisions
NAME LATESTCREATED LATESTREADY READY REASON
configuration.serving.knative.dev/istioinit-gke istioinit-gke-0001 istioinit-gke-0001 True
NAME URL READY REASON
route.serving.knative.dev/istioinit-gke http://istioinit-gke.default.example.com True
NAME CONFIG NAME K8S SERVICE NAME GENERATION READY REASON
revision.serving.knative.dev/istioinit-gke-0001 istioinit-gke istioinit-gke-0001 1 True

Which means we can send traffic to it just as before:

$ for i in {1..1000}; do curl -s  -H "Host: istioinit-gke.default.example.com" http://${GATEWAY_IP}/version; sleep 1; done
11111111

Now deploy a configuration for version 2:

apiVersion: serving.knative.dev/v1
kind: Configuration
metadata:
name: istioinit-gke
namespace: default
spec:
template:
metadata:
name: istioinit-gke-0002
spec:
containers:
- image: gcr.io/mineral-minutia-820/istioinit:2
imagePullPolicy: IfNotPresent

As before we’ve got a new revision but traffic still goes to 1

$ kubectl get ksvc,configuration,routes,revisions
NAME LATESTCREATED LATESTREADY READY REASON
configuration.serving.knative.dev/istioinit-gke istioinit-gke-0002 istioinit-gke-0002 True
NAME URL READY REASON
route.serving.knative.dev/istioinit-gke http://istioinit-gke.default.example.com True
NAME CONFIG NAME K8S SERVICE NAME GENERATION READY REASON
revision.serving.knative.dev/istioinit-gke-0001 istioinit-gke istioinit-gke-0001 1 True
revision.serving.knative.dev/istioinit-gke-0002 istioinit-gke istioinit-gke-0002 2 True

To split traffic, we’ll update the the route now to send 50/50 traffic split:

route_12.yaml:

apiVersion: serving.knative.dev/v1
kind: Route
metadata:
name: istioinit-gke
namespace: default
spec:
traffic:
- revisionName: istioinit-gke-0001
percent: 50
- revisionName: istioinit-gke-0002
percent: 50
---

Then Verify:

$ for i in {1..1000}; do curl -s  -H "Host: istioinit-gke.default.example.com" http://${GATEWAY_IP}/version; sleep 1; done
2111111221222212222112

Thats it, this is a small snippet that shows traffic splitting w/ knative (well…atleast ast of 1/9/20).

good luck.