AWS LambdaのCustom RuntimeでRustを実行してみた #reinvent

re:Invent 2018のKeynote Day2、ついにLambdaでCustom Runtimeがサポートされることが発表されました!自分でカスタマイズしたRuntimeでLambdaが利用できるようになります。やったぜ!!

【アップデート】 もう言語で悩まない!AWS LambdaでCustom Runtimeが利用できるようになりました! #reinvent

ということで早速、LambdaでRustファンクションを実行してみました!!

やってみた

開発環境

  • macOS: 10.12.6
  • rustup: 1.15.0 (f0a3d9a84 2018-11-27)
  • Rust: 1.30.1 (1433507eb 2018-11-07)

Rustの開発環境セットアップ

以下ブログを参考にRustの開発環境をセットアップします。

Rustの開発環境セットアップ

ローカルでRustファンクション作成

公式ブログを参考にRustファンクションを作成していきます。

プロジェクトを作成

Lambdaファンクション用のプロジェクトを作成します。

$ cd MY_WORKSPACE
$ cargo new my_lambda_function --bin
$ cd my_lambda_function
$ tree
.
├── Cargo.toml
└── src
    └── main.rs

1 directory, 2 files

Cargo.tomlを編集

Lambdaで実行するためにCargo.tomlを編集します。

[package]
name = "my_lambda_function"
version = "0.1.0"
authors = ["jogan.naoki <jogan.naoki@classmethod.jp>"]
autobins = false

[dependencies]
lambda_runtime = "^0.1"
serde = "^1"
serde_json = "^1"
serde_derive = "^1"
log = "^0.4"
simple_logger = "^1"

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

dependenciesにはLambda実行に関する依存情報を記述します。

  • serde: Lambda関数のイベントとレスポンスをシリアル化およびデシリアライズする
  • log、simple_logger: ログを出力する

カスタムランタイムを使用する場合、Lambdaは展開パッケージにbootstrapという実行可能ファイルが含まれることを期待しています。 その対応のためpackageにはautobins = falsebinにはname = "bootstrap"をそれぞれ追加します。

コンパイル環境構築

コンパイル環境を構築します。

$ rustup target add x86_64-unknown-linux-musl
$ brew install filosottile/musl-cross/musl-cross
$ mkdir .cargo
$ echo '[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"' > .cargo/config
$ ln -s /usr/local/bin/x86_64-linux-musl-gcc /usr/local/bin/musl-gcc

メイン関数を修正

メイン関数を以下のように修正します。

$ cat src/main.rs
#[macro_use]
extern crate lambda_runtime as lambda;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate log;
extern crate simple_logger;

use lambda::error::HandlerError;

use std::error::Error;

#[derive(Deserialize, Clone)]
struct CustomEvent {
    #[serde(rename = "firstName")]
    first_name: String,
}

#[derive(Serialize, Clone)]
struct CustomOutput {
    message: String,
}

fn main() -> Result<(), Box<dyn Error>> {
    simple_logger::init_with_level(log::Level::Info)?;
    lambda!(my_handler);

    Ok(())
}

fn my_handler(e: CustomEvent, c: lambda::Context) -> Result<CustomOutput, HandlerError> {
    if e.first_name == "" {
        error!("Empty first name in request {}", c.aws_request_id);
        return Err(c.new_error("Empty first name"));
    }

    Ok(CustomOutput {
        message: format!("Hello, {}!", e.first_name),
    })
}

ファンクションをビルド

以下のコマンドでLambda用のアプリケーションをビルドします。

$ cargo build --release --target x86_64-unknown-linux-musl

Lambdaにアップロードするため、ファイルをzip化します。

$ zip -j rust.zip ./target/x86_64-unknown-linux-musl/release/bootstrap

Lambdaファンクション作成

Lambdaファンクションを作成します。

ランタイムには独自のランタイムを指定します。ロールはLambda_basic_executionで問題ありません。

先ほど作成したzipファイルをアップロードし、ファンクションを保存します。

テスト実行

テストイベントを作成します。

{
  "firstName": "Rustacean"
}

これで準備完了です。最後にテストを実行してみましょう!!

LambdaでRustが実行できました!!

まとめ

LambdaでCustom Runtimeがサポートされたことにより、Lambdaの可能性がまた大きく広がりました!!ランタイムが対応していなくてLambda上での実行を諦めていた、あんなスクリプトやこんなスクリプト、これを機にLambda対応してみてはいかがでしょうか。

ランタイムとして現在利用可能なものは以下になります。

サードパーティにより順次以下のランタイムが追加される予定です。

  • Erlang (Alert Logic)
  • Elixir (Alert Logic)
  • Cobol (Blu Age)
  • N|Solid (NodeSource)
  • PHP (Stackery)