#実行環境
- macOS Monterey 12.1
- rustup 1.24.3
#ディレクトリ構造
ディレクトリ構造は下記のようになりました。
.
├── .cargo
│ └── config
├── src
│ └── main.rs
├── Cargo.toml
├── Makefile
└── template.yaml
これらのファイルを一つずつ見ていきます。
Makefile
とtemplate.yaml
はsam build
時に使用します。
#事前準備
今回はmacOS上でビルドしますが、Lambdaの実行コンテナはLinuxベースのため、クロスコンパイルで対応します。
以下のコマンドを入力し、cargoでクロスコンパイルできるようにしておきます。
$ brew install filosottile/musl-cross/musl-cross
$ rustup target add x86_64-unknown-linux-musl
$ ln -s /usr/local/bin/x86_64-linux-musl-gcc /usr/local/bin/musl-gcc
参考: Lambda が Custmon Runtimeに対応したのでRustで試してみた
#ファイル内容
##.cargo/config
クロスコンパイル用にlinkerを指定します。
[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"
##Cargo.toml
以下はCargo.toml
の内容です。
パッケージ名はhello-rust
にしました。
[package]
name = "hello-rust"
version = "0.1.0"
edition = "2021"
# binの名前はbootstrapで固定
[[bin]]
name = "bootstrap"
path = "src/main.rs"
[dependencies]
# Lambda用3点セット
lambda_runtime = "0.4"
tokio = "1.15"
serde_json = "1.0"
anyhow = { version = "1.0", features = ["backtrace"] }
log = "0.4"
env_logger = "0.9"
まず、生成されるバイナリの名前をbootstrap
にしておく必要があります。
これはAWS Lambdaでカスタムランタイムを構築した際のエンドポイントとなります。
参考: AWS Lambda のカスタムランタイム
次に、[dependencies]
に lambda_runtime、tokio、serde_json の3つのクレートを追加します。
この3つがRustでLambdaアプリケーションを構築する際の基本クレートです。
今回はその他にエラー処理用に anyhow 、ロガーとして env_logger を採用しています。
##src/main.rs
次にソースコードを見ていきます。
use std::process;
use anyhow::{Context, Result};
use lambda_runtime::handler_fn;
use serde_json::{json, Value};
async fn lambda_handler(event: Value, _: lambda_runtime::Context) -> Result<Value> {
let name = event["name"].as_str().context("name does not exist.")?;
Ok(json!({ "message": format!("Hello {}!", name) }))
}
#[tokio::main]
async fn main() {
env_logger::init();
let func = handler_fn(lambda_handler);
if let Err(err) = lambda_runtime::run(func).await {
log::error!("{:?}", err);
process::exit(1);
}
}
###main
main
関数は、Lambda実行コンテナの初回実行時 (いわゆるコールドスタート時) にのみ実行されます。
lambda_runtime::handler_fn
の引数にLambda関数が呼び出されたときにハンドラとなる関数を渡し、lambda_runtime::run
でイベントを待ち受けます。
毎回走るような処理ではないので、ここはほとんど定型文になるかと思います。
####ハンドラ関数の制約
handler_fn
に渡すハンドラは、以下の制約を満たす必要があります。
- 非同期関数であること
- 引数にeventとcontextを受け取ること
- eventは、
serde
がデシリアライズできる型であること - contextは
lambda_runtime::Context
型であること
- eventは、
- 戻り値の型は
core::result::Result
型かつ、Ok
側の型パラメータはserde
がシリアライズできる型であること
この制約を満たせば、例えば以下のようなハンドラにすることも可能です。
async fn lambda_handler(event: Vec<i32>, _: Context) -> Result<String, Error>
###lambda_handler
実際のイベントを処理する関数です。
関数名は任意ですが、ハンドラにするために上述の制約を満たす必要があります。
「1. 非同期関数であること」の制約があるので、 (たとえ内部で非同期処理を行っていなくとも) 関数にasync
キーワードを付ける必要があります。
async fn lambda_handler(event: Value, _: lambda_runtime::Context) -> Result<Value> {
let name = event["name"].as_str().context("name does not exist.")?;
Ok(json!({ "message": format!("Hello {}!", name) }))
}
今回はとりあえずSAMデプロイするまでが目的なので、ハンドラの内容自体は受け取ったイベントからname
を取り出してJSONを返すのみにとどめました。
##template.yaml
AWS SAMを使ってビルドおよびデプロイを行うために、SAMテンプレートを用意します。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
HelloRustFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: hello-rust
Runtime: provided
# ハンドラ名は使われないが、ないとエラーになるので適当な文字列を入れておく
Handler: bootstrap.is.real.handler
CodeUri: .
Environment:
Variables:
RUST_LOG: info
RUST_BACKTRACE: 1
RUST_LOG_STYLE: never
Metadata:
# makeを使ったビルドを行う
BuildMethod: makefile
Runtime
にはprovided
を指定します。
RustでLambda関数を作成する場合、ビルドしたbootstrap
ファイルがそのまま実行可能ファイルとなります。
そのためHandler
項目は本来は不要なのですが、SAMの仕様上これがないとデプロイできないので、何か適当な文字列を入れておきます。
それと、Metadata
属性を宣言してBuildMethod: makefile
を指定します。
これによりsam build
時にmake
が走るようになります。
ここでのmakefile
は先頭が小文字なので注意してください。
##Makefile
sam build
時のmake
用に、プロジェクト直下にMakefile
を置きます。
ここでは最低でも以下の2つのタスクを実行する必要があります。
- ソースコードのコンパイル
- 出来上がった実行可能ファイルを所定の場所へ移動
それを踏まえて、以下のように記述しました。
build-HelloRustFunction:
cargo build --release --target x86_64-unknown-linux-musl
cp ./target/x86_64-unknown-linux-musl/release/bootstrap $(ARTIFACTS_DIR)
ターゲット名はbuild-【template.yamlに記述した対象リソースの論理ID】
となります。
Makefile
の仕様上、インデントは (空白ではなく) タブ文字で記述する必要があることに注意してください。
ソースコードをビルドし、$(ARTIFACTS_DIR)
へ実行バイナリを格納しています。
Windows/macOSの場合、linux-x86_64用にクロスコンパイルしておく必要があります。
この実行バイナリはsam deploy
でS3へアップロードされる際に自動でzip圧縮されます。
ここでの$(ARTIFACTS_DIR)
は (デフォルトでは) .aws-sam/build/HelloRustFunction/
です。
#SAMビルド、SAMデプロイ
これで必要なファイルが出揃ったので、あとはそのままsam build
とsam deploy
を行います。
sam build
sam deploy --guided
sam deploy
時の入力内容は通常通りなので省略しますが、実行すれば以下のようにLambda関数がデプロイされます。
テストを実行してみます。
想定通りの出力が返ってくることが確認できます。
無事、Rustで作ったLambdaアプリケーションをSAMデプロイすることができました。
#おわりに
Rustは実行速度も速いので、AWS Lambdaと相性が良いように感じています。
次はコンテナイメージから構築してSAMデプロイしてみようと思います。
最後までお読み頂きありがとうございました。
質問や不備についてはコメント欄かTwitterまでお願いします。