💭

Axumで作ったWebアプリケーションをLambdaで公開する

2024/10/18に公開

AWSのLambda、割と無料の幅が広いなって思っています。
個人的こじんまりしたWebアプリケーション作るのには最適かなぁと。

100万リクエスト!こじんまりしたものならほぼ無料でOKだと思います。
320万秒も十分でしょう。

問題はLambdaの最大の弱点、Coldスタートの待ち時間です。
Node.jsやPHPといったスクリプト言語のWebフレームワークだと初期処理が比較的重いと感じています。
またメモリ使用量の観点からもスクリプト言語よりRustのWebFrameworkが最適だと判断しました。

そんなわけで、Rust(Axum)を使った最小のプログラムと、ビルドについて書いていきたいと思います。

今回のゴール

Axumを最小構成でLambdaで使えるようにするという感じです。
具体的にはこんな感じ

  • Lambda(arm64)向けビルド
  • カスタムランタイムを使用
  • 最小構成
  • ローカルの環境で実行した場合は https://localhost でアクセスできるようにする
  • ビルドにDockerを使用

ソースコード

cargo.toml

cargo.tomlはこのような感じです。
特に重要なのは[[bin]]のnameにbootstrapを指定していることです。
カスタムランタイムの場合はbootstrapという名前の実行ファイルを作る必要があります。

cargo.toml
[package]
name = "test_axum_lambda"
version = "0.1.0"
edition = "2021"

[[bin]]
name = "bootstrap"
path = "src/main.rs"

[dependencies]
anyhow = "1.0.89"
axum = "0.7.7"
axum-aws-lambda = "0.9.0"
axum-server = { version = "0.7.1", features = ["tls-rustls"] }
dotenvy = "0.15.7"
lambda_http = "0.13.0"
tokio = { version = "1.40.0", features = ["full"] }
tower = { version = "0.5.1", features = ["full"] }
tower-http = { version = "0.6.1", features = ["full"] }

main.rs

main.rsについてはサンプルに少々手を加えたものとなります。
AWS_LAMBDA_FUNCTION_NAME環境変数をチェックして存在する場合はLambdaで実行中という判断をしています。
lambda以外の場合は、ループバックアドレスで待ち受ける形となっています。
証明書のファイル名に関しては環境変数(TLS_CERT, TLS_PEM)から受け取ります。

Rustlsを使用する場合、X.509 v3証明書が必要であることに注意してください。

main.rs
use std::{env, net::SocketAddr};

use axum::{
    routing::get,
    Router,
};
use axum_server::tls_rustls::RustlsConfig;

#[tokio::main]
async fn main() -> anyhow::Result<()>{
    dotenvy::dotenv().expect(".env file not found");

    // build our application with a single route
    let app = Router::new().route("/", get(|| async { "Hello, World!" }));

    // run our app with hyper, listening globally on port 3000
    if let Err(_) = env::var("AWS_LAMBDA_FUNCTION_NAME") {
        // Run app on local server
        let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
        let cert = env::var("TLS_CERT")?;
        let pem = env::var("TLS_PEM")?;

        let config = RustlsConfig::from_pem_file(cert, pem).await?;
        axum_server::bind_rustls(addr, config)
            .serve(app.into_make_service())
            .await?;


    } else {
        lambda_http::run(app).await.unwrap();
    }

    Ok( () )
}

ビルド

CPUをarm64にするとちょっとお安くて高速らしいのでarm64向けビルドをします。
私は下記のようなスクリプトでDockerを使用したビルドを行っています。
最終的に作成された bootstrap.zip をLambda関数としてアップロードしてください

build.ps1
docker pull messense/rust-musl-cross:aarch64-musl
$CARGO_PATH = $HOME+"/.cargo/registry"
docker run --rm -it -v ${PSScriptRoot}:/tmp/lima/build -v ${CARGO_PATH}/.cargo/registry:/root/.cargo/registry -w /tmp/lima/build messense/rust-musl-cross:aarch64-musl cargo build --release
zip -j bootstrap.zip target/aarch64-unknown-linux-musl/release/bootstrap

最後に

今回Lambda関数を関数URLで公開し、実行したところ下記のようになりました。
素の起動速度はやはり速い・・・・!これだったら初回アクセス時もっさりするなぁとか感じることはないと思います。

REPORT RequestId: 2ae17459-c6a6-4d67-99a5-754c53f9b85b Duration: 39.67 ms Billed Duration: 62 ms Memory Size: 128 MB Max Memory Used: 13 MB Init Duration: 22.31 ms 

Discussion