Using Golang for your AWS Lambda Functions
Tags: go, aws, lambda, sustainability
Published: 2023-08-04 10:05:23

I was sparked on a [XKE](https://xke.xebia.com/faq/) to do a short experiment with using Golang for my [AWS Lambda Functions](https://aws.amazon.com/lambda). The trigger for this was something my colleague Mark van Holsteijn said. We where talking about sustainability. During this talk Mark made the comment that we should drop Python for our functions then. (He also recently wrote a [blog](https://xebia.com/blog/how-to-create-and-deploy-a-golang-aws-cloudformation-custom-provider-in-less-the-5-minutes/) on Golang using Golang for custom providers.)

Let me compare some numbers between Python and Go. (source: [Which Programming Languages Use the Least Electricity?](https://thenewstack.io/which-programming-languages-use-the-least-electricity/)))

- Energy: 75.88 vs 3.23
- Time: 71.90 vs 2.83
- Memory: 2.80 vs 1.05

As you can see, based on these numbers Golang is clear winner. But are there other advantages? So, like always everything comes with it's own pros and cons. Lets explore those when you do chose for Golang.

## Pre-compiled binary vs compiled at run time

Let's start with the obvious one, a pre compiled binary vs code that is compiled at run time. This is one of the reasons why Golang is more energy efficient as you only compile once vs every time you run it. But the advantage of Python is that you can actually see the source code in the AWS Console and tweak it. This makes it a great candidate for easy development and fast testing. This also a problem! When you are running production workloads. You don't want your developers altering your code through the console. Yes, IAM can help you here but altering a binary is not possible through the console.

## You need to compile

Why is the need to compile an advantage? Next to the performance improvement you actually check if your program compiles. If it does not compile you cannot deploy it. Compare this with Python, you can upload malformed code. This would than break your workload because of an indent issue. You can build in checks to prevent this from happening, and you should! But you need to do something extra, with Golang this comes out of the box.

## Dependencies

In python you have the option to do inline code in [AWS CloudFormation](https://aws.amazon.com/cloudformation) templates. I would never recommend doing this. When you needs other dependencies than that there are available. You will need to bundle them yourself and upload them as a zipfile. When you are missing a dependency the invocation of your function will fail at runtime. When you use Golang the dependencies are all build into the binary. Especially when you set the `CGO_ENABLED=0` option in your compile options. This will make sure that all the needed libraries are included.

## Tutorial

Let's deploy a small stack including a single Lambda function. We will be using [AWS SAM](https://aws.amazon.com/serverless/sam/) for the deployment, so please make sure you have that installed.

### Prepare the file structure

Create the folders and files we will be needing:

```shell
mkdir aws-lambda-sample-golang
cd aws-lambda-sample-golang
touch template.yaml
mkdir my-function
touch my-function/Makefile
touch my-function/main.go
```

### SAM Template

A SAM Template is nothing more that a CloudFormation template. But it contains the `AWS::Serverless-2016-10-31` transformation. This allows you to use the `AWS::Serverless::Function` resource. When CloudFormation parses this resource it will converted to CloudFormation resources.

Add the following to the `template.yaml` file:

```yaml
Transform: AWS::Serverless-2016-10-31
Resources:

  MyFunction:
    Type: AWS::Serverless::Function
    Metadata:
      BuildMethod: makefile
    Properties:
      Runtime: provided.al2
      CodeUri: ./my-function
      Handler: bootstrap

  MyFunctionLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /aws/lambda/${MyFunction}
      RetentionInDays: 7
```

A few things are going on here. You can see the `BuildMethod` set in the `Metadata` property. With this configuration you are saying to SAM that you want to use a `Makefile`. This `Makefile` is used to compile the lambda function.
- `CodeUri` will point to the root of your Lambda functions code.
- `Handler` will point to your binary output file.

Lets create the `Makefile`, we need to place it in the root folder of the function `my-function`. Add the following content to the file:

```Makefile
build-MyFunction:
	GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o bootstrap
	cp ./bootstrap $(ARTIFACTS_DIR)/.
```

The syntax used here is `build-<LOGICAL-ID-OF-CLOUDFORMATION>`. So, if your LogicalId in the template is `HelloWorld`. Your `Makefile` target would be: `build-HelloWorld`. The `go build` command will build your binary. The copy command will copy the binary in the artifacts directory. This is used when you deploy your template.

> Note the `GOARCH` if you use an ARM function you need to specify it here as well.

### Lambda Code

We need to initialize our go module, and pull in the `github.com/aws/aws-lambda-go` dependency:

```shell
cd my-function
go mod init my-function
go get github.com/aws/aws-lambda-go
cd ..
```

Now we can fill the `my-function/main.go` with some business logic:

```golang
package main

import (
	"context"
	"log"

	"github.com/aws/aws-lambda-go/lambda"
)

type MyEvent struct {
	Name string `json:"name"`
}

func Handler(ctx context.Context, event MyEvent) {
	log.Printf("Welcome %s", event.Name)
}

func main() {
	lambda.Start(Handler)
}
```

And done!


### Deploy the template

To deploy the template we first need to build the binary. AWS SAM as build in support for building and packaging Lambda functions.

> Note: AWS SAM will create a bucket in your account used for uploading the artifacts.

```shell
sam build
sam deploy \
    --stack-name aws-lambda-sample-golang \
    --capabilities CAPABILITY_IAM \
    --resolve-s3
```

### Testing

You can now invoke the Lambda function, with a `{"name": "Joris Conijn"}` payload. The logs should now show:

> Welcome Joris Conijn

For a full working copy you could also clone my [Nr18/aws-lambda-sample-golang](https://github.com/Nr18/aws-lambda-sample-golang) repository.

## Conclusion

For me Golang would become my default choice for AWS Lambda functions. The fact that it's "more sustainable" and that it protects you from obvious failures convinced me. That beings said I have a software engineering background. So, for me it's relatively easy to switch languages. How about you, would you use Golang as your default choice?

Photo by [Narcisa Aciko](https://www.pexels.com/photo/photo-of-wind-turbines-lot-1292464/)