Kubeless Runtime Variants

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 branch 2.4
  • 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.4, 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.

NodeJS

Example

module.exports = {
  foo: function (event, context) {
    console.log(event);
    return event.data;
  }
}

Description

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 [email protected] \
                                --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

Server implementation

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.

Distroless Variant

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 [email protected] \
                                --dependencies package.json \
                                --handler test.foobar \
                                --from-file test.js

Python

Example

def handler(event, context):
    print (event)
    return event['data']

Description

Python functions should define the desired method. You can specify dependencies using a requirements.txt file.

Server implementation

For python we use Bottle and we also add routes for health check and monitoring metrics.

Ruby

Example

def handler(event, context)
  puts event
  JSON.generate(event[:data])
end

Description

Ruby functions should define the desired method. You can specify dependencies using a Gemfile file.

Server implementation

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 :-)

Go

Example

package kubeless

import "github.com/kubeless/kubeless/pkg/functions"

func Handler(event functions.Event, context functions.Context) (string, error) {
    return event.Data, nil
}

Description

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.

Server implementation

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.

Debugging compilation

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

Timeout handling

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.

Java

Example

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!";
    }
}

Description

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

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

.NET Core (C#)

Example

using System;
using Kubeless.Functions;

public class module
{
    public object handler(Event k8Event, Context k8Context)
    {
        return k8Event.Data;
    }
}

Deploy it using the following command:
bash
kubeless function deploy helloget --from-file helloget.cs --handler module.handler --runtime dotnetcore2.0

Description

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

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

Ballerina

Example

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";
}

Description

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

Server implementation

For the Ballerina runtime we start a Ballerina HTTP server with two resources, '/' and '/healthz'.

Use a custom runtime

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]

Use a custom livenessProbe

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": ""