How to run OpenWhisk Actions on Knative?

Priti Desai
Apache OpenWhisk
Published in
6 min readMay 1, 2019

It’s now time to show case what it takes to run an existing OpenWhisk action on Knative. Matt Rutkowski and I are very excited to share our process of building and serving OpenWhisk actions on Knative here. We started prototyping OpenWhisk NodeJS Runtime with a hello world action. Later, extended the runtime to handle more complex use cases such as:

Let’s look at how to run simple hello world OpenWhisk action on Knative here. We will explore all the above use cases, one per blog post.

helloworld.js

Pre-requisites:

Before we proceed, please make sure you have Knative installed on Docker Desktop.

I started experimenting with Docker Desktop but the same set of instructions also worked on minikube which I will cover in follow up post.

Note: Knative 0.5.0 version using Docker Desktop which will require you raise your default minimum memory size for Kube from 8 GiB to 9 GiB otherwise you may experience “out of memory” errors deploying images to pods.

Build and Serve OpenWhisk Runtime on Knative

It’s a six step process, first three steps are one time deployment per Knative installation. The whole build and serve process is based on Knative Source-to-URL workflow.

  1. Register Secrets for Your Container Registry on Knative
  2. Create Service Account for our Knative Builds
  3. Install the Build Template for the NodeJS Runtime
  4. Deploy NodeJS Runtime with Hello World Action Code
  5. Serve NodeJS Runtime as a Knative Service — Runtime with Function Baked-In
  6. Run Hello World Action
Build and Serve OpenWhisk Runtime on Knative

Step 1: Register Secrets for Your Container Registry on Knative

Knative needs access to your container registry in order to push locally built container image. I am using Docker Hub here but you can use any registry of your choice by changing the registry name under annotation.

Save this file as docker-secret.yaml after replacing DOCKERHUB_USERNAME_BASE64_ENCODED and DOCKERHUB_PASSWORD_BASE64_ENCODED with your Docker Hub username/password.

docker-secret.yaml.tmpl

Apply the secret resource manifest for Docker Hub:

$ kubectl apply -f docker-secret.yaml
secret/dockerhub-user-pass created

Verify secret exists:

$ kubectl get secret
NAME TYPE DATA AGE
dockerhub-user-pass kubernetes.io/basic-auth 2 21s

Step 2: Create Service Account for our Knative Builds

Create a service account to link the build process with the registry secret created in Step 1 so that Knative build system can push container images to the registry using these credentials.

service-account.yaml
$ kubectl apply -f service-account.yaml
serviceaccount/openwhisk-runtime-builder created

Verify the service account exists:

$ kubectl get serviceaccount/openwhisk-runtime-builder
NAME SECRETS AGE
openwhisk-runtime-builder 2 3m46s

Step 3: Install the Build Template for the NodeJS Runtime

As part of this effort, we have updated OpenWhisk runtime to support Knative Build Template which can be deployed once in a Kubernetes cluster and shared among all NodeJS OpenWhisk applications.

$ kubectl apply -f https://raw.githubusercontent.com/apache/incubator-openwhisk-runtime-nodejs/master/core/nodejsActionBase/buildtemplate.yaml
buildtemplate.build.knative.dev/openwhisk-nodejs-runtime created

Verify the Build Template exists:

$ kubectl get buildtemplate
NAME AGE
openwhisk-nodejs-runtime 2m

Build Template defines a set of parameters needed while building the OpenWhisk NodeJS runtime. Parameters marked as required have to be defined in a build file. Here is the list of important parameters:

  • TARGET_IMAGE_NAME: Name of the image to be tagged and pushed (Required).
  • DOCKERFILE: Name of the dockerfile (Required).
  • OW_RUNTIME_PLATFORM: Flag to indicate the platform, one of [“openwhisk”, “knative”, … ] (default is openwhisk).
  • OW_ACTION_NAME: Name of the action (default is no name).
  • OW_ACTION_CODE: JavaScript source code to be evaluated (default is no code).
  • OW_PROJECT_URL: Location of a remote file which contains NodeJS action code (default is no url).

Step 4: Deploy NodeJS Runtime with Hello World Action Code

Configure the build file to point to your Docker Hub repo by replacing DOCKER_USERNAME with your username. Also, notice the way we have defined build template parameters here in build.yaml.

build.yaml

Deploy NodeJS runtime with action code:

$ kubectl apply -f build.yaml
build.build.knative.dev/nodejs-10-helloworld created

Verify the build pod exists:

$ kubectl get build.build.knative.dev/nodejs-10-helloworld
NAME SUCCEEDED REASON STARTTIME COMPLETIONTIME
nodejs-10-helloworld True 8m

If for any reason, there is a failure creating the build, we can troubleshoot the deployment with:

kubectl logs <pod> — all-containers

One single command can get us logs from all the containers in a build pod.

$ kubectl get pods  | grep nodejs-10-helloworld-pod
nodejs-10-helloworld-pod-93dd80 0/1 Completed 0 8m
$ kubectl logs nodejs-10-helloworld-pod-93dd80 --all-containers

kubectl get pod/<pod> -c <container>

If you are having difficulty finding the failure logs, get the list of init containers from that build pod and get the logs from each of the containers in a build pod.

$ kubectl get pod/nodejs-10-helloworld-pod-93dd80 -o yaml

The above kubectl command lists the init containers and their status under initContainerStatuses:

  • build-step-credential-initializer
  • build-step-git-source-0
  • build-step-add-ow-env-to-dockerfile
  • build-step-build-openwhisk-nodejs-runtime

Get the logs from the last container build-step-build-openwhisk-nodejs-runtime:

kubectl logs nodejs-10-helloworld-pod-93dd80 -c build-step-build-openwhisk-nodejs-runtime
INFO[0007] Downloading base image node:10.15.0-stretch
error building image: getting stage builder for stage 0: Get https://index.docker.io/v2/: net/http: TLS handshake timeout

Step 5: Serve NodeJS Runtime as a Knative Service — Runtime with Function “Baked-In”

Now that we have built the OpenWhisk NodeJS runtime image with the helloworld function baked into it, we can deploy that image as a Knative Service.

Configure the service template to point to the Docker Hub repo where the OpenWhisk runtime (built in step 4) will be pulled from. Replace ${DOCKER_USERNAME} and create service.yaml:

service.yaml

Deploy the runtime:

$ kubectl apply -f service.yaml
service.serving.knative.dev/nodejs-helloworld created

If for any reason, there is a failure creating the service, we can troubleshoot the deployment either by getting logs or executing shell commends:

kubectl exec <pod> -c <container> -- cmd

After getting the name of the pod with:

$ kubectl get pods | grep nodejs-helloworld-
nodejs-helloworld-00001-deployment-fb94b59d-gwhw9 3/3 Running 0 76s

Get the list of containers under that pod with:

$ kubectl describe nodejs-helloworld-00001-deployment-fb94b59d-gwhw9

Run shell commands inside user-container with:

$ kubectl exec nodejs-helloworld-00001-deployment-fb94b59d-gwhw9 -c user-container -- env 
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=nodejs-helloworld-00001-deployment-fb94b59d-gwhw9
K_REVISION=nodejs-helloworld-00001
K_CONFIGURATION=nodejs-helloworld
K_SERVICE=nodejs-helloworld
PORT=8080
KUBERNETES_PORT=tcp://10.96.0.1:443
...

Step 6: Run Hello World Action

Depending on your Knative installation, determine how to access your Knative gateway and run:

curl -H "Host: nodejs-helloworld.default.example.com" -X POST http://<IP_ADDRESS>:<PORT>
{"payload":"Hello World!"}

Hooray 💃 We have our simple hello world function running on Knative.

2-Stage Build using Knative Build Template

Now, it’s also possible to inject OpenWhisk functions into runtime after creating Knative service which is commonly knows as 2-stage build. In 2-stage build, we can build the OpenWhisk runtimes using Knative build template, serve it using Knative service, and then invoke the service to inject function code for which the process differs from Step 4. In Step 4, do not specify OW_ACTION_CODE and OW_ACTION_NAME in the build file instead add it in the query data.

build.yaml

Deploy NodeJS runtime:

$ kubectl apply -f build.yaml
build.build.knative.dev/nodejs-10-helloworld created

Note that the pod name still has helloworld which is specified in the build.yaml under metadata. You can assign any name to it. Also, the build can be deployed multiple times and served with different actions per service. The same service can not be initiated multiple times.

$ curl -H "Host: nodejs-helloworld.default.example.com" -d '{
"init": {
"name": "nodejs-helloworld",
"code": "function main() {return {payload: 'Hello World'};}"
}
}' -H "Content-Type: application/json" http://<IP_ADDRESS>:<PORT>
{"payload":"Hello World!"}

Subsequent invocations without init data to the same function results in:

curl -H "Host: nodejs-helloworld.default.example.com" -X POST http://<IP_ADDRESS>:<PORT>
{"payload":"Hello World!"}

This is it for now, stay tuned for more articles covering various different use cases.

Enjoy! 👊

--

--

Priti Desai
Apache OpenWhisk

Developer Lead @IBM. Tekton maintainer. Co-founder of License Scanner @Cyclonedx