This is part 3 of a series on serverless applications with Knative. Part 1 is here, Part 2 is here and if you are interested in a deep dive tutorial then its here Knative tutorial
Introduction
In Part-1 of this series, you were introduced on how deploy your first serverless service using Knative Serving and in Part-2 you were able to understand how to build the serverless services using Knative Build. You may still have following questions, however:
- What is a Revision? Why is it needed?
- What is a route and what is its relation to a revision?
- Can I split traffic between revisions?
This post will answer these questions, but to get started I will answer the easiest of these questions: What is a Revision?
Revision is an immutable snapshot of the serverless workload at a given point in time. Sound confusing? Imagine a revision to be like a version control “tag.” You can create any number of tags based on a commit but you can never edit or push a commit to a named tag.
But why revision?
In Knative serving, things are driven via a Configuration to make a separation between code (container images) and config. One of the good practices in configuration management is that we should always be able to rollback the application state to any “last known good configuration”, to be able to allow this type of rollback Knative creates an unique revision for each and every configuration change.
Application Overview
For this post, let’s use OKD, the community distribution of Kubernetes that powers Red Hat OpenShift. To run OKD locally, we can use Minishift, a single node OKD cluster that can be used for development purposes.
These instructions will detail on how to install and configure Knative Serving on Minishift.The demo application we will be building is a greeter application using Spring Boot. As part of this demo, we will also be able to route the traffic to multiple revisions of the same greeter service.
First, clone the repo using the following command:
<span>git clone <span>https://github.com/workspace7/knative-serving-blogs</span></span>
The sources for the examples shown in this post are available in the directory called “part-2” located in the root of the source repository. For convenience, we will refer to this directory (part-2) as $PROJECT_HOME in rest of this post.
Deploying the Serverless Service
In Part 1 of this series we utilized the all-in-one service based deployment workflow which created the needed objects such as a configuration, route and revisions automatically.
In Part 2 we added build to the service to have a source-to-url workflow. It was similar to the all-in-one service based workflow but instead of having to build the container images separately we embedded it as part of the service deployment.
In this post, we introduce you to another way of deploying your serverless workloads, where you will manually create the required objects such as configuration (with integrated build) and routes.
Increase max user namespace
minishift ssh
sudo -i
sysctl user.max_user_namespaces=15000 && sysctl -p
Skip Tag to Digest
val=$(oc -n knative-serving get cm config-controller -oyaml \
| yq r - data.registriesSkippingTagResolving \
| awk '{print $1",docker-registry.default.svc:5000"}')
oc -n knative-serving get cm config-controller -oyaml \
| yq w - data.registriesSkippingTagResolving $val \
| oc apply -f -
Check the Part-2 of this blog series for more information about the significance of these settings.
Before we can deploy the services, let’s have the build template deployed, this template will be used build section of the configuration to allow building of container images from sources:-
cd $PROJECT_HOME/buildoc apply -f templates/java-buildah-template.yaml
By running oc apply -f config/java/configuration_rev1.yaml -n myproject
from $PROJECT_HOME, we trigger the deployment of the greeter service. You can watch the pod status using the command oc get pods -w -n myproject
. A successful service deployment should create a Kubernetes Deployment like “greeter-00001-deployment”:
Though you have created a new service deployment via configuration, your service is not accessible yet as you have not yet defined a Knative route to it. Let’s create a route by running the command oc apply -f route/route_default.yaml -n myproject
Now you can run the script ${PROJECT_HOME}/bin/call.sh
to invoke the service and see a response “Java Knative on OpenShift”.
TIP:
Time out is configurable as well, for this demo sake let us change the scale-to-zero-threshold from 5m to 30s and the scale-to-zero-grace-period from 2m to 1m.
oc -n knative-serving get cm config-autoscaler -o yaml \
| yq w - data.scale-to-zero-threshold 30s \
| kubectl apply -f -
oc -n knative-serving get cm config-autoscaler -o yaml \
| yq w - data.scale-to-zero-grace-period 1m \
| kubectl apply -f -
If there are no invocations to the application for 1 minute(default idle time), you will see the application getting automatically scaled down to zero. Running the command ${PROJECT_HOME}/bin/call.sh
again, you will notice the pod comes up to serve the request again.
Let’s examine the configuration and route objects that were created previously.
Configuration
Running this command will return the configuration object that was created for greeter service.
oc get configurations.serving.knative.dev greeter -o yaml
The yaml output of the above command is edited for brevity and shown below,
apiVersion: serving.knative.dev/v1alpha1
kind: Configuration
metadata:
generation: 1
labels:
serving.knative.dev/route: greeter
name: greeter
namespace: myproject
spec:
generation: 1
revisionTemplate:
metadata:
creationTimestamp: null
labels:
app: greeter
spec:
container:
image: docker-registry.default.svc:5000/myproject/greeter:0.0.1
name: ""
resources: {}
status:
latestCreatedRevisionName: greeter-00001
latestReadyRevisionName: greeter-00001
observedGeneration: 1
Analyzing the highlighted lines above
- The configuration object now has a label serving.knative.dev/route with a value of route name that was created using this configuration, in this case greeter.
- The status section has two attributes called
span> and span> both of them point to the one and only revision greeter-00001, which is the latest available revision.
Route
Running this command, will return the route object that was created for the greeter service.
<span>oc get routes.serving.knative.dev greeter -o yaml</span>
For a seasoned Kubernetes user the command above is unidomatic, as you would be wondering why should I use the fully qualified name such as routes.serving.knative.dev ?
To know why let’s execute the command oc api-resources | grep route
, the command should return two API resources with name routes that are of kind Route.
OpenShift does already have a route object called route.openshift.io. After deploying Knative serving it has added one more Custom Resources Definitions(CRD) called routes.serving.knative.dev.
In this current context we are more concerned with using Knative serving route, hence we used oc get routes.serving.knative.dev
with a fully qualified name to avoid any ambiguity.
TIP:
You can also use the short name rt like
oc get rt greeter -oyaml
to get greeter route oroc get rt
to get all the Knative routes
The YAML output of the above command is edited for brevity and shown below,
apiVersion: serving.knative.dev/v1alpha1
kind: Route
metadata:
generation: 1
name: greeter
namespace: myproject
spec:
generation: 1
traffic:
- configurationName: greeter
percent: 100
status:
domain: greeter.myproject.example.com
domainInternal: greeter.myproject.svc.cluster.local
targetable:
domainInternal: greeter.myproject.svc.cluster.local
traffic:
- percent: 100
revisionName: greeter-00001
Analyzing the highlighted lines above
span> the spec.traffic tells you
- Which service identified configurationName to which the traffic will be routed. The serving.knative.dev/route label of the configuration object identified by configurationName will be updated with the name of the route.
- What will be the percentage of traffic that needs to be routed, in this case 100%
This gives all the possible traffic to revisionName distribution, in this case as we have configured the route with configurationName “greeter” and the configuration has only one revision namely “greeter-00001”, all the traffic will be routed to greeter-00001.
-
DNS name of the route that can be used when calling the service from outside of the cluster. In this case “greeter.myproject.example.com” -
span> - DNS name of the route that can to be used when calling the service from within the cluster. In this case “greeter.myproject.svc.cluster.local”
Rolling out a new Revision
Before rolling out a new revision, let’s make sure your previous revision is still responsive. Invoking the service via ${PROJECT_HOME}/bin/call.sh
will send you a response like “Java Knative on OpenShift”.
As part of this new revision roll out, we will add the environment variable MESSAGE_PREFIX
to the configuration which will then be used by the application in the greeting response.
By running oc apply -f config/java/configuration_rev2.yaml -n myproject
, we trigger the deployment of the greeter service. You can watch the pod status using the command oc get pods -w -n myproject
. A successful service deployment should create a Kubernetes Deployment like “greeter-00002-deployment”:
Now you can run the script ${PROJECT_HOME}/bin/call.sh
to invoke the service and see a response “Hello Knative on OpenShift” instead of earlier “Java Knative on OpenShift”.
On reexamining our updated configuration via the command oc get routes.serving.knative.dev greeter -o yaml
we see:
apiVersion: serving.knative.dev/v1alpha1
kind: Configuration
metadata:
generation: 1
labels:
serving.knative.dev/route: greeter
name: greeter
namespace: myproject
spec:
generation: 2
revisionTemplate:
metadata:
creationTimestamp: null
labels:
app: greeter
spec:
container:
env:
- name: MESSAGE_PREFIX
value: Hi
image: docker-registry.default.svc:5000/myproject/greeter:0.0.1
name: ""
resources: {}
status:
latestCreatedRevisionName: greeter-00002
latestReadyRevisionName: greeter-00002
observedGeneration: 2
Analyzing the highlighted lines above
- The route label remains the same as you have not updated the route
- The
span> has been updated to add the environment variable but you are still using the same container image as previous revision - The
span> and span> are updated to “greeter-00002” which is the new revision of the service that was created as a result of the configuration change ( adding environment variable)
You can get the list of available revisions via the command oc get revisions -n myproject
or via OpenShift webconsole as shown below:
Distributing traffic between revisions
A common requirement in today’s distributed service world is to split traffic between revisions. This feature could be useful for testing or it could be used to try some new or selected features with a smaller percentage of users (Canary Release).
The route you deployed earlier was tagged to the configurationName, but you did not specify what revision to use. In such cases, Knative-serving by default will route all of the traffic to the latest revision.
To make your route distribute the traffic between revisions we need to define the route as follows,
apiVersion: serving.knative.dev/v1alpha1
kind: Route
metadata:
name: greeter
spec:
traffic:
- revisionName: greeter-00001
percent: 90
- revisionName: greeter-00002
percent: 10
In the traffic distribution above, you specify that you want the traffic to be split 90% to 10% between revision 1 (greeter-00001) and revision 2 (greeter-00002) of the greeter service.
Run the command oc apply -f route/route_rev1-90_rev2-10.yaml -n myproject
to deploy the updated route.
Now you can run the script ${PROJECT_HOME}/bin/call.sh
to invoke the service and see various responses of “Hi Knative on OpenShift” and “Java Knative on OpenShift”. As you have defined revision 1 (greeter-00001) to have more percentage of traffic, you will see more “Java Knative on OpenShift” compared to “Hi Knative on OpenShift”.
How does Knative manages traffic distribution ?
The traffic distribution between the revision of Knative services are managed by Isito via its virutalservices.
Knative serving automatically create one virtualservice per configuration, executingthe command oc get virtualservices you will see a virtualservice called “greeter” ( name of the virtualservice is same as that of the configuration) listed.
Examining the virtualservice, you will notice that the virtualservice is configured to route the traffic between two revisions greeter-00001 and greeter-00002.
Running oc get virtualservices greeter -o yaml
will show response as shown below: (The yaml response has been trimmed for brevity)
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: greeter
spec:
route:
- destination:
host: greeter-00001-service.myproject.svc.cluster.local
port:
number: 80
weight: 90
- destination:
host: greeter-00002-service.myproject.svc.cluster.local
port:
number: 80
weight: 10
Point to Ponder
An interesting point to note the traffic distribution(route) when the deployment is dormant i.e. scaled down to zero. You can use the command oc get virtualservices greeter -o yaml to get details about the greeter virutalservice:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: greeter
...
spec:
....
route:
- destination:
host: activator-service.knative-serving.svc.cluster.local
port:
number: 80
weight: 100
timeout: 60s
As you see all the traffic is routed to “activator” service ( Knative serving component). When the dormant service receives a request, the activator will activate i.e. scale up the service to its desired count ( replicas) and start to proxy the request to the activated service.
Try this!
The route folder in the $PROJECT_HOME has few more samples to demonstrate various traffic distribution amongst the service revisions like 50%-50%, 75%-25%.
After each deployment try to analyze the Istio virtualservice using the command oc get virtualservice greeter -oyaml
and observe the traffic distribution between routes of the revisions.
Summary
At this point, you should have an understanding of what a Revision is in Knative and appreciate its significance. You learned how to execute a configuration and setup a route workflow along with distributing the traffic between revisions. Lastly you also came to know how Knative serving activates a dormant service to start serving requests.
About the author
Browse by channel
Automation
The latest on IT automation for tech, teams, and environments
Artificial intelligence
Updates on the platforms that free customers to run AI workloads anywhere
Open hybrid cloud
Explore how we build a more flexible future with hybrid cloud
Security
The latest on how we reduce risks across environments and technologies
Edge computing
Updates on the platforms that simplify operations at the edge
Infrastructure
The latest on the world’s leading enterprise Linux platform
Applications
Inside our solutions to the toughest application challenges
Original shows
Entertaining stories from the makers and leaders in enterprise tech