LoginSignup
8
2

More than 1 year has passed since last update.

RustでAWS Lambda関数を書いてSAMデプロイする

Last updated at Posted at 2022-01-03

#実行環境

  • macOS Monterey 12.1
  • rustup 1.24.3

#ディレクトリ構造
ディレクトリ構造は下記のようになりました。

.
├── .cargo
│   └── config
├── src
│   └── main.rs
├── Cargo.toml
├── Makefile
└── template.yaml

これらのファイルを一つずつ見ていきます。
Makefiletemplate.yamlsam 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を指定します。

.cargo/config
[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"

##Cargo.toml
以下はCargo.tomlの内容です。
パッケージ名はhello-rustにしました。

Cargo.toml
[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_runtimetokioserde_json の3つのクレートを追加します。
この3つがRustでLambdaアプリケーションを構築する際の基本クレートです。

今回はその他にエラー処理用に anyhow 、ロガーとして env_logger を採用しています。

##src/main.rs
次にソースコードを見ていきます。

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. 非同期関数であること
  2. 引数にeventとcontextを受け取ること
    1. eventは、serdeがデシリアライズできる型であること
    2. contextはlambda_runtime::Context型であること
  3. 戻り値の型は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テンプレートを用意します。

template.yaml
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. ソースコードのコンパイル
  2. 出来上がった実行可能ファイルを所定の場所へ移動

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

Makefile
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 buildsam deployを行います。

sam build
sam deploy --guided

sam deploy時の入力内容は通常通りなので省略しますが、実行すれば以下のようにLambda関数がデプロイされます。

hello-rust.jpg

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

test.jpg
result.jpg

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

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

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

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

8
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2