Amazon Graviton Three Ways
The Frugal Architect, was introduced by Dr. Werner Vogels during re:Invent 2023. It consists of several laws for building cost efficient architectures in the cloud. This is where the Graviton (ARM based) CPU from AWS comes in. Graviton delivers the best cost vs performance, most workloads can utilize Graviton with very small modifications. Not all, but many workloads can. Switching to Graviton is often an easy way to save on cost without sacrificing performance. I have helped migrate several workloads to Graviton over the last couple of years, on average the cost has been decreased with 20-25%.
In this post we'll dive into Graviton and I'll show how run Graviton for three different workloads. We'll look at a AWS Lambda based workload written in Python, one written in Golang, and finally a container based Java Spring Boot application running in Fargate. I'll show how to move the existing workloads from X86 to Graviton. I will build and deploy everything from a Macbook with Apple silicone, but I will show the techniques for building and deploying from anywhere, including CI/CD tools like GitHub Actions.
Introduction
The transition to Amazon Graviton for the three workloads represents a strategic move to exploit Graviton's strengths, such as lower power consumption, better price-performance ratio, and enhanced processing capabilities. For developers and enterprises, understanding how to optimize their applications for Graviton is crucial, as it could lead to substantial performance gains and cost savings. This introduction to Graviton, tailored to these three specific AWS Cloud workloads, paves the way for a more in-depth exploration of the practicalities and benefits of this migration.
Prerequisites
Before we start there are a couple of things that must be installed. Make sure you have Python, Golang, Java, Docker, AWS CLI with AWS SAM CLI installed.
Architecture Overview
First of all, let us do an overview of the architecture for the three different workloads, what components that are involved and how the workloads are deployed. We'll create a very basic Hello World API, yes yes I know it's boring, for all three workloads. The Lambda based will utilize API Gateway and the Container based Java workload will use an Application Load Balancer.
Python based Lambda function
First of all, let's build and deploy the API using SAM CLI and run them on the standard X86 based CPU.
Deploy and run Python Lambda on X86
Creating an API backed by a Lambda function using SAM is straight forward, we deploy a template with an HttpApi and a function integration.
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Create a HTTP API with Graviton based Lambda function in Python
Parameters:
Application:
Type: String
Description: Name of the Application owning this Stack Resources
Globals:
Function:
Timeout: 5
Runtime: python3.12
Resources:
HttpApi:
Type: AWS::Serverless::HttpApi
Properties:
CorsConfiguration:
AllowMethods:
- GET
AllowOrigins:
- "*"
AllowHeaders:
- "*"
Tags:
Application: !Ref Application
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/
Handler: hello-world.handler
Events:
HelloGet:
Type: HttpApi
Properties:
Path: /hello
Method: get
ApiId: !Ref HttpApi
The SAM config file I use looks like this, it configures the stack name and region, and sets the template parameters.
version: 0.1
default:
global:
parameters:
stack_name: http-api-lambda-python-graviton-tutorial
region: eu-north-1
resolve_s3: true
confirm_changeset: false
fail_on_empty_changeset: false
capabilities: CAPABILITY_NAMED_IAM
deploy:
parameters:
parameter_overrides:
- Application=graviton-tutorial
The Python code is really simple and just return a status 200 and a message.
def handler(event, context):
return {"statusCode": 200, "body": "Hello World!"}
To deploy the template we use SAM CLI command
sam deploy --config-env default
From the stack output we can grab the API endpoint and test that we get a proper response back, we need to append /hello to the endpoint since that is the path we set in the integration.
If we then head over to the AWS Console we can verify that the function is running on X86 based CPU.
Migrate Python Lambda to Graviton
The migration to Graviton for Python is as simple as flipping a switch, or in our case specify a different architecture. So for the Lambda function we just specify arm64 as the architecture.
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
+ Architectures:
+ - arm64
CodeUri: src/
Handler: hello-world.handler
Events:
HelloGet:
Type: HttpApi
Properties:
Path: /hello
Method: get
ApiId: !Ref HttpApi
Then we just deploy once again using SAM.
sam deploy --config-env default
After the deployment we can once again head over to the Console and check the function architecture. We can clearly see that we have switched to arm64 (Graviton).
With that, we have migrated our function to Graviton by flipping a switch.
Golang based Lambda function
Running a Golang Lambda function is a bit different, first of all there is no managed runtime for Golang that we can use with Graviton. We need to use the Amazon Linux based provided.al2 runtime.
Deploy and run Golang Lambda on X86
When running on the provided.al2 runtime our handler need to be named bootstrap, which we also declare in our template. We also need to specify our build method in the meta-data section of the function. I will be using Make file when building. The Make file is not that complex for this function. We create a section to build it and add the command for building with an amd64 architecture.
build-HelloWorldFunction:
GOOS=linux GOARCH=amd64 go build -o bootstrap main.go
cp ./bootstrap $(ARTIFACTS_DIR)/.
The template is similar to the python variant, with the modifications to add the build method.
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: Create a HTTP API with Graviton based Lambda function in Golang
Parameters:
Application:
Type: String
Description: Name of the Application owning this Stack Resources
Globals:
Function:
Timeout: 5
MemorySize: 512
Resources:
HttpApi:
Type: AWS::Serverless::HttpApi
Properties:
CorsConfiguration:
AllowMethods:
- GET
AllowOrigins:
- "*"
AllowHeaders:
- "*"
Tags:
Application: !Ref Application
HelloWorldFunction:
Type: AWS::Serverless::Function
Metadata:
BuildMethod: makefile
Properties:
CodeUri: hello-world/
Handler: bootstrap
Runtime: provided.al2
Events:
HelloGet:
Type: HttpApi
Properties:
Path: /hello
Method: get
ApiId: !Ref HttpApi
Outputs:
ApiEndpoint:
Description: HTTP API endpoint URL
Value: !Sub https://${HttpApi}.execute-api.${AWS::Region}.amazonaws.com
The SAM config file I use looks like this, it configures the stack name and region, and sets the template parameters.
version: 0.1
default:
global:
parameters:
stack_name: http-api-lambda-golang-graviton-tutorial
region: eu-north-1
resolve_s3: true
confirm_changeset: false
fail_on_empty_changeset: false
capabilities: CAPABILITY_NAMED_IAM
deploy:
parameters:
parameter_overrides:
- Application=graviton-tutorial
Now when building this function we can't just run the sam deploy command, like we did with Python. We also need to run the actual build phase in this case. So we end up with a build followed by a deploy command.
sam build
sam deploy --config-env default
When the stack has been deployed, it does take a few seconds longer to build and deploy Golang than it did with Python. Once again we grab the API endpoint, from the stack output, and test that we get a proper response back, we need to append /hello to the endpoint since that is the path we set in the integration.
If we then head over to the AWS Console we can verify that the function is running on X86 based CPU. We can also see that our handler is bootstrap and that we run on the custom Amazon Linux runtime.
Migrate Golang Lambda to Graviton
The migration to Graviton for Golang is not as simple as it was for Python, there are a few extra steps, but it's still fairly straight forward.
First of all we need to update our Make file so we build for arm based CPU instead of the amd64 we did before, we set the GOARCH to arm64.
build-HelloWorldFunction:
GOOS=linux GOARCH=arm64 go build -o bootstrap main.go
cp ./bootstrap $(ARTIFACTS_DIR)/.
We also update the template and set the Architectures to arm64.
HelloWorldFunction:
Type: AWS::Serverless::Function
Metadata:
BuildMethod: makefile
Properties:
+ Architectures:
+ - arm64
CodeUri: hello-world/
Handler: bootstrap
Runtime: provided.al2
Events:
HelloGet:
Type: HttpApi
Properties:
Path: /hello
Method: get
ApiId: !Ref HttpApi
Now, let's build and deploy using SAM once again.
sam build
sam deploy --config-env default
After the deployment we can once again head over to the Console and check the function architecture. We can clearly see that we have switched to arm64 (Graviton) and that we still on the Amazon Linux 2 Runtime.
With that, we have migrated our function to Graviton. A few extra steps, making sure we build for arm, but still a straight forward process.
Java based container
With the two Lambda based workloads migrated, let's start looking at a Java based container workload running in Fargate. Before we start we need to deploy required resources as VPC, ECR repository, and more.
To setup the needed infrastructure and service running in ECS follow my blog post: Run a java service serverless with ECS and Fargate. I will assume you have a ECS cluster setup according to that guide.
Navigate to the ECS Cluster, in the services tab click on the service name, select Tasks tab and click the task ID. Confirm that you have a configuration that looks like this.
To start migrating this Java based service to Graviton, we first of all need to build the Docker image for ARM.
./gradlew clean build -Pversion=1234
aws ecr get-login-password --region eu-north-1 | docker login -u AWS --password-stdin ACCOUNT.dkr.ecr.eu-north-1.amazonaws.com
IMAGE="ACCOUNT.dkr.ecr.eu-north-1.amazonaws.com/graviton-tutorial-hello:latest"
docker buildx build --platform linux/arm64 --push -t $IMAGE .
We need to set the platform to linux/arm64 to build for Graviton. After pushing the new image to ECR we have to update the TaskDefinition and change CpuArchitecture to ARM64.
...
ServiceTaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: !Sub ${Application}-${Service}-task
RequiresCompatibilities:
- FARGATE
NetworkMode: awsvpc
ExecutionRoleArn:
Fn::ImportValue: !Sub ${ServiceInfraStackName}:cluster-role
TaskRoleArn: !Sub ${Application}-${Service}-task-role
+ RuntimePlatform:
+ CpuArchitecture: ARM64
Cpu: 512
Memory: 1024
ContainerDefinitions:
- Name: !Ref Service
Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${Application}-${Service}:${ServiceTag}
Cpu: 512
Memory: 1024
Environment:
- Name: NAME
Value: !Ref Service
PortMappings:
- ContainerPort: 8080
Protocol: tcp
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: !Sub /ecs/${Application}/${Service}
awslogs-region: !Sub ${AWS::Region}
awslogs-stream-prefix: !Ref Service
Tags:
- Key: Name
Value: !Sub ${Application}-${Service}-task
...
After that change and a redeploy we should now end up with a new Task running in our service with this configuration.
And that is basically it, we have migrated our service to Graviton. Now, not all Java services are this easy but most that I have worked with actually are.
Cost saving
As mentioned in the beginning I have migrated several workloads to Graviton and the data I have collected show that you can save between 20 - 25% on cost. If we look at our basic example running 0.5vCPU and 1Gb of memory and running this container around the clock the cost would look something like this in the eu-north-1 (Stockholm) Region.
X86 Cost
vCPU cost is $0.04048 per hour, memory cost is $0.004445 per GB per hour. Calculating with 720 hours per months.
vCPU: 0.04048 * 0.5 * 720 = ~$16 memory: 0.004445 * 1 * 720 = ~$3.5 total: ~19.5
Graviton Cost
vCPU cost is $0.03238 per hour, memory cost is $0.00356 per GB per hour. Calculating with 720 hours per months.
vCPU: 0.03238 * 0.5 * 720 = ~$11.7 memory: 0.00356 * 1 * 720 = ~$2.6 total: ~14.5
This is a cost saving of ~$5 or 25%!
Graviton also has a lower carbon footprint, so not only is this good for your cost, it's also good for the environment!
Final Words
In this post I showed how to migrate three different workloads from X86 to Graviton based compute. Now, not all workloads are this easy. However, after doing several migrations myself I can say that most that I have worked on actually are. You can also check out the repo aws-graviton-getting-started from AWS for more tips and trix.
Don't forget to follow me on LinkedIn and X for more content, and read rest of my Blogs