Testing AWS Accelerate with Go and Graviton

Credits:

  1. https://aws.amazon.com/blogs/compute/accelerating-serverless-development-with-aws-sam-accelerate/
  2. https://docs.aws.amazon.com/lambda/latest/dg/go-image.html

Let's start with setting the correct AWS profile, just to be sure, that you don't expose working credentials (in case you have multiple environments): export AWS_PROFILE="test"

Then let the fun begin:

Initialize the app by running sam init command.

Choose AWS QuickStart Templates then Zip (artifact is a zip uploaded to S3) then go1.x and specify the desired project name, e.g. gravitonus.

Check if everything works already by running sam local start-api. In my case there was already an error, stating something like:

Exception on /hello [GET]
Traceback (most recent call last):
  File "/usr/local/Cellar/aws-sam-cli/1.34.1/libexec/lib/python3.8/site-packages/docker/api/client.py", line 261, in _raise_for_status
    response.raise_for_status()
  File "/usr/local/Cellar/aws-sam-cli/1.34.1/libexec/lib/python3.8/site-packages/requests/models.py", line 943, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 404 Client Error: Not Found for url: http+docker://localhost/v1.35/images/create?tag=rapid-1.34.1-x86_64&fromImage=public.ecr.aws%2Fsam%2Femulation-go1.x%3Arapid-1.34.1-x86_64

It turned out that there were some docker issues - obsolete images, lack of disc space. Just as a side-note - try to get feedback as soon as possible to avoid much investigation later on.

I also simplified the hello-world template code, code, so it become something like this (and renamed hello-world to handler)

package main

import (
	"fmt"
	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
)

func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	return events.APIGatewayProxyResponse{
		Body:       fmt.Sprintf("Hello, world"),
		StatusCode: 200,
	}, nil
}

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

AWS introduced recently AWS SAM Accelerate feature to speed up development, so I wanted to test it out. This is easily achievable by running sam sync --stack-name gravitonus --region eu-west-1. It deploys the current stack. Navigating to the API Gateway URL (you can get the URL from the console output) gives the desired "Hello, world", which means, we can go further. Let's do some changes to the template and see what else sync can do.

So, changing the response to return "Hello, Gravitonus" and renaming HelloWorld occurrences in the template.yaml and running the sam sync command gives me the list of updates. It takes time since old CloudFormation resources are deleted and new ones created. But if I only have some code changes, e.g. after replacing "Hello, Gravitonus" to "Hello, Gravitonus v2" it takes literally seconds to update the stack.

Cool, let's switch back to the original idea. Simply changing the architecture in template.yaml to arm64 won't work, as Go is not supported yet, so we need to use a container. So let's change the template to use Docker image. In order to do so, we need to create a Dockerfile:

FROM public.ecr.aws/lambda/provided:al2 as build
# install compiler
RUN yum install -y golang
RUN go env -w GOPROXY=direct
# cache dependencies
ADD go.mod go.sum ./
RUN go mod download
# build
ADD . .
RUN go build -o /handler
# copy artifacts to a clean image
FROM public.ecr.aws/lambda/provided:al2
COPY --from=build /handler /handler
ENTRYPOINT [ "/handler" ]

Note, that in order to use arm64 architecture, we need the Amazon Linux 2 image. Here are corresponding changes to the template.yaml file:

Resources:
  GravitonusFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      PackageType: Image
      Runtime: go1.x
      Architectures:
        - arm64
      Tracing: Active # https://docs.aws.amazon.com/lambda/latest/dg/lambda-x-ray.html
      Events:
        CatchAll:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /hello
            Method: GET
      Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object
        Variables:
          PARAM1: VALUE
    Metadata:
      DockerTag: go1.x-v1
      DockerContext: ./handler
      Dockerfile: Dockerfile

I tried the sync command, but apparently, it doesn't work yet and gives me Error: Unable to upload artifact gravitonusfunction:v1 referenced by ImageUri parameter of GravitonusFunction resource. 500 Server Error: Internal Server Error ("invalid reference format"). So let's wait when the AWS team releases the feature. And meanwhile, I've used the regular build and deploy:

sam build
sam deploy --guided

Also, instead of blindly believing that everything works, let's get some OS info in the output. Fast googling pointed me to the "github.com/matishsiao/goInfo" repository. And here is the sample output I've got after using it:

GoOS:linux,
Kernel:Linux,
Platform:aarch64,
OS:GNU/Linux,
CPUs:2

Summary

Sync feature works for simplest cases, it's still in beta yet. I'm waiting for the release. I also doubt, that it will replace CI builds, but for small projects and fast-prototyping it definitely worth exploring.

Graviton architecture is not yet supported as a standalone (Zip) package for the Go language but works with custom containers. I'd try to use it for some of my projects and write next time about the experience gained.

Don't forget to clean-up resources and delete stack. sam delete --stack-name gravitonus --region eu-west-1 will do the job.

Code

The final result is here: https://github.com/abarbarov/gravitonus

That's all, folks!