IBM recently launched a service called OpenWhisk; a distributed compute service to execute application logic in response to events. The most notable advantages of such serverless framework is:
- Auto-Scaling: automatically instantiate multiple instances to fulfill concurrent requests.
- Pay-Per-Use: pay only for the compute time you use, in millisecond increments!
OpenWhisk supports Node.JS, Python, Swift, and Docker. However, it does not natively support Node-Red, a graphical editor for “wiring” the Internet of Things.
Being Node-Red one of my favorite tools, I have been thinking about ways to use the Node-Red graphical editor and still benefiting from OpenWhisk’s auto-scaling. Here’s what I want to achieve:
Proof of concept Architecture:
Since OpenWhisk accepts Docker container, I decided to try out build a Docker image of with Node-Red app, push that image to DockerHub and have OpenWhisk using that image on-demand.

Sample Node-Red ‘Compute App.’
This the Node-Red flow I use to emulate a massive computation process that takes 5 seconds. The 5 seconds delay is to ensure that OpenWhisk will not use the same instance to serve concurrent requests. I wanted to force OpenWhisk to provision multiple instances.
A way I implement to confirm the uniqueness of each Node-Red run-time instance it by having the code generating a random id at start-up, and carry that number throughout the life of the process. I call it the ‘instance id’. And you’ll see that number in the response JSON.
[{"id":"a20dbc9c.07b21","type":"http in","z":"5d386e3f.b9637","name":"","url":"/init","method":"post","swaggerDoc":"","x":200,"y":180,"wires":[["9310d36a.e3fea"]]},{"id":"1cc439b7.cbc226","type":"http response","z":"5d386e3f.b9637","name":"","x":710,"y":180,"wires":[]},{"id":"c9d917bc.24dd88","type":"http in","z":"5d386e3f.b9637","name":"","url":"/run","method":"post","swaggerDoc":"","x":200,"y":220,"wires":[["d9203786.e38d28"]]},{"id":"65eaa64a.821668","type":"http response","z":"5d386e3f.b9637","name":"","x":710,"y":220,"wires":[]},{"id":"d9203786.e38d28","type":"function","z":"5d386e3f.b9637","name":"response","func":"var instance = context.get('inst') || 0;\nif (!instance){\n instance = Math.random();\n context.set('inst', instance);\n}\nmsg.payload = { 'text': 'It works! Instance ID: ' + instance };\nreturn msg;","outputs":1,"noerr":0,"x":380,"y":220,"wires":[["78753f7a.39edf"]]},{"id":"e81ec6d9.f53598","type":"http in","z":"5d386e3f.b9637","name":"","url":"/test","method":"get","swaggerDoc":"","x":200,"y":260,"wires":[["42e8ca5a.76ffd4"]]},{"id":"d40a8882.725068","type":"http response","z":"5d386e3f.b9637","name":"","x":710,"y":260,"wires":[]},{"id":"78753f7a.39edf","type":"delay","z":"5d386e3f.b9637","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":540,"y":220,"wires":[["65eaa64a.821668"]]},{"id":"42e8ca5a.76ffd4","type":"function","z":"5d386e3f.b9637","name":"response","func":"var instance = context.get('inst') || 0;\nif (!instance){\n instance = Math.random();\n context.set('inst', instance);\n}\nmsg.payload = { 'text': 'It works! Instance ID: ' + instance };\nreturn msg;","outputs":1,"noerr":0,"x":380,"y":260,"wires":[["d40a8882.725068"]]},{"id":"9310d36a.e3fea","type":"function","z":"5d386e3f.b9637","name":"response","func":"var instance = context.get('inst') || 0;\nif (!instance){\n instance = Math.random();\n context.set('inst', instance);\n}\nmsg.payload = { 'text': 'It works! Instance ID: ' + instance };\nreturn msg;","outputs":1,"noerr":0,"x":380,"y":180,"wires":[["1cc439b7.cbc226"]]}]
Build the Node-Red ‘compute app’ for OpenWhisk
Step 1: Create a folder and add you flows.json and the Dockerfile below:
Step 2: The Dockerfile extends the Node-Red image.
1
2
3
4
5
|
FROM nodered/node-red-docker
RUN npm install node-red-node-wordpos ENV PORT 8080 ADD flows.json /data/flows.json EXPOSE 8080:8080 |
Step 3: Build the image and push to DockerHub.
1
2
|
docker build -t isratx/mynodered . docker push isratx/mynodered |
Step 4: Setup a new Action in OpenWhisk.
Results for single OpenWhisk request:
Below OpenWhisk’s Invocation Console, showing the result of a single invoking of the Nore-Red image. OpenWhisk bills me for 5sec, matching the execution time I designed in the ‘compute app’. In the response text, you see the Instance ID, mentioned above a way to verify the uniqueness of a process.
Concurrent Requests
After confirming the concept works for a single request, now we’ll run the setup with five concurrent requests. Below is a sample app in Node-Red. Its job is just to send five requests at the same time.
Sample Parent App
This sample app calls five OpenWhisk actions concurrently.
[{"id":"148617d5.c10598","type":"inject","z":"d3a8bc3c.e14e9","name":"go","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":90,"y":200,"wires":[["bab44a0e.2a14a8"]]},{"id":"bab44a0e.2a14a8","type":"function","z":"d3a8bc3c.e14e9","name":"","func":"msg.headers = {\n 'Authorization': 'Basic <your auth here>',\n 'Content-Type': 'application/json'\n};\nmsg.payload = { \"arg\": \"choose a value\"};\nvar space = '<your space here>';\nmsg.url = 'https://openwhisk.ng.bluemix.net/api/v1/namespaces/' + space + '/actions/DockerNodeRed?blocking=true';\nreturn msg;","outputs":1,"noerr":0,"x":210,"y":200,"wires":[["9376160f.746b38","9f589329.1022c","8a2fcf15.3a0ab","a181fcee.229a6","2e2cd7ae.0756d8"]]},{"id":"9376160f.746b38","type":"http request","z":"d3a8bc3c.e14e9","name":"OpenWhisk Action","method":"POST","ret":"txt","url":"","tls":"","x":390,"y":100,"wires":[["c535fe90.468d2"]]},{"id":"c535fe90.468d2","type":"function","z":"d3a8bc3c.e14e9","name":"","func":"var m = JSON.parse(msg.payload);\n\nif (m.hasOwnProperty('response') )\n{\n msg.payload = m.response.result.text;\n return msg;\n}\nreturn msg;\n","outputs":1,"noerr":0,"x":570,"y":200,"wires":[["5aa9477e.705c28"]]},{"id":"5aa9477e.705c28","type":"debug","z":"d3a8bc3c.e14e9","name":"","active":true,"console":"false","complete":"false","x":710,"y":200,"wires":[]},{"id":"10d476de.7c0a69","type":"function","z":"d3a8bc3c.e14e9","name":"","func":"msg.headers = {\n 'Authorization': 'Basic <your auth here>',\n 'Content-Type': 'application/json'\n};\n\n//msg.payload - JSON.parse(msg.payload);\n//var id = msg.payload.activationId;\nvar id = ''; \n\nvar base = \"https://openwhisk.ng.bluemix.net/api/v1\";\n\nvar args = \"/namespaces/<your space here>/activations/\"+ id ;\n\nmsg.url = base + args;\n\nreturn msg;","outputs":1,"noerr":0,"x":210,"y":480,"wires":[["a8936e27.7640f"]]},{"id":"44d570c0.5e7dc","type":"debug","z":"d3a8bc3c.e14e9","name":"","active":true,"console":"false","complete":"false","x":630,"y":480,"wires":[]},{"id":"8a2fcf15.3a0ab","type":"http request","z":"d3a8bc3c.e14e9","name":"OpenWhisk Action","method":"POST","ret":"txt","url":"","tls":"","x":390,"y":220,"wires":[["c535fe90.468d2"]]},{"id":"9f589329.1022c","type":"http request","z":"d3a8bc3c.e14e9","name":"OpenWhisk Action","method":"POST","ret":"txt","url":"","tls":"","x":390,"y":160,"wires":[["c535fe90.468d2"]]},{"id":"a181fcee.229a6","type":"http request","z":"d3a8bc3c.e14e9","name":"OpenWhisk Action","method":"POST","ret":"txt","url":"","tls":"","x":390,"y":280,"wires":[["c535fe90.468d2"]]},{"id":"2e2cd7ae.0756d8","type":"http request","z":"d3a8bc3c.e14e9","name":"OpenWhisk Action","method":"POST","ret":"txt","url":"","tls":"","x":390,"y":340,"wires":[["c535fe90.468d2"]]},{"id":"c0161e93.02c18","type":"inject","z":"d3a8bc3c.e14e9","name":"go","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":90,"y":480,"wires":[["10d476de.7c0a69"]]},{"id":"a8936e27.7640f","type":"http request","z":"d3a8bc3c.e14e9","name":"OpenWhisk Activations","method":"GET","ret":"txt","url":"","tls":"","x":410,"y":480,"wires":[["44d570c0.5e7dc"]]}]
OpenWhisk Results
OpenWhisk Activity Log
When analyzing timestamps & instance Id, we can conclude request #5 successfully. However, it took extra 1min and 3 seconds to give us a response. Further, OpenWhisk only returned an Activation Id instead of the JSON response object.
Since I’m new to OpenWhisk, and it is an experimental service, I’ll assume these issues are either because a config parameter I missed or a current limitation/bug of the service.
Overall this was a successful experiment that confirms that you can have a Node-Red app running on OpenWhisk!
What’s Next?
Make the development more seamless.
- Can I pass flows.json as an argument instead of baking into the image?
- What about having flows.json in a Cloudant database, and pass URI as an argument?
Resources:
These were the articles I read to help me perform this experiment:
You must be logged in to post a comment.