Build a Serverless Golang Function with OpenFaaS

In this post I want to show you how to build a Serverless function in Go with our new Golang template created by the OpenFaaS community.

OpenFaaS is the only Serverless framework which puts containers in the spotlight and allows any code or binary for Linux or Windows to become a serverless function.

Pre-reqs

  • Go 1.8.3 or later (we use this on the host system to vendor packages)
  • Docker
  • OpenFaaS deployed (locally or remotely)
  • OpenFaaS CLI (installed in this tutorial)

This guide assumes you have already deployed OpenFaaS on your laptop or the cloud.

You can deploy OpenFaaS by following the Swarm or Kubernetes guides here:

OpenFaaS Deployment guides

Go

Go (golang) is well suited to writing serverless applications because it has a modern set of libraries covering everything from Marshaling to HTTP to Image Manipulation and much more. This results in concise code which is easy to understand and also offers a simple API for concurrency through channels.

Your code can compile down to a small binary with no other external runtimes required - it can offer C-like speed and power but with type-safety.

We'll start with hello world then move onto a function which makes use of a third-party (vendored) library to create hashes from Go structs.

Let's get started

So let's grab the OpenFaaS CLI and create a new Go function using the language template:

  • Install with curl for the latest version, or with brew
$ curl -sL cli.openfaas.com | sudo sh

If you already have the CLI then run the command again so that you're up to date and in-sync.

We'll get hello world working, then move onto something more interesting where we also vendor a package.

1. Hello world

  • Create a function from a template

Use faas-cli new to create a template in one of the supported languages, or faas-cli new --lang Dockerfile to use a custom Dockerfile.

  • Create a folder for your functions
$ mkdir -p $GOPATH/src/functions && \
  cd $GOPATH/src/functions

Then use the CLI to create your Go function:

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

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

We now have a folder called gohash with a handler.go file inside:

package function

import (
	"fmt"
)

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

Now you build / deploy and invoke the function.

If you are on a multi-node cluster, you'll need to edit the gohash.yml file which was generated by the CLI. Put your Docker Hub account into the image: section. i.e. image: alexellis2/gohash:latest.

$ faas-cli build -f gohash.yml

The build uses your local Docker client.

If you're on a remote cluster or a multi-node cluster push the image to a registry or the Docker Hub:

$ faas-cli push -f gohash.yml

Now let's deploy the function:

$ faas-cli deploy -f gohash.yml

Note: as of 0.7.6 of the OpenFaaS CLI you can replace faas-cli build/push/deploy with a single command: faas-cli up.

If this is on a remote cluster add the parameter --gateway http://...

Finally you can invoke your function using the OpenFaaS UI or the CLI with:

echo -n "test" | faas-cli invoke gohash

If you'd like to delete the function run this:

echo -n "test" | faas-cli delete gohash

2. Use a 3rd party dependency

We'll use a quick example of a project called structhash which generates a hash in a string format of arbitrary structs. This is useful for creating checksums and hashes of graphs within memory.

structhash is a Go library for generating hash strings of arbitrary data structures.

This is a snippet from the structhash QuickStart on GitHub:

package main

import (
    "fmt"
    "crypto/md5"
    "crypto/sha1"
    "github.com/cnf/structhash"
)

type S struct {
    Str string
    Num int
}

func main() {
    s := S{"hello", 123}

    hash, err := structhash.Hash(s, 1)
    if err != nil {
        panic(err)
    }
    fmt.Println(hash)
    // Prints: v1_41011bfa1a996db6d0b1075981f5aa8f
}
  • Build a custom function

Let's take a quick look at the signature of our Go template:

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

You can see the input is of type []byte which means you can also deal with binary data, or just convert this to a string as we are doing above.

  • Vendor the library

Let's go ahead and vendor the dependency of github.com/cnf/structhash so we can use it in our function.

Install Go's vendoring tool dep:

$ go get -u github.com/golang/dep/cmd/dep

dep needs Golang 1.8 or later, if you can't update from Golang 1.7 or 1.6 then please try another vendor tool like: vndr.

Invoke vendoring on the library:

$ cd $GOPATH/src/functions/gohash
$ dep init && \
  dep ensure -add github.com/cnf/structhash && \
  ls
cd $GOPATH/src/functions

Vendoring is Golang's approach to dependency management. It involves copying the entire source tree of any dependent projects into your project.

You'll now see these new files in the ./gohash folder:

Gopkg.lock Gopkg.toml ./vendor

A copy of the library is copied inside the vendor folder.

Now let's update the code, build, deploy and invoke it.

  • Update gohash/handler.go:
package function

import (
	"fmt"
	"github.com/cnf/structhash"
)

type S struct {
	Str string
	Num int
}

// Handle a serverless request
func Handle(req []byte) string {
	s := S{string(req), len(req)}

	hash, err := structhash.Hash(s, 1)
	if err != nil {
		panic(err)
	}
	return fmt.Sprintf("%s", hash)
}

The code populates our request and its length into the struct ready to be turned into a hash.

Build and deploy:

$ cd $GOPATH/src/functions
$ faas-cli build -f gohash.yml && \
  faas-cli deploy -f gohash.yml

When you're ready invoke it with some input:

$ echo "Serverless" | faas-cli invoke gohash

If you're running into a build error, then please make sure you ran dep in the correct location and that you're using Go 1.8 or later as advised.

  • Rinse-repeat

Now just edit your handler.go file and run faas-cli build followed by faas-cli deploy for each change.

A note on pushing: If you're using a remote cluster you will also need to run faas-cli push so the function can be deployed on any available host.

As part of the build we check that your formatting is correct according to the Golang standard. An easy way to get correct formatting is to run the gofmt tool or to use an IDE like VSCode.

$ gofmt -s -w gohash/handler.go

If you head over to http://localhost:8080/ you will also be able to invoke the function through the built-in UI and at http://localhost:9090 you will see all the metrics which have been recorded automatically through Prometheus.

  • Want to go deeper?

Checkout the Dockerfile in the "template/golang" folder which uses Alpine Linux and multi-stage builds to produce a tiny function image.

Test everything

We've included a unit test in this post along with the sample function. So any tests you have in the function's folder are run and validated as part of your build. If you haven't quite mastered unit testing in Go you can delete the file or head over to my tutorial:

gohash/handler_test.go

package function

import "testing"

func TestHandleReturnsCorrectResponse(t *testing.T) {
	expected := "v1_b8dfcbb21f81a35afde754b30e3228cf"
	resp := Handle([]byte("Hello World"))

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

Wrapping up

We've just built a native Golang function and deployed it to OpenFaaS including vendored dependencies. OpenFaaS supports any process or code as a function but our unique templates mean you can get to serverless even faster.

"Function templates" - from my presentation at JeffConf Milan

Did you know OpenFaaS won the InfoWorld award for Best Cloud Software 2017?

Star the OpenFaaS repo and find out why so many people are choosing OpenFaaS - the only Cloud Native Serverless framework for Kubernetes and Docker Swarm.

See also:

Mentions: thank you to Richard Gee for proof reading and testing these instructions.