Unlocking Performance - Deploying Rust Lambdas on Graviton with AWS CDK
Youssef Chlih · June 17, 2024 · 6 min read
Introduction
Rust is a language known for being fast and safe. It’s great for making programs run quickly and reliably. AWS Lambda is a cool way to run code without worrying about servers. Together, Rust and Lambda make a powerful match.
In this adventure, we’ll explore how Rust works with Lambda, and most importantly, how to deploy it using AWS CDK on Graviton. We’ll see how these pieces fit together to create amazing things in the cloud. Let’s dive in and see what we can discover!
Prerequisites
Before we set sail, ensure you have the following tools installed:
- Rust: Essential for building Rust code.
- Node.js: Required to run the AWS CDK.
- Cargo: Rust’s package manager that aids in building, testing, and running Rust code.
- Cargo-lambda: A handy tool for cross-compiling Rust code for AWS Lambda.
- AWS CLI Necessary for interacting with AWS services and setting up AWS credentials.
- AWS CDK: A framework for defining cloud infrastructure in code and provisioning it through AWS CloudFormation.
Additionally, ensure you have an active AWS account and have configured your credentials using the aws configure
command (AWS CLI).
Creating a Rust Lambda Function with AWS CDK
To kick things off, let’s create a new Rust project using the command:
cargo new rust-lambda-cdk
Once the project is created, navigate to the project directory and add the following dependencies to your Cargo.toml
file:
[dependencies]
lambda_http = "0.9.3"
tokio = { version = "1", features = ["macros"] }
tracing = { version = "0.1", features = ["log"] }
tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt"] }
The previous dependencies are required to create a simple Rust Lambda function:
lambda_http
: A library that provides a simple abstraction for handling HTTP requests and responses.tokio
: An asynchronous runtime for Rust.tracing
: A framework for instrumenting Rust programs.tracing-subscriber
: A subscriber for the tracing framework that logs events.
Next, create a new file named main.rs
in the src
directory and add the following code:
use lambda_http::{aws_lambda_events::query_map::QueryMap, run, service_fn, Body, Request, RequestExt, Response};
use tracing_subscriber::filter::{EnvFilter, LevelFilter};
async fn handler(event: Request) -> Result<Response<Body>, lambda_http::Error> {
let query_parameters:QueryMap = event.query_string_parameters();
let artifact_name:&str = match query_parameters.first("file") {
Some(name) => name,
None => {
let response:Response<Body> = Response::builder()
.status(400)
.body(Body::from("Missing 'file' parameter"))
.unwrap();
return Ok(response);
}
};
Ok(Response::builder()
.status(200)
.body(Body::from(format!("Searching for artifact {}", artifact_name)))
.unwrap())
}
#[tokio::main]
async fn main() -> Result<(), lambda_http::Error> {
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy(),
)
// disable printing the name of the module in every log line.
.with_target(false)
// disabling time is handy because CloudWatch will add the ingestion time.
.without_time()
.init();
run(service_fn(handler)).await
}
The previous code defines a simple Lambda function that reads a query parameter named `file` and returns a message indicating that it is searching for the specified artifact.
Next, try to build the project using the following command:
cargo lambda build --release
The previous command will generate a ZIP file containing the compiled Rust Lambda function.
Deploying the Rust Lambda Function with AWS CDK
Now that we have our Rust Lambda function ready, let’s create an AWS CDK project to deploy it.
First, create a deploy directory and navigate to it, to create a new CDK project using the following command:
mkdir deploy && cd deploy
cdk init app --language typescript
Next, rename the file inside `bin` directory to `index.ts`, and add `lambda-rs.ts` file to the `lib` directory, this file will contain the CDK stack that deploys the Rust Lambda function. Naming & Renaming files here is optional, but it's a good practice to keep the project organized.
Your project structure inside deploy directory, should look like this:
├── README.md
├── bin
│ └── index.ts
├── lib
│ └── lambda-rs.ts
├── cdk.json
├── tsconfig.json
├── package-lock.json
├── package.json
Next, add the following code to the lambda-rs.ts
file:
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { Construct } from 'constructs';
export class LambdaRS extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const rustLambda = new lambda.Function(this, 'LambdaRS', {
runtime: lambda.Runtime.PROVIDED_AL2023,
code: lambda.Code.fromAsset('../target/lambda/rust-lambda-cdk/bootstrap.zip'),
handler: 'bootstrap',
memorySize: 128,
timeout: cdk.Duration.seconds(5),
architecture: lambda.Architecture.ARM_64,
});
const fnUrl = rustLambda.addFunctionUrl({
authType: lambda.FunctionUrlAuthType.NONE,
});
new cdk.CfnOutput(this, 'LambdaFunctionUrl', {
description: 'Url of the lambda function',
value: fnUrl.url,
});
}
}
The previous code defines a new CDK stack that creates a Lambda function using the compiled Rust Lambda ZIP file. The function is configured to run on Graviton instances using the `architecture` property.
The final binary is named `bootstrap` and is located in the `target/lambda/lambda-rust-cdk` directory. the reason for this is that provided runtime (PROVIDED_AL2023 : Amazon Linux 2023) expects the binary to be named `bootstrap`.
Next, let’s update the index.ts
file to specify the stack to deploy, as shown below:
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { LambdaRS } from '../lib/lambda-rs';
const app = new cdk.App();
new LambdaRS(app, 'LambdaRS', {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION
}
});
Verify the `cdk.json` file to ensure that the app is pointing to the correct index file, as shown below:
{
"app": "npx ts-node --prefer-ts-exts bin/index.ts"
}
Run npm to install the required dependencies:
npm install
Next, Navigate to root folder and build the Rust Lambda for Graviton instances using the following command:
cargo lambda build --release --arm64 --output-format zip
to be able to deploy using CDK, your aws account need to be bootstrapped, if you haven't done that yet, you can do it using the following command:
npm --prefix deploy run cdk bootstrap aws://ACCOUNT-NUMBER-1/REGION-1
Boostrapping works on a per-region basis, so you need to bootstrap the specific region you want to deploy to. Ideally, you should bootstrap the same region that you have set in your CDK stack and your AWS CLI configuration (when running aws configure
).
Next, try to synthesize the CDK stack using to verify that the stack is correctly defined:
npm --prefix deploy run cdk synth LambdaRS --verbose
Finally, deploy the CDK stack using the following command:
npm --prefix deploy run cdk deploy LambdaRS --verbose
Conclusion
You can test the deployed Rust Lambda function by invoking the generated URL. You should see a message indicating that the function is searching for the specified artifact. The url should have the following format:
https://<api-id>.execute-api.<region>.amazonaws.com/?file=my-file-name
In this adventure, we explored how to create a Rust Lambda function and deploy it using AWS CDK on Graviton. We learned how to build a simple Rust Lambda function and deploy it using AWS CDK. We also saw how to optimize the deployment for Graviton instances to improve performance and reduce costs. The code for this adventure is available on GitHub.