eyecatch

Deploy Rust Axum app on Lambda with aws-lambda-web-adapter

Posted on 2024/01/28
# Technology

Nowadays, building Rust REST Web API servers is becoming increasingly popular. I developed a Rust web server using Axum for my work at an E-commerce service. Since our business is quite small and operates on a low budget, we are keen on saving money. Rust with Lambda proves to be a great solution for us at the moment. The 'aws-lambda-web-adapter' is a tool that allows running web applications on AWS Lambda with ease.

For busy people

You need to develop with

  • Rust with Axum
  • Deploy on AWS Lambda with a container
  • Amazon ECR to store Docker image

Just add one line to your Dockerfile!!
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.5.0 /lambda-adapter /opt/extensions/lambda-adapter

FROM rust:1.74.1-slim-bullseye as base

FROM base as builder
WORKDIR /app
COPY . /app
RUN apt-get update
RUN apt-get install -y "Packages you need"

RUN cargo build --release
RUN strip /app/target/release/my-app -o /my-app

FROM debian:bullseye-slim
RUN apt-get update
RUN apt-get install -y "Packages you need"

# JUST ADD A LINE BELOW!!
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.5.0 /lambda-adapter /opt/extensions/lambda-adapter
COPY --from=builder /my-app /

EXPOSE 8080 # <- should same to AWS_LWA_PORT (Lambda traffic port)

CMD [ "/my-app" ]

What is aws-lambda-web-adapter

aws-lambda-web-adapter is a tool to run web applications on AWS Lambda. In the past, need a specific framework to build a web server on Lambda, which is hard to translate an environment from Lambda to EC2 when the business is expanded, often necessary to rebuild with a different framework. And we want to use familiar web frameworks such as Axum, Laravel, Next and so on.

aws-lambda-web-adapter provide a easy way to develop your web app with a familiar framework!!

You can see bunch of examples from a link below.
https://github.com/awslabs/aws-lambda-web-adapter/tree/main/examples

Prepare project

Let make a simple Axum app with root and health routes!!

[package]
name = "rust-axum-lambda"
version = "0.1.0"
edition = "2021"

[dependencies]
axum = "0.7.4"
tokio = { version = "1.15.0", features = ["net", "rt-multi-thread"] }
use axum::{routing::get, Router};

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/", get(root))
        .route("/health", get(health));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

async fn root() -> &'static str {
    "Hello, World!"
}

async fn health() -> &'static str {
    "OK"
}

Check responses.

$ cargo run

Compiling rust-axum-lambda v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 0.84s
Running `target/debug/rust-axum-lambda`



$ curl -X GET http://localhost:8080

Hello, World!



$ curl -X GET http://localhost:8080/health

OK                                                                                                                                              

Cool, we can see response from the server locally.

Use aws-lambda-web-adapter

To use aws-lambda-web-adapter we just add a line into Dockerfile, that's all!!

COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.0 /lambda-adapter /opt/extensions/lambda-adapter

FROM rust:1.74.1-slim-bullseye as base

FROM base as builder
WORKDIR /app
COPY . /app
RUN cargo build --release
RUN strip /app/target/release/rust-axum-lambda -o /rust-axum-lambda

FROM debian:bullseye-slim
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.5.0 /lambda-adapter /opt/extensions/lambda-adapter
COPY --from=builder /rust-axum-lambda /

EXPOSE 8080

CMD [ "/rust-axum-lambda" ]

Deploy to Lambda

First of all, we need to create ECR to store you docker image on AWS. I create repository on ap-northeast-1 because I live in Tokyo.

Next, push docker image to ECR. ECR provide commands to push the image. Replace {repository_id} with your's.

$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin {repository_id}.dkr.ecr.ap-northeast-1.amazonaws.com

$ docker build -t rust-axum-lambda .
or for Apple Silicon (MacOS M1/M2) because Apple Silicon is ARM64 architecture.
$ docker build --platform linux/amd64 -t rust-axum-lambda .

$ docker tag rust-axum-lambda:latest {repository_id}.dkr.ecr.ap-northeast-1.amazonaws.com/rust-axum-lambda:latest

$ docker push {repository_id}.dkr.ecr.ap-northeast-1.amazonaws.com/rust-axum-lambda:latest

We can see latest image on ECR if commands success.

Then move to Lambda, create new function with container image we just pushed.

We got the new function!! Let test request with Function URL we can set up this in configuration tab. I NONE auth type for testing.

Let request to the server!!

$ curl -X GET https://{function_url}.lambda-url.ap-northeast-1.on.aws/

Hello, World!

$ curl -X GET https://{function_url}.lambda-url.ap-northeast-1.on.aws/health

OK

We got responses from root and /health route as well!!

Execution Time

Let measure the execution time.

Cold start: 0.917074s

$ curl -o /dev/null -s -w 'Total: %{time_total}s\n' -X GET https://{function_url}.lambda-url.ap-northeast-1.on.aws/
Total: 0.917074s

During running: 0.075715s

$ curl -o /dev/null -s -w 'Total: %{time_total}s\n' -X GET https://{function_url}.lambda-url.ap-northeast-1.on.aws/
Total: 0.075715s

It's simple app though, even the cold start, it's enough for e-commerce site.

Where I struggled

I struggle those points when I build app in real app.

Timeout for different AWS_LWA_READINESS_CHECK_PORT

Lambda default port is 8080 . My app port was 3000 . You should keep same port with Lambda and app port.

No resepose for AWS_LWA_READINESS_CHECK_PATH

Lambda readiness check path is / as default. If your app doesn't have / route. Lambda return timeout. I had health point at /api/health .

You can change those values with env. Check more detail below.
https://github.com/awslabs/aws-lambda-web-adapter?tab=readme-ov-file#configurations