#実行環境

    • 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に渡すハンドラは、以下の制約を満たす必要があります。

    1. 非同期関数であること

 

    1. 引数にeventとcontextを受け取ること

eventは、serdeがデシリアライズできる型であること
contextはlambda_runtime::Context型であること

戻り値の型は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つのタスクを実行する必要があります。

    1. ソースコードのコンパイル

 

    出来上がった実行可能ファイルを所定の場所へ移動

それを踏まえて、以下のように記述しました。

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関数がデプロイされます。

hello-rust.jpg

テストを実行してみます。

result.jpg

想定通りの出力が返ってくることが確認できます。
無事、Rustで作ったLambdaアプリケーションをSAMデプロイすることができました。

#おわりに
Rustは実行速度も速いので、AWS Lambdaと相性が良いように感じています。
次はコンテナイメージから構築してSAMデプロイしてみようと思います。

最後までお読み頂きありがとうございました。
質問や不備についてはコメント欄かTwitterまでお願いします。

#参考
カスタムランタイムの構築
Rust Runtime for AWS Lambda

广告
将在 10 秒后关闭
bannerAds