S3EventからLamdaコンテナを動かす

## 今回やること
S3 EventでLambdaジョブ(コンテナ)を実行させる

- 入力: csvファイル
- 出力: jpgファイル

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54100/7a32469d-334a-41d1-b160-bf8499f80922.png)


### やりたかったこと
S3のPutObjectに応じたS3イベントを使って、
Python（Maplotlib/Pandas)を動かして、生成物を再度S3へ保存。

#### 経緯
- S3 EventとLambdaを組み合わせて簡単に作ろうとした。
- Maplotlibのボリュームが大きく、レイヤーの容量上限250MBにどうしても入りきらない。
- Lmadaコンテナを使う

## やってみる

やった結果：

https://github.com/bd8z/s3event

### Step1　S3イベントの確認する
print(event)だけするLambdaとS3インベントをつないで、
実行の様子とeventメッセージをCloudWatchLogsで確認する。
3つのファイル（hoge.png/hoge1.png/hoge2.png）を同時に放り込んだ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54100/21fba00a-046e-c4c4-82e4-3e3bccb349da.png)
Putしたオブジェクトの数だけLambdaが実行されていることが確認できる。


https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/notification-content-structure.html

公式ドキュメントにメッセージ構造が示されている。


### Step2 Dockerイメージを作成する

ドキュメントに従って必要なものを用意する
- lamda_function.py
- requirements.txt
- Dockerfile

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/python-image.html

```python:lamda_function.py
import json
import uuid

import boto3
import matplotlib.pyplot as plt
import pandas as pd

s3client = boto3.client("s3")

def handler(event, context):
    records = event["Records"]
    outBucketName = "s3event-experiment"

    for record in records:
        
        csvfilname = "/tmp/" + str(uuid.uuid4()) + ".csv"

        tmp_key = record["s3"]["object"]["key"]
        tmp_bucket = record["s3"]["bucket"]["name"]
        
        s3client.download_file(tmp_bucket, tmp_key, csvfilname)

        df = pd.read_csv(csvfilname,dtype=float)
        colList = list(df.columns)

        plt.figure(figsize=(12,3))
        plt.plot(df[colList[0]],df[colList[1]])
        plt.plot(df[colList[0]],df[colList[2]])

        keyname =  str(uuid.uuid4()) + ".jpg"
        jpgfilename = "/tmp/" + keyname
        plt.savefig(jpgfilename)
        with open(jpgfilename, "rb") as f:
            s3client.put_object(Body=f,Bucket=outBucketName,Key=keyname,ContentType="image/jpeg")
        
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }
```

`CMD [ "lambda_function.handler" ]`にてLambda関数がキックされる。このため、lamda_function.pyはDcokerイメージのルートディレクトリにCOPYする必要がある。ハンドラの名前やPythonファイルの名前は自由に変更することもできそう。`/tmp/`以下のファイル名は、被る可能性を排除するためuuidを使った。


```Dockerfile:Dockerfile
FROM public.ecr.aws/lambda/python:3.11

# Copy requirements.txt
COPY requirements.txt .

# Copy function code
COPY Scritpts/lambda_function.py .

# Install the specified packages
RUN pip install -r requirements.txt

# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile)
CMD [ "lambda_function.handler" ]
```

Dockerfile作成後、ECRにプッシュする


### Step3 全体の実行結果を確認する。
出力のバケットと入力のバケットを同じバケットとした。
大体3秒ぐらいで実行完了している。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54100/74068d94-f474-13d6-8ee2-4fbe7eb24bfd.png)


## そのほか色々思ったこと
- 実行時間
今回使ったECRイメージは300MBもある。にもかかわらず、End2Endで3秒ぐらいの時間しかかからなかった。コンテナの起動が爆速だ。これならAPIGatewayなどと組み合わせて色々な用途で使えそう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54100/a12fe122-2c0a-5ee8-6c35-afce76843838.png)
ECRにpushしたイメージをLambdaに登録する際、1分ぐらいの時間がかかった。コンテナ版Lambdaは実行の立ち上がりが非常に速い理由がここにあるのかもしれない。デプロイ時間中にイメージがLambda実行基盤にキャッシュされているとか…。

- 制約
Lambdaなので、実行時間最大15分の制約がある。最大永続ストレージ容量もコンテナ最大サイズも10GBなのでよほど引っ掛かることはなさそう。
VPCも使えなさそう。マネコンからは通常のランタイム版LambdaではVPCが選べるが、コンテナ版LambdaではVPCが選べなかった。VPC的なタスクでは従来通りのFargateを使うしかないのか。
Lambdaレイヤーが使えないので、共通コンポーネントをLambda上で共有できない。CodeGuruなどレイヤーを使った他サービスの利用もできない。Lambdaでもありコンテナでもあるので、この実行形態に合ったサービスを使いたい。