Skip to content
This repository has been archived by the owner on Dec 15, 2021. It is now read-only.

Add the support for perl runtime. #1014

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions docker/runtime/perl/Dockerfile.5.26
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Copyright 2019 VMware, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

FROM perl:5.26-threaded
MAINTAINER Sankar Tanguturi [email protected]

RUN apt-get update && \
apt-get upgrade -y && \
BUILD_PACKAGES="curl make gcc" && \
apt-get -y install $BUILD_PACKAGES --no-install-recommends && \
curl -L http://cpanmin.us | perl - Dancer2 && \
apt-get remove --purge -y curl make gcc && \
apt-get autoremove -y && \
apt-get clean && \
apt-get autoclean && \
echo -n > /var/lib/apt/extended_states && \
rm -rf /var/lib/apt/lists/* && \
rm -rf /usr/share/man/?? && \
rm -rf /usr/share/man/??_* && \
rm -rf ~/.cpanm/*

WORKDIR /
ADD kubeless.pl .

USER 1000

CMD ["perl", "/kubeless.pl"]
23 changes: 23 additions & 0 deletions docker/runtime/perl/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright 2019 VMware, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

build5.26:
docker build -t kubeless/perl:5.26$$RUNTIME_TAG_MODIFIER -f Dockerfile.5.26 .

push5.26:
docker push kubeless/perl:5.26$$RUNTIME_TAG_MODIFIER

# Mandatory jobs
build-all: build5.26
push-all: push5.26
118 changes: 118 additions & 0 deletions docker/runtime/perl/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@

# Introduction
`kubeless` framework supports deploying functions written in `python`, `golang`, `nodejs`, `ruby`, `php`, `java` and `dotnet`. Now, the support is added to deploy `perl` functions without much effort.

# Architecture
* [Dancer](http://perldancer.org) is a simple but powerful web application framework designed for `perl`. `Dancer` is used to implement the `perl` runtime support in `kubeless`.
* A Docker Image is already created with `Dancer`, `perl 5.26` (compiled with Thread support) and other necessary dependencies. The main entry file i.e. `kubeless.pl` is packaged inside the docker image and is started when the image is deployed and started.
* The main entry file `kubeless.pl` is a `dancer` perl application that starts and waits for the incoming HTTP requests.
* When `kubeless` deploys the docker image, few key pieces of information are provided in the following environrment variables.

```perl
MOD_NAME => 'The name of the module to be loaded.'
FUNC_HANDLER => 'The name of the function to be invoked.'
FUNC_PORT => 'Port on which the web application should listen.'
FUC_TIMEOUT => 'Timeout (in seconds) in which the function should complete the execution.'
```

* The `kubeless.pl` file parses the incoming requests, executes the function (in a separate thread) and returns the result. The function must complete it's execution in a fixed timeout. (Default is 10 seconds). If the function is not done executing, it will
be killed and an error is returned to the client.

## Default Route End-points
* Following are few default routes defined by `perl` framework in `kubelss`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is a typo kubelss --> kubeless

* `/healthz` : GET Method is implemented for /healthz route. This route can be used to check if the application is up and running or not. A successful GET request will return 'OK' string in the response.
* `/metrics` : This route is useful to capture the event monitoring. Currently, it's not implemented and will return 'Not implemented' string in the response.
* All other routes that match `/*` path are routed to the function handler. `GET`, `POST`, `PATCH`, `DELETE` methods are implemented for all these routes.

## Structure of a perl module
Following is a template for a simple perl module.
```perl
# helloget.pm
# The file name must end with .pm suffix
# Each function will be invoked with two arguments.
# First one is a reference to an event object.
# Second one is a reference to a function context object.
use strict;
sub hello_world {
$event_ref = shift;
$func_ref = shift;
# The POST data in the incoming HTTP request is available
# in data attribute of event object.
$data = $event_ref->{data};
# If the client POSTS the JSON data, then the $data
# will be a perl has variable with proper key / value / pairs.
# Ex: JSON data: {'id' : 123, 'foo' : 'bar'}
# data{id} = 123, data{foo} = 'bar'
return "Result Value"
}

# The last line of the perl module MUST be 1
1
```

## Building Perl Docker image
```bash
cd docker/runtime/perl
make # This will build docker image
```
* By default, kubeless/perl:5.26 is the tag created for the images built.
* You can use the following commands to push to your own repository.
```bash
docker tag kubeless/perl:5.26 <username>/perl-kubeless:5.26
docker push <username>/perl-kubeless:5.26
```

## Examples
```bash
cd examples/perl
make # Make file to run all tests.
#check all .pm files
```

### Updating the kubeless config map.
```
kubectl edit -n kubeless configmap kubeless-config
# Add the following entry
{
"ID": "perl",
"compiled": false,
"versions": [
{
"name": "perl526”,
"version": "5.26”,
"runtimeImage": "stanguturi/perl-kubeless:5.26”,
"initImage": "perl:5.26-threaded"
}
],
"depName": "",
"fileNameSuffix": ".pm",
}
# load the controller manager.
kubectl delete pods -n kubeless -l kubeless=controller
```

## Deploying the perl module
* Write a proper perl module as mentioned above.
* Currently only perl5.26 is the supported perl runtime.
* Use the following commandi in `kubeless` to deply a function.
```
kubeless deploy function hello_world \
--from-file helloget.pm
--handler helloget.hello
--runtime perl5.26
```

## Tips for writing a proper perl Module
* The name of the perl module file must end with .pm extension.
* The last line of the perl module must be 1
```perl
1
```
* Make sure that there are no compilation errors in the perl module.
* Make sure that the perl module runs with perl 5.26.
* Add `use strict` at the beginning of the perl module.
* The `perl` package installed in the `container image` is built with `threading support`. If the perl module has any dependies with `perl threads`, it should just work.
* Make sure that the function finishes the execution in the specific timeout. The default timeout is 10 seconds.
* Make sure that a proper scalar string is returned from the perl function.

**Happy Perl function Deploying**
159 changes: 159 additions & 0 deletions docker/runtime/perl/kubeless.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#!/usr/bin/env perl
# Copyright 2019 VMware, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

use threads;
use JSON::MaybeXS qw(encode_json decode_json);
use Dancer2;
use Time::HiRes qw(usleep);

# Port on which the dancer app will run.
my $func_port = $ENV{FUNC_PORT} || 8080;

# Timeout in seconds for the main function to finish.
my $timeout = int($ENV{FUNC_TIMEOUT} || 10);

# Module name and function handler.
# While deploying the function in kubeless,
# the module name is specified in --from-file command line argument and
# the function name is specified in --handler command line option.
my $mod_name = $ENV{MOD_NAME};
my $func_handler = $ENV{FUNC_HANDLER};

# This is the standard convention followed for kubeless runtimes.
# The main module file will be located in /kubeless/ directory.
# The runtime script needs to prepare the proper path and load it.
my $mod_path = "/kubeless/" . $mod_name . ".pm";

# Only for testing. Need to remove for production purposes.
if (!$mod_name) {
$mod_path = $ENV{MOD_PATH};
}
# TODO: Known bug in Perl Dancer app. Need to set the port here.
set port => $func_port;

# TODO: Set the proper serializer app for JSON. But known bug in Dancer app.
# set serializer => 'JSON';

my %function_context = (
'function' => $func_handler,
'module' => $mod_path,
'timeout' => $timeout,
'runtime' => $ENV{'FUNC_RUNTIME'},
'memory-limit' => $ENV{'FUNC_MEMORY_LIMIT'},
);

# It's always better to load the module in the beginning itself. If there
# are any errors, they will be caught early while the container is loading.
# The same format / style is being followed in other runtimes i.e. python,
# nodejs, etc.
eval {
require($mod_path);
}; if ($@) {
print("Error while invoking the module at $mod_path. Error: " . $@ . " \n");
exit(1);
}

# Endpoint for /healthz. This is one of the main requirements by kubeless
# runtimes.
get '/healthz' => sub {
return 'OK';
};

# Endpoint for /metrics. This is one of the main requirements by kubeless
# runtimes.
get '/metrics' => sub {
# TODO: Needs to implement. We need to use proper prometheus client
# package in perl. But it seems to be complicated. Lets leave this as is
# for now.
return '!!!!! To be Implemented !!!!!';
};

# Wrapper around the main function execution. This function will run the
# main function in a thread and waits for the timeout. If the function doesn'test
# complete in the timeout, then it is explicitly killed and a proper error
# is returned to the client.
sub thread_wrap {
my $event_ref = shift;
my $func_ref = shift;

my $return;

# If the function takes a lot of time to finish, the Main
# thread sends a kill signal to terminate the function.
# Registering a new function for the KILL signal is the only cleanest
# way to provide the timeout functionality.
$SIG{'KILL'} = sub { threads->exit(); };

eval {
my $function = $func_ref->{function};
my $func_result = &{\&{$function}}($event_ref, $func_ref);
$return = $func_result;
}; if ($@) {
$return = "Error while invoking the function. Error: '" . $@ . "' \n";
}

return $return;
}

# Main route mapping for all the incoming HTTP requests from the client.
any ['get', 'post', 'patch', 'delete'] => qr{/.*} => sub {
my $content_type = request->header('content-type');
my $data = request->body();
if ($content_type eq 'application/json') {
eval {
$data = decode_json($data);
}; if ($@) {
response->status(500);
return "Unable to decode the json body. Error: '" . $@ . "' \n";
}
}

# TODO: Implement extensions
# my %extensions = ( 'request' => request );

my %event = (
'data' => $data,
'event-id' => request->header('event-id'),
'event-type' => request->header('event-type'),
'event-time' => request->header('event-time'),
'event-namespace' => request->header('event-namespace'),
);

my $thr = threads->create('thread_wrap', \%event, \%function_context);
my $sleep_interval = 250000;
my $timeout_usecs = $timeout * 1000000;
for (my $i = 0; $i < $timeout_usecs ; $i = $i + $sleep_interval) {
if ($thr->is_running()) {
usleep($sleep_interval);
} else {
last;
}
}

my $result;
if ($thr->is_joinable()) {
$result = $thr->join();
} else {
$thr->kill('KILL')->detach();
$result = "**** Error: Task killed since it exceeded " .
$timeout . " seconds ... ***\n";
response->status(500);
}

return "$result";
};

# Main entry point for the Dancer web application framework.
dance;
22 changes: 21 additions & 1 deletion examples/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,34 @@ get-python-url-deps:
kubeless function deploy get-python-url-deps --runtime python2.7 --handler helloget.foo --from-file https://raw.githubusercontent.com/kubeless/kubeless/v1.0.0-alpha.1/examples/python/hellowithdeps.py --dependencies https://raw.githubusercontent.com/kubeless/kubeless/v1.0.0-alpha.1/examples/python/requirements.txt

get-python-url-deps-verify:
kubeless function call get-python-url-deps |egrep Google
kubeless function call get-python-url-deps |egrep Google

get-node-url-zip:
kubeless function deploy get-node-url-zip --runtime nodejs6 --handler index.helloGet --from-file https://github.com/kubeless/kubeless/blob/master/examples/nodejs/helloFunctions.zip?raw=true

get-node-url-zip-verify:
kubeless function call get-node-url-zip |egrep hello.world

get-perl-526:
kubeless function deploy get-perl-526 --runtime perl5.26 --handler helloget.hello --from-file perl/helloget.pm

get-perl-526-verify:
kubeless function call get-perl-526 | egrep hello.world

get-perl-custom-port:
kubeless function deploy get-perl-custom-port --runtime perl5.26 --handler helloget.hello --from-file perl/helloget.py --port 8085

get-perl-custom-port-verify:
kubectl get svc get-perl-custom-port -o yaml | grep 'targetPort: 8085'
kubeless function call get-perl-custom-port | egrep hello.world

timeout-perl:
kubeless function deploy timeout-perl --runtime perl5.26 --handler hello_sleep.hello_sleep --from-file perl/helloleep.pm

timeout-perl-verify:
$(eval MSG := $(shell kubeless function call timeout-perl 2>&1 || true))
echo $(MSG) | egrep "Error: Task killed since it exceeded"

scheduled-get-python:
kubeless function deploy scheduled-get-python --schedule "* * * * *" --runtime python2.7 --handler helloget.foo --from-file python/helloget.py

Expand Down
21 changes: 21 additions & 0 deletions examples/perl/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2019 VMware, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Create a custom image with a perl function
FROM stanguturi/perl-kubeless:5.26
ENV FUNC_HANDLER=hello \
MOD_NAME=helloget
ADD helloget.pm /
RUN mkdir -p /kubeless/
RUN chown 1000:1000 /kubeless
ENTRYPOINT [ "bash", "-c", "cp /helloget.pm /kubeless/ && perl /kubeless.pl"]
Loading