Skip to content

OpenFaaS First Function

In this example we are going to deploy Python3 function to our OpenFaaS.

Before we start make sure all is working and you followed the guides before how to setup K3s on Raspberry Pi 4, create TLS private registry and install faas-cli/OpenFaaS,

Read the official documentation

Always the best and first thing to do is read the official OpenFaaS documentation.

Lets start

Notice

Keep in mind we are going this on control01 which is my control node for Kubernetes cluster and where we setup Docker. Normally you should not do this. Official recommendation is to build on your workstation and push via OpenFaas gateway, like its in documentation.

Create new directory where you going work in, I called mine openfaas_scripts. We need to download templates into this directory, faas-cli is reading a skeleton of the function from that directory when creating new function.

cd openfaas_scripts
faas-cli template pull

Now you should have new folder called template.

Template store

We are going to use Python3 because thats what I'm familiar with the most, but the beauty of OpenFaaS is that you are not limited to one language. You can list supported templates like this:

ubuntu@control01:~/openfaas_scripts$ faas-cli template store list

NAME                     SOURCE             DESCRIPTION
csharp                   openfaas           Classic C# template
dockerfile               openfaas           Classic Dockerfile template
go                       openfaas           Classic Golang template
java8                    openfaas           Java 8 template
java11                   openfaas           Java 11 template
java11-vert-x            openfaas           Java 11 Vert.x template
node12                   openfaas           HTTP-based Node 12 template
node                     openfaas           Classic NodeJS 8 template
php7                     openfaas           Classic PHP 7 template
python                   openfaas           Classic Python 2.7 template
python3                  openfaas           Classic Python 3.6 template
python3-dlrs             intel              Deep Learning Reference Stack v0.4 for ML workloads
ruby                     openfaas           Classic Ruby 2.5 template
ruby-http                openfaas           Ruby 2.4 HTTP template
python27-flask           openfaas           Python 2.7 Flask template
python3-flask            openfaas           Python 3.7 Flask template
python3-flask-debian     openfaas           Python 3.7 Flask template based on Debian
python3-http             openfaas           Python 3.7 with Flask and HTTP
python3-http-debian      openfaas           Python 3.7 with Flask and HTTP based on Debian
golang-http              openfaas           Golang HTTP template
golang-middleware        openfaas           Golang Middleware template
python3-debian           openfaas           Python 3 Debian template
powershell-template      openfaas-incubator Powershell Core Ubuntu:16.04 template
powershell-http-template openfaas-incubator Powershell Core HTTP Ubuntu:16.04 template
rust                     booyaa             Rust template
crystal                  tpei               Crystal template
csharp-httprequest       distantcam         C# HTTP template
csharp-kestrel           burtonr            C# Kestrel HTTP template
vertx-native             pmlopes            Eclipse Vert.x native image template
swift                    affix              Swift 4.2 Template
lua53                    affix              Lua 5.3 Template
vala                     affix              Vala Template
vala-http                affix              Non-Forking Vala Template
quarkus-native           pmlopes            Quarkus.io native image template
perl-alpine              tmiklas            Perl language template based on Alpine image
crystal-http             koffeinfrei        Crystal HTTP template
rust-http                openfaas-incubator Rust HTTP template
bash-streaming           openfaas-incubator Bash Streaming template
cobol                    devries            COBOL Template

Creating new function

Creating new function in OpenFaaS is super easy run:

faas-cli new --lang python3 mailme

--lang specify the template name from above, and the next parameter is name of our function, in my case its mailme

Now you should have new folder called mailme and in it:

root@control01:/home/ubuntu/openfaas_scripts/mailme# ll
total 12
drwx------ 2 ubuntu ubuntu 4096 Feb  2 17:27 ./
drwxrwxr-x 5 ubuntu ubuntu 4096 Feb  2 16:19 ../
-rw-rw-r-- 1 ubuntu ubuntu    0 Jan 31 13:47 __init__.py
-rw-rw-r-- 1 ubuntu ubuntu 2568 Feb  2 17:27 handler.py
-rw-rw-r-- 1 ubuntu ubuntu    0 Jan 31 13:47 requirements.txt

and also mailme.yml outside of it.

This is super simple:

  • handler.py - This is where your function will live
  • requirements.txt - If you require any additional modules to be present that are not in Python3 by default, here you add the names and they will get auto magically installed ( it works as any requirements.txt in standalone python)

What will our function do ?

I wanted something simple that would also include some core stuff that OpenFaaS supports. Especially secrets and passing variables to script from outside... Our function will send mail. Simple as that 🙂

Its good to define some input parameters ahead starting so we do not change stuff in the middle of coding. Our function will accept 3 parameters in yaml format.

  • api-key - a secret api-key, that will live in Kubernetes secrets and will be presented to your function as readable file. ( so its not hard coded into code ) and shared with other functions if you want. Side read about Kubernetes secretes kubernetes.io
  • msg - What message to send
  • to - To what email

Input parameters in yaml are:

{
"api-key": "gr35p4inyyr4e9",
"msg": "test msg",
"to": "my@gmail.com"
}

Secrets

Secrets are preferred way to pass sensitive informations to your function. They need to be created ahead of deployment of function. We are going to put our api-key and password for smtp server we are going to use to send mail.

kubectl create secret generic api-key --from-literal api-key="gr35p4inyyr4e9" --namespace openfaas-fn
kubectl create secret generic email-pass --from-literal email-pass="smtp_email_password_goes_here" --namespace openfaas-fn

In case you want to list your secret names use:

root@control01:/home/ubuntu/openfaas_scripts# kubectl get secret -n openfaas-fn
NAME                  TYPE                                  DATA   AGE
api-key               Opaque                                1      3d22h
default-token-jr2bh   kubernetes.io/service-account-token   3      11d
email-pass            Opaque                                1      3d22h

To delete secret:

kubectl delete secret -n openfaas-fn <secret_name>

To read the secret:

root@control01:/home/ubuntu/openfaas_scripts# kubectl get secret api-key -n openfaas-fn -o json | jq '.data '
{
  "api-key": "Z3IzNXA0aW55eXI0ZTk="
}
echo "Z3IzNXA0aW55eXI0ZTk=" | base64 --decode
gr35p4inyyr4e9

Secrets in OpenFaaS

To get to the secret inside your OpenFaaS function you first need to create it, than define it your function yaml file

My whole mailme.yml looks like this ( I will get to the other parameters later on )

version: 1.0
provider:
  name: openfaas
  gateway: http://openfaas.cube.local:8080
functions:
  mailme:
    lang: python3
    handler: ./mailme
    image: registry.cube.local:5000/mailme:latest

    environment:
      smtp_server: smtp.websupport.sk
      smtp_login: admin@rpi4cluster.com
      sender: admin@rpi4cluster.com

    secrets:
      - api-key
      - email-pass

You can see there is section called secrets here we specify the names of secrets we created.

But how the fuck i get to them inside my function ? I'm glad you asked. They will be presented as a file. And its up to you to read and use it.

They will always be in format:

'/var/openfaas/secrets/' + secret_name

Thats secrets, but what about not so secret stuff I don't want to bake directly into code and be able to change without recompiling everything ? Well environmental variables is what you want.

Environment variables in OpenFaas

These are variables defined outside your code, something you might like to change without going through of rebuilding your whole function. There are for non sensitive data, perhaps configuration.

In this case you can see above in my mailme.yml I defined three such variables.

  • smtp_server - server we are going to use to send my mail through.
  • smtp_login - User name for that server ( Remember we have the password in Kubernetes secrets )
  • sender - Whose the sender of this message

Super simple right ?

To get to these in your code, you need to query environmental variables from OS, this depends very much on language you going to use... for example Pythin 3 can do

os.getenv(var_name)
That should be it, secrets and environment variables. Lets get to the main function.

OpenFaaS Function

Go to ../mailme/handler.py the handler.py is your function, and its pre populated with:

def handle(req):
    """handle a request to the function
    Args:
        req (str): request body
    """

    return req

This function handle(input) is called when your function is invoked by OpenFaaS. We are going to extend it with my mailing functionality.

Notice

Read the comments in code to get whats going on

# import libraries we going to use
# no shebang is needed at the start
# all libs I'm importing are native to python so I did not put anything in requirements.txt
import os
import json
import smtplib
from smtplib import SMTPException
from smtplib import SMTPDataError
from email.mime.text import MIMEText
from email.utils import formataddr



def get_secret(secret_name):
    '''
    - Returns secret value if exists, if not return False
    - Secret needs to be create before the function is build
    - Secret needs to be defined in functions yaml file
    '''
    try:
        with open('/var/openfaas/secrets/' + secret_name) as secret:
            secret_key = secret.readline().rstrip()
            return secret_key
    except FileNotFoundError:
        return False

def get_variable(var_name):
    '''
    - Returns environment variable value if exists, if not return False
    - Variable needs to be defined in functions yaml file
    '''
    return os.getenv(var_name, False)


def api_key_check(provided_key):
    '''
    - Check if provided api key is valid
    '''
    if get_secret('api-key') == provided_key:
        return True
    else:
        return False


def key_present(json, key):
    '''
    - Return true if Key exist in json
    '''
    try:
        _x = json[key]
    except KeyError:
        return False
    return True


def handle(req):
    """handle a request to the function
    Args:
        req (str): request body
    """

    # If there is value passed to function
    if req:
        # check if its json formated by trying to load it
        try:
            json_req = json.loads(req)
        except ValueError as e:
            return "Bad Request", 400
    else:
        return "Bad Request", 400

    # Before anything check if api key from secret match api key provided
    # Might me good to implement this so there is no random spamming of function
    if key_present(json_req, 'api-key'):
        key = json_req["api-key"]
        if api_key_check(key) is False:
            return "Unauthorized", 401
    else:
        return "Unauthorized", 401

    # Cool if we are here api key was authorized
    # Let check if in posted body are keys that we need ( msg, to)
    if key_present(json_req, 'msg'):
        msg_text = json_req["msg"]
    else:
        return "Bad Request", 400

    if key_present(json_req, 'to'):
        to = json_req["to"]
    else:
        return "Bad Request", 400

    # So we have values for message, to whom to send it, lets get sender
    sender = get_variable('sender')

    # Lets try to build message body and send it out.
    try:
        msg = MIMEText(msg_text)
        msg['From'] = formataddr(('Author', get_variable('sender')))
        msg['To'] = formataddr(('Recipient', to))
        msg['Subject'] = 'OpenFaas Mailer'

        mail_server = smtplib.SMTP_SSL(get_variable('smtp_server'))
        mail_server.login(get_variable('smtp_login'), get_secret('email-pass'))
        mail_server.sendmail(sender, to, msg.as_string())
        mail_server.quit
        return "request accepted", 202
    except SMTPException:
        return "Failed to send email", 500
    except SMTPDataError:
        return "Failed to send email", 500

As you can see the function is quite simple, and contains no sensitive data.

Build and Push OpenFaaS function.

Next go where the mailme.yml is and if you followed my guide from before ( and fixed buildx to know where your repository is 🙂 ) we should have no problem with following.

Notice

This is specific for arm64 for more information about building a pushing OpenFaaS functions look here:

faas-cli publish -f mailme.yml --platforms linux/arm64

This should build the function docker image and push it to your repository. Ending with with something like this:

#25 pushing layers
#25 pushing layers 4.7s done
#25 pushing manifest for registry.cube.local:5000/mailme:latest
#25 pushing manifest for registry.cube.local:5000/mailme:latest 0.1s done
#25 DONE 18.1s
Image: registry.cube.local:5000/mailme:latest built.
[0] < Building mailme done in 57.37s.
[0] Worker done.

Total build time: 57.37s

Fantastic! last steps are ahead. Deploy this sucker to OpenFaaS on your kubernetes server !

faas-cli deploy -f mailme.yml

Example of successful deployment.

buntu@control01:~/openfaas_scripts$ faas-cli deploy -f mailme.yml
Deploying: mailme.
WARNING! Communication is not secure, please consider using HTTPS. Letsencrypt.org offers free SSL/TLS certificates.

Deployed. 202 Accepted.
URL: http://openfaas.cube.local:8080/function/mailme.openfaas-fn

Check it in kubernetes

ubuntu@control01:~/openfaas_scripts$ sudo kubectl get pod -n openfaas-fn
NAME                        READY   STATUS    RESTARTS   AGE
mailme-7b94f89946-7fxwz     1/1     Running   0          70s
nodeinfo-6c4754548b-6zxkq   1/1     Running   2          10d

Two functions are deployed here, but only the mailme-7b94f89946-7fxwz is related to what we doing. It status us Running, which is great ! If there is some error there run following command to get some feels why its not working.

sudo kubectl describe pod mailme-7b94f89946-7fxwz -n openfaas-fn

Invoking OpenFaaS function

You have couple of options.

faas-cli

echo '{ "api-key": "gr35p4inyyr4e9", "msg": "test msg", "to": "vladoportos@gmail.com" }' | faas-cli invoke mailme

And mails was delivered, you just have to trust me on this 🙂

curl

curl openfaas.cube.local:8080/function/mailme -d '{ "api-key": "gr35p4inyyr4e9", "msg": "test msg", "to": "vladoportos@gmail.com" }'

Web UI

Open the web UI of OpenFaaS, refer to section about installing OpenFaaS to know how I got it. Docker-Registry TLS

Important

The three toggles on top * Text * JSON * Download Are what output you expecting from your function, not input... so don't switch to json if your function is not returning json...

OpenFaaS web UI

All of them should return.

('request accepted', 202)

Did you like it or was it helpful ? Get your self a drink and maybe get me one as well 🙂


Last update: February 8, 2021

Comments