Rust in AWS Lambda

Rust in AWS Lambda

In today's WOD, we'll learn how to deploy a Rust-lambda in AWS using CDK. We'll start relatively easy today. However, we'll explore more advanced setups using rust and different AWS services in the coming months.

  • A reference project can be found here

  • This post is derived from this original post from AWS

  • I assume you have access to AWS & related access keys

Start by installing Rust.

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Then install a crate (package) that will help us during the deployment, or download the binary by following these docs.

cargo install cargo-lambda

Next, create a new project using cargo-lambda. The tool will ask for some information, then generate a template project for you.

cargo lambda new demo-1

# ? Is this function an HTTP function? Yes
# ? Which service is this function receiving events from? Amazon Api Gateway REST Api

Your cargo.toml should now look something like the following.

# cargo.toml
[package]
name = "demo-1"
version = "0.1.0"
edition = "2021"

[dependencies]
lambda_http = { version = "0.7", default-features = false, features = ["apigw_rest"] }
lambda_runtime = "0.7"
tokio = { version = "1", features = ["macros"] }
tracing = { version = "0.1", features = ["log"] }
tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] }

In brief, tokio allows us to use an async runtime and the AWS packages help us interface between our code and the Lambda runtime.

Now it's time to write our entry point (main) and add our handler.

// main.rs
use lambda_http::{run, service_fn, Body, Error, Request, RequestExt, Response};

async fn function_handler(event: Request) -> Result<Response<Body>, Error> {
    let echo = event
        .query_string_parameters()
        .first("message")
        .unwrap_or("world!")
        .to_string();
    let resp = Response::builder()
        .status(200)
        .header("content-type", "text/html")
        .body(format!("<h1>Hello {}</h1>", echo).into())
        .map_err(Box::new)?;

    Ok(resp)
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    tracing_subscriber::fmt()
        .with_max_level(tracing::Level::INFO)
        .with_target(false)
        .without_time()
        .init();

    run(service_fn(function_handler)).await
}

Compile our lambda and "prepare" it for deployment.

# Build the binary
cargo lambda build --release

The project should now look something like this.

.
├── Cargo.lock
├── Cargo.toml
├── src
│   └── main.rs
└── target
    ├── CACHEDIR.TAG
    ├── debug
    ├── lambda
    ├── release
    └── x86_64-unknown-linux-gnu

Make sure you have the executable where it should be...

file target/lambda/demo-1/bootstrap
# target/lambda/demo-1/bootstrap: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.0.0, stripped

Cheers, now everything regarding our implementation is complete. Lastly, we just need to define our infrastructure using CDK and then deploy it!

Deploying the binary using CDK

npm install -g aws-cdk
mkdir infra && cd infra
cdk init app --language typescript

The project structure should now look something like this.

.
├── Cargo.toml
├── infra
│   ...
│   ├── lib
│   └── tsconfig.json
└── src
    └── main.rs

Time to write our stack definition.

// infra/lib/infra-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { Construct } from 'constructs';
import { RetentionDays } from 'aws-cdk-lib/aws-logs';

export class InfraStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const api = new apigateway.RestApi(this, "api", {
      restApiName: "rust-api",
      deployOptions: {
        stageName: "dev",
      },
    });
    const helloLambda = new lambda.Function(this, "rust-hello", {
      functionName: "hello-rust",
      code: lambda.Code.fromAsset(
        "../target/lambda/demo-1"
      ),
      runtime: lambda.Runtime.PROVIDED_AL2,
      handler: "not.required",
      environment: {
        RUST_BACKTRACE: "1",
      },
      logRetention: RetentionDays.ONE_DAY,
    });
    const messages = api.root.addResource("messages");

    messages.addMethod(
      "GET",
      new apigateway.LambdaIntegration(helloLambda, { proxy: true })
    );
  }
}

Deploy & Test your endpoint

npm run cdk deploy && cd -

# Execute your Lambda via the created API gateway
curl https://gbbb85bvai.execute-api.eu-west-1.amazonaws.com/dev/messages
# <h1>Hello world!</h1>                                                                                                                                                             

curl https://gbbb85bvai.execute-api.eu-west-1.amazonaws.com/dev/messages\?message\=ggwp
# <h1>Hello ggwp</h1>

Remove the stack

cd stack && npm run cdk destroy && cd -

Great, you made it this "far". You're now a fully certified binary-lambda-slinger. Feel free to give us input on what you'd like to see in the future! (as long as it's related to serverless)


Elva is a serverless-first consulting company that can help you transform or begin your AWS journey for the future.