Google Cloud Functions for Go

Note: I have been using GCF’s Go support recently. I migrated a few of my projects and writing this article as a guidance if anyone wants to give a try. You need to apply for early access if you want to try.

Hello World

Let me start with a simple “hello world” to introduce you to the overall build+deploy experience. GCF expects an http.HandlerFunc to be the entry point.Create a package called “hellofunc” and add a trivial handler:

$ cat hello/fn.go
package hello
import (
"fmt"
"net/http"
)
func HelloWorld(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}

In order to deploy, use the following command. It will create a new function called hellofunc and will use HelloWorld as the entry point. The Go runtime to be used will be Go.11.

$ gcloud alpha functions deploy hello --entry-point HelloWorld --runtime go111 --trigger-http
Deploying function (may take a while - up to 2 minutes)...

Deploying may take a while as it is noted. Once deployed, you will be able to see the HTTP endpoints on your terminal. You can also view your functions at the Cloud Console.

On the console, you can see “hello” function is deployed. You can access to logs and basic metrics such as number of invocations, execution time and memory usage.

Dependencies

If you have external dependencies, you have to vendor them under the library package. This is possible by any vendoring tool. I imported the golang.org/x/sync/errgroup package as an example. I will use Go 1.11 modules to vendor my dependencies.

$ export GO111MODULE=on
$ cd hello
$ go mod init && go mod vendor
$ tree
hellofunc
├── fn.go
├── go.mod
├── go.sum
└── vendor
├── golang.org
...

The dependency is vendored locally and I can redeploy the function again.

$ gcloud alpha functions deploy hello --entry-point HelloWorld --runtime go111 --trigger-http
Deploying function (may take a while - up to 2 minutes)...
availableMemoryMb: 256
entryPoint: HelloWorld
httpsTrigger:
url: https://us-central1-bamboo-shift-504.cloudfunctions.net/hello
...

Function is redeployed at https://us-central1-bamboo-shift-504.cloudfunctions.net/hello. See it yourself. You can also call the function from command line:

$ gcloud alpha functions call hello
executionId: x71xpor7tasd
result: Hello, World!

I also generated some load from my laptop to the function to provide you a more realistic response time data. I made 1000 requests, 10 concurrently at a time. You can see that there are some outliers but most calls fall into the 213 milliseconds bucket.

Code Organization

In Go, we organize packages by responsibility. This also fits well with serverless design patterns — a function is representing one responsibility. I create a new module for each function, provide function-specific other APIs from the same module.

The main entry point handler is always in fn.go, this helps me to quickly find the main handler the way main.go would help me to find the main function.

Common functionality lives in a separate module and vendored on the function package because GCF CLI uploads and deploys only one module at a time. We are thinking about how this situation can be improved but, currently a module should contain all of its dependencies itself.

An example tree is below. Package config contains configuration-related common functionality. It is a module and is imported and vendored by the other functions (hello and user).

$ tree
fns
├── config (commonly used module)
│ ├── config.go
│ ├── go.mod
│ └── go.sum
├── hello
│ ├── fn.go
│ ├── go.mod
│ ├── go.sum
│ └── vendor (contains all dependencies + config)
│ ├── ...
│ └── modules.txt
└── user
├── fn.go
├── go.mod
└── vendor (contains all dependencies + config)
├── ...
└── modules.txt

Chaining Handlers

Unlike other providers, we decided to go with Go idiomatic handler APIs (func(ResponseWriter, *Request)) as the main entry point. This allows you to utilize existing middlewares available in the Go ecosystem more easily. For example, in the following example, I am using ochttp to automatically create traces for incoming HTTP requests.

package hello
import (
"fmt"
"net/http"
   "go.opencensus.io/plugin/ochttp"
)
func HelloWorld(w http.ResponseWriter, r *http.Request) {
fn := func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello world")
}
traced := &ochttp.Handler{
Handler: http.HandlerFunc(fn),
}
traced.ServeHTTP(w, r)
}

For each incoming request, an incoming trace span is created. If you register an exporter, you can upload the traces to any backend we support including Stackdriver Trace of course.


GCF for Go is very early stage and still is improving. I was dogfooding it recently and giving feedback. I encourage you to do the same. Fill the form to apply for early access.