Deploying 12-factor apps to Knative

This is the first in a collection of articles as I figure out what’s what with Knative for Kubernetes. The full set of articles are:


At Google Next18, Google announced that the “serverless add-on” to GKE would be open sourced as Knative. Described as the building blocks for serverless platforms, it infers that Knative might not “just work”, and you might need Google, Pivotal, or RedHat in order to use Knative. Maybe. These are strange times in open source. From my initial exploration, Knative works; and when we add Heroku/Cloud Foundry buildpacks it starts to feel more and more like the Heroku/Cloud Foundry we know and love than raw Kubernetes.

In this article you and I will install Knative into your own Kubernetes (knctl install), and then we will have a lot of fun with Knative (knctl deploy).

Knative can do many tricks for operators running their stateless apps on Kubernetes. The most enticing feature for me is autoscaling: scaling up when more load appears, and fabulously scaling down to zero when no one loves your application anymore.

Download and install the handy knctl Knative CLI that will both install Knative into your Kubernetes, and deploy your applications.

On MacOS you can fetch it with our homebrew tap:

brew install starkandwayne/kubernetes/knctl

In this article I will assume you’re using Minikube. With Minikube you can configure with node ports rather than a load balancer:

minikube start --memory=8192 --cpus=3 \
  --kubernetes-version=v1.11.3 \
  --vm-driver=hyperkit \
  --bootstrapper=kubeadm
knctl install --node-ports --exclude-monitoring

See Knative docs for tips on installing to different Kubernetes.

The knctl install command will take a few minutes. Or ten minutes. Or longer. It is downloading a dozen or so container images. Neither your Internet bandwidth nor the size of the container images are easily changeable, so sit back and relax. Or go for a 10 minute walk.

If knctl install fails, it is likely that your Internet is slow and the Docker images had not finished downloading before the command timed out. Run kubectl get pods --all-namespaces until you see all pods running, then run the knctl install command again to continue.

Your Kubernetes cluster is now “serverless”. Sweet.

Run kubectl get pods --all-namespaces to see all the pods that make up bare Knative.

$ kubectl get pods --all-namespaces
NAMESPACE         NAME                                        READY   STATUS      RESTARTS   AGE
istio-system      istio-citadel-7d8f9748c5-zgm9x              1/1     Running     0          21m
istio-system      istio-cleanup-secrets-j4pkx                 0/1     Completed   0          21m
istio-system      istio-egressgateway-676c8546c5-dnwsd        1/1     Running     0          21m
istio-system      istio-galley-5669f7c9b-g774b                1/1     Running     0          21m
istio-system      istio-ingressgateway-5475685bbb-q5f2x       1/1     Running     0          21m
istio-system      istio-pilot-5795d6d695-9klrz                2/2     Running     0          21m
istio-system      istio-policy-7f945bf487-2wh88               2/2     Running     0          21m
istio-system      istio-sidecar-injector-d96cd9459-7knkm      1/1     Running     0          21m
istio-system      istio-statsd-prom-bridge-549d687fd9-lcmb7   1/1     Running     0          21m
istio-system      istio-telemetry-6c587bdbc4-t4jql            2/2     Running     0          21m
istio-system      knative-ingressgateway-7f4477dd99-n9wz2     1/1     Running     0          4m
knative-build     build-controller-7dcc4b7544-rkgwb           1/1     Running     0          4m
knative-build     build-webhook-fb6484576-sr4fk               1/1     Running     0          4m
knative-serving   activator-77d46b585d-b6g8n                  2/2     Running     0          4m
knative-serving   controller-85768cfd45-t8ktc                 1/1     Running     0          4m
knative-serving   webhook-56dd548f8-hjw44                     1/1     Running     0          4m
...

Deploy pre-built application

Let’s deploy an existing public Docker image as an autoscaling stateless application to Knative, in your current Kubernetes namespace:

kubectl create ns helloworld
knctl deploy \
      --namespace helloworld \
      --service hello \
      --image gcr.io/knative-samples/helloworld-go \
      --env TARGET=Rev1

The output will show that a new service hello is created, and its first revision hello-00001 is created and tagged as latest, and previous (being the first revision).

Name  hello
Waiting for new revision to be created...
Tagging new revision 'hello-00001' as 'latest'
Tagging new revision 'hello-00001' as 'previous'
Succeeded

We can construct a curl request into the Knative routing layer to our hello service:

$ knctl curl --namespace helloworld --service hello
Running: curl '-H' 'Host: hello.helloworld.example.com' 'http://192.168.64.8:32380'
Hello World: Rev1!
Succeeded

If this does not immediately print Hello World: Rev1! it may be because the system is still downloading the gcr.io/knative-samples/helloworld-go image. Wait and try again.

I’m using minikube with node ports ingress, which means I cannot setup nice DNS routing. I’ll save discussion of using a public load balancer, DNS, Knative Routing, and https://github.com/cppforlife/kwt for a future post.

After knctl deploy our Kubernetes will be running a single pod of our service:

$ kubectl get pods --namespace helloworld
NAME                                      READY   STATUS    RESTARTS   AGE
hello-00001-deployment-5864685cbc-v8r7n   3/3     Running   0          15s

Revisions of our service is a first-class feature of Knative. We can see that we have our single initial revision, which is receiving 100% of all traffic:

$ knctl revisions list --namespace helloworld --service hello
Revisions for service 'hello'
Name         Tags      Allocated Traffic %  Annotations  Age
hello-00001  previous  100%                 -            1m
             latest
1 revisions

The next time we knctl deploy will create a new revision and allocate all traffic to it:

$ knctl deploy \
      --namespace helloworld \
      --service hello \
      --image gcr.io/knative-samples/helloworld-go \
      --env TARGET=Rev2
Name  hello
Waiting for new revision (after revision 'hello-00001') to be created...
Tagging new revision 'hello-00002' as 'latest'
Tagging older revision 'hello-00001' as 'previous'
Succeeded

Requests now go to our new revision:

$ knctl revisions list --namespace helloworld --service hello
Revisions for service 'hello'
Name         Tags      Annotations  Conditions  Age  Traffic
hello-00002  latest    -            4 OK / 4    20s  100% -> hello.helloworld.example.com
hello-00001  previous  -            4 OK / 4    1m   -
$ knctl curl --namespace helloworld --service hello
Running: curl '-H' 'Host: hello.helloworld.example.com' 'http://192.168.64.8:32380'
Hello World: Rev2!

After deploying the second revision, initially both revisions’ pods will continue running:

$ kubectl get pods --namespace helloworld
NAME                                      READY   STATUS    RESTARTS   AGE
hello-00001-deployment-5864685cbc-v8r7n   3/3     Running   0          1m
hello-00002-deployment-7874bf89b8-4b4k5   3/3     Running   0          29s

We’ll see later that Knative automatically shuts down pods under zero load or if no routes point to a revision.

Routing

An overview of Knative Serving Module includes this diagram of routes to revisions of the service:

Knative Serving API

Revisions are an immutable snapshot of code, dependencies, and configuration. Revisions that are not referenced by routes are retired and their Kubernetes resources are deleted.

We can view the current routing of domains:

$ knctl routes list --namespace helloworld
Routes in namespace 'helloworld'
Name   Traffic         All Traffic Assigned  Ready  Domain                        Age
hello  100% -> hello:  true                  true   hello.helloworld.example.com  2h

Scale to zero

If you walk away for five minutes and come back, you will discover you have no hello-00001 pods running, or they are being terminated:

$ kubectl get pods --namespace helloworld
NAME                                      READY   STATUS        RESTARTS   AGE
hello-00001-deployment-5c79d4f99b-h622d   2/3     Terminating   0          6m
hello-00002-deployment-7ff7bf89d9-f72mn   3/3     Running       0          5m

And later, hello-00002 will terminate:

$ kubectl get pods --namespace helloworld
NAME                                      READY   STATUS        RESTARTS   AGE
hello-00001-deployment-5c79d4f99b-h622d   0/3     Terminating   0          6m
hello-00002-deployment-7ff7bf89d9-f72mn   3/3     Terminating   0          5m

And then:

$ kubectl get pods --namespace helloworld
No resources found.

The next time you knctl curl the service Knative will dynamically spin up a single pod to service the request.

$ knctl curl --namespace helloworld --service hello

There will be a multiple second lag on this first request as Knative spins up your revision again.

$ kubectl get pods --namespace helloworld
NAME                                      READY   STATUS    RESTARTS   AGE
hello-00002-deployment-7ff7bf89d9-f8755   3/3     Running   0          56s

I’m not yet sure why hello-00001-deployment-... pod does not get scaled down and terminated.

Future articles

In future articles I’ll explore:

  • Knative Build to automatically build container images from your own bespoke code (either sourced locally or from a Git repository) using Dockerfile or Cloud Foundry buildpacks
  • Setting up DNS into a Kubernetes Load Balancer to provide normal public URLs into Knative routes and services
  • Options for traffic splitting between different revisions (say 10% to latest revision, and 90% to previous revision; and then changing that thru to 100% for the latest and 0% for the previous revisions)
  • Knative Eventing for binding and delivery of CloudEvents within applications to provide even more serverlessness atop your servers

Community

The Knative core team hang out in their own Knative Slack. Request to join at https://slack.knative.dev

The knative-dev Google Group has summaries and proposals.

Thanks

Thanks to Google’s Mark Chmarny for being the first person to answer my barage of "But, why?" questions on Knative and Kubernetes during Spring One Platform 2018. He was a good sport.

Thanks to Pivotal’s Dmitriy Kalinin for spending time with me to bring Knative to life, and showing me his knctl Knative CLI and kwt Kubernetes Workstation Tool tools. A beautiful CLI for Knative helped bring the promise of Knative to life much more than a barrage of YAML files and low-level kubectl commands.

Spread the word

twitter icon facebook icon linkedin icon