Functions as a Service with Nomad and OpenFaaS

Functions as a Service with Nomad and OpenFaaS

Jan 10 2018 Nic Jackson

The concept of decomposing your applications into small units of work popularized by AWS Lambda, Google Functions, and Azure Functions is one of the most popular trends in modern software architecture. The main benefits with Serverless, are that you do not have to manage the underlying application infrastructure, and developers can concentrate on delivering business value.

While the developer workflow for Serverless can be incredibly attractive, there are some reasons why this approach may not be suitable for your organization:

  1. You have regulatory or corporate restrictions which do not allow you to run in shared environments.
  2. You already have a scheduler cluster and CI/CD workflow for your existing Microservices.
  3. Serverless still requires an operational overhead to provision and deploy functions, data-stores, and connectors such as message queues.
  4. You want more control over the operating system and runtime platform

OpenFaaS (or Functions as a Service) is a framework for building serverless functions but with containers. With OpenFaaS you can package any process or container as a serverless function for either Linux or Windows - just bring your Nomad cluster. The project focuses on ease of use through its UI and CLI which can be used to test and monitor functions in tandem with Prometheus enabling auto-scaling.

» Architecture

OpenFaaS builds around Docker; all functions are packaged into Docker images, this is the same workflow commonly used for Microservices. The three core components consist of a gateway, a provider, and a monitoring element.

The Gateway is the external API which allows the administration and execution of functions. It does not directly interact with Nomad, but delegates this responsibility to a Provider which manages the lifecycle of a function: deployments, scale, and secrets management. Both the Gateway and the Provider emit metrics such as invocation count and timing data which is collected using Prometheus.

Prometheus also has the capability of broadcasting alerts based on stored metrics. This capability allows the Gateway to listen for alerts and react to them. A typical example of this is to automatically trigger the scaling of a function based on load or other factors.

Let's take a look at how easy it is to create and deploy a function using OpenFaaS on Nomad.

» Running OpenFaaS on Nomad

To run OpenFaaS on your Nomad cluster, you can use the example Job file from the Nomad provider Github repository: https://raw.githubusercontent.com/hashicorp/faas-nomad/master/nomad_job_files/faas.hcl

$ wget https://raw.githubusercontent.com/hashicorp/faas-nomad/master/nomad_job_files/faas.hcl faas.hcl
$ nomad run faas.hcl

Or why not launch the interactive terminal right from this post, the interactive Terminal has Nomad, Consul and OpenFaaS pre-installed.

To try the interactive terminal, please view this post on a device with a larger screen.

» Creating a new function

To create a new function, we can leverage the OpenFaaS CLI faas-cli which is available for most platforms such as Windows, Linux, and MacOS. It also supports many programming languages such as Node, Python, and Go.

Languages available as templates:
- csharp
- go
- go-armhf
- node
- node-arm64
- node-armhf
- python
- python-armhf
- python3
- ruby

In addition to the official templates, you can also use community-submitted templates. We are going to use the bundled Go template and create a function called bcrypt.

$ faas-cli new -lang go bcrypt
Folder: bcrypt created.
  ___                   _____           ____
 / _ \ _ __   ___ _ __ |  ___|_ _  __ _/ ___|
| | | | '_ \ / _ \ '_ \| |_ / _` |/ _` \___ \
| |_| | |_) |  __/ | | |  _| (_| | (_| |___) |
 \___/| .__/ \___|_| |_|_|  \__,_|\__,_|____/
      |_|


Function created in folder: bcrypt
Stack file written: bcrypt.yml

The new command creates the entry point for your function, bcrypt/handler.go and a function definition bcrypt.yml in the current folder:

$ tree -L 2
.
├── bcrypt
│   └── handler.go
├── bcrypt.yml
└── template
    ├── csharp
    ├── go
    ├── go-armhf
    ├── node
    ├── node-arm64
    ├── node-armhf
    ├── python
    ├── python-armhf
    ├── python3
    └── ruby

If we look at handler.go which contains our example function code, we can see it only contains a single function with a simple interface. The payload is sent to the function as a slice of bytes, and the output is a simple string.

package function

import (
    "fmt"
)

// Handle a serverless request
func Handle(req []byte) string {
    return fmt.Sprintf("Hello, Go. You said: %s", string(req))
}

Let's replace this function with something which will allow us to hash a string using bcrypt. Edit the file bcrypt/handler.go and replace the contents with the following function:

$ vim bcrypt/handler.go
package function

import (
        "golang.org/x/crypto/bcrypt"
)

// Handle a serverless request
func Handle(req []byte) string {
        hash, err := bcrypt.GenerateFromPassword(req, bcrypt.DefaultCost)
        if err != nil {
                return ""
        }

        return string(hash)
}

Once you have saved your file, let's run gofmt to check there are no syntax errors:

gofmt -s -w bcrypt/handler.go

We are using the bcrypt library which is in the golang experimental pacakge so we need to vendor the dependencies, we can do this using dep, the dependency management tool for go.

$ cd bcrypt
$ dep init
  Using master as constraint for direct dep golang.org/x/crypto
  Locking in master (b3c9a1d) for direct dep golang.org/x/crypto
$ cd -

The next step is to build our function, and to deploy it to Nomad, before we do, we need to edit the bcrypt.yml file and change the image field adding a Docker registry.

provider:
  name: faas
  gateway: https://my.gateway.com

functions:
  bcrypt:
    lang: go
    handler: ./bcrypt
    image: my.docker.registry.com/bcrypt

We also change the gateway field and point to the gateway running on our Nomad cluster.

If you are using the interactive terminal, the environment variables FAAS_GATEWAY and DOCKER_REGISTRY contain the correct values to add to your bcrypt.yml file.

$ sed -i "s#^\(\s*image\s*:\s*\).*#\1${DOCKER_REGISTRY}/bcrypt#" bcrypt.yml
$ sed -i "s#^\(\s*gateway\s*:\s*\).*#\1${FAAS_GATEWAY}#" bcrypt.yml

Now let's build our function:

$ faas-cli build --yaml bcrypt.yml
[0] > Building: bcrypt.
Clearing temporary build folder: ./build/bcrypt/
#...
Removing intermediate container a5e0be2bfdc9
Successfully built 74cbd7f5e103
Successfully tagged bcrypt:latest
Image: bcrypt built.
[0] < Builder done.

The build process executes in a Docker container, other than Docker, and the faas-cli you do not need any other dependencies. Before we deploy the function we need to push it to a Docker registry, we can do this using the faas-cli push command.

$ faas-cli push --yaml bcrypt.yml
[0] > Pushing: bcrypt.
The push refers to a repository [2886795314-5000-ollie01.environments.katacoda.com/bcrypt]
974e4f38d961: Layer already exists 
8af4da1e470e: Layer already exists 
f6a874b4e2c7: Layer already exists 
ec8a829eb761: Layer already exists 
60273f4e6fd4: Pushed 
2aebd096e0e2: Layer already exists 
latest: digest: sha256:b253f51abbcc53b69c84eed015cc244871de6049e4410200230c2612408a4075 size: 1574
[0] < Pushing done.

Now deploy the function to OpenFaaS on our Nomad cluster.

$ faas-cli deploy --yaml bcrypt.yml
Deploying: bcrypt.
Removing old function.
Deployed.
URL: https://2886795314-8080-ollie01.environments.katacoda.com/function/bcrypt

200 OK

When we deploy a function the OpenFaaS gateway delegates to the Nomad provider which creates the job on the cluster. You can check that the function is running using the nomad status command.

$ nomad status
ID               Type     Priority  Status   Submit Date
OpenFaaS-bcrypt  service  1         running  01/10/18 17:37:16 UTC
faas-nomadd      system   50        running  01/10/18 17:14:08 UTC

To test our function, we can use faas-cli, or we can call the function using its HTTP interface. First, let's use the faas-cli command:

$ echo "password" | faas-cli invoke --yaml bcrypt.yml bcrypt
$2a$10$zFYttFRKZorR3zHsoquQ5.qypL0/4vTbzjzM2bWdzOBtMq8XhXSZG

We can also invoke it using curl or another HTTP client using the gateway URL:

$ curl -d 'Nic' $FAAS_GATEWAY/function/bcrypt
$2a$10$FddcGwse0z0PlX6o3tr.SuGgUK9poanZLvqdGHAjz.L9Jp.J5qGI6

That is all that is required to get the most basic function created and deployed, to see a more comprehensive example including how you can take a test-driven approach to creating functions, why not take a look at my example for sending tweets: https://github.com/nicholasjackson/open-faas-functions/tree/master/tweet

» Summary

We hope you enjoyed this post, and we look forward to hearing about the fantastic things you build with Nomad and OpenFaaS.

If you would like to dive in deeper, please take a look at the OpenFaaS documentation: https://github.com/openfaas/faas/blob/master/guide/README.md

For more information on the Nomad provider, including how to configure monitoring with Prometheus, please see the documentation in our GitHub repo: https://github.com/hashicorp/faas-nomad

And finally, if you are looking for some inspiration for the wonderful things you can do with OpenFaaS, please follow the blog of Alex Ellis, the creator of OpenFaaS: https://blog.alexellis.io

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×