コンテナイメージからLambda関数をデプロイする

## はじめに
コンテナイメージからLambda関数のデプロイにトライ。
（コンテナイメージからのデプロイ自体は2020年のアップデート）

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


## コンテナイメージからLambdaをデプロイする
コンテナイメージからLambda関数をデプロイすることでデプロイ可能なMaxのサイズが10GBになる。当時のre:Inventの動画を見ても機械学習などパッケージが大きいケースでもLambdaを使いやすく、というのが念頭に置かれている印象。

2020年の記事

https://aws.amazon.com/jp/blogs/news/new-for-aws-lambda-container-image-support/

zipファイルアーカイブからLambda関数をデプロイする方法では、解凍後のzipファイルのサイズが250MBまでに制限される

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/gettingstarted-limits.html


コンテナイメージからデプロイすれば依存関係の管理も楽ではあるけど、単純にWebアプリをLambdaに移植しても入出力インターフェースの違いから正常に動かないことがある。(Lambdaにはコードのエントリポイントとなるhandler関数があってイベントオブジェクトとコンテキストオブジェクトを受け取っているけど。そこがLambda独自仕様の部分)

この状態だとコンテナ化しても移植性はいまひとつになってしまう……のでHTTPを話すフレームワークであればLambda Web Adapterを使うとよさげ。

## Lambda Web Adapter
Lambda Extensionに追加することでNext.jsやFlaskなどHTTPを話すフレームワークのWebアプリをそのままLambdaでも動かせる、というもの。
Web AdapterにLambdaイベント->HTTPリクエスト変換、HTTPレスポンス->Lambdaレスポンス変換を担ってもらい、インターフェースの違いを意識せずに動かせるようにする。

Lambda Web AdapterはAWS公式OSSで、dockerイメージ、zipパッケージ両方に対応している。
詳細は以下のREADMEで。

https://github.com/awslabs/aws-lambda-web-adapter

https://aws.amazon.com/jp/builders-flash/202402/lambda-container-runtime/


## 実際にデプロイする
EC2にDocker入れて操作する。

### EC2にDockerインストール
```shell_session
$ sudo yum install -y docker 
$ sudo service docker start
```
### lambda_function.pyを作成する(Pythonの場合)

中身は好きなコードでどうぞ。

サンプルコード

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

### requirements.txtを作成する
インストールしたいパッケージをrequirements.txtに記載する。

### Dockerfileを作成
簡単なコードであれば参考リンクのサンプル通り5文で大丈夫。Lambda Web Adapterを使いたい場合だとCOPY文が一行増える。

```dockerfile
FROM public.ecr.aws/lambda/python:3.12

COPY requirements.txt ${LAMBDA_TASK_ROOT}

RUN pip install -r requirements.txt

COPY lambda_function.py ${LAMBDA_TASK_ROOT}

CMD [ "lambda_function.lambda_handler" ]

```
参考

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


上記リンクのサンプルでは
`CMD [ "lambda_function.lambda_handler" ]`の部分は
`CMD [ "lambda_function.handler" ]`になっているけど、ここは自分のコードに合わせて修正する。
コンソールから関数作るとハンドラは`lambda_function.lambda_handler`になるはず。(Pythonの場合)

関数コードのファイル名がlambda_function.pyでなければ`COPY lambda_function.py ${LAMBDA_TASK_ROOT}`も修正する。

### dockerイメージを構築
```
docker build --platform linux/amd64 -t docker-image:test .
```

### ECRに対してdocker CLIを認証する
```
aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <your_account_id>.dkr.ecr.us-east-1.amazonaws.com
```
以下のようなエラーが出たときは`/var/run/docker.sock`でpermission deniedになっているので属性を変更する必要がある。(参考リンクでは666)
```
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/json": dial unix /var/run/docker.sock: connect: permission denied
```
参考

https://newbedev.com/got-permission-denied-while-trying-to-connect-to-the-docker-daemon-socket-at-unix-var-run-docker-sock-get-http-2fvar-2frun-2fdocker-sock-v1-24-containers-json-dial-unix-var-run-docker-sock-connect-permission-denied-docker-code-example

### ECRにリポジトリを作成する
リポジトリはLambda関数と同じリージョンを指定する。

```
aws ecr create-repository --repository-name <repository name> --region <region> --image-scanning-configuration scanOnPush=true --image-tag-mutability MUTABLE
```
repositoryUriが出力されるので控えておく。
```shell
{
    "repository": {
        "repositoryArn": "arn:aws:ecr:<region>:<accountID>:repository/<repository name>",
        "registryId": "xxxxxxxxxxx",
        "repositoryName": "<repository name>",
        "repositoryUri": "<accountID>.dkr.ecr.us-east-1.amazonaws.com/<repository name>",
        "createdAt": "2024-06-02T06:10:26.698000+00:00",
        "imageTagMutability": "MUTABLE",
        "imageScanningConfiguration": {
            "scanOnPush": true
        },
        "encryptionConfiguration": {
            "encryptionType": "AES256"
        }
    }
}
```
GUIでも確認できる。
![repo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3458266/a44acea1-8f17-c304-4c8b-994e5e82adc3.png)



### ローカルイメージのタグ付け
ローカルイメージをECRリポジトリにタグ付け(latest)
```
docker tag docker-image:test <ECRrepositoryUri>:latest
```

### ECRリポジトリにローカルイメージをpushする

docker push
```
docker push <accountID>.dkr.ecr.<region>.amazonaws.com/<repository name>:latest
```

最初に上記のコマンドを実行したらno basic auth credentialsと言われてしまって、原因は認証してるリージョン違いとかトークンの有効期限切れとか[色々](https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/common-errors-docker.html)あるらしいけど、今回は以下のコマンドを実行して再度Docker CLIを認証したらpushできた。
```
aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <your_account_id>.dkr.ecr.us-east-1.amazonaws.com
```

イメージができている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3458266/00bd43c7-018d-bb2a-2d79-e0d53578dc77.png)

Lambda側でも関数の概要などは見れるけど、コードの表示はできない。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3458266/44c49417-0217-6037-a825-45cf8b4953b6.png)

### Lambda関数を作成する
GUIからでもaws lambda create-functionコマンドのどちらでも

マネコンだとロールの設定忘れにくいかもしれない。
![container.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3458266/8dd26ee1-9871-4cb4-415a-4b1a9b9e15a4.png)


### Lambda関数を実行する

response.jsonに実行結果(returnした戻り値)が出力される
```
aws lambda invoke --function-name <function-name> response.json
```

依存関係のファイル作ってないと実行したときにエラーが出る。(今回はコードの中でboto3使ってたのにrequirements.txtが空のままビルドしていた)
```
"Unable to import module 'lambda_function': No module named 'boto3'", "errorType": "Runtime.ImportModuleError", "requestId": "f03937c4-719e-4bf4-85a7-dbf769282d06", "stackTrace": []}
```

LambdaがBedrockのAPIにリクエスト投げるのにLambdaに何の権限もついてない、とかだともちろん関数実行時エラーになるので、実行に必要な権限があればつけておく。

### Lambda関数を更新する
新しいイメージをECRリポジトリにアップロードしてからupdate-function-codeを実行する。
```
update-function-code --function-name <function name> --image-uri <imageUri>
```


## おわりに
Lambdaはちょっとしたコードを実行する、といった用途で使っていたけどもう少しスケールの大きいこともできそう、という印象。手持ちのLambda関数コードだとLambda Web Adapterの出る幕が無かったのでサンプルコードでWeb Adapter試したい。