Golang Microservices - How to Make a Golang Lambda Function

Golang rocks, microservices rock, let's combine the two with some lazy DevOps and get rocking.

Golang Microservices - How to Make a Golang Lambda Function

Continuing my series of content on microservices, AWS and how much Golang rocks, I wanted to do a quick (but probably not quick) article on how to use Golang to write microservice Lambda functions on AWS. This is honestly one of my main use-cases for Golang nowadays, so it's a fairly good subject for me to write on.

btw, if you want to see my articles in video form, I put most of them up on YouTube

The behind-the-scenes

So to get something straight really quick, Golang Lambda functions work a bit differently from, say, Python or NodeJS Lambda functions. Golang is a compiled language, while the other two aforementioned languages run in an interpreter and a runtime. This means the end deliverable for a Golang Lambda is a compiled executable, while the end deliverable for a Python Lambda is a human-readable Python script.

Basically what you're doing with a Golang Lambda is uploading your compiled executable to S3 and the Lambda fetches the executable and runs it when triggered. This means you'll have to build the code first before uploading it, which I honestly count as a plus since your code will have gone through one layer of checks at compile time before being triggered in the cloud.

Getting started

As I've mentioned before, my favorite kind of DevOps is the laziest kind of DevOps, and my normal workflow with Golang Lambdas is no different. I use a quick CLI command, which is a series of sub-commands joined by &&, to compile my Golang Lambda, zip it (which AWS requires you to do to upload it to S3), and upload the zip file to AWS via the AWS CLI.

Theoretically, I'd have some terraform files to create the Lambda function before uploading, but that would only be the case if I really understood Terraform. I'm still a Terraform moron though, so I do it manually. I'm going to mostly skip that step, aside from showing you a couple of gotchas.

Head on over to the handy-dandy AWS console and hit the Lambda section and hit Create Function. Name the function something memorable and hit the dropdown to select Go 1.x as the runtime. Don't worry about the permissions or anything else, we're going to keep this simple.

Make sure you also make note of what region you created the Lambda in, since they're region dependent. I created mine in us-east-2 because whatever.

The code

Now let's work on the code. Like I said, we're going to keep this very simple, literally just printing out Hello world, some information about the input that was passed to it and the timestamp.

First, let's set up a function for main to call as the Lambda function. This way we can set all of our Lambda functions to call main, and main will call the correct function. Main can also be used to set things up before passing them down the line to the secondary function.

func main() {
	lambda.Start(testLambdaFunc)
}

Then, let's create a custom struct that will hold the information passed to us from the caller of the Lambda function.

type Input struct {
	Message string `json:"message"`
	Id      int    `json:"id"`
}

Finally, in the secondary function testLambdaFunc(), let's parse out the data in our custom struct and print our message.

func testLambdaFunc(ctx context.Context, input Input) (int64, error) {
	ts := time.Now().UnixMilli()
	fmt.Printf("Timestamp: %d\n\tInput ID: %s\n\tInput Message: %s\n", ts, input.Id, input.Message)
	return ts, nil
}

The lazy DevOps way

Now we have to actually deliver the GoLang executable to AWS. Since I have the AWS CLI on my PC, this is pretty trivial, and consists of a couple of steps.

  • Compile the code into an executable
  • Zip it
  • Upload the zip file to the Lambda we have already created

I like to copy/paste the Windows and Mac/Linux commands to the top of my file in case I decide to work on them on another workstation with a different OS. Here's what that looks like in our case:

// Build and update command (Mac and I think Linux):
// env GOOS=linux GOARCH=amd64 go build -o ./tmp/main main && zip -j ./tmp/main.zip ./tmp/main && aws lambda update-function-code --function-name TestFunctionForBlog --region us-east-2 --zip-file fileb://.//tmp/main.zip
// Build and update command (Windows - requires 7zip):
// set GOOS=linux&& set GOARCH=amd64&& go build -o ./tmp/main main && 7z a ./tmp/main.zip ./tmp/main && aws lambda update-function-code --function-name TestFunctionForBlog --region us-east-2 --zip-file fileb://./tmp/main.zip

For the Windows command, we set a couple of environment variables (namely, GOOS, which is the OS that the Go executable will be compiled to, and GOARCH which is the architecture that the Go executable will be compiled for) and build the executable, putting it into the ./tmp/ directory. Then, we use 7zip (which you have to install and then include 7z.exe in your PATH for Windows, annoying I know) to zip it up and the AWS CLI to update the function with the new zip file.

Notably, you can copy/paste these comments from any single Golang project to any other one and all you will have to do is change the function name and maybe the region. That's why it's the perfect lazy DevOps solution.

Now we run it and we should get a giant JSON blob in our CLI if it worked.

Testing our Lambda

If the above steps worked, our Lambda is deployed. We can check this by looking at the last updated time in the AWS console.

Now let's go over to the Test tab and create a test JSON object to pass in as input to our Lambda function. Make sure to follow the data model described by your json tags in your Golang code.

If we press the test button, we should see that the Lambda runs (incredibly quickly) and we get the output we expect in the logs.

Nice! Now, obviously this is pretty simple, but this more or less all you really need to know to get started with Golang Lambda functions. The rest is up to your use case and experimentation. Hack the planet!