Oct 01, 2018 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:
- Deploying 12-factor apps to Knative
- Building and deploying applications to Knative
- Adding public traffic to Knative on Google Kubernetes Engine
- Adding a custom hostname domain for Knative services
- Build Docker images inside your Kubernetes with Knative Build
- Binding secrets to Knative services
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 (
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.
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.
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.
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
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
$ 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.
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.
An overview of Knative Serving Module includes this diagram of routes to revisions of the service:
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
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
$ 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.
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
Dockerfileor 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
The knative-dev Google Group has summaries and proposals.
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