By default Kubeless has support for the following runtimes:
- Python: For the branches 2.7, 3.4 and 3.6
- NodeJS: For the branches 6 and 8, as well as NodeJS distroless for the branch 8
- Ruby: For the branches 2.3, 2.4 and 2.5
- PHP: For the branch 7.2
- Golang: For the branch 1.10
- .NET: For the branch 2.0
- Ballerina: For the branch 0.981.0
You can see the list of supported runtimes executing:
$ kubeless get-server-config
INFO[0000] Current Server Config:
INFO[0000] Supported Runtimes are: python2.7, python3.4, python3.6, nodejs6, nodejs8, ruby2.3, ruby2.4, ruby2.5, php7.2, go1.10, dotnetcore2.0, java1.8, ballerina0.981.0
Each runtime is encapsulated in a container image. The reference to these images are injected in the Kubeless configuration. You can find the source code of all runtimes in docker/runtime
.
module.exports = {
foo: function (event, context) {
console.log(event);
return event.data;
}
}
NodeJS functions should export the desired method using module.exports
. You can specify dependencies using a package.json
file. It is also possible to return an object instead of a string, this object will be stringified before returning.
When using the Node.js runtime, it is possible to configure a custom registry or scope in case a function needs to install modules from a different source. For doing so it is necessary to set up the environment variables NPM_REGISTRY and NPM_SCOPE when deploying the function:
$ kubeless function deploy myFunction --runtime nodejs6 \
--env NPM_REGISTRY=http://my-registry.com \
--env NPM_SCOPE=@myorg \
--dependencies package.json \
--handler test.foobar \
--from-file test.js
Depending on the size of the payload sent to the NodeJS function it is possible to find the error 413 PayloadTooLargeError
. It is possible to increase this limit setting the environment variable REQ_MB_LIMIT
. This will define the maximum size in MB that the function will accept:
$ kubeless function deploy myFunction --runtime nodejs6 \
--env REQ_MB_LIMIT=50 \
--handler test.foobar \
--from-file test.js
For Webpack Users
Your webpacked functions will be require()
-d in so your bundle should work out of the box. However, if your bundle size is approaching 1mb you should take advantage of Kubeless' ability to install dependencies for you instead of bundling them all into your payload.
You will need to customize your webpack config to suit your own project, but below is an sample config of how to achieve this in Webpack 4.x:
webpack.config.js
const path = require("path");
const nodeExternals = require("webpack-node-externals");
const CopyWebpackPlugin = require("copy-webpack-plugin");
module.exports = {
entry: {
handlers: "./handlers.js"
},
node: {
__filename: true,
__dirname: true
},
target: "node",
// do not include dependencies in the bundle
externals: [nodeExternals()],
devtool: "source-map",
module: {
rules: [
{
test: /\.js$/,
use: "babel-loader",
// do not transpile the depedencies
exclude: /node_modules/
}
]
},
plugins: [
// do include the project's `package.json` in the bundle
new CopyWebpackPlugin([
{
from: path.join(__dirname, "path", "to", "your", "package.json"),
to: "package.json"
}
])
]
};
Additionally, in your babel config, you can specify the transpile target to be the version of node you're using for your runtime. This is an example for Babel 7.x:
module.exports = {
plugins: [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-transform-runtime"
],
// note the target node version here for nodejs8
presets: [["@babel/preset-env", { targets: { node: "8.10" } }]]
};
For the Node.js runtime we start an Express server and we include the routes for serving the health check and exposing the monitoring metrics. Apart from that we enable CORS requests and Morgan for handling the logging in the server. Monitoring is supported if the function is synchronous or if it uses promises.
There is the distroless variant of the Node.js 8 runtime. The distroless Node.js runtime contains only the kubeless function and its runtime dependencies. In particular, this variant does not contain package manager, shells or any other programs which are part of a standard Linux distribution.
The same example Node.js function from above can then be deployed:
$ kubeless function deploy myFunction --runtime nodejs_distroless8 \
--env NPM_REGISTRY=http://my-registry.com \
--env NPM_SCOPE=@myorg \
--dependencies package.json \
--handler test.foobar \
--from-file test.js
def handler(event, context):
print (event)
return event['data']
Python functions should define the desired method. You can specify dependencies using a requirements.txt
file.
For python we use Bottle and we also add routes for health check and monitoring metrics.
def handler(event, context)
puts event
JSON.generate(event[:data])
end
Ruby functions should define the desired method. You can specify dependencies using a Gemfile
file.
For the case of Ruby we use Sinatra as web framework and we add the routes required for the function and the health check. Monitoring is currently not supported yet for this framework. PR is welcome :-)
package kubeless
import "github.com/kubeless/kubeless/pkg/functions"
func Handler(event functions.Event, context functions.Context) (string, error) {
return event.Data, nil
}
Go functions require to import the package github.com/kubeless/kubeless/pkg/functions
that is used to define the input parameters. The desired method should be exported in the package. You can specify dependencies using a Gopkg.toml
file, dependencies are installed using dep
.
The Go HTTP server doesn't include any framework since the native packages includes enough functionality to fit our needs. Since there is not a standard package that manages server logs that functionality is implemented in the same server. It is also required to implement the ResponseWriter
interface in order to retrieve the Status Code of the response.
If there is an error during the compilation of a function, the error message will be dumped to the termination log. If you see that the pod is crashed in a init container:
NAME READY STATUS RESTARTS AGE
get-go-6774465f95-x55lw 0/1 Init:CrashLoopBackOff 1 1m
That can mean that the compilation failed. You can obtain the compilation logs executing:
$ kubectl get pod -l function=get-go -o yaml
...
- containerID: docker://253fb677da4c3106780d8be225eeb5abf934a961af0d64168afe98159e0338c0
image: andresmgot/go-init:1.10
lastState:
terminated:
containerID: docker://253fb677da4c3106780d8be225eeb5abf934a961af0d64168afe98159e0338c0
exitCode: 2
finishedAt: 2018-04-06T09:01:16Z
message: |
# kubeless
/go/src/kubeless/handler.go:6:1: syntax error: non-declaration statement outside function body
...
You can see there that there is a syntax error in the line 6 of the function. You can also retrieve the same information with this one-liner:
$ kubectl get pod -l function=get-go -o go-template="{{range .items}}{{range .status.initContainerStatuses}}{{.lastState.terminated.message}}{{end}}{{end}}"
<no value># kubeless
/go/src/kubeless/handler.go:6:1: syntax error: non-declaration statement outside function body
One peculiarity of the Go runtime is that the user has a Context
object as part of the Event.Extensions
parameter. This can be used to handle timeouts in the function. For example:
func Foo(event functions.Event, context functions.Context) (string, error) {
select {
case <-event.Extensions.Context.Done():
return "", nil
case <-time.After(5 * time.Second):
}
return "Function returned after 5 seconds", nil
}
If the function above has a timeout smaller than 5 seconds it will exit and the code after the select{}
won't be executed.
package io.kubeless;
import io.kubeless.Event;
import io.kubeless.Context;
public class Foo {
public String foo(io.kubeless.Event event, io.kubeless.Context context) {
return "Hello world!";
}
}
Java functions must use io.kubeless
as package and should import both io.kubeless.Event
and io.kubeless.Context
packages. Function should be made part of a public class and should have a function signature that takes Event
and Context
as inputs and produces String
output. Once you have Java function meeting the requirements it can be deployed with Kubeless as below. Where handler part --handler Foo.foo
takes Classname.Methodname
format.
kubeless function deploy get-java --runtime java1.8 --handler Foo.foo --from-file Foo.java
Kubeless supports Java functions with dependencies. Kubeless uses Maven for both dependency management and building user given functions. Users are expected to provide function dependencies expresses in Maven pom.xml format.
Lets take Java function with dependency on org.joda.time.LocalTime
.
package io.kubeless;
import io.kubeless.Event;
import io.kubeless.Context;
import org.joda.time.LocalTime;
public class Hello {
public String sayHello(io.kubeless.Event event, io.kubeless.Context context) {
System.out.println(event.Data);
LocalTime currentTime = new LocalTime();
return "Hello world! Current local time is: " + currentTime;
}
}
Dependencies are expressed through standard Maven pom.xml file format as below.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>function</artifactId>
<name>function</name>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.kubeless</groupId>
<artifactId>params</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<parent>
<groupId>io.kubeless</groupId>
<artifactId>kubeless</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
</project>
Notice the reference to kubeless
parent pom module and dependency on params
artifact. pom.xml should also use function
as artifact ID.
Once you have Java function with dependencies and pom.xml file expressing the dependencies Java function can be deployed with Kubeless as below.
kubeless function deploy get-java-deps --runtime java1.8 --handler Hello.sayHello --from-file java/HelloWithDeps.java --dependencies java/pom.xml
using System;
using Kubeless.Functions;
public class module
{
public object handler(Event k8Event, Context k8Context)
{
return k8Event.Data;
}
}
Deploy it using the following command:
kubeless function deploy helloget --from-file helloget.cs --handler module.handler --runtime dotnetcore2.0
To get started using .NET Core with kubeless, you should use the following commands:
dotnet new library
dotnet add package Kubeless.Functions
.NET Core (C#) functions supports returns for any primitive or complex type. The method signature needs to have first an Kubeless.Functions.Event
followed by an Kubeless.Functions.Context
. The models are definied as it follows:
public class Context
{
public string ModuleName { get; }
public string FunctionName { get; }
public string FunctionPort { get; }
public string Timeout { get; }
public string Runtime { get; }
public string MemoryLimit { get; }
}
public class Event
{
public object Data { get; }
public string EventId { get; }
public string EventType { get; }
public string EventTime { get; }
public string EventNamespace { get; }
public Extensions Extensions { get; }
}
Dependencies are handled in .csproj
extension. You can use the regular .csproj
file outputted by the dotnet new library
command.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Kubeless.Functions" Version="0.1.1" />
<PackageReference Include="YamlDotNet" Version="4.3.1" />
</ItemGroup>
</Project>
The runtime already have built-in the package Kubeless.Functions:0.1.1
, necessary to all functions - so you don't need to include that. Then, if you have a function which does not need any external references than Kubeless.Functions
, you don't need to even send the --dependencies
flag on kubeless cli.
You can deploy them using the command:
kubeless function deploy fibonacci --from-file fibonacci.cs --handler module.handler --dependencies fibonacci.csproj --runtime dotnetcore2.0
import kubeless/kubeless;
import ballerina/io;
public function foo(kubeless:Event event, kubeless:Context context) returns (string|error) {
io:println(event);
io:println(context);
return "Hello Ballerina";
}
The Ballerina functions should import the package kubeless/kubeless
. This package contains two types Event
and Context
.
$ kubeless function deploy foo
--runtime ballerina0.981.0
--from-file foo.bal
--handler foo.foo
When using the Ballerina runtime, it is possible to provide a configuration via kubeless.toml
file. The values in kubeless.toml file are available for the function. The function(.bal file) and conf file should be in the same directory.
The zip file containing both files should be passed to the Kubeless CLI.
foo
├── hellowithconf.bal
└── kubeless.toml
$ zip -r -j foo.zip foo/
$ kubeless function deploy foo
--runtime ballerina0.981.0
--from-file foo.zip
--handler hellowithconf.foo
For the Ballerina runtime we start a Ballerina HTTP server with two resources, '/' and '/healthz'.
The Kubeless configuration defines a set of default container images per supported runtime variant.
These default container images can be configured via Kubernetes environment variables on the Kubeless controller's deployment container. Or modifying the kubeless-config
ConfigMap that is deployed along with the Kubeless controller. For more information about how to modify the Kubeless configuration check this guide.
Apart than changing the configuration, it is possible to use a custom runtime specifying the image that the function will use. If you are interested in developing a new runtime from scratch (i.e. for a new language) you should follow this guide. In the linked guide you can find the requirements that a new runtime should fulfill and how you can submit new runtimes to the Kubeless project.
In any case, if you want to use one of the existing runtimes but you want to modify it to support a specific feature you can easily do that. The first thing is to modify the files in docker/runtime
folder. For example, if we want to add the lodash
npm
module globally in the NodeJS runtime we can modify its Dockerfile:
...
RUN apt-get update && apt-get install git
+ RUN npm install -g lodash
...
Now we can use the Makefile in the folder to generate the base image:
▶ make build8
docker build -t kubeless/nodejs:8$RUNTIME_TAG_MODIFIER -f Dockerfile.8 .
Sending build context to Docker daemon 7.059MB
Step 1/10 : FROM node:8
---> 55791187f71c
Step 2/10 : RUN apt-get update && apt-get install git
---> Using cache
---> 70f1565e9353
Step 3/10 : RUN npm install -g lodash
---> Running in 03602280a37d
+ [email protected]
added 1 package in 1.369s
...
Successfully built d68eccb2568b
Successfully tagged kubeless/nodejs:8
We can now retag the image and push it using a different account:
▶ docker tag kubeless/nodejs:8 andresmgot/nodejs-with-lodash:8
▶ docker push andresmgot/nodejs-with-lodash:8
The push refers to repository [docker.io/andresmgot/nodejs-with-lodash]
5a9aabfdd819: Pushed
...
8: digest: sha256:dfd26034130e5aae5a3db7b3df969649c44c3f7d1168bee7c4e1e6e7e75726d7 size: 3261
Finally in order to use this new flavor we need to add it to the Kubeless config. We will just copy the official nodejs
runtime and rename it to reflect the changes:
▶ kubectl edit -n kubeless configmap kubeless-config
# Add the following object within the "runtime-images" array
# {
# "ID": "nodejsWithLodash",
# "compiled": false,
# "versions": [
# {
# "name": "node8",
# "version": "8",
# "runtimeImage": "andresmgot/nodejs-with-lodash:8",
# "initImage": "node:8"
# }
# ],
# "depName": "package.json",
# "fileNameSuffix": ".js"
# },
configmap "kubeless-config" edited
NOTE: You should just use lowercase and uppercase characters for the ID. The runtime selection is made concatenating the runtime ID and the version (i.e. nodejsWithLodash8 for this example)
The last step in order to deploy a function with the new runtime is to restart the Kubeless controller pod:
▶ kubectl delete pods -n kubeless -l kubeless=controller
pod "kubeless-controller-manager-67fbc78f6d-w2vnk" deleted
▶ kubeless function deploy my-nodejs-func --runtime nodejsWithLodash8 --handler helloget.foo --from-file examples/nodejs/helloget.js
INFO[0000] Deploying function...
INFO[0000] Function my-nodejs-func submitted for deployment
INFO[0000] Check the deployment status executing 'kubeless function ls my-nodejs-func'
# Wait for the function pod to be deployed
▶ kubectl exec -it my-nodejs-func-55546fcf68-78fpz -- npm list -g | grep lodash
+-- [email protected]
One can use kubeless-config to override the default liveness probe. By default, the liveness probe is http-get
this can be overriden by providing the livenessprobe info in kubeless-confg
under runtime-images
. It has been implemented in such a way that each runtime can have its own liveness probe info. To use custom liveness probe paste the following info in runtime-images
:
"version": [],
"livenessProbeInfo": {
"exec": {
"command": [
"curl",
"-f",
"http://localhost:8080/healthz"
]
},
"initialDelaySeconds": 5,
"periodSeconds": 5,
"failureThreshold": 3,
"timeoutSeconds": 30
},
"depname": ""