Why a Gateway on top of Knative application
To explain this let me touch on my previous blog post. Assuming that Knative serving is already available in a Kubernetes environment, the way to deploy an application is using a manifest which looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | apiVersion: serving.knative.dev/v1alpha1 kind: Service metadata: name: sample-boot-knative-service namespace: default spec: runLatest: configuration: revisionTemplate: spec: container: image: bijukunjummen/sample-boot-knative-app: 0.0 . 3 -SNAPSHOT env: - name: ASAMPLE_ENV value: "sample-env-val" |
Now to invoke this application, I have to make the call via an ingress created by Knative serving, which can be obtained the following way in a minikube environment:
1 | export GATEWAY_URL=$(echo $(minikube ip):$(kubectl get svc knative-ingressgateway -n istio-system -o 'jsonpath={.spec.ports[?(@.port==80)].nodePort}' )) |
The request now has to go through the ingress and the ingress uses a Host http header to then route the request to the app. The host header for the deployed service can be obtained using the following bash script:
1 | export APP_DOMAIN=$(kubectl get services.serving.knative.dev sample-boot-knative-service -o= "jsonpath={.status.domain}" ) |
and then a call via the knative ingress gateway made the following way, using CURL:
1 2 3 4 5 6 7 8 9 | -H "Accept: application/json" \ -H "Content-Type: application/json" \ -H "Host: ${APP_DOMAIN}" \ -d $'{ "id" : "1" , "payload" : "one" , "delay" : "300" }' |
or using httpie:
1 | http http: //${GATEWAY_URL}/messages Host:"${APP_DOMAIN}" id=1 payload=test delay=1 |
There are too many steps involved in making a call to the application via the knative ingress:
My objective in this post is to simplify the users experience in making a call to the app by using a Gateway like Ambassador.
Integrating Ambassador to Knative
There is nothing special about installing Ambassador into a Knative environment, the excellent instructions provided here worked cleanly in my minikube environment.
Now my objective with the gateway is summarized in this picture:
With Ambassador in place, all the user has to do is to send a request to Ambassador Gateway and it would take care of plugging in the Host header before making a request to the Knative Ingress.
So how does this work, fairly easily! assuming Ambassador is in place, all it needs is a configuration which piggybacks on a Kubernetes service the following way:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | --- apiVersion: v1 kind: Service metadata: name: sample-knative-app-gateway annotations: getambassador.io/config: | --- apiVersion: ambassador/v0 kind: Mapping name: sample-boot-knative-app prefix: /messages rewrite: /messages service: knative-ingressgateway.istio-system.svc.cluster.local host_rewrite: sample-boot-knative-service. default .example.com spec: type: LoadBalancer ports: - name: ambassador port: 80 targetPort: 80 selector: service: ambassador |
Here I am providing configuration via a Service annotations, intercepting any calls to /messages uri and forwarding these request to the knative ingressgatway service (knative-ingressgateway.istio-system.svc.cluster.local) and adding the host header of "sample-boot-knative-service.default.example.com".
Now the interaction from a user perspective is far simpler, all I have to do is to get the url for this new service and to make the api call, in a minikube environment using the following bash script:
1 2 3 | export AMB_URL=$(echo $(minikube ip):$(kubectl get svc sample-knative-app-gateway -n default -o 'jsonpath={.spec.ports[?(@.port==80)].nodePort}' )) http http: //${AMB_URL}/messages id=1 payload=test delay=1 |
It may be easier to try this on a real code, which is available my github repo here.