How to run OpenWhisk Actions on Knative?
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:
- Creating and Invoking Javascript Actions (this article covers it)
- Actions with JSON in / JSON out Interface
- Creating Asynchronous Actions
- Creating Actions to call External APIs
- Packaging Actions as Node.js module with NPM packages
- OpenWhisk Web Actions
- Actions with access to HTTP body parameters (mapped to
__OW_BODY
) - Anonymous Invocation via HTTP GET, PUT, DELETE, POST, or OPTIONS
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.
Pre-requisites:
Before we proceed, please make sure you have Knative installed on Docker Desktop.
- Docker Desktop for Mac Docker Community Edition 2.0.1.0 2019–01–11 which includes (1) Docker 18.09.1 and (2) Kubernetes 1.13.0
- Kubectl (
brew install kubernetes-cli
) - Knative 0.5.0
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.
- Register Secrets for Your Container Registry on Knative
- Create Service Account for our Knative Builds
- Install the Build Template for the NodeJS Runtime
- Deploy NodeJS Runtime with Hello World Action Code
- Serve NodeJS Runtime as a Knative Service — Runtime with Function Baked-In
- Run Hello World Action
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.
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.
$ 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
.
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
:
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.
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 thebuild.yaml
undermetadata
. 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! 👊