Lambdaでコンテナイメージから関数を作成する

# 目的
Lambdaの関数を作成する際、3つのケースから選択できます。
- 一から作成
- 設計図の使用
- コンテナイメージ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621838/df642e70-7f56-a11c-6d31-64492721ca18.png)

今回はこの「コンテナイメージ」から関数の作成を行う方法について、AWSベースイメージを使う場合と代替イメージを使う場合に分けて示します。

公式サイトは以下↓
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/images-create.html

# コンテナを使用するメリット
- カスタムランタイムの使用:
    - 従来のLambdaでサポートされている実行環境に依存することなく、好みの言語、フレームワーク、ライブラリを選択し、独自の実行環境を構築できる
- バージョン管理:
    - コンテナのイメージはイメージレジストリであるECRで管理されるため、特定のバージョンやタグと関連付けることができる。
- 移植性と再現性:
    - コンテナイメージの共有さえしてしまえば、異なる環境で一貫性のある実行が可能。

# 実際にやってみる

## 前提条件
この記事の手順を行うためには[AWS CLI 2](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)及び[Docker Desktop for Windows](https://docs.docker.com/get-docker/)がインストールされている必要があります。

```powershell
> aws --version
aws-cli/2.11.13 Python/3.11.3 Windows/10 exe/AMD64 prompt/off
```

```powershell
> docker version
Client:
 Version:           20.10.23

Server: Docker Desktop 4.17.0 (99724)
 Engine:
  Version:          20.10.23
```

## ファイル構成
```
project
    │─ app.py
    │─ Dockerfile
    │─ requirements.txt
    └─ entry.sh
```

## 実行する関数の用意 (app.py)
まずはLambdaで実行する関数を用意します。
ここではシンプルにLambda関数が正常に動作すれば固定のレスポンスを返すようにしています。

```python:app.py
import json

def handler(event, context):
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }
```

次に、使用するコンテナイメージが「AWSベースイメージ」の場合、「代替イメージ」場合に分けて手順を確認していきます。

## AWSベースイメージを使う場合 (Dockerfile, (requirements.txt))
AWSベースイメージを使う場合、AWSの公式Dockerレジストリにあるコンテナイメージを利用します。
https://gallery.ecr.aws/lambda/python

これはLambdaで動作するための依存関係やライブラリを含めた状態で提供してくれているため、動作の確保が期待できるものとなっています。

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

# requirements.txtを使用して関数の依存関係をインストールする。
COPY requirements.txt  .
RUN  pip3 install -r requirements.txt --target "${LAMBDA_TASK_ROOT}"

# AWS ベースイメージには、環境変数「LAMBDA_TASK_ROOT=/var/task」が含まれています。
COPY app.py ${LAMBDA_TASK_ROOT}

# Dockerイメージ内の実行時のデフォルトコマンドを設定します。
CMD [ "app.handler" ]
```

※requirements.txtは依存関係を追加したい場合のみ利用してください。

## 代替イメージを使う場合 (Dockerfile, requirements.txt, entry.py)
AWSが用意しているイメージではなく、独自で用意したものを使いたい場合もあるかと思います。
ここではDockerが用意している生のPythonイメージをLambdaで使えるようにします。

AWSベースイメージの場合と比べて以下のファイルが異なります。
- Dockerfile
- requirements.でのtxt
- entry.sh


### Dockerfile
Dockerが提供するイメージには、Lambdaで動作するための依存関係やライブラリは含まれていない状態となります。
そこで、重要なのはAWS Lambda の実行環境を再現するためのバイナリファイルを取得する必要があることです。


```Dockerfile:Dockerfile
FROM python:3.11

# AWS Lambdaランタイムインターフェースエミュレータのinstall
ADD https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie /usr/bin/aws-lambda-rie
COPY entry.sh "/entry.sh"
RUN chmod 755 /usr/bin/aws-lambda-rie /entry.sh

# 実行ファイルをコンテナ内に配置。
ARG APP_DIR="/var/task/"
WORKDIR ${APP_DIR}
COPY app.py ${APP_DIR}

COPY requirements.txt .
RUN pip install --upgrade pip && pip install -r ./requirements.txt

ENTRYPOINT [ "/bin/bash", "/entry.sh" ]
CMD [ "app.handler" ]
```

### requirements.txt
次に、Pythonに「awslambdaric」という、AWS Lambdaランタイムインターフェースクライアント（awslambdaric）を起動し、関数のハンドラを実行するためのモジュールをインストールするよう定義します。

```requirements.txt
awslambdaric
```

### entry.sh
最後にAWS Lambda関数が実行される際のエントリーポイントを定義するためのスクリプトを作成します。
スクリプト内で「AWS_LAMBDA_RUNTIME_API」 環境変数の値に応じて、ローカル環境での実行か、またはAWS Lambdaランタイムでの実行かを切り替える役割を果たしています。
※ローカル環境での実行はAWS Lambdaランタイムインターフェースエミュレータを使用します。

```sh:entry.sh
# localの場合
if [ -z "${AWS_LAMBDA_RUNTIME_API}" ]; then
    exec /usr/bin/aws-lambda-rie /usr/local/bin/python -m awslambdaric $1
# AWS Lambda上の場合
else
    exec /usr/local/bin/python -m awslambdaric $1
fi
```


## ローカルでの動作確認
各種ファイルの用意ができたらローカルにて動作確認してみましょう。

まず、Dockerfileからコンテナイメージを作成します。

```powershell
> docker build -t <イメージ名>
```

次に、コンテナイメージからコンテナを作成します。

```powershell
> docker run -p 9000:8080 <イメージ名>
```

コンテナの作成まで出来たら、最後にコンテナ内で動かしているappエンドポイントへリクエストを行います。

```powershell
> curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}'
{"statusCode": 200, "body": "\"Hello from Lambda!\""}
```
※リクエストに引数を渡したい場合は '{"hoge": "hogehoge"}' のようにしてください。

レスポンスが帰ってくればOKです。

## ECRへプッシュ
※以下のコマンドは[AWS CLI 2](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)をインストールしている前提になります。
次に、ローカルで作成したイメージをAWS ECR リポジトリへプッシュします。

まず、AWS ECR レジストリへの認証に使用するトークンを取得し、Docker CLIを使用してAWS ECRへログインします。

```powershell
> aws ecr get-login-password --region <リージョン> | docker login --username AWS --password-stdin <アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com
Login Succeeded
```


AWS ECR リポジトリにプッシュするために、ローカルのコンテナイメージにタグをつけます。
```powershell
> docker tag <イメージ名>:<タグ名> <アカウントID>.dkr.ecr.<リージョン>.amazonaws.com/<ECR リポジトリ名>:<タグ名>
```

AWS ECR リポジトリへイメージをプッシュします。
```powershell
> docker push <イメージID>.dkr.ecr.ap-northeast-1.amazonaws.com/<ECR リポジトリ名>:<タグ名>
```

以上でECRへのプッシュが完了します。


## Lambda関数の作成
ECRまでプッシュできたら、あとはAWS コンソール画面からLambda関数をコンテナイメージから作成します。
![lambda.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621838/a0f4fa4d-3c4a-9450-d140-d1720eb3801d.png)
関数作成後、テストをしてレスポンスを得られればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621838/f6467dc0-48c4-b16e-34b1-3873a85d3395.png)


# まとめ
サーバーレスに加え、実装環境もコンテナ化することでなんか今風になりましたね。
ローカルで動作テストをしたものと同じコンテナをLambdaでも使えることから、環境依存などがない一貫性ある実装が可能となりました。

また、独自のコンテナもLambdaで動かす方法を理解することで、さらなる応用も効きそうですね。