いわさです。
先日 AWS SAM CLI の v1.74.0 がリリースされました。
リリース通知のタイトルにもなっていますが、Rust のビルドがサポート(ベータ)されたというアナウンスがありました。
プログラミング言語ですので Lambda のことだろうと推測出来ますが Lambda のマネージドランタイムで Rust はサポートされていません。また、Amazon Linux 2 カスタムランタイムを使って Rust を Lambda で実行することは以前から出来ました。
では、今回の SAM CLI のアップデートで何が出来るようになったのでしょうか。
今回のリリースに含まれるいくつかのプルリクエストを確認しながら内容を把握し、実際に新しく出来るようになった機能を使ってみましたので紹介します。
What's New アナウンスはコチラ。
BuildMethod で Cargo Lambda がサポートされた
先にまとめです。
AWS SAM のメタデータ BuildMethod で rust-cargolambda が利用出来るようになっています。
BuildMethod は SAM CLI でsam build
を行う際の、対象リソースの処理方法を定義した属性です。
そして、SAM CLI はカスタムランタイムの Rust 用テンプレートが以前から用意されていました。
SAM CLI のテンプレート Rust 対応してたのか pic.twitter.com/9nfAxUhoNm
— いわさ (@Tak1wa) October 16, 2022
しかしその時は Makefile を使って独自にビルドする方法でした。 これが、今回の rust-cargolambda を指定することで、Cargo Lambda を使ってビルド出来るようになりました。
試してみる
では SAM CLI の最新版を使ってデプロイして動作確認するところまで試してみたいと思います。
ちなみに今回 sam build を行ったローカル環境は MacBook Pro (Apple M1 Max) です。
sam init でテンプレートを確認
まずはsam init
で生成されるテンプレートから確認してみます。
カスタムランタイムを指定した上で、Rust を指定します。
% sam init --runtime provided.al2
Which template source would you like to use?
1 - AWS Quick Start Templates
2 - Custom Template Location
Choice: 1
Choose an AWS Quick Start application template
1 - Hello World Example
2 - Infrastructure event management
3 - Multi-step workflow
Template: 1
Which runtime would you like to use?
1 - aot.dotnet7 (provided.al2)
2 - go (provided.al2)
3 - graalvm.java11 (provided.al2)
4 - graalvm.java17 (provided.al2)
5 - rust (provided.al2)
Runtime: 5
:
SAM テンプレートには次のように BuildMethod が設定されていました。
template.yaml
:
Resources:
:
PutFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Metadata:
BuildMethod: rust-cargolambda # More info about Cargo Lambda: https://github.com/cargo-lambda/cargo-lambda
Properties:
CodeUri: ./rust_app # Points to dir of Cargo.toml
Handler: bootstrap # Do not change, as this is the default executable name produced by Cargo Lambda
Runtime: provided.al2
Architectures:
- x86_64
:
アプリケーションコード自体はリクエスト内容を解析して DynamoDB へ登録するだけのシンプルな実装です。
rust_app/src/main.rs
use aws_sdk_dynamodb::{model::AttributeValue, Client};
use lambda_http::{service_fn, Body, Error, Request, RequestExt, Response};
use std::env;
/// Main function
#[tokio::main]
async fn main() -> Result<(), Error> {
// Initialize the AWS SDK for Rust
let config = aws_config::load_from_env().await;
let table_name = env::var("TABLE_NAME").expect("TABLE_NAME must be set");
let dynamodb_client = Client::new(&config);
// Register the Lambda handler
//
// We use a closure to pass the `dynamodb_client` and `table_name` as arguments
// to the handler function.
lambda_http::run(service_fn(|request: Request| {
put_item(&dynamodb_client, &table_name, request)
}))
.await?;
Ok(())
}
:
いや、シンプルな実装ですとか言いましたが Rust のコードは今回初めて見ました。
C++ に近いみたいな噂は聞いてましたけど、たしかになんとなく C++ っぽいので読むだけなら初見でもいけそうです。
どうやら main 関数で次のモジュールを使うことで API Gateway からのリクエスト処理用の Lambda ハンドラを用意しているようです。
間違っていたらスマンという感じです。
ビルドの条件
続いてsam build
をしてみましょう。
冒頭のリリース通知で少し触れられていましたが、今回の機能はベータという位置づけです。
以前 SAM CLI を使って Terraform の関数をローカル実行するベータ機能を紹介したことがあります。
その時と同じようにsam build
でベータ機能を使うフラグを指定する必要があります。
% sam build
Starting Build use cache
Build method "rust-cargolambda" is a beta feature.
Please confirm if you would like to proceed
You can also enable this beta feature with "sam build --beta-features". [y/N]:
今回 Cargo Lambda を使って Rust をビルドするので当然ながらそれらのセットアップが済んでいる必要があります。
以下のエラーが発生した場合は Cargo Lambda がインストールされていない場合です。
% sam build --beta-features
Experimental features are enabled for this session.
Visit the docs page to learn more about the AWS Beta terms https://aws.amazon.com/service-terms/.
Starting Build use cache
Cache is invalid, running build and copying resources for following functions (PutFunction)
Building codeuri: /Users/iwasa.takahito/work/hoge0227rust/hoge0227rust/rust_app runtime: provided.al2 metadata: {'BuildMethod': 'rust-cargolambda'} architecture: x86_64 functions: PutFunction
Running RustCargoLambdaBuilder:CargoLambdaBuild
Build Failed
Error: RustCargoLambdaBuilder:CargoLambdaBuild - Cargo Lambda failed: Cannot find Cargo Lambda. Cargo Lambda must be installed on the host machine to use this feature. Follow the gettings started guide to learn how to install it: https://www.cargo-lambda.info/guide/getting-started.html
ちなみに私は「M1 Mac なんだから x86 で User Container だろう」と思い込んで--use-container
をずっと使っていて、次のようなエラーが発生しうまくいきませんでした。
% sam build --beta-features --use-container
Experimental features are enabled for this session.
Visit the docs page to learn more about the AWS Beta terms https://aws.amazon.com/service-terms/.
Starting Build use cache
Starting Build inside a container
Cache is invalid, running build and copying resources for following functions (PutFunction)
Building codeuri: /Users/iwasa.takahito/work/hoge0227rust/hoge0227rust/rust_app runtime: provided.al2 metadata: {'BuildMethod': 'rust-cargolambda'} architecture: x86_64 functions: PutFunction
Fetching public.ecr.aws/sam/build-provided.al2:latest-x86_64 Docker container image......................................................................................................................................................................................................................................................................................................................................................................................................................
Mounting /Users/iwasa.takahito/work/hoge0227rust/hoge0227rust/rust_app as /tmp/samcli/source:ro,delegated inside runtime container
Build Failed
Error: RustCargoLambdaBuilder:Resolver - Path resolution for runtime: provided of binary: cargo was not successful
Rust (cargo) と Cargo Lambda のクロスコンパイル機能にお任せで良いみたいです。
実行
sam build
が出来たらあとは通常どおりsam deploy
します。
テンプレートを見ると次のように API Gateway と DynamoDB をも作成されそうです。
template.yaml
:
Resources:
Table:
Type: AWS::Serverless::SimpleTable # More info about SimpleTable Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-simpletable.html
Properties:
PrimaryKey:
Name: id
Type: String
PutFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Metadata:
BuildMethod: rust-cargolambda # More info about Cargo Lambda: https://github.com/cargo-lambda/cargo-lambda
Properties:
:
Events:
HelloWorld:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /{id}
Method: put
Environment:
Variables:
TABLE_NAME: !Ref Table
Policies:
- DynamoDBWritePolicy: # More info about SAM policy templates: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-policy-templates.html
TableName: !Ref Table
:
デプロイ後に実際 API Gateway リソースも確認してみましたがパスパラメータで適当な ID を指定して PUT すれば良いようです。
また、以下のコードからするとリクエストボディの内容をそのまま payload 値として登録してくれそうなので、適当な文字列を PUT リクエストの Body にセットしてみます。
main.rs
:
async fn put_item(
client: &Client,
table_name: &str,
request: Request,
) -> Result<Response<Body>, Error> {
// Extract path parameter from request
let path_parameters = request.path_parameters();
let id = match path_parameters.first("id") {
Some(id) => id,
None => return Ok(Response::builder().status(400).body("id is required".into())?),
};
// Extract body from request
let body = match request.body() {
Body::Empty => "".to_string(),
Body::Text(body) => body.clone(),
Body::Binary(body) => String::from_utf8_lossy(body).to_string(),
};
// Put the item in the DynamoDB table
let res = client
.put_item()
.table_name(table_name)
.item("id", AttributeValue::S(id.to_string()))
.item("payload", AttributeValue::S(body))
.send()
.await;
// Return a response to the end-user
match res {
Ok(_) => Ok(Response::builder().status(200).body("item saved".into())?),
Err(_) => Ok(Response::builder().status(500).body("internal error".into())?),
}
}
:
こんな感じだろうか。
% curl -X PUT -d "hogehogehoge" "https://qa5ovyp5nd.execute-api.ap-northeast-1.amazonaws.com/Prod/111/"
item saved
% curl -X PUT -d "fugafugafuga" "https://qa5ovyp5nd.execute-api.ap-northeast-1.amazonaws.com/Prod/222/"
item saved
登録出来ていそうです。
DynamoDB のテーブルをスキャンします。テーブル名はsam deploy
の出力から取得しました。
% aws dynamodb scan --table-name hoge0227rust-Table-92EBA75A5UAY
{
"Items": [
{
"payload": {
"S": "fugafugafuga"
},
"id": {
"S": "222"
}
},
{
"payload": {
"S": "hogehogehoge"
},
"id": {
"S": "111"
}
}
],
"Count": 2,
"ScannedCount": 2,
"ConsumedCapacity": null
}
登録されていますね。なるほど。
さいごに
本日は AWS SAM CLI の BuildMethod で Cargo Lambda がサポートされたようだったので実際に試してみました。
SAM CLI で Rust を動かしている方は是非今回のアップデートを試してみてください。
また、本機能はベータ機能になります。この記事は本日時点で情報で作成されていますが、今後変更や廃止の可能性もありますのでその点は気にしておきましょう。