There’s been a lot of talk lately about serverless, Kubernetes, and Knative. So let’s start by explaining where Knative fits in this ecosystem and what makes it unique. Knative is great for application developers who already use Kubernetes, providing them with tools to focus less on infrastructure and plumbing and more on the code they are writing. This mission is not too different from other serverless platforms, but where most serverless platforms have a top-down approach of providing a new code-centric interface, Knative focuses on building these tools and services in a way that elevates the existing Kubernetes experience. This provides an ever growing class of developers (Kubernetes users) with immediate benefits and an easy on-ramp to serverless development. To this end, Knative is built with the same patterns (controllers), API (kube-api), and infrastructure (kubernetes resources) as Kubernetes itself. Knative also provides scale-to-zero functionality, which enables true zero-cost usage for idle applications, and Blue/Green deployments for testing new versions of your serverless applications.

Knative components

There are three main sub-projects that comprise Knative:

  • Serving provides scale-to-zero, request-driven compute functionality. Essentially the execution and scaling components of a serverless platform.
  • Build provides expressive run-to-completion functionality that’s useful for creating CI/CD workflows. This is what Serving uses to turn a source repository in to a container image that contains your application.
  • Eventing provides abstracted delivery and subscription mechanisms which allow for building loosely-coupled and event-driven serverless applications.

Importantly, Knative aims for these components (and many more sub-components) to be loosely coupled. This means it should be feasible for service providers and users to mix, match and replace these components as they see fit. For example, although Serving can make use of Build to turn your source repository in to a serverless application, a savvy user could also implement their own Build machinery, which uses another build platform but still uses Knative serving.

The Serving component

In this article, we’ll focus on the Serving sub-project as it is a natural starting point for diving into Knative. There are four main resources that a Knative serving user should be familiar with: Service, Route, Configuration, and Revision.

Knative structure

Source: https://github.com/knative/docs/tree/master/serving

Revision: This represents a single instance of your application. It contains a specific image and a specific set of configuration options (such as environment variables) in immutable form. It is responsible for managing the resources to realize these including the Kubernetes deployment which runs your application.

Configuration: This is the interface for creating revisions and as such most of the application life-cycle management occurs via this resource. Think of this as your deployment script: a configuration is responsible for defining the application image and its configuration, similar to a revision, except these values are mutable. This allows for updating an environment variable or image tag within a configuration in order to deploy a new version. Whenever these values are changed a new instance of the application (Revision) is created. Consequently, there is always at least one Revision for a given Configuration.

Route: The directing of traffic to a specific revision occurs via this resource. When using a Service for a simple deployment, you don’t often need to be aware of this resource, as it operates in a default mode by sending all traffic to the latest (newest) revision. Users can also use this resource to specify traffic splits by percentage (e.g., a/b deployments where 10 percent of traffic goes to revision 2 and 90 percent goes to revision 1). This topic is not covered here in this article, but you can find a demonstration of this functionality here: https://github.com/knative/docs/tree/master/serving/samples/traffic-splitting.

(Knative) Service: Not to be confused with the Kubernetes service resource, this Knative Service is the highest-level resource that ties together a complete serverless application. For simple usage (such as our hello-world example below), this is the only resource that users need to interact with to deploy their application. When a Service is created a Route and Configuration are also created, which are explained in more detail below.

Hello, World!

We’ll assume that you have a working Knative installation and kubectl access to the Knative cluster. (See https://github.com/knative/docs/blob/master/install/README.md for more information on installing Knative.)

Let’s define the following Knative Service by using kubectl by writing the following content to a file (service.yaml) and running:

$ kubectl -f service.yaml

apiVersion: serving.knative.dev/v1alpha1
kind: Service
metadata:
  name: helloworld-go
  namespace: default
spec:
  runLatest:
    configuration:
      revisionTemplate:
        spec:
          container:
            image: <helloworld-go docker image>
            env:
            - name: TARGET
              value: "Go Sample v1"

This is all we need to create a request-driven instance of the helloworld-go application.

Look up the IP address where you can reach your application by copying the EXTERNAL IP field from the following command:

$ kubectl get svc knative-ingressgateway --namespace istio-system

NAME                     TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)                                      AGE
knative-ingressgateway   LoadBalancer   10.23.247.74   35.203.155.229   80:32380/TCP,443:32390/TCP,32400:32400/TCP   2d

Then look up the domain for your application with the following command, copying the DOMAIN field:

$ kubectl get ksvc helloworld-go

NAME            DOMAIN                              LATESTCREATED         LATESTREADY           READY     REASON
helloworld-go   helloworld-go.default.example.com   helloworld-go-00001   helloworld-go-00001   True

You can then make a request to your application by using the following command:

$ curl -H "Host: {DOMAIN}" http://{IP_ADDRESS}

Hello World: Go Sample v1!

Congratulations, you’ve just successfully deployed your first Knative serverless application!

Service

Now let’s have a look at the resources which comprise our application. Let’s start by looking at the service we defined with:

$ kubectl get ksvc helloworld-go

NAME            DOMAIN                              LATESTCREATED         LATESTREADY           READY     REASON
helloworld-go   helloworld-go.default.example.com   helloworld-go-00001   helloworld-go-00001   True

This shows us the domain of our application, and the latest created and ready revisions of our application. As this is the exact resource where we defined originally, there is not much more to see here so let’s dive in to some of the other resources Knative created for us.

Configuration

When we defined our Knative service, a configuration was automatically created for our application. We can view this configuration by using the following command:

$ kubectl get configuration helloworld-go -o yaml

apiVersion: serving.knative.dev/v1alpha1
kind: Configuration
metadata:
  name: helloworld-go
  namespace: default
  <other_metadata>
spec:
  generation: 1
  revisionTemplate:
    metadata:
      annotations:
        sidecar.istio.io/inject: "false"
      creationTimestamp: null
    spec:
      container:
        env:
        - name: TARGET
          value: Go Sample v1
        image: <image_url>/helloworld-go
        name: ""
        resources: {}
      containerConcurrency: 1
      timeoutSeconds: 1m0s
status:
  conditions:
  <conditions>
  latestCreatedRevisionName: helloworld-go-00001
  latestReadyRevisionName: helloworld-go-00001
  observedGeneration: 1

Some of the output has been removed to make reading this easier. As you can see, based on the command we issued, the name of this configuration (helloworld-go) matches the name of the service we defined – this is always the case when defining a service. The most interesting component is the spec.revisionTemplate section.

Revision

Let’s look at the revision that was created from our configuration by running the following command:

$ kubectl get revision helloworld-go-00001 -o yaml

apiVersion: serving.knative.dev/v1alpha1
kind: Revision
metadata:
  name: helloworld-go-00001
  namespace: default
  <metadata>
spec:
  container:
    env:
    - name: TARGET
      value: Go Sample v1
    image: <image_url>
    name: ""
    resources: {}
  containerConcurrency: 1
  generation: 1
  timeoutSeconds: 1m0s
status:
  conditions:
  <conditions>
  serviceName: helloworld-go-00001-service

Same as with our configuration, we removed some of the revision output to make it easier to read. As mentioned earlier, whenever our configuration is modified a new revision is created, also an initial revision is created for any configuration. As this is the first revision, it gets the revision number 00001. You should also notice that the revision spec matches our revisionTemplate from the configuration above.

Let’s now experiment with changing our configuration (via our service) and observe the creation of a new revision. We can do this by changing the line value: "Go Sample v1" in our service.yaml file to: value: "Go Sample v2".

We can then update the service by using: kubectl apply -f service.yaml.

Let’s have a look at our current revisions by using the following command:

$ kubectl get revision

NAME                  SERVICE NAME                  READY     REASON
helloworld-go-00001   helloworld-go-00001-service   True
helloworld-go-00002   helloworld-go-00002-service   True

As you can see, a new revision (helloworld-go-00002) has been created for us. We can also see the response for our application has changed by re-running the curl command:

$ curl -H "Host: {DOMAIN}" http://{IP_ADDRESS}

Hello World: Go Sample v2!

Route

Let’s now take a look at the route that Knative created for our service, by issuing the following command:

$ kubectl get route helloworld-go -oyaml

apiVersion: serving.knative.dev/v1alpha1
kind: Route
metadata:
  name: helloworld-go
  namespace: default
  <other metadata>
spec:
  generation: 1
  traffic:
  - configurationName: helloworld-go
    percent: 100
status:
  address:
    hostname: helloworld-go.default.svc.cluster.local
  <conditions>
  domain: helloworld-go.default.example.com
  domainInternal: helloworld-go.default.svc.cluster.local
  traffic:
  - percent: 100
    revisionName: helloworld-go-00002

As you can see, this resource’s spec section is fairly lightweight compared to our other resources, as it only consists of a generation and traffic section. The traffic section is what allows a user to specify either a configuration (like we have here), which will always direct traffic to the most recent ready revision or to a specific revision instead. When targeting a specific revision, it’s worth noting that you can specify a list of revisions–each with their own target percentage, which then allows a user to roll out changes gradually, sending only a small percentage of traffic to a new revision intially.

Conclusion

In this article, you discovered how to deploy a simple helloworld serverless application, which scales entirely based on request load. You also learned about the components that make up Knative’s Serving project and how to inspect the base building blocks with kubectl. This article only touched the surface of Knative and I encourage you to seek out more information, available in the Knative docs repository: https://github.com/knative/docs. If you’re interested in flexing your new Knative skills, check out how to Automate Knative installation on the cloud.