Going Serverless with OpenFaaS and Golang - The Ultimate Setup and Workflow

martinheinz

Martin Heinz

Posted on November 16, 2019

Going Serverless with OpenFaaS and Golang - The Ultimate Setup and Workflow

Note: This was originally posted at martinheinz.dev

Serverless applications have been cool and hype for a while now and some interesting and useful platforms/tools/frameworks are emerging now. One of them is OpenFaaS which is open source Function as a Service tool for developing cloud-native serverless applications.

In this blog post, I want to show you how to setup (IMHO) the ideal project and workflow for developing OpenFaaS functions, as well as creating your first function using Golang.

Note: Full source code for this post can be found here: https://github.com/MartinHeinz/openfaas-functions

Why OpenFaaS

You might be asking, why - out of all the frameworks like AWS Lambda, Google Cloud Run, OpenWhisk etc. - would I choose OpenFaaS? Well, there are quite few reasons for me:

  • No Vendor-Lock - One big reason for me to choose OpenFaaS over other alternatives, is the fact, that I'm not gonna get vendor locked. Why does it matter so much, though? It matters, because at some point, you might decide, that you want to move to other platform or even completely move away from serverless, which you can't really do with tools like AWS Lambda that forces you to use AWS.

  • Cloud Native - Next advantage is that it runs in cloud environments like Kubernetes, OpenShift or k3s (used in this tutorial). I consider this an advantage because spinning up small Kubernetes cluster is pretty cheap, fast and simple nowadays and deploying OpenFaaS in it is simple too, as you will see below.

  • Any Language - OpenFaaS is able to use any runtime, thanks to the fact that all the functions run in Docker containers. You can even build your own templates from which you build your functions.

  • Performance - Another nice thing about OpenFaaS, is its performance, more specifically - zero-scale or in other words - ability to scale pods to zero when idle, and HTTP-based (beta) templates with lower latency, caching and persistent connections (you will see that in next blog post).

Setting Up

To start building some cool functions, we first need to install few tools. Here's list of them with instructions:

  • k3s - To run the functions we need a cloud environment and considering that we will be using local machine, we should probably choose the most lightweight option possible, here I will use Ranchers k3s (alternatively you could also use KinD):

To install use following commands:

curl -SLsf https://get.k3sup.dev/ | sudo sh
  • OpenFaaS - As mentioned previously, OpenFaaS is a Cloud Native platform, so we need to put it into our Cloud environment (k3s):
k3sup app install openfaas
  • faas-cli - To be able to interact with the deployed OpenFaaS we also need its CLI:
curl -sL https://cli.openfaas.com | sudo sh
faas-cli --version
  • Taskfile - Lastly, our setup uses Taskfile - replacement for Make - to automate common tasks. To install it, you need to run:
cd ~
curl -sL https://taskfile.dev/install.sh | sh
task --version

First Function

Now that we have all our tools ready, let's build our first OpenFaaS function. Before we actually do any coding, we need to pull templates on top of which we will build the functions:

~ $ faas-cli template pull  # Pull from default store
~ $ faas-cli template store list  # Check what we have available
NAME                     SOURCE             DESCRIPTION
...
go                       openfaas           Classic Golang template
...

Above, we can see the one template we are interested in - the go template. Let's create function from it:

~ $ faas-cli new --lang go first-func --prefix=martinheinz
...
Function created in folder: first-func
Stack file written: first-func.yml
...

From the output, we can see which files were created - handler.go and first-func.yml. First of them contains function responsible for handling incoming requests, other one is YAML stack file, that describes the function - it contains information like image name, path to handler, language etc. There are also more values that can be specified, those you can find here. You probably noticed that we also specified prefix flag - we need that, to be able to push image of our built function to remote repository, which is in this case Docker Hub and the prefix is Docker Hub username. OpenFaaS will then use this image from remote registry, when we deploy the function.

I will leave fiddling with code and implementing cool features to you and I will just build, push and deploy the Hello World function as is:

faas-cli build -f first-func.yml
faas-cli push -f first-func.yml
faas-cli deploy -f first-func.yml

And just like that, we have our function running in OpenFaaS on k3s. We can view it both in UI and commandline. For UI view, you can navigate to OPENFAAS_URL (which should be http://127.0.0.1:31112) and for shell you can run following commands to display and invoke the function:

~ $ echo "Martin" | faas-cli invoke first-func
Hello, Go. You said: Martin

I think, this was quite simple and we can build upon this. In the next blog post, we will explore how to make use of newer (beta) of-watchdog templates as well as how to build our own template store including our optimized templates using Go modules. You can have a sneak peak in my template repository here.

Unit Tests

It might seem like we're done here, but there's one thing we are missing - Unit Test(s).

To write our unit test we simply create handler_test.go file alongside handler.go and put this test in there:

package function

import (
    "testing"
)

func TestHandleReturnsCorrectResponse(t *testing.T) {
    expected := "Hello, Go. You said: First Test!"
    response := Handle([]byte("First Test!"))

    if response != expected {
        t.Fatalf("Expected: %v, Got: %v", expected, response)
    }
}

You might have actually noticed, that the go test command was ran during Docker build, so all we need to run tests is build the function like before:

faas-cli build -f first-func.yml
...
RUN CGO_ENABLED=${CGO_ENABLED} GOOS=linux \
    go build --ldflags "-s -w" -a -installsuffix cgo -o handler . && \
    go test $(go list ./... | grep -v /vendor/) -cover
 ---> Running in fc879c8559f8
?       handler [no test files]
ok      handler/function    0.002s  coverage: 100.0% of statements
...

You might think that it is redundant to test little pieces of code like these kinds of serverless functions, but you should not neglect testing just because the code is simple or short, everything should be tested if possible.

Made Simple with Taskfile

In the beginning of the post, I asked you to install Taskfile, but we haven't actually used it yet, so let's change that and make the workflow simpler and create, build, deploy or debug the function with Taskfile targets that I already created for you:

First step - create the function with specified name, using some template:

  task create TPL=go FUNC=first-func

in the background this task downloads templates for you, creates the function and then adds the files to git staging area.

Next, let's build it:

  task build FUNC=first-func

Nothing fancy, it just builds the function, but you can also go ahead and build, push and deploy the function in one command:

  task run FUNC=first-func

Now, to check if everything works as expected, let's check logs:

  task logs FUNC=first-func

This will give description of kubernetes deployment object as well as function logs that should look like this:

2019/11/12 19:33:47 Version: 0.18.1 SHA: b46be5a4d9d9d55da9c4b1e50d86346e0afccf2d
2019/11/12 19:33:47 Timeouts: read: 5s, write: 5s hard: 0s.
2019/11/12 19:33:47 Listening on port: 8080
2019/11/12 19:33:47 Writing lock-file to: /tmp/.lock
2019/11/12 19:33:47 Metrics listening on port: 8081

And if logs aren't enough, you can debug the function with:

  task debug FUNC=first-func

This will run the function locally in Docker and attach terminal to the to watchdog process logs. Now, that the function is running on your machine, you can use cURL to invoke it:

  curl -vvv --header "Content-Type: application/json" \
            --request POST \
            --data '{"key":"value"}' \
            127.0.0.1:8081

To see what's happening inside the Taskfile.yml or to check out little more detailed documentation, please check out Taskfile.yml in my repository here and README.md here.

The Simplest CI/CD

The final thing we need to setup is CI/CD pipeline and we can do that with one last Taskfile target that I omitted in previous section - task verify - this command downloads faas-cli, if not present on the machine and builds each function in the repository using YAMLs stored in functions directory:

...
install:
  - curl -sL https://taskfile.dev/install.sh | sh

before_install:

script:
  - ./bin/task verify
...

For a full code listing, please see files in repository, specifically .travis.yml here and verify.sh here.

Conclusion

As you can see from this article, it can be really easy to start developing serverless applications and I hope you will give it a shot, considering that serverless is not just hype anymore and these technologies are quite mature. If you want to see more details and complete source code from this post please see my repository here and in case you have some suggestions/improvements/issues feel free to submit issue or just star it. 🙂

As mentioned earlier - in the next blog post, we will look at how to set up own template store, create custom templates and use it to create better functions.

💖 💪 🙅 🚩
martinheinz
Martin Heinz

Posted on November 16, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related