Functions as a Service with Nomad and OpenFaaS
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:
- You have regulatory or corporate restrictions which do not allow you to run in shared environments.
- You already have a scheduler cluster and CI/CD workflow for your existing Microservices.
- Serverless still requires an operational overhead to provision and deploy functions, data-stores, and connectors such as message queues.
- 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
» 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
Sign up for the latest HashiCorp news
More blog posts like this one
Terraform Enterprise improves deployment flexibility with Nomad and OpenShift
Customers can now deploy Terraform Enterprise using Red Hat OpenShift or HashiCorp Nomad runtime platforms.
Nomad’s internal garbage collection and optimization discovery during the Nomad Bench project
A look into Nomad’s internal garbage collection process and the optimization discovered during the bench project.
New approaches to measuring Nomad performance
See how the HashiCorp Nomad team re-examined how to capture performance for a workload orchestrator, resulting in new metrics to better capture Nomad’s performance.