OpenWhisk Docker Actions

OpenWhisk recently announced the following changes to Docker-based Actions.

Developers can now deploy runtime files to the Action environment prior to invocation.

This makes it much easier to support (almost) any programming language in OpenWhisk. Awesome!

Let’s start by explaining how this new feature works…

Docker Actions

Docker Actions in OpenWhisk are built from the following repository using the python:2.7.12-alpine base image. This image is available on Docker Hub as openwhisk/dockerskeletion.

The image includes a Python application which implements the HTTP API used to handle platform requests, e.g. invoke the action with these parameters.

This service executes a file (/action/exec) for each invocation. Replacing this file allows us to control the runtime environment.

Request parameters are passed, using a JSON string, as the first command-line argument. Response values are interpreted as JSON written to stdout.

Developers can now include a zip file when creating Docker-based Actions. This archive will be extracted into the /action directory prior to invocations. If the archive contains a file named exec this will replace the exectuable file called by the invocation handler.

Testing It Out

Using the wsk command-line, developers can create Actions using this Docker image.

If the archive file is missing, the /action/exec path contains the the following stub file.

$ wsk action create skeleton --docker openwhisk/dockerskeleton
ok: created action skeleton
$ wsk action invoke skeleton --blocking --result
{ "error": "This is a stub action. Replace it with custom logic." }

Let’s update this stub file to return a custom greeting.

$ cat exec
#!/bin/bash
echo "{ \"hello\": \"ran without a docker pull!\" }"
$ ./exec
{ "hello": "ran without a docker pull!" }
$ zip exec.zip exec
  adding: exec (stored 0%)
$ wsk action create custom_docker_action exec.zip --docker
ok: created action custom_docker_action
$ wsk action invoke custom_docker_action --blocking --result
{ "hello": "ran without a docker pull!" }

The archive file could include a static binary, or even a complete runtime, to replace the exec stub.

All files in the archive file will be available under the /action directory.

Running Locally

The openwhisk/dockerskeleton image exposes a Python-based HTTP server on port 8080.

Pulling the openwhisk/dockerskeleton image from Docker Hub allows us to run it locally for development.

$ docker pull openwhisk/dockerskeleton
$ docker run -it -p 8080:8080 openwhisk/dockerskeleton

The platform uses the following HTTP endpoints to initialise and invoke Actions.

  • POST /init -> Set up Action source from JSON payload.
  • POST /run -> Invoke Action

Initialising The Environment

Before invoking Actions using this image, we need to deploy and unpack the archive file into the /action directory.

Reviewing the Python source code, the platform triggers this by sending a HTTP POST with the following JSON to /init endpoint.

{
  "value": {
    "binary": true,
    "code": "..."
  }
}

code contains the archive file as a base64 encoded string.

Let’s try this out using the action archive we created above.

$ base64 exec.zip  | echo "\"$(cat)\"" | jq '{value: {binary: true, code: .}}' > init.json
$ cat init.json
{
  "value": {
    "binary": true,
    "code": "UEsDBAoAAAAAAOlqMEr1+JNAQQAAAEEAAAAEABwAZXhlY1VUCQADRcl8WFDJfFh1eAsAAQT1AQAABBQAAAAjIS9iaW4vYmFzaAplY2hvICJ7IFwiaGVsbG9cIjogXCJyYW4gd2l0aG91dCBhIGRvY2tlciBwdWxsIVwiIH0iClBLAQIeAwoAAAAAAOlqMEr1+JNAQQAAAEEAAAAEABgAAAAAAAEAAADtgQAAAABleGVjVVQFAANFyXxYdXgLAAEE9QEAAAQUAAAAUEsFBgAAAAABAAEASgAAAH8AAAAAAA=="
  }
}

Now we can issue the HTTP request to push this archive into the container.

$ http post localhost:8080/init < init.json
HTTP/1.1 200 OK
Content-Length: 2
Content-Type: text/html; charset=utf-8
Date: Mon, 16 Jan 2017 14:11:04 GMT

OK

Accessing the container filesystem allows us to verify the archive has been extracted correctly.

$ docker ps
CONTAINER ID        IMAGE                         COMMAND                  CREATED             STATUS              PORTS                    NAMES
b37a7dc1cab1        openwhisk/dockerskeleton      "/bin/bash -c 'cd ..."   About an hour ago   Up About an hour    0.0.0.0:8080->8080/tcp   relaxed_davinci
$ docker exec -it b37a7dc1cab1 /bin/sh
/ # cd /action
/action # ls
exec
/action # cat exec
#!/bin/bash
echo "{ \"hello\": \"ran without a docker pull!\" }"

Invocation Requests

Action invocations are triggered by sending a HTTP POST to the /run endpoint.

This endpoint expects the following JSON body.

{
  "value": {
    "foo": "bar"
  }
}

The inner object parameters under the value property are passed, as a JSON string, to the executable as the first command-line argument.

Sending this request to our container will trigger the shell script from our archive and return the JSON response.

$ echo "{}" | jq '{value: .}' | http post localhost:8080/run
HTTP/1.1 200 OK
Content-Length: 44
Content-Type: application/json
Date: Mon, 16 Jan 2017 14:17:15 GMT

{
    "hello": "ran without a docker pull!"
}

Conclusion

Recent updates to Docker-based Actions in OpenWhisk make it much easier to customise the runtime environment.

Being able to deploy arbitrary files into the runtime container, prior to invocation, simplifies the process of supporting new runtimes.

Hopefully this blog post has shown you how to get started with this feature.

Over the next few weeks, we’re going to show you how to use this approach to run lots of new programming languages on the platform. Stay tuned for updates…