AWS Lambda を Rust で実装して Serverless Framework でデプロイするまで

Rust で  [AWS Lambda](https://aws.amazon.com/jp/lambda/) を実装する機会がありましたが、ちょっとクセがあったので、備忘録も兼ねて記事にしておきます。

デプロイも簡略化できるよう、 [Serverless Framework](https://www.serverless.com/) を使ってデプロイできるようにします。

# 環境

```bash
$ rustc -V
rustc 1.45.0 (5c1f21c3b 2020-07-13)
$ cargo --version
cargo 1.45.0 (744bd1fbb 2020-06-15)
```

# とりあえず動かすまで

- 通常通り、プロジェクト作成

    ```bash
    cargo new sample_lambda_function
    ```

- 使用する crete 等を `Cargo.toml` に記述

    ```toml:Cargo.toml
    [package]
    name = "sample_lambda_function"
    version = "0.1.0"
    authors = ["example name <example@mail.com>"]
    edition = "2018"

    [dependencies]
    lambda_runtime = "0.2.1"
    serde = { version = "1.0.117", features = ["derive"] }
    ```

- `package.json` を作成して以下のように編集する

    ```json:package.json
    {
      "scripts": {
          "build": "docker run --rm -v $PWD:/code -v $HOME/.cargo/registry:/root/.cargo/registry -v $HOME/.cargo/git:/root/.cargo/git softprops/lambda-rust:latest",
          "deploy": "sls deploy --stage dev --verbose"
      },    
      "devDependencies": {
        "serverless": "2.8.0",
        "serverless-rust": "0.3.8"
      }
    }
    ```

- 上記を作成したら `yarn install` で Serverless Framework を手元のプロジェクトにインストールする
- とりあえず動かすための Lambda 関数を作成する。 `src/main.rs` を以下のように実装する：
    - メイン関数は `lambda` の マクロを用いて、ハンドラ関数を呼び出す。
    - ハンドラ関数には、イベントとコンテキストを引数に指定する。

    ```rust:main.rs
    use std::error::Error as StdError;
    use lambda_runtime::{error::HandlerError, lambda, Context};
    use serde::{Deserialize, Serialize};

    #[derive(Deserialize)]
    #[serde(rename_all = "camelCase")]
    struct CustomEvent {
        first_time: String,
    }

    #[derive(Serialize)]
    #[serde(rename_all = "camelCase")]
    struct CustomOutput {
        message: String,
    }

    fn main() -> Result<(), Box<dyn StdError>> {
        lambda!(handler);
        Ok(())
    }

    fn handler(
        event: CustomEvent,
        _context: Context,
    ) -> Result<CustomOutput, HandlerError> {
        Ok(CustomOutput {
            message: format!("Hello, {}!", event.first_time),
        })
    }
    ```

- 手元でビルドをしてみる。ビルドには `softprops/lambda-rust` の Docker イメージを利用する
    - [https://hub.docker.com/r/softprops/lambda-rust/](https://hub.docker.com/r/softprops/lambda-rust/)

    ```bash
    yarn build
    ```

    - エラーなく無事にビルドできたら OK
- `serverless.yml` を以下のように作成する
    - 作成される関数名は `[seivice で指定した名前]-[ステージ名]-[functions の下で指定した名前]` となる。

    ```yaml:serverless.yml
    service: sample-lambda-function
    provider:
      name: aws
      runtime: rust
      memorySize: 128
      region: us-east-2
    package:
      individually: true

    plugins:
      - serverless-rust

    custom:
      # this section customizes of the default 
      # serverless-rust plugin settings 
      rust:
        dockerTag: 'latest'

    functions:
      hello_world:
        handler: sample_lambda_function
    ```

- デプロイは以下のコマンドで可能。

    ```bash
    yarn deploy
    ```

# 複数の Lambda 関数をデプロイする

- ライブラリ + 複数バイナリの構成にする方法を採用する
- 構成としてはこんな感じ

    ```
    .
    |-- Cargo.toml
    |-- node_modules
    |-- package.json
    |-- serverless.yml
    |-- src
    |   `-- bin
    |       |-- function-a.rs
    |       `-- function-b.rs
    `-- target
    ```

- `Cargo.toml` に以下の記述を追加する

    ```toml:Cargo.toml
    [[bin]]
    name = "function-a"
    path = "src/bin/function-a.rs"

    [[bin]]
    name = "function-b"
    path = "src/bin/function-b.rs"
    ```

- `serverless.yml` には以下のように書く
    - ハンドラの指定は、「【`Cargo.toml` の `[package]` の `name`】.【`Cargo.toml` の `[[bin]]` の `name`】」と記述する

    ```yaml:serverless.yml
    functions:
      function-a:
        handler: sample_lambda_function.function-a
      function-b:
        handler: sample_lambda_function.function-b
    ```

- ビルドとデプロイのコマンドは先程と同じ。
    - 複数の関数は関連した関数としてデプロイされる。

# 参考資料

- [Rustで始めるCustom Runtime Lambda入門](https://dev.classmethod.jp/articles/developers-io-2020-connect-day6-custom-runtime-lambda-empowered-by-rust/)
  - 記事の内容全般について参考にしました。