Deployment of Go services to AWS Lambda made easy with `serverless-go-plugin`
Introduction
Recently, I started writing a few toy projects with Go and I've realised that Serverless Framework does not offer great support for Go-based services out of the box. I started looking around and found serverless-go-plugin
which greatly simplified the setup of my serverless Go services. In today's post I wanted to share how you can take advantage of this plugin to quickly build and deploy Go-based applications to AWS Lambda with Serverless Framework
Prerequisites
We will be using Serverless Framework to develop and deploy our application. We will also need to have go
installed. In my case it's go1.21.3
, but any modern version of Go should be fine.
Setting up our service
We will setup our service manually as I could not find a good Go template anywhere. Let's start with creating a directory and initializing our Go module:
mkdir serverless-go-service
cd serverless-go-service
go mod init example.com/serverless-go-service
In the next step, let's create our Serverless Framework configuration file, serverless.yml
in the root of the directory:
service: serverless-go-service
frameworkVersion: '^3'
provider:
name: aws
architecture: arm64
runtime: provided.al2
region: us-east-1
plugins:
- serverless-go-plugin
package:
individually: true
functions:
hello:
handler: bootstrap
events:
- httpApi: '*'
package:
patterns:
- '!**/**'
- './bin/hello/bootstrap'
Okay, that's a lot of boilerplate setup for a single function, so let's go over some of the parts in more details. When it comes to Go Lambda functions, for a long time we had an option of using a dedicated go1.x
runtime, or custom runtimes such as provided.al2
. With the deprecation of go1.x
scheduled for December 31, 2023, it leaves us the provided
family of runtimes as our only option. In our case, we will be using provided.al2
, which is the latest available runtime, based on Amazon Linux 2023. When using provided
runtimes, there is a requirement to name the executable for your function bootstrap
. Serverless Framework doesn't have any built-in utilities for handling Go code, so we're simply taking advantage of package
configuration where we're specifying individual packaging and we're pointing to a specific binary that should be included in our function package. But how to get that binary? Right now we only have a filepath, let's add some code!
Let's create the following file under the path functions/hello/main.go
:
package main
import (
"context"
"github.com/aws/aws-lambda-go/lambda"
)
func handler(ctx context.Context) (string, error) {
return "hello there!", nil
}
func main() {
lambda.Start(handler)
}
It's a very simple function that will just return hello there!
string when called. Let's also make sure to add our dependency by running the following command in the root of the project:
go get github.com/aws/aws-lambda-go
Now that we have our code ready, we also need to compile it to the expected bootstrap
file. We can do it, for example, with the following command from the root of the project.
GOOS=linux go build -ldflags="-s -w" -o ./bin/hello/bootstrap ./functions/hello
After that, we're finally ready to deploy our project. But what if we could make it much more convenient?
Making things easier with serverless-go-plugin
The approach presented above works okay, but it requires separate compliation step and extra package
configuration for each of our functions, so clearly the developer experience is not perfect in such setup. Fortunately, we can avoid all of that by taking advantage of serverless-go-plugin. It is a plugin created by Maciej Winnicki, that automates away all these steps, seamlessly integrating with both serverless deploy
and serverless deploy function
commands. In order to take advantage of the plugin, we need to install it first. The easiest way to do so will be to install it locally by running the following commands:
npm init -y
npm i --save-dev serverless-go-plugin
In the process, we also initialized package.json
file as we didn't need it previously.
Next, let's modify our serverless.yml
configuration to take advantage of the plugin:
service: serverless-go-service
frameworkVersion: '^3'
provider:
name: aws
architecture: arm64
deploymentMethod: direct
runtime: provided.al2
region: us-east-1
plugins:
- serverless-go-plugin
functions:
hello:
handler: ./functions/hello
events:
- httpApi: '*'
custom:
go:
supportedRuntimes: ["provided.al2"]
buildProvidedRuntimeAsBootstrap: true
Let's try to dissect the changes a little bit. The main config for the plugin is placed under custom.go
, where we specify runtimes that we want to recognize. In our case it's provided.al2
. Additionally, we need to specify buildProvidedRuntimeAsBootstrap: true
. This is because the plugin was initially created with go1.x
runtime in mind and using provided
runtimes was an alternative. In the future it might change and this configuration might be no longer needed, but for now we need to keep it. Additionally, now we can simply configure our handler
to point to our code, instead of reconfiguring package.patterns
for each of the functions. Now, during sls deploy
, serverless-go-plugin
will take care of compliation and packaging of all our functions.
Note: The serverless-go-plugin
is not perfect and at the moment it does not support the newest provided.al2023
runtime, that's why the example is using provided.al2
instead.
Summary
Thanks to serverless-go-plugin
, we were able to simplify the configuration and packaging process for our Go-based services. If you'd like to try it out yourself, you can find the full example from this blog post here. Thanks for reading!