AWS Lambda Hands On
Table of Contents
AWS – a pioneer and de facto leader in serverless computing – had already added Go as a supported runtime to AWS Lambda back at the beginning of 2018.
Advantages of Go (Golang) for AWS Lambda
While all Lambda runtimes offer the same advantages in terms of scalability and share many concepts, there are some notable advantages when you use Go: Runtime versioning scheme, cold start performance, and pricing.
Runtime Versioning
The Go runtime for AWS Lambda has a very significant difference from every other runtime available today. While other runtimes support only specific versions of a language (for example, Python 2.7, 3.6, and 3.7 are three separate runtimes), the Go runtime supports any 1.x release, which already spans over seven years of releases.
Whenever a new version of Go is released, it’s possible to use it from day one, without having to wait for AWS to release a newly updated runtime. Such a feature would not be possible were Go not a statically compiled language with its Go 1 compatibility guarantee.
Cold Starts
When a Lambda function hasn’t had an invocation for a while, or when a spike in traffic requires additional functions to spawn, there’s a small penalty associated with it – the often dreaded cold start. Fortunately, Go has one of the fastest cold start times. You can read more on how to minimize AWS Lambda cold starts here.
Pricing
The pricing model of AWS Lambda is per invocation, plus the duration of the invocation rounded up to the nearest 100ms. The price per 100ms also depends on the amount of memory allocated to the function.
While Go isn’t necessarily as memory-hungry as some dynamic languages, there is a small catch: a direct correlation between the CPU performance and allocated memory. While Go might also squeeze more performance out of a throttled CPU than some other languages, this remains an important point to consider.
With all this in mind, you can rest assured that Go is an excellent choice for AWS Lambda: A performant, cost-efficient language with the bonus of being able to run the latest (or even prerelease) version of the Go language without waiting for AWS to update their runtimes.
Getting Started with AWS Lambda and Go (Golang)
So, let’s get some coding done. You are going to create an HTTP triggered AWS Lambda function that responds with JSON. We’ll start by returning a response that contains the 1,000th of the Fibonacci number and then improve it to returning a random Fibonacci number.
Before we can start, however, there are a few prerequisites we must fulfill.
Prerequisites
You’ll need an AWS account for this. If you don’t yet have one, sign up for a free account here. The AWS free tier includes one million invocations every month and enough credit to run a single 128MB function continuously, all at no charge.
For building and deploying your functions, you’ll be using the Serverless Framework, which is the most widely used tool for the job. Assuming you have a recent version of Node.js installed, you can install the Serverless CLI with the following npm command:
npm install -g serverless
Once you have the Serverless CLI installed, you must configure it to use the AWS access keys of your account:
serverless config credentials --provider aws --key <access key ID> --secret <secret access key>
If you don’t have Go and nodejs installed yet, use brew install them:
brew install go nodejs
The Lambda-Fib Function
Now that you have everything you need, let’s create a new project using Go modules for your function. For example, let’s name the module lambda-fib. In a new, empty directory, initialize the Go module, and install the AWS Lambda for Go library:
go mod init lambda-fib
go get github.com/aws/aws-lambda-go
After this, you can proceed to create a main.go file that implements your handler function and starts the process:
package main
import (
"context"
"encoding/json"
"math/big"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
// fibonacciBig calculates Fibonacci number using bit.Int
func fibonacciBig(n uint) *big.Int {
if n <= 1 {
return big.NewInt(int64(n))
}
var n2, n1 = big.NewInt(0), big.NewInt(1)
for i := uint(1); i < n; i++ {
n2.Add(n2, n1)
n1, n2 = n2, n1
}
return n1
}
type response struct {
FibonacciNum1000 *big.Int `json:"fibonacci_num_1000"`
}
func handleRequest(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
resp := &response{
FibonacciNum1000: fibonacciBig(1000),
}
body, err := json.Marshal(resp)
if err != nil {
return events.APIGatewayProxyResponse{}, err
}
return events.APIGatewayProxyResponse{Body: string(body), StatusCode: 200}, nil
}
func main() {
lambda.Start(handleRequest)
}
This previous code can be broken into a few simple steps:
- Define a High-Performance function for computing Fibonacci number.
- Define a response struct that supports JSON serialization and defines the HTTP response body of a successful invocation of your AWS Lambda function.
- Create a request handler function, which creates a response struct containing the 1,000th of the Fibonacci number and then proceeds to serialize it as JSON. In case the serialization fails, you return the error; if everything goes well, you respond with your serialized JSON as the response body and a status code of 200.
- Register your handler function in the main function using the AWS Lambda for Go library.
The Handler Function
It’s worth taking some time to understand how the handler function works. While there are multiple valid handler signatures, the one you used is the complete one. The context argument provides information on the invoked function, its environment, and also the deadline of the invocation. Returning an error value from the handler function signals that the invocation failed and automatically logs the value of the error.
That leaves the request and response structs in your handler function signature. Lambda functions are invoked either by AWS services or by using an AWS SDK (e.g., from another Lambda function). Data passed in and out of a Lambda function is in JSON format. In your case, the AWS Lambda for Go library automatically handles the serialization and deserialization between JSON and Go values.
When calling Lambda functions using the AWS SDK, the structure of the input and output JSON data is up to the developer. For AWS Lambda functions invoked by AWS services, the data structure depends on the invoking service. Amazon API Gateway is the service that triggers Lambda functions in response to HTTP calls. For API Gateway, this means the request is always of type events.APIGatewayProxyRequest and the response will always be of type events.APIGatewayProxyResponse.
The AWS Lambda for Go library contains the data definitions for each AWS service that can invoke Lambda functions.
Deployment
Your function is now ready and you can proceed by deploying it with the Serverless Framework. For that, we must first create a serverless.yml file that defines what we are deploying:
---
service: lambda-fibonacci
provider:
name: aws
runtime: go1.x
region: ap-northeast-2
stage: dev
package:
exclude:
- ./**
include:
- ./bin/**
functions:
lambda-fib:
handler: bin/lambda-fib
events:
- http:
path: /
method: get
Here you name both your service and function lambda-fib, but a service could instead contain multiple functions with different names. You also need to configure your API Gateway by specifying that the function responds to HTTP events of a particular HTTP method and at a given request path.
Next up, build the code as an x86-64 Linux executable, and deploy it. Create a Makefile:
ifneq (,)
.error This Makefile requires GNU Make.
endif
.PHONY: all app deploy update clean
GO := go
BUILD := GO111MODULE=on CGO_ENABLED=0 GOOS=linux GOARCH=amd64
FLAGS := -installsuffix cgo -ldflags="-s -w"
all: app deploy
app:
$(BUILD) $(GO) build -o bin/lambda-fib $(FLAGS) main.go
deploy:
serverless deploy
update:
$(GO) get -u -v ./...
clean:
rm -f bin/lambda-fib
Deploy it:
make
Once finished, the command prints the URL for the endpoint. Use curl
request it, and make sure it responds with the 1,000th of the Fibonacci number.
Improving Your Service
Now that you have a working service, you can improve it by also returning the a random Fibonacci number between 1 and 1000 of the requester. To do so, add an the randomInt
function and useing it:
...
// randomInt generates a random integer between min and max
func randomInt(min, max int64) uint {
rand.Seed(time.Now().UnixNano())
return uint(min + rand.Int63n(max-min+1))
}
...
resp := &response{
FibonacciNum1000: fibonacciBig(randomInt(1, 1000)),
}
...
By rebuilding and redeploying the Lambda function, you can now see the returning is random Fibonacci number:
make
Summary
In this post, we’ve looked at some of the advantages of using Go for writing AWS Lambda functions. We saw what it takes to set up an environment to develop, build, and deploy functions with the Serverless Framework. We then proceeded to create and deploy a Lambda function behind an API Gateway that showcased a real-world usage scenario of calling third-party APIs.