Lambda+Rustのデプロイメントをコンテナイメージと.zipファイルアーカイブの両方で試す

##はじめに
表題の通りです。
SAM CLIからカスタムランタイムを使ってLambdaをデプロイの設定情報を作っていたのですが、ImageとZipどちらがいいのかわからずどちらも試しました。
今後どちらも選択的に使えるようにまとめておきます。


##Zip
####構成

```bash
lambda_rust
  ├──.aws-sam
  ├──samconfig.yaml
  ├──stock_data
  |  ├──Cargo.lock
  |  ├──Cargo.toml
  |  ├──Makefile
  |  └──src/main.rs
  └──template.yaml
```
####設定
```toml:template.toml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  Sample rust application for Lambda
  
Resources:
  HelloRustFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: HelloRust
      Role: arn:aws:iam::{Lambdaを実行するロール}
      PackageType: Zip
      CodeUri: ./stock_data
      Runtime: provided.al2 # Amazon Linux 2
      Handler: bootstrap.is.real.handler
    Metadata:
      BuildMethod: makefile
```
```Dockerfile:Makefile
build-HelloRustFunction:
	cargo build --release --target x86_64-unknown-linux-musl

	# バイナリをbootstrapにして、Artifacts Directoryに格納する
	# Current Artifacts Directory : /workspace/stock_data/.aws-sam/build/HelloRustFunction
	cp ./target/x86_64-unknown-linux-musl/release/hello /workspace/stock_data/.aws-sam/build/HelloRustFunction/bootstrap
```
```toml:samconfig.toml
version = 0.1
[default]
[default.deploy]
[default.deploy.parameters]
stack_name = "{ストック名}"
s3_bucket = "{デプロイする先のS3バケット名}"
region = "ap-northeast-1"
confirm_changeset = false
capabilities = ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM", "CAPABILITY_AUTO_EXPAND"]
```
```toml:Cargo.toml
[package]
name = "stock_data"
version = "0.1.0"
authors = ["***** <****@****>"]
edition = "2018"

[dependencies]
lambda_runtime = "0.4"
lambda_http = "0.4.0"
serde_json = "1.0.59"
tokio = "1.11.0"

[[bin]]
name = "hello"
path = "src/main.rs"
```
```rust:main.rs
use lambda_runtime::{handler_fn, Context, Error};
use serde_json::{json, Value};

// エントリポイント
// Lambda特有のものを集約させる
#[tokio::main]
async fn main() -> Result<(), Error> {
    let func = handler_fn(my_handler);
    lambda_runtime::run(func).await?;
    Ok(())
}

async fn my_handler(event: Value, _: Context) -> Result<Value, Error> {
    let first_name = event["firstName"].as_str().unwrap_or("world");

    Ok(json!({ "message": format!("Hello, {}!", first_name) }))
}

#[cfg(test)]
mod tests {
    use super::*;

    //async関数は#[test]では使用できない
    //#[test]
    #[tokio::test]
    async fn my_handler_response() -> Result<(), Error> {
        let event = json!({
            "firstName": "AWS Lambda on Rust"
        });
        let json = json!({
            "message": "Hello, AWS Lambda on Rust!",
        });
        let response = my_handler(event, Context::default()).await?;
        assert_eq!(response, json);
        Ok(())
    }
}
```
####参考情報
AWS公式ブログがRust Runtime for AWSで書いています。makefileは使われていません。

https://aws.amazon.com/jp/blogs/opensource/rust-runtime-for-aws-lambda/

こちらの資料、長いですが、219ページ目あたりからの方がブログより参考になりそう

https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-application-model.pdf

かなり参考にさせていただきました。makefileを利用し、かつ複数関数のデプロイが行われています。

https://techpilot.dev/article/aws-lambda-rust


##Image
####構成

```bash
lambda_rust
  ├──.aws-sam
  ├──samconfig.yaml
  ├──stock_data
  |  ├──Cargo.lock
  |  ├──Cargo.toml
  |  ├──Dockerfile # Zipとの相違点
  |  └──src/main.rs
  └──template.yaml
```
####設定
```toml:template.toml（Function名も変えていますが変える必要はないです）
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  Sample rust application for Lambda
  
Resources:
  HelloRustFunctionImage:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: HelloRustImage
      Role: arn:aws:iam::{Lambdaを実行するロール}
      MemorySize: 512
      CodeUri: ./stock_data 　　　　　　　　　　　　# Zipとの相違点
      PackageType: Image 　　　　　　　　　　　　　　　　　　# Zipとの相違点
      #Runtime: provided.al2      # Zipとの相違点
      #Handler: bootstrap.is.real.handler # Zipとの相違点
    Metadata:
      Dockerfile: Dockerfile 　　　　　　　　　　# Zipとの相違点
      DockerContext: ./stock_data # Zipとの相違点
      DockerTag: v1 　　　　　　　　　　　　　　　　　　　　　　　　　　　　# Zipとの相違点
```
```Dockerfile:Dockerfile
FROM rust:latest as build-image

WORKDIR /rust/stock_data
COPY src/ /rust/stock_data/src/
COPY Cargo.toml Cargo.lock /rust/stock_data/

RUN rustup update && \
    rustup target add x86_64-unknown-linux-musl
RUN cargo build --release --target x86_64-unknown-linux-musl

FROM public.ecr.aws/lambda/provided:al2

# 実行ファイルを起動するようにするため、ファイル名を "bootstrap" に変更する
COPY  --from=build-image /rust/stock_data/target/x86_64-unknown-linux-musl/release/hello ${LAMBDA_RUNTIME_DIR}/bootstrap

# カスタムランタイムはハンドラ名利用しないため、適当な文字列を指定する。
CMD [ "lambda-handler" ]
```
```toml:samconfig.toml
version = 0.1
[default]
[default.deploy]
[default.deploy.parameters]
stack_name = "{ストック名}"
s3_bucket = "{デプロイする先のS3バケット名}"
image_repository = "{ImageをpushするECRのリポジトリ}" # Zipとの相違点
region = "ap-northeast-1"
confirm_changeset = false
capabilities = ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM", "CAPABILITY_AUTO_EXPAND"]
```
```toml:Cargo.toml
# Zipと相違ないため略
```
```rust:main.rs
// Zipと相違ないため略
```
####参考情報
情報収集していて、Lambda+Rust+Imageでのデプロイは少ないように感じました。

一番参考にしたのは、AWS Toolkit for Visual Studio Codeのgo-imageのテンプレートでした。
VS CodeでコマンドではなくToolkitを使ってLambda関数を作成するとテンプレートとしてサンプルのhelloworldが生成されます。以下はそのテンプレートのgithubソースです。ビルドファイルをアップする必要があるRustにおいて、同じくビルドファイルを作成するGolangの`template.yaml`や`Dockerfile`書き方は非常に参考になりました。

https://github.com/aws/aws-toolkit-vscode/tree/master/src/testFixtures/workspaceFolder/go1-image-sam-app

プログラムビルドとビルドファイルを埋め込んだImageの作成を分けられていますが、RustでImageデプロイしている貴重な情報。

https://dev.classmethod.jp/articles/rust-app-container-on-lambda-function/

Lambdaのカスタムランタイム イメージの構成や環境変数まわりの確認に利用

https://www.denialof.services/lambda/

##余談
実は、これを試す前はDockerfileでかけるImageの方がデプロイ環境が把握しやすいのでImage派でした。
Zipを試してみたきっかけは、コンソールでソースが見れるのはZipという表示を見かけたからです。

Imageではソースをコンソール上からは見ることができない。
![スクリーンショット 2021-09-09 14.28.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380300/c00d9eb0-03ee-b684-3b3a-ef7146a415fa.png)

RustのZipをデプロイして、コンソールを確認してみました。
![スクリーンショット 2021-09-09 14.29.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380300/5128ecf1-af5d-8607-9517-0fd32784b0b1.png)
かなしい。

##おわりに
Zipでやりたかったコンソール上でのソースコードは見れませんでしたが、今後もZipで進めようと思います。
理由としては、Imageの場合（私の手順の場合）、`Cargo build`用にコンテナを立ちあげてデプロイする手順がありますが、私は[VSCodeからコンテナに接続しその中でSAMを入れています](https://qiita.com/c3drive/items/348a99c1ea92d4ce246f)。SAM実行にてさらにコンテナの中でコンテナが起動して`Cargo build`し、さらにもう一つコンテナを立ち上げてビルドファイルをコピーするという、SAMを実行しているコンテナと合わせて4つ同時にコンテナが起動します。実行時MacBookAirが数回死にました。
また、Zip手順ではsam buildを実行するとSAMを入れている環境、つまり私の場合だと開発環境のコンテナ内で`Cargo build`が行われますが、Imageでは、「SAM実行にてさらにコンテナの中でコンテナが起動して`Cargo build`」の手順と完全に同じ事をしているに、コンテナが1つ余計に立ち上がるというデメリットしかないです。
もちろんその部分をMakefileに移し、最終的なビルドファイルの処理だけを切り替えればこれらのデメリットは無くなると思いますが、あまりにPCが死に過ぎてもうImageを試す気になれないのでZipで行こうと思います。
もう少しカスタムランタイム の中に持ち込みたい素材が増えてきたら変わってくる気がしますが、それまではZipで行こうと思います。