Amazon Graviton Three Ways
Tags: aws, serverless, graviton
Published: 2024-01-16 16:05:54

---
title: Amazon Graviton Three Ways
description: The Frugal Architect. Many workloads can utilize the effective Graviton (ARM) based CPU, Switching to Graviton is often an easy way to save on cost. Graviton delivers the best cost vs performance. This blog post will show the migration process for three different workloads.
cover_image: https://jimmydqv.com/assets/img/post-graviton-three-ways/graviton-three-ways-cover-image.png
tags: aws, serverless, graviton
canonical_url: https://jimmydqv.com/graviton-three-ways/index.html
published: true
---

[The Frugal Architect](https://thefrugalarchitect.com/), 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.

![Image showing architecture overview.](https://jimmydqv.com/assets/img/post-graviton-three-ways/overview.png)

## 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.

``` yaml

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.

``` yaml
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.

``` python
def handler(event, context):
    return {"statusCode": 200, "body": "Hello World!"}
```

To deploy the template we use SAM CLI command

``` bash

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.

![Image showing python based API response.](https://jimmydqv.com/assets/img/post-graviton-three-ways/http-api-response-python.png)

If we then head over to the AWS Console we can verify that the function is running on X86 based CPU.

![Image showing the function running on X86 in the console.](https://jimmydqv.com/assets/img/post-graviton-three-ways/lambda-python-x86.png)

### 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.

``` diff-yaml

  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.

``` bash

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).

![Image showing the function running on Arm in the console.](https://jimmydqv.com/assets/img/post-graviton-three-ways/lambda-python-arm.png)

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.

``` bash
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. 

``` yaml
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.

``` yaml

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.

``` bash

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.

![Image showing python based API response.](https://jimmydqv.com/assets/img/post-graviton-three-ways/http-api-response-golang.png)

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.

![Image showing the function running on X86 in the console.](https://jimmydqv.com/assets/img/post-graviton-three-ways/lambda-golang-x86.png)

### 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.

``` bash
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.

``` diff-yaml

  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.

``` bash

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.

![Image showing the function running on Arm in the console.](https://jimmydqv.com/assets/img/post-graviton-three-ways/lambda-golang-arm.png)

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](https://jimmydqv.com/java-application-on-ecs-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.

![Image showing the ECS task runnig x86.](https://jimmydqv.com/assets/img/post-graviton-three-ways/ecs-task-java-x86.png)

To start migrating this Java based service to Graviton, we first of all need to build the Docker image for ARM.

``` bash
./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.

``` diff-yaml
...
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.

![Image showing the ECS task running ARM.](https://jimmydqv.com/assets/img/post-graviton-three-ways/ecs-task-java-arm.png)

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
](https://github.com/aws/aws-graviton-getting-started/tree/main?tab=readme-ov-file#transitioning-to-graviton) from AWS for more tips and trix.

Don't forget to follow me on [LinkedIn](https://www.linkedin.com/in/dahlqvistjimmy/) and [X](https://x.com/jimmydahlqvist) for more content, and read rest of my [Blogs](https://jimmydqv.com)