This blog post explains how to leverage Graviton2 with your Lambda function.
As a part of the series Serverless Rust you can check out the other parts:
Part 1 describes how to set up Rust and VsCode.
Graviton2
Apparently, AWS Graviton claim Graviton2 processors provide better price performance over comparable current-generation x86-based instances. Furthermore, functions that use arm64 architecture (Graviton2 processor) offer a lower cost per Gb/s compared with the equivalent function running on an x86-based CPU and from the Lambda Pricing page it seems to provide up to 34% price-performance improvement. The 20% reduction in duration costs also applies when using Provisioned Concurrency. You can further reduce your costs by up to 17% with Compute Savings Plans.
Project structure
The structure of the project is pretty standard.
In this post, we focus on some snippets of a few files.
- cargo.toml
- Makefile
- template.yml
cargo.toml
Each Rust project has this file cargo.toml. Cargo is Rust's build system and package manager.
You can add library
cargo add lambda_runtime
You can build your project.
cargo build
[package]
name = "my-app"
version = "0.1.0"
edition = "2022"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
futures = "0.3.17"
aws-config = "0.3.0"
aws-types = "0.3.0"
log = "0.4.14"
serde = {version = "1.0", features = ["derive"] }
serde_json = "1.0.68"
simple_logger = "1.13.0"
tokio = "1.13.0"
lambda_runtime = "0.4.1"
aws_lambda_events = "0.5.0"
# APP
[[bin]]
name = "handler"
path = "src/bin/lambda/handler.rs"
A helpful VsCode extension is crates because it will help you with the versions
The last note on this file is the bin part. The default binary filename is src/main.rs, defaulting to the package's name. Additional binaries are stored in the src/bin/ directory, and you could have, for example:
# APP
[[bin]]
name = "app-random-api"
path = "src/bin/lambda/app-random-api.rs"
[[bin]]
name = "app-lambda-authorizer"
path = "src/bin/lambda/app-lambda-authorizer.rs"
Makefile
Lambda function run Rust as custom runtime, and so we need to declare the Metadata resource attribute with a BuildMethod: makefile entry. The Makefile is responsible for compiling the custom runtime and copying the build artefacts. The location of the Makefile is specified by the CodeUri property of the function resource and must be named Makefile.
# the same name that you find in the cargo.toml bin section
FUNCTIONS := app-random-api app-lambda-authorizer
ARCH := aarch64-unknown-linux-gnu
build:
rm -rf ./build
rm -rf ./target
cross build --release --target $(ARCH)
mkdir -p ./build
${MAKE} ${MAKEOPTS} $(foreach function,${FUNCTIONS}, build-${function})
build-%:
mkdir -p ./build/$*
cp -v ./target/$(ARCH)/release/$* ./build/$*/bootstrap
deploy:
sam deploy --guided --no-fail-on-empty-changeset --no-confirm-changeset --stack-name xxx --template-file template.yml
delete:
sam delete --stack-name xxxx
To be able to use arm64 architecture
- we could install CROSS based on Docker and it will have some issue with M1 Macs
- we could install cargo-zigbuild and Zig for cross-compilation
if you don't and run
cargo build --release --target x86_64-unknown-linux-musl
You will get this beautiful error:
error: failed to run custom build command for `ring v0.16.20`
Caused by:
process didn't exit successfully: `/Users/fra0005d/git/rust-examples/rust-dynamodb/target/release/build/ring-e4e3d5f387d41c64/build-script-build` (exit status: 101)
--- stdout
OPT_LEVEL = Some("3")
TARGET = Some("x86_64-unknown-linux-musl")
HOST = Some("x86_64-apple-darwin")
CC_x86_64-unknown-linux-musl = None
CC_x86_64_unknown_linux_musl = None
TARGET_CC = None
CC = None
CROSS_COMPILE = None
CFLAGS_x86_64-unknown-linux-musl = None
CFLAGS_x86_64_unknown_linux_musl = None
TARGET_CFLAGS = None
CFLAGS = None
CRATE_CC_NO_DEFAULTS = None
DEBUG = Some("false")
CARGO_CFG_TARGET_FEATURE = Some("fxsr,sse,sse2")
--- stderr
running "musl-gcc" "-O3" "-ffunction-sections" "-fdata-sections" "-fPIC" "-m64" "-I" "include" "-Wall" "-Wextra" "-pedantic" "-pedantic-errors" "-Wall" "-Wextra" "-Wcast-align" "-Wcast-qual" "-Wconversion" "-Wenum-compare" "-Wfloat-equal" "-Wformat=2" "-Winline" "-Winvalid-pch" "-Wmissing-field-initializers" "-Wmissing-include-dirs" "-Wredundant-decls" "-Wshadow" "-Wsign-compare" "-Wsign-conversion" "-Wundef" "-Wuninitialized" "-Wwrite-strings" "-fno-strict-aliasing" "-fvisibility=hidden" "-fstack-protector" "-g3" "-U_FORTIFY_SOURCE" "-DNDEBUG" "-c" "-o/Users/fra0005d/git/rust-examples/rust-dynamodb/target/x86_64-unknown-linux-musl/release/build/ring-e7f51b4231eb5b63/out/aesni-x86_64-elf.o" "/Users/fra0005d/.cargo/registry/src/github.com-1ecc6299db9ec823/ring-0.16.20/pregenerated/aesni-x86_64-elf.S"
thread 'main' panicked at 'failed to execute ["musl-gcc" "-O3" "-ffunction-sections" "-fdata-sections" "-fPIC" "-m64" "-I" "include" "-Wall" "-Wextra" "-pedantic" "-pedantic-errors" "-Wall" "-Wextra" "-Wcast-align" "-Wcast-qual" "-Wconversion" "-Wenum-compare" "-Wfloat-equal" "-Wformat=2" "-Winline" "-Winvalid-pch" "-Wmissing-field-initializers" "-Wmissing-include-dirs" "-Wredundant-decls" "-Wshadow" "-Wsign-compare" "-Wsign-conversion" "-Wundef" "-Wuninitialized" "-Wwrite-strings" "-fno-strict-aliasing" "-fvisibility=hidden" "-fstack-protector" "-g3" "-U_FORTIFY_SOURCE" "-DNDEBUG" "-c" "-o/Users/fra0005d/git/rust-examples/rust-dynamodb/target/x86_64-unknown-linux-musl/release/build/ring-e7f51b4231eb5b63/out/aesni-x86_64-elf.o" "/Users/fra0005d/.cargo/registry/src/github.com-1ecc6299db9ec823/ring-0.16.20/pregenerated/aesni-x86_64-elf.S"]: No such file or directory (os error 2)', /Users/fra0005d/.cargo/registry/src/github.com-1ecc6299db9ec823/ring-0.16.20/build.rs:653:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
warning: build failed, waiting for other jobs to finish...
error: build failed
AWS SAM
The last and more straightforward step is the serverless template with AWS SAM.
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: sam-app
Globals:
Function:
MemorySize: 1024
Architectures: ["arm64"]
Handler: bootstrap
Runtime: provided.al2
Timeout: 29
Environment:
Variables:
RUST_BACKTRACE: 1
RUST_LOG: info
Resources:
LambdaRandomFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: build/app-random-api/
Policies:
- AWSLambdaBasicExecutionRole
Metadata:
BuildMethod: makefile
LambdaAuthorizerFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: build/app-lambda-authorizer/
Policies:
- AWSLambdaBasicExecutionRole
Metadata:
BuildMethod: makefile
Outputs:
LambdaRandomFunction:
Description: "Hello Rust Lambda Function ARN"
Value: !GetAtt LambdaRandomFunction.Arn
LambdaAuthorizerFunction:
Description: "Hello Rust Lambda Function ARN"
Value: !GetAtt LambdaAuthorizerFunction.Arn
Conclusion
It can be frustrating hitting this problem without help. However, the Rust community is constructive, and in the case of using Rust on AWS, many Solution Architects are happy to help you. So they did with me, and I hope this post will help you speed up your journey with Rust.