Exploring interoperability between OpenWhisk and resin.io for IoT

Alex Glikson
Apache OpenWhisk
Published in
5 min readJan 31, 2017

Edge or Fog computing is gaining momentum in a plethora of IoT-enabled scenarios, and Cloud vendors are exploring opportunities to play a prominent role. One promising direction is to extend ‘serverless’ platforms such as OpenWhisk to edge devices and gateways, acting upon IoT events in proximity to the physical ‘thing’.

The ability to run the same code in the cloud and on IoT gateways, as well as to manage its lifecycle using modern DevOps tools, greatly improves the overall agility and maintainability of IoT applications. This is especially true in cases when they need to run on potentially large fleets of geographically dispersed devices and gateways, collecting large amounts of data that can’t be easily shipped to the cloud (e.g., due to bandwidth or privacy constraints).

A basic building block to enable such capability is to be able to run individual OpenWhisk actions on IoT gateways (e.g., raspberry pi), preserving (as much as possible) the developer experience, runtime capabilities, interfaces, etc. Notably, deployment and life cycle management of actions in OpenWhisk is based on Docker, and there are existing solutions to manage applications running on IoT devices with Docker. So, can we use one of such solutions to run and manage OpenWhisk actions at the edge?

Docker at the edge: resin.io

To start exploring this question, we selected resin.io — a promising open source based solution to run dockerized applications on edge devices, with an impressive list of (above 20) supported devices. Once you boot a device from a custom image they provide (based on ResinOS), it is discovered by their cloud service, and allows you to deploy arbitrary dockerized applications on it (currently limited to one application container per device, but there are plans and workarounds to run multiple). The way you define an application is by pushing a project to a dedicated git repository (hosted by resin.io), accompanied with an (augmented) Dockerfile. A docker image customized for your devices is built, stored in their internal docker registry, and rolled out to your devices. Then you can monitor and manage the environment via their dashboard or APIs. One interesting feature is an HTTP tunnel (on ports 80 and 8080) from a dedicated resin.io URL into the actual container on your device — which might be useful if you need incoming http communication, for management or other needs (e.g., from the cloud, or from your laptop).

OpenWhisk actions and Docker

Now, what does it take to deploy an run an OpenWhisk action? A recent blog post elaborates on running docker-based OpenWhisk actions locally (e.g., for development/testing purposes). In a nutshell, one can use a ‘skeleton’ docker action to bring up a ‘shell’ docker container that listens to /init and /run REST commands, which can be used to load the actual action code (e.g., packages in a zip file) and to run the action (using a particular convention to pass inputs, outputs and logs).

Putting the pieces together

This is all we need to put the pieces together. Here we go:

  • Create a resin.io application with one or more devices (e.g., you can use QEMU-based devices for experiments):
application on resin.io dashboard
  • Start with a clone of the skeleton docker action directory. Copy Dockerfile to Dockerfile.template, replace base image with one from resin.io, using template notation (this will generate an image suitable for the hardware architecture of your devices — assuming that your original Dockerfile didn’t have anything arch-specific besides the base image):
$ svn checkout https://github.com/openwhisk/openwhisk/trunk/core/actionProxy
A actionProxy/Dockerfile
A actionProxy/README.md
A actionProxy/actionproxy.py
A actionProxy/build.gradle
A actionProxy/delete-build-run.sh
A actionProxy/invoke.py
A actionProxy/stub.sh
Checked out revision 1034.
$ cd actionProxy
$ cat Dockerfile | sed 's/python:2.7.12-alpine/resin\/%%RESIN_MACHINE_NAME%%-alpine-python:2.7.12-slim/g' > Dockerfile.template
  • Push the code into resin.io repo (associated with the application created in #1), wait for the unicorn (indicating successful deployment).
$ git init
$ git remote add resin gh_glikson@git.resin.io:gh_glikson/qemu64.git
$ git add Dockerfile.template actionproxy.py stub.sh
$ git commit -m "commit to resin"
[master (root-commit) 95ed813] commit to resin
4 files changed, 341 insertions(+)
create mode 100644 Dockerfile.template
create mode 100644 actionproxy.py
create mode 100755 invoke.py
create mode 100644 stub.sh
$ git push --force resin master
[...new image is built. if successful, an ascii unicorn is shown...]
  • Enable Public Device URL for one or more devices (in resin.io dashboard)
  • Create a sample action script (must be called ‘exec’)
$ cat > exec
#!/bin/sh
echo By convention, all the stdout besides the last line is passed to activation logs verbatim.
echo "{ \"result\": \"last line is the action result, must be valid JSON\" }"
^D
  • Load the action code using the Public Device URL (following the method described here)
$ zip exec.zip exec
adding: exec (deflated 26%)
$ base64 --wrap=0 exec.zip | echo "\"$(cat)\"" | jq '{value: {binary: true, code: .}}' > init.json
$ cat init.json
{
"value": {
"binary": true,
"code": "UEsDBBQAAAAIAAa4PkqMlCoIiAAAALgAAAAEABwAZXhlY1VUCQADXKmPWGOpj1h1eAsAAQToAwAABOgDAABVjbEOwjAQQ/d+hQlrRXdGRgYYWLukzYmedE1Q7xKpQvw7DZ3YbMt+Ph66gWOnU0PjlHBZMaZYKBqn2MKLwCaCWkjZMJByIP1F4tUgHAmseHlVCrAEPxoXX9eQ9FQUWobNzqed797o3UKaxXp33vQfpnIrYBvvnRZz1vqL4oUDro/7rXf4uOYLUEsBAh4DFAAAAAgABrg+SoyUKgiIAAAAuAAAAAQAGAAAAAAAAQAAALSBAAAAAGV4ZWNVVAUAA1ypj1h1eAsAAQToAwAABOgDAABQSwUGAAAAAAEAAQBKAAAAxgAAAAAA"
}
}
$ http post http://173528365e1259aa89ccc5913a02b2af1bb5851414565d6cf7221fca44cec8.resindevice.io:8080/init < init.json
HTTP/1.1 200 OK
Vary: Accept-Encoding
X-Powered-By: Express
connection: keep-alive
content-length: 2
content-type: text/html; charset=utf-8
date: Mon, 30 Jan 2017 21:08:04 GMT
OK
  • Invoke the action using the same URL and method:
$ echo "{}" | jq '{value: .}' | http post http://173528365e1259aa89ccc5913a02b2af1bb5851414565d6cf7221fca44cec8.resindevice.io:8080/run
HTTP/1.1 200 OK
Vary: Accept-Encoding
X-Powered-By: Express
connection: keep-alive
content-length: 69
content-type: application/json
date: Mon, 30 Jan 2017 21:12:35 GMT
{
"result": "last line is the action result, must be valid JSON"
}

Notice that if you look at resin.io logs, you will see following:

Notice the line printed to stdout by the action script.

This is it! Now your OpenWhisk action is running on the device, behaving (roughly) the same as if it was running in the cloud.

What’s Next?

Of course, there is a lot more to be done in order to make this useful for real applications. Some of the challenges worth exploring going forward are:

  1. Adapting to multi-architecture nature of edge devices (large part of this is already handled by resin.io)
  2. Expanding the logic running at the edge beyond a single action (e.g., leveraging Docker-in-Docker and Node-RED)
  3. Further integration with OpenWhisk (‘native’ languages, activation logs, feeds, etc)
  4. Integration with IoT platform(s) such as IBM Watson IoT Platform
  5. Etc…

Stay tuned! Meanwhile, you can experiment with the simple integration outlined in this post, and share your thoughts/insights.

--

--