Deploy a Go Microservice in AWS
by Bautista Bambozzi — 20 min
In this article, we'll showcase how to deploy a Go microservice to AWS Lambda. We'll also launch it on ARM instead of x86, to further leverage the cost-efficiency of ARM. Everything we'll do in this article will fall under the free tier. However, you're expected to double-check you're not accidentally creating other resources. You're also expected to have the following:
- A) Have an AWS Account
- B) Have the AWS CLI credentials installed and configured.
- C) Have the AWS CDK installed and configured.
- D) Have the Go Programming Language installed.
- E) Have a zip utility or equivalent installed (It's pre-installed in Linux).
Bootstrapping the application
We'll start off with a simple one-liner to create our working directory, bootstrap the application and get our dependencies. After that, we'll get ready to rock-n-roll 🎸!
mkdir hello-lambda && cd hello-lambda && cdk init --language go && go get
The AWS CDK has now created the hello-lambda.go file with the following:
- The NewHelloLambdaStack() function lets you define and add resources to the stack. We can use queues, lambdas, and more!
- The env() function holds the environment, which is the target AWS account and AWS Region that stacks are deployed to
- The main() function will add our stack to our app.
// hello-lambda/main.go
package main
import (
"github.com/aws/aws-cdk-go/awscdk/v2"
// "github.com/aws/aws-cdk-go/awscdk/v2/awssqs"
"github.com/aws/constructs-go/constructs/v10"
"github.com/aws/jsii-runtime-go"
)
type HelloLambdaStackProps struct {
awscdk.StackProps
}
func NewHelloLambdaStack(scope constructs.Construct, id string, props *HelloLambdaStackProps) awscdk.Stack {
var sprops awscdk.StackProps
if props != nil {
sprops = props.StackProps
}
stack := awscdk.NewStack(scope, &id, &sprops)
// The code that defines your stack goes here
// example resource
// queue := awssqs.NewQueue(stack, jsii.String("HelloLambdaQueue"), &awssqs.QueueProps{
// VisibilityTimeout: awscdk.Duration_Seconds(jsii.Number(300)),
// })
return stack
}
func main() {
defer jsii.Close()
app := awscdk.NewApp(nil)
NewHelloLambdaStack(app, "HelloLambdaStack", &HelloLambdaStackProps{
awscdk.StackProps{
Env: env(),
},
})
app.Synth(nil)
}
func env() *awscdk.Environment {
// If unspecified, this stack will be "environment-agnostic".
// Account/Region-dependent features and context lookups will not work, but a
// single synthesized template can be deployed anywhere.
//---------------------------------------------------------------------------
return nil
// Some comments by aws here
}
}
Creating our lambda function
Let us create a new directory where we'll create our lambda function.
mkdir lambda-function && cd lambda-function && go mod init lambda-function && touch main.go
We'll then proceed to our main.go located inside lambda-function and create a simple function that does the following:
- A) Unmarshalls JSON into a Go struct
- B) Examines the contents of a property inside the struct
- C) Returns an error if the property is empty / not found
- D) Returns the very same property is everything is alright
- E) Create the main() function that will execute our lambda.
We can do a simple implementation like so:
// hello-lambda/lambda-function/main.go
package main
import (
"fmt"
"github.com/aws/aws-lambda-go/lambda"
)
type Letter struct {
Content string `json:"content"` // A)
}
func HandleRequest(letter Letter) (string, error) {
if letter.Content == "" { // B)
return "", fmt.Errorf("letter content should not be empty") // C)
}
return fmt.Sprintf("Received letter %+v\n", letter), nil // D)
}
func main() {
lambda.Start(HandleRequest) // E)
}
Once we've set up our lambda function, we're ready to move on to compiling our main.go file to a native executable. We're going for gold here, so we'll compile for the ARM architecture. This will make our lambdas even more cost-effective than they already are. We need to set the build to target the Linux OS and the ARM architecture, respectively. We name the output 'bootstrap' and then proceed to zip it.
GOOS=linux GOARCH=arm64 go build -o bootstrap && zip lambda-function.zip bootstrap
You should have a lambda-function.zip in your current working directory (hello-lambda/lambda-function/lambda-function.zip). We will now go back to our previous directory, to hello-lambda/hello-lambda.go. We're going to set up the CDK to deploy our lambda function ✨! We'll want to do the following:
- A) Add a new lambda function to our Stack.
- B) Select the runtime (we'll use Amazon Linux 2023)
- C) Select the location of the asset
- D) Provide the handler (In the case of Go, we'll always go with main)
- E) Select the arm architecture
- F) Return our new stack with all our additions incorporated!
func NewHelloLambdaStack(scope constructs.Construct, id string, props *HelloLambdaStackProps) awscdk.Stack {
var sprops awscdk.StackProps
if props != nil {
sprops = props.StackProps
}
stack := awscdk.NewStack(scope, &id, &sprops)
// A)
awslambda.NewFunction(stack, jsii.String("hello-lambda"), &awslambda.FunctionProps{
Runtime: awslambda.Runtime_PROVIDED_AL2023(), // B)
Code: awslambda.AssetCode_FromAsset(jsii.String("lambda-function/lambda-function.zip"), nil), // C)
Handler: jsii.String("main"), // D)
Architecture: awslambda.Architecture_ARM_64(), // E)
})
// F)
return stack
}
Deploying to AWS
Now, for the grand finale, we'll deploy our app to AWS. We can evaluate the changes that will be done and deployed by running
cdk diff
After we've confirmed everything is alright, we're allowed to smile as we watch it deploy with the elegant:
cdk deploy
After responding Y to the classic 'are you sure?' prompt, we'll go back to the AWS Lambda console to check that our lambda is working correctly. In the event JSON section, place the following JSON object:
{
"content": "Hello, Lambda!"
}
Then, scroll upwards and click on the 'test' button to send the payload. And Voila 👌! You've created your own lambda function in the cloud 🌤️! You can now destroy your deployment by running
cdk destroy