Knative: Serving your Serverless Services

This is part 1 of a series on serverless applications with Knative. Part 2 is here and part 3 is here.

 

There has been a lot of talk about serverless. so let’s ask first: What is serverless computing? To understand, here is the  CNCF definition of serverless computing:

Serverless computing refers to the concept of building and running applications that do not require server management. It describes a finer-grained deployment model where applications, bundled as one or more functions, are uploaded to a platform and then executed, scaled, and billed in response to the exact demand needed at the moment

Let’s dissect the definition to see what makes an ideal serverless platform.

  • Building and running applications

  A platform to run and orchestrate applications i.e Kubernetes which is your new application server.

  • No server management

No dedicated application servers, all applications are containerized and deployed via Kubernetes Deployments and Services.

  • Executed, scaled, and billed in response to the exact demand

The ability to scale up from zero and then scaling down back to zero is a natural feature of Kubernetes via its ReplicationSet.

One of the things that Kubernetes currently does not offer to a serverless platform is a finer grained deployment model.  But with Knative Serving, Kubernetes finally has what is needed to be able to run serverless workloads.

Knative Serving with Istio provides the deployment model for our services to be run as serverless workloads.

Let’s get started on how to deploy our first serverless application on Kubernetes. For this blog, I will be using 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.

Application Overview

The demo application we will be building is a greeter application built using Spring Boot. You can find the sources of the application in the GitHub repository.

Git clone the resources using the command:

git clone https://github.com/workspace7/knative-serving-blogs

All the sources for the examples shown in this blog are available in the directory “part-1” located in the root of the source repository.

For convenience we will refer to this directory as $PROJECT_HOME in rest of this post.

Containerize Application

Before we turn this application into a serverless service, we need to containerize the application.

To be able attach to the Docker daemon of the OKD cluster, run the command:

(minishift docker-env) && eval(minishift oc-env)

To build the application container image of the greeter application, run the command:

./mvnw -DskipTests clean compile jib:dockerBuild

This application uses jib, a tool to build Java applications as container images.

After the successful run of the above command, doing docker images |grep dev.local/greeter should list you one image that we have built just now.

Deploying Serverless Service

By running kubectl apply -f service.yaml -n myproject, we will be triggering the deployment of the our first serverless service. You can watch the pod status using the command oc get -n myproject pods -w. A successful service deployment should create a Kubernetes Deployment like “greeter-00001-deployment”

Once all the pods are up (which typically takes few minutes) we can run run the script ${PROJECT_HOME}/bin/call_greeter.sh to see a response “Java::Knative on OpenShift”.

TIP:

Time out is configurable as well, for this demo sake let us change:

    • scale-to-zero-threshold from 5m to 1m kubectl -n knative-serving get cm config-autoscaler -o yaml | yq w -  data.scale-to-zero-threshold 1m | kubectl apply -f -
    • scale-to-zero-grace-period from 2m to 30s kubectl -n knative-serving get cm config-autoscaler -o yaml | yq w -  data.scale-to-zero-grace-period 30s | kubectl apply -f -

This command makes use of a command line based YAML editing tool called yq.

When you leave the application without any requests for around 1 min, you will see the it getting auto scaled down to zero automatically. Running the command ${PROJECT_HOME}/bin/call_greeter.sh again, you will notice the pod comes up to serve the request again with the output  “Java::Knative on OpenShift”.

Congratulations! You have now depoyed your first serverless application on Kubernetes!

Now, to explain what just happened. Let’s start with understanding the main building blocks of Knative-Serving that makes your traditional server-based Spring Boot application into a modern serverless application. Each of these building blocks are Custom Resources Definitions(CRD) defined and deployed as part of Knative-Serving:

  • Configuration(configuration.serving.knative.dev)

The  Configuration is the core of the serverless application, it helps define the desired state by tagging the deployment to the latest revision or pinning it to a known revision. This also helps the application adhere to principles of Twelve-Factor App methodology by keeping code and configuration separate.

  • Revision(revision.serving.knative.dev)

The Revision can be thought of like a named group of changes that are performed on the service via its configuration, similar to a git commit. The revision history is maintained by Knative-Serving, which helps in deploying the required revision at at any given point of time. Following Twelve-Factor App methodology each configuration change triggers the creation of new revision.

  • Route(route.serving.knative.dev)

The Route defines the network endpoint via DNS names that allow clients to consume the service. It is possible to have one too many routes for the same service. When deploying a service using Service ,a default route is created matching the configuration, with 100% of the traffic routed to revision configured via Configuration revision strategy namely runLatest or pinned.

The routing configurations are defined via ConfigMap “config-domain” in the “knative-serving” namespace, the config map defines possible domain names that can be used as a domain of the route.

 kubectl -n knative-serving get cm config-domain -o yaml | yq r - data

All routes are accessed via “knative-ingressgateway” , the default DNS name of the route will be the like [servicename].[namespace].[domain value from config-domain]

 e.g. greeter.solutions.example.com

  • Service(service.serving.knative.dev)

When deploying a serverless workload we usually need to create and deploy all the above resources. But with Service it is possible to create the other resource objects automatically, thereby managing the entire lifecycle of the serverless workload.

Now we’ve explored the finer grained Knative Serving deployment model and how it helps to deploy serverless services. To understand it better inline with our demo application we shall start seeing the core resource file “service.yaml”:

kind: Service
metadata:
name: greeter
spec:
runLatest:
configuration:
revisionTemplate:
spec:
container:
image: dev.local/greeter:0.0.1

This service creates a service.serving.knative.dev service with a name “greeter” which runs the latest revision as part of its serverless workload.

The configuration defines a “revisionTemplate” defining which container image that will be deployed and serving the requests. The “revisionTemplate” is a very important element of the configuration.serving.knative.dev that lets the Knative-Serving to automatically rollout new revisions when anything within the  “revisionTemplate” changes e.g. adding a environment variable to the container or changing the image name etc.

To see it in action lets update the “service.yaml” with an extra environment variable called “MESSAGE_PREFIX”:

kind: Service
metadata:
name: greeter
spec:
runLatest:
configuration:
revisionTemplate:
spec:
container:
image: dev.local/greeter:0.0.1
env:
- name: MESSAGE_PREFIX
value: Hello

Deploying the greeter service again using kubectl apply -f service.yaml will cause a new revision like “greeter-00002” will be created and a new Kubernetes Deployment, like “greeter-00002-deployment”, to be rolled out.

You can check the available revisions using the command kubectl get revisions.serving.knative.dev. Invoking the service via  ${PROJECT_HOME}/bin/call_greeter.sh to see a response like “Hello, Java::Knative on OpenShift”.

Try this!

You can try the same idling technique without sending any request to the service for 1 min, but by this time you would have noticed that “greeter-00001” would have already scaled to zero or was starting to scale down. With current time expiry will cause new revision “greeter-00002” to scale down.

Now invoking the service again via ${PROJECT_HOME}/bin/call_greeter.sh will bring back only “greeter-00002” (the latest revision that has been configured) to send you a response like “Hello,Java::Knative on OpenShift”.

Summary

I hope you now have a better understanding of Knative-Serving, its building blocks, and how to deploy your first serverless application.

Categories
Containers, Kubernetes, Serverless
Tags
, , ,