Runtimes
Apache OpenServerless (OPS) Runtimes Explained
Apache OpenServerless (OPS) is a serverless platform built on Apache OpenWhisk, designed to execute functions in a scalable, event-driven environment. OPS leverages OpenWhisk’s runtime model while extending its capabilities to support additional customization via Docker.
Overview of OPS Runtimes
A runtime in OPS is a preconfigured environment that executes a serverless function.
Since serverless platforms allocate compute resources dynamically, a runtime ensures:
- Portability – Developers can write code in different languages without managing dependencies.
- Scalability – The system provisions and scales runtimes automatically based on demand.
- Isolation – Each Action runs within its own dedicated runtime environment, ensuring security and consistency.
- Resource Efficiency – The platform optimizes resource allocation by suspending inactive runtimes and reusing active ones when possible.
OPS natively supports the following runtimes:
However, since OPS is built on OpenWhisk, it inherits compatibility with all OpenWhisk-supported runtimes (e.g., Java, Go) and allows users to define custom runtimes using Docker containers.
For greater flexibility, developers can also package their own runtime environments using Docker to create “black box” actions.
How OPS Runtimes Work
1. Runtime Lifecycle
OPS follows OpenWhisk’s runtime model, where each function invocation occurs in an isolated container. The lifecycle includes:
- Initialization: A container is provisioned with the selected runtime.
- Execution: The function code runs within the container.
- Idle: The container is paused (but retained) for reuse (Warm Start).
- Destroying: Idle containers are garbage-collected after some time.
2. Cold vs. Warm Starts
- Cold Start: A new container is created, increasing latency.
- Warm Start: Reuses a paused container for faster execution.
3. Runtime Composition
Each runtime includes:
- Language Interpreter/Compiler: (e.g., Python 3.12, Node.js 21).
- Action Interface: A proxy that implements a canonical protocol to integrate with the OpenWhisk platform.
- Dependencies: Preinstalled libraries (e.g.,
requests
for Python).
Actions
Actions are the fundamental execution units in Apache OpenServerless. They are stateless functions that run on the OpenWhisk platform.
An action can be used to update a database, respond to an API call, communicate with another system, ecc.
To use a function as an action, it must conform to the following:
The function accepts a dictionary as input and produces a dictionary as output. The input and output dictionaries are
key-value pairs, where the key is a string and the value is any valid JSON value. The dictionaries are
canonically represented as JSON objects when interfacing to an action via the REST API or the ops
CLI.
The function must be called main
or exposed as main
1 - NodeJS
Creating and invoking JavaScript actions
The process of creating JavaScript actions is similar to that of other actions.
The following sections guide you through creating and invoking a single JavaScript action,
and demonstrate how to bundle multiple JavaScript files and third party dependencies.
- Create a package
directory
. Create a JavaScript file with the following content inside our packages/nodejs
. For this example, the file name is hello.js
.
//--web true
//--kind nodejs:default
function main() {
return { msg: 'Hello world' };
}
The JavaScript file might contain additional functions.
However, by convention, a function called main
must exist to provide the entry point for the action.
You directory structure should looks like this:
nodejs_app
└── packages
└── nodejs
└── hello.js
- Create an action from the following JavaScript function. For this example, the action is called
hello
.
/home/openserverless/.ops/tmp/deploy.pid
PID 278075
> Scan:
>> Action: packages/nodejs/hello.js
> Deploying:
>> Package: nodejs
$ $OPS package update nodejs
ok: updated package nodejs
>>> Action: packages/nodejs/hello.js
$ $OPS action update nodejs/hello packages/nodejs/hello.js --web true --kind nodejs:default
ok: updated action nodejs/hello
build process exited with code 0
UPLOAD ASSETS FROM web
==================| UPLOAD RESULTS |==================
| FILES : 0
| COMPLETED : 0
| ERRORS : 0
| SKIPPED : 0
| EXEC. TIME : 2.37 ms
======================================================
URL: http://opstutorial.localhost:80
Note: To use a specific version of NodeJs runtime, change the kind property --kind nodejs:18
, or --kind nodejs:20
in the hello.js
file.
Creating asynchronous actions
JavaScript functions that run asynchronously may need to return the activation result after the main
function has returned. You can accomplish this by returning a Promise in your action.
- Save the following content in a file called
asyncAction.js
inside the folder packages/nodejs
.
//--web true
//--kind nodejs:default
function main(args) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve({ done: true });
}, 2000);
})
}
Notice that the main
function returns a Promise, which indicates that the activation hasn’t completed yet, but is expected to in the future.
The setTimeout()
JavaScript function in this case waits for two seconds before calling the callback function. This represents the asynchronous code and goes inside the Promise’s callback function.
The Promise’s callback takes two arguments, resolve and reject, which are both functions. The call to resolve()
fulfills the Promise and indicates that the activation has completed normally.
A call to reject()
can be used to reject the Promise and signal that the activation has completed abnormally.
- Run the following commands to create the action and invoke it:
ops ide deploy
ops action invoke nodejs/asyncAction --result
Notice that you performed a blocking invocation of an asynchronous action.
- Fetch the activation log to see how long the activation took to complete:
ops activation list --limit 1 nodejs/asyncAction
Datetime Activation ID Kind Start Duration Status Entity
2024-03-27 19:46:43 64581426b44e4b3d981426b44e3b3d19 nodejs:21 cold 2.033s success openserverless/asyncAction:0.0.1
ops activation get 64581426b44e4b3d981426b44e3b3d19
{
...
"start": 1743101268649,
"end": 1743101270964,
...
}
Comparing the start
and end
time stamps in the activation record, you can see that this activation took slightly over two seconds to complete.
Using actions to call an external API
The examples so far have been self-contained JavaScript functions. You can also create an action that calls an external API.
This example invokes a Yahoo Weather service to get the current conditions at a specific location.
- Save the following content in a file called
weather.js
.
const fetch = require('node-fetch')
const getWeatherForecast = async (latitude, longitude) => {
const url = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&hourly=temperature_2m,relative_humidity_2m,wind_speed_10m`
try {
const response = await fetch(url)
if (!response.ok) {
throw new Error('Error during the request')
}
const data = await response.json()
return data
} catch (error) {
console.error('Error:', error)
return JSON.Stringify(error)
}
};
function main(args) {
const {latitude, longitude} = args
return await getWeatherForecast(latitude, longitude)
}
Note that the action in the example uses a Node.js library to fetch forecast data. However, you can use a library available in runtime dependencies or you can add a package.json file and specify the action’s dependencies.
Check a specific version of the Node.js runtime for packages available in the runtime environment.
This example also shows the need for asynchronous actions. The action returns uses ‘async/await’ to fetch the data from another system. When the action is completed, the action will return the values from the other sistem.
- Create an action from the
weather.js
file:
ops ide deploy
- Use the following command to run the action, and observe the output:
ops action invoke nodejs/weather --param latitude "51.509865" --param longitude "-0.118092" --result
Using the --result
flag means that the value returned from the action is shown as output on the command-line:
{
"elevation": 21,
"generationtime_ms": 0.03039836883544922,
"hourly": {
"temperature_2m": [
12.8,
12.9,
...
],
"time": [
"2025-03-27T00:00",
"2025-03-27T01:00",
...
]
},
"hourly_units": {
"temperature_2m": "°C",
"time": "iso8601"
},
"latitude": 51.5,
"longitude": -0.120000124,
"timezone": "GMT",
"timezone_abbreviation": "GMT",
"utc_offset_seconds": 0
}
This example also passed a parameter to the action by using the --param
flag and a value that can be changed each time the action is invoked.
Packaging actions as Node.js modules with NPM libraries
Instead of writing all your action code in a single JavaScript source file, actions can be deployed from a zip file containing a Node.js module.
Archive zip files are extracted into the runtime environment and dynamically imported using require()
during initialisation. Actions packaged as a zip file MUST contain a valid package.json
with a main
field used to denote the module index file to return.
ops ide deploy
will include automatically node_modules
folder in a zip file means external NPM libraries can be used on the platform.
Note: remember that each runtime has a set of dependecies already installed. if it’s possible use this set of libraries. It’s better to don’t load too much external libraries because it will deteriorate the action’s performance
Simple Example
{
"name": "my-action",
"main": "index.js",
"dependencies" : {
"left-pad" : "1.1.3"
}
}
- Create the following
index.js
file:
//--web true
//--kind nodejs:default
const leftPad = require("left-pad")
function main (args) {
const lines = args.lines || [];
return { padded: lines.map(l => leftPad(l, 30, ".")) }
}
ops ide deploy
When creating an action from a folder, ops ide deploy
will create automatically the following artifacts:
- node_modules
- package-lock.json
- <foldername>.zip
the zip fill is used by ops
to create/update the function in your ops environment
- Invoke the action as normal.
ops action invoke nodejs/packageAction --param lines "[\"and now\", \"for something completely\", \"different\" ]" --result
{
"padded": [
".......................and now",
"......for something completely",
".....................different"
]
}
Using JavaScript Bundlers to package action source files
Using a JavaScript module bundler can transform application source files (with external dependencies) into a single compressed JavaScript file. This can lead to faster deployments, lower cold-starts and allow you to deploy large applications where individual sources files in a zip archive are larger than the action size limit.
Here are the instructions for how to use three popular module bundlers with the Node.js runtime. The “left pad” action example will be used as the source file for bundling along with the external library.
Using rollup.js
In this example we will use rollupjs to deliver the action
- Create rollupAction folder inside packages/nodejs. Then re-write the
index.js
to use ES6 Modules, rather than CommonJS module format.
Create the package.json
and copy the content from the previous example
import leftPad from 'left-pad';
function myAction(args) {
const lines = args.lines || [];
return { padded: lines.map(l => leftPad(l, 30, ".")) }
}
export const main = myAction
Make sure you export the function using the const main = ...
pattern. Using export {myAction as main}
does not work due to tree-shaking. See this blog post for full details on why this is necessary.
- Create the Rollup.js configuration file in
rollup.config.js
with the following contents.
import commonjs from 'rollup-plugin-commonjs';
import resolve from 'rollup-plugin-node-resolve';
export default {
input: 'index.js',
output: {
file: 'bundle.js',
format: 'cjs'
},
plugins: [
resolve(),
commonjs()
]
};
Note: run the following command inside the rollupAction folder
- Install the Rollup.js library and plugins using NPM.
npm install rollup rollup-plugin-commonjs rollup-plugin-node-resolve --save-dev
- Run the Rollup.js tool using the configuration file.
npx rollup --config
- Create an action using the bundle source file.
ops action create nodejs/rollupAction bundle.js --kind nodejs:20
- Invoke the action as normal. Results should be the same as the example above.
ops action invoke nodejs/rollupAction --result --param lines "[\"and now\", \"for something completely\", \"different\" ]"
Using webpack
In this example we will use webpack to deliver the action
- Create webpackAction folder inside packages/nodejs. Then re-write the
index.js
to export the main
function using as a global reference.
Then, create the package.json
and copy the content from the previous example
const leftPad = require('left-pad');
function myAction(args) {
const lines = args.lines || [];
return { padded: lines.map(l => leftPad(l, 30, ".")) }
}
global.main = myAction
This allows the bundle source to “break out” of the closures Webpack uses when defining the modules.
- Create the Webpack configuration file in
webpack.config.js
with the following contents.
module.exports = {
entry: './index.js',
target: 'node',
output: {
filename: 'bundle.js'
}
};
- Install the Webpack library and CLI using NPM.
npm install webpack-cli --save-dev
- Run the Webpack tool using the configuration file.
npx webpack --config webpack.config.js
- Create an action using the bundle source file.
ops action create nodejs/webpackAction dist/bundle.js --kind nodejs:21
- Invoke the action as normal. Results should be the same as the example above.
ops action invoke nodejs/webpackAction --result --param lines "[\"and now\", \"for something completely\", \"different\" ]"
2 - PHP
Creating and invoking PHP actions
The process of creating PHP actions is similar to that of other actions.
The following sections guide you through creating and invoking a single PHP action,
and demonstrate how to bundle multiple PHP files and third party dependencies.
- Create a Python_app folder and then create a
package
directory.Now create a Python file with the following content inside our packages/php
. For this example, the file name is hello.php
.
<?php
//--web true
//--kind php:default
function main(array $args) : array
{
$name = $args["name"] ?? "stranger";
$greeting = "Hello $name!";
echo $greeting;
return ["greeting" => $greeting];
}
?>
The PHP file might contain additional functions.
However, by convention, a function called main
must exist to provide the entry point for the action.
Your directory structure should looks like this:
PHP_app
└── packages
└── php
└── hello.php
- Run the following command to deploy the action
- Ops will create the action automatically. For this example, the action is called
php/hello
.
/home/openserverless.ops/tmp/deploy.pid
PID 220917
> Scan:
>> Action: packages/php/hello.php
> Deploying:
>> Package: php
$ $OPS package update php
ok: updated package php
>>> Action: packages/php/hello.php
$ $OPS action update php/hello packages/php/hello.php
ok: updated action php/hello
build process exited with code 0
UPLOAD ASSETS FROM web
==================| UPLOAD RESULTS |==================
| FILES : 0
| COMPLETED : 0
| ERRORS : 0
| SKIPPED : 0
| EXEC. TIME : 2.61 ms
======================================================
URL: http://openserverless.localhost:80
Note: To use a specific version of Python runtime, change the kind property --kind php:8.1
, or --kind php:8.3
in the hello.py
file.
- To invoke the action run the following command:
ops action invoke php/hello --param name Marina --result
3 - Python
Creating and invoking Python actions
The process of creating Python actions is similar to that of other actions.
The following sections guide you through creating and invoking a single Python action,
and demonstrate how to bundle multiple Python files and third party dependencies.
- Create a Python_app folder and then create a
package
directory.Now create a Python file with the following content inside our packages/python
. For this example, the file name is hello.py
.
#--web true
#--kind python:default
def main(args):
name = args.get("name", "stranger")
result = f"Hello {name}!"
print(result)
return {"greeting": result}
The Python file might contain additional functions.
However, by convention, a function called main
must exist to provide the entry point for the action.
Your directory structure should looks like this:
Python_app
└── packages
└── Python
└── hello.py
- Run the following command to deploy the action
- Ops will create the action automatically. For this example, the action is called
python/hello
.
/home/openserverless/.ops/tmp/deploy.pid
PID 278075
> Scan:
>> Action: packages/python/hello.py
> Deploying:
>> Package: python
$ $OPS package update python
ok: updated package python
>>> Action: packages/python/hello.py
$ $OPS action update python/hello packages/python/hello.py --web true --kind python:default
ok: updated action python/hello
build process exited with code 0
UPLOAD ASSETS FROM web
==================| UPLOAD RESULTS |==================
| FILES : 0
| COMPLETED : 0
| ERRORS : 0
| SKIPPED : 0
| EXEC. TIME : 2.37 ms
======================================================
URL: http://opstutorial.localhost:80
Note: To use a specific version of Python runtime, change the kind property --kind python:3.11
, or --kind python:3.12
in the hello.py
file.
- To invoke the action run the following command:
ops action invoke python/hello --param name Marina --result
Packaging Python actions in zip files
Sometimes you action would be more complex and probably you prefer to create multiple file in order to organize better your code.
In this example we’re creating a more complex python action.
- Create a folder
complex_action
inside packages/python
folder. Then create two python files
main.py
#--web true
#--kind python:default
import utils
def main(args):
name = args.get("name", "stranger")
result = utils.concat_string("Nice to meet you,",name)
return {"greeting": result}
utils.py
def concat_string(first_string: str, second_string: str):
return f"{first_string} {second_string}"
Note: The filename of the source file containing the entry point (e.g., main
) must be __main__.py
.
- Deploy the action using
ide deploy
- Now you can invoke your action. In this case the action name is
python/complex_action
ops action invoke python/complex_action --param name Marina --result