In my previous post I demonstrated how the OpenFaaS CLI and build-templates can enable functions on any container orchestrator such as Kubernetes or Google Cloud Run. In today’s post I will focus on how to deploy your Golang code to OpenFaaS either as a function or as a stateless microservice.

What is serverless?

In 2016 I introduced Functions as a Service (FaaS) as a set of behaviours and properties:

Serverless & FaaS:

  • tends involve invoking short-lived functions (Lambda has a default 1-sec timeout)
  • does not publish TCP services - often accesses third-party services or resources
  • tends to be ad-hoc/event-driven such as responding to webhooks
  • should deal gracefully with spikes in traffic
  • despite name, runs on servers backed by a fluid, non-deterministic infrastructure
  • when working well makes the complex appear simple
  • has several related concerns: infrastructure management, batching, access control, definitions, scaling & dependencies

Since then I went on to build out a more refined serverless contract for containers:

This community now has over 2.5 years of experience of working with end-users who are solving real problems with OpenFaaS. For some teams and businesses, rewriting an entire application just to fit into the constraints of a function is not worth the benefit of the cost.

See also: end-user community

Fortunately the OpenFaaS workloads contract means that you don’t have to pick between a function or a microservice.

See also: Introducing Stateless microservices

Tutorial

Here we’ll show you how to build a function using two of the available templates and compare the differences. We’ll then show you how to construct a microservice followed by how we could get the best of both worlds using a Golang middleware template.

I will be using the OpenFaaS CLI which is available via brew install faas-cli, GitHub releases or on Linux/Mac with:

 # use `sudo sh` to move to /usr/local/bin/
curl https://cli.openfaas.com | sh

Golang Function template (classic)

The classic Golang template for OpenFaaS forks one process for every incoming request meaning that state is not retained between invocations. This is a similar model to cgi-bin and uses UNIX STDIO pipes to send the request to the function and to retrieve the response.

Before OpenFaaS had the concept of templates, every function or microservice had its own Dockerfile and entrypoint code. When this template was announced it greatly improved the developer-experience of both writing and maintaining code.

See also: Build a Serverless Golang Function with OpenFaaS (2017)

$ faas-cli new --lang go classic-go

Function created in folder: classic-go
Stack file written: classic-go.yml

Notes:
You have created a new function which uses Golang 1.10.4
To include third-party dependencies, use a vendoring tool like dep:
dep documentation: https://github.com/golang/dep#installation

You’ll get a handler which looks like this:

package function

import (
	"fmt"
)

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

You don’t need to manage a Dockerfile or worry about healthchecks or reading HTTP request and response objects. Your HTTP request headers are available via environment variables if required such as Http_Path and Http_X_Custom_Header

A YAML stack file is also generated for each function and can be used to build multiple functions in parallel, which adds value to the Docker toolchain.

classic-go.yml

provider:
  name: openfaas

functions:
  classic-go:
    lang: go
    handler: ./classic-go
    image: classic-go:latest

To add additional functions to the YAML file just pass --append classic-go.yml to faas-cli new.

This original Go template is widely used and is maintained for backwards compatibility.

Golang Function template (HTTP)

A newer template was developed to give full access and control to the underlying HTTP request and response. This template also moves from using STDIO to HTTP so that database connections can be persisted across invocations. It also allows data to be loaded into memory or cached between requests.

To use this template just pull it from the template store.

$ faas-cli template store pull golang-http
$ faas-cli new --lang golang-http http-go

You’ll see a slightly different signature which makes the HTTP request and response available through an object instead of environment variables:

handler.go

package function

import (
	"fmt"
	"net/http"

	"github.com/openfaas-incubator/go-function-sdk"
)

// Handle a function invocation
func Handle(req handler.Request) (handler.Response, error) {
	var err error

	message := fmt.Sprintf("Hello world, input was: %s", string(req.Body))

	return handler.Response{
		Body:       []byte(message),
		StatusCode: http.StatusOK,
	}, err
}

See also: golang-http README for examples of how to use a database connection and HTTP paths.

Golang Microservice

Existing Docker image

If you already have a Golang microservice then you can convert it to be built by the faas-cli tool-chain or simply add it to your function’s YAML file.

The skip_build flag will allow the service to be deployed without the faas-cli trying to also build it for you. This method is also useful when consuming third-party functions.

provider:
  name: openfaas

functions:
  customer-service:
    image: docker.io/org/customer-service:0.1.0
    skip_build: true
    labels:
      com.openfaas.scale.min: 1
      com.openfaas.scale.max: 10   

See also: YAML stack file reference

New service using a Dockerfile

If you want to build your own Dockerfile and host your own HTTP server then you can do the following using the dockerfile template:

$ faas-cli new --lang dockerfile go-microservice

This will create go-microservice.yml and the folder go-microservice. Simply copy your Dockerfile and source code into the folder.

Let’s look at how much code we’d need for a middleware handler, Dockerfile and main.go entrypoint.

FROM golang:1.10.4-alpine3.8 as build

RUN mkdir -p /go/src/handler
WORKDIR /go/src/handler
COPY . .

RUN CGO_ENABLED=0 GOOS=linux \
    go build --ldflags "-s -w" -a -installsuffix cgo -o handler . && \
    go test $(go list ./... | grep -v /vendor/) -cover

FROM alpine:3.9
# Add non root user and certs
RUN apk --no-cache add ca-certificates \
    && addgroup -S app && adduser -S -g app app \
    && mkdir -p /home/app \
    && chown app /home/app

WORKDIR /home/app

COPY --from=build /go/src/handler/handler    .

RUN chown -R app /home/app

RUN touch /tmp/.lock #  Write a health check for OpenFaaS here or in the HTTP server start-up

USER app

CMD ["/home/app/handler"]

The middleware will simply inject a HTTP header of X-Served-Date with the date the request was processed.

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"
)

func addServedHeader(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("X-Served-Date", time.Now().String())
}

func makeRequestHandler(middleware http.HandlerFunc) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		middleware(w, r)

		w.Write([]byte("OK"))
	}
}

func main() {
	s := &http.Server{
		Addr:           fmt.Sprintf(":%d", 8080),
		ReadTimeout:    3 * time.Second,
		WriteTimeout:   3 * time.Second,
		MaxHeaderBytes: 1 << 20, // Max header of 1MB
	}

	next := addServedHeader
	http.HandleFunc("/", makeRequestHandler(next))
	log.Fatal(s.ListenAndServe())
}

Now run faas-cli up -f go-microservice.yml and invoke the service:

$ curl -i http://127.0.0.1:8080/function/go-microservice ;echo
HTTP/1.1 200 OK
Content-Length: 2
Content-Type: text/plain; charset=utf-8
Date: Wed, 10 Apr 2019 15:08:46 GMT
X-Call-Id: 0b441f6e-8afa-4d82-80dd-48206b1e0e3e
X-Served-Date: 2019-04-10 15:08:46.215153582 +0000 UTC m=+21.331669185

OK

You’ll now be able to invoke your function and see the additional header added. We got to build our own Dockerfile and use the Golang HTTP stdlib which is something you don’t usually see in the world of FaaS frameworks.

Golang middleware template

Now if that all felt a little excessive and repetitive to you, then don’t worry because you’re not the only one. Imagine what that would look like with several dozen of those services? Now what if you need to fine-tune the code in one of the Dockerfiles or update a component?

Fortunately the community came up with a third solution which combines the best parts of the Golang HTTP stdlib and OpenFaaS templates. Let’s look at the example above using the golang-middleware template.

$ faas-cli template store pull golang-middleware
$ faas-cli new --lang golang-middleware go-middle

handler.go

package function

import (
	"fmt"
	"io/ioutil"
	"net/http"
)

func Handle(w http.ResponseWriter, r *http.Request) {
	var input []byte

	if r.Body != nil {
		defer r.Body.Close()

		body, _ := ioutil.ReadAll(r.Body)

		input = body
	}

	w.WriteHeader(http.StatusOK)
	w.Write([]byte(fmt.Sprintf("Hello world, input was: %s", string(input))))
}

This is the standard example with further examples on the README for the golang-http template linked above.

package function

import (
	"net/http"
	"time"
)

func Handle(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("X-Served-Date", time.Now().String())
	w.Write([]byte("OK"))
}

Now you can run faas-cli up -f go-middle.yml and test the new endpoint. You’ll see that it’s functionally equivalent but the maintenance of dozens of similar functions will be trivial compared to tending to boilerplate code.

Here’s the equivalent result like we saw above from our microservice which included a Dockerfile.

$ curl -i http://127.0.0.1:8080/function/go-middle ;echo
HTTP/1.1 200 OK
Content-Length: 2
Content-Type: text/plain; charset=utf-8
Date: Wed, 10 Apr 2019 15:11:49 GMT
X-Call-Id: 1d51dcaf-424a-48a7-a28f-6c5034b46f29
X-Duration-Seconds: 0.000577
X-Served-Date: 2019-04-10 15:11:49.998860566 +0000 UTC m=+1.512602566

OK

As part of a blog post on how to build a full Single Page App with OpenFaaS I used the golang-middleware template to build a function to consume events from GitHub and to store them in a Postgres database.

You can read the code here for handler.go. Confidential data is configured through the use of secrets and non-confidential configuration us set through environment variables in stack.yml.

Custom templates

The easiest way to start building your own template is to checkout some of the examples available from the community in the template store or the Official Classic templates. And remember the OpenFaaS workloads contract.

If you have a binary, HTTP server or shell script that runs on Linux, then the chances are you can package and deploy it with OpenFaaS for a highly scalable, fault-tolerant serverless experience on any cloud.

Wrapping up

A goal of Serverless and FaaS is to reduce boilerplate code and process so that you can focus on shipping code instead of worrying about infrastructure.

OpenFaaS provides a flexible templating system and build process through the use of the Docker image format and its user-friendly faas-cli. We saw several examples of how to package a Golang function and a Golang microservice into containers for use with OpenFaaS.

You can learn how to deploy OpenFaaS to Kubernetes and build functions and microservices using the OpenFaaS workshop.

Join the community

The OpenFaaS community values are: developers-first, operational simplicity, and community-centric.

If you have comments, questions or suggestions or would like to join the community, then please join us on OpenFaaS Slack.

You can follow me @alexellisuk and @openfaas on Twitter

You may also like:

Alex Ellis

Founder of @openfaas. Your Cloud Native Partner @OpenFaaS Ltd.