Docker × Python × LambdaでサクッとWeb APIを建てるテンプレート

:::note
この記事は元々**2024年8月16日**に個人ブログで公開された内容をQiitaに移行したものです。
:::

Docker × Python × AWS Lambdaで楽にWeb APIを建てるためのテンプレを作成しました。

## 普通にLambdaでAPIを建てるのとなにが違うの？

AWS Lambdaを使うとGUI上でサクッとWebAPIを建てることができて便利ですが、以下のような不便さもあります。

- Pythonのライブラリをインストールするのが面倒くさい。（ライブラリをインスールしたレイヤーを追加しなければならない。）
- ローカルでテストできない。（コードをアップデートするたびにデプロイし直さないといけない）

特に１つ目に関しては重要で、画像処理したいケースや機械学習のモデルを動かしたいケースなど、ライブラリが必要になるケースはかなり多いと思います。しかしLambdaで何らかのライブラリを使いたかったら、ライブラリをインスールしたのレイヤーをいちいち追加しなければいけません。しかもレイヤーを作る際はLambdaで使ってるPythonとバージョンを合わせないといけないのも地味に面倒です。

今回ご紹介する方法ですと、任意のライブラリをインストールしたWeb APIをサクッと建てることができます。しかもデプロイ前にローカルでAPIテストもできます。

## テンプレート用意

テンプレートをGitHubに公開しています。

https://github.com/ShimeiYago/lambda-api-template

このテンプレを使って新しいリポジトリを作成し、お手元に`git clone`しておいてください。

![git-template.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3981597/a030ec89-f21c-4b7b-9b54-d179f53f749e.jpeg)


## 1. セットアップ

### 1.1. AWS LambdaのRIEをダウンロードする

RIEとは「Runtime Interface Emulator」の略で、AWS Lambdaの環境をローカルでエミュレートするためのツールです。これを後で作るDockerイメージ内に組み込むことで、ローカルでLambdaのテストをできます。

リポジトリのルートに移動して以下のコマンドを実行してください。

```bash
curl -Lo aws-lambda-rie https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie
```

```bash
chmod +x aws-lambda-rie
```

### 1.2. 必要なPythonライブラリを追加する

何か使いたいPythonライブラリがある場合は、"requirements.txt"にそのライブラリを追記してください。逆に追加のライブラリが必要ない場合このステップはスキップして構いません。

例えば画像処理ライブラリのPillowを使いたい場合は以下のように追記します。バージョンは適切なものを指定してください。

```plain
awslambdaric # ここは必須
Pillow==9.5.0 # ここ追加
```

### 1.3. DockerイメージをBuildする

以上の準備が整ったらDockerイメージを作成します。

```bash
docker build -t lambda-api .
```

以上でAPIのセットアップは完了です。

## 2. ローカルでテストする

作成したDockerイメージを使ってDockerコンテナを起動してみます。

```bash
docker run --rm -p 8000:8080 lambda-api:latest
```

すると、Web APIが[localhost:8000/2015-03-31/functions/function/invocations](http://localhost:8000/2015-03-31/functions/function/invocations) でアクティブになりますので、ここにPOSTリクエストを送れば期待通りのレスポンスが返ってくるはずです。

試しにリクエクトを送ってみたい場合は以下を実行してみてください。

```bash
curl -X POST -H "Content-Type: application/json" -d '{}' localhost:8000/2015-03-31/functions/function/invocations
```

`{"message": "Hello World"}`と表示されればOKです。

## 3. 作成したイメージをECRへアップロードする

ステップ1.3.で作成したイメージをAWSのECR（AWSが提供するDockerイメージ管理サービス）へアップロードしていきます。

※ここからは前提としてAWS CLIを使用できるようにしておいてください。

### 3.1. 環境変数設定

まず以下のように３つの環境変数を設定しておきます。

```bash
REGION="ap-northeast-1"
```

```bash
ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
```

```bash
ECR_REPO_NAME="lambda-api"
```

`REGION`と`ECR_REPO_NAME`は任意の値に変えてもらって構いません。

### 3.2. ECRにログイン

```bash
aws ecr get-login-password --region ${REGION} | docker login --username AWS --password-stdin ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com
```

`Login Succeeded`と表示されればOKです。

### 3.3. ECRのリポジトリを作成する

```bash
aws ecr create-repository --repository-name $ECR_REPO_NAME
```

このコマンドでECRのリポジトリを作成します。（この時点ではまだ中身は空）

ちなみにこのコマンドはリポジトリの枠を作成するだけのコマンドなので、初回だけ実行します。後でイメージを上げ直したい場合などがあっても２回目を実行する必要はありません。

### 3.4. イメージにDockerタグをつける

ステップ1.3.で作成したイメージに、ECR用のDockerタグをつけます。

```bash
docker tag lambda-api:latest ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/${ECR_REPO_NAME}:latest
```

これでECRにpushする準備は完了です。

### 3.5. イメージをECRにpushする

```bash
docker push ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/${ECR_REPO_NAME}:latest
```

これでpush完了です。

ブラウザでAWSのECRにアクセスしてみると、無事イメージが上がっていることが確認できますね。

![ecr-image-1024x353.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3981597/e6cff2c5-ef08-40d1-a12e-87b05dd3b9fa.png)


## 4. AWS Lambda作成

ここからはGUI上でのポチポチ操作になります。

ブラウザ上でAWS Lambdaにアクセスして、「関数の作成」をクリックします。  
※イメージをpushしたECRと同じリージョンにいる必要があることを注意してください。

そしてステップ3.5.で追加したイメージを元にLambda関数を作成します。

![create-aws-lambda-896x1024.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3981597/63000da9-3a82-4012-a275-0043cc3ee28b.jpeg)


イメージのアーキテクチャがわからない場合は、以下のコマンドで確認してください。

```bash
docker image inspect lambda-api:latest | grep Architecture
```

`amd64`と出た場合は`x86_64`と同義です。

## 5. API Gatewayと繋げる

ここまでだとまだLambda関数を作成しただけで、Web APIとして呼び出すことはできません。  
AWSのAPI Gatewayという機能を使って、このLambda関数をWeb APIとして呼び出せるようにしていきます。

### 5.1. APIを作成する

API Gatewayのページにアクセスし、「APIを作成」をクリックします。  
APIのタイプを訊かれるので、「REST API」を選択して、適当なAPI名でAPIを作成します。

### 5.2. メゾットを作成する

ステップ5.1.で作成したAPIの設定ページで、「メゾットを作成」をクリックします。

そして以下のように設定を行います。

![create-method.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3981597/7396bb37-9ea9-4178-b6b5-d9ec2fb79780.jpeg)

選択するLambda関数はステップ4.で作成したものを選択してください。

### 5.3. APIをデプロイする

作成したAPIの設定ページで、「APIをデプロイ」をクリックします。

ここでは「ステージ」と呼ばれるものの名前を入力します。「ステージ」はAPIのバージョン管理のようなもので、テスト用や本番用などの環境を分けることができます。

今回は「test」など適当なステージ名をつけておきます。



![deploy-api.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3981597/d634d36d-1fff-4d0e-8e17-b823c283a6a0.jpeg)

これでAPIを呼び出せるようになりました。

### 5.3. APIをテストする

最後にデプロイされたAPIをテストしてみましょう。

デプロイした「ステージの詳細」から、Web APIのURLを確認できます。




このURLに対してPOSTリクエストを送ってみて、期待通りのレスポンスが返って来れば成功です！

```bash
curl -X POST -H "Content-Type: application/json" -d '{}' https://xxx.ap-northeast-1.amazonaws.com/test
```

## 6. 実装変更と再デプロイ

最後に、APIの実装の変更方法と、再デプロイの手順を簡単に説明しておきます。

### 6.1. APIの実装を変更する

テンプレートのままだと、このAPIは"Hello World"と書かれたシンプルなJSONを返すような処理になっています。このAPIのメイン処理は"app.py"に記述されており、この中身は自由に変更して大丈夫です。  
なお、リクエストBodyは`event`パラメータそのものとして受け取ることができますので、必要な場合はそれを参照してください。

### 6.2. APIを再デプロイする

実装を変更してそれを反映させるためには、Lambdaが参照しているイメージを更新すれば良いだけです。

まず「ステップ1.3.」「ステップ3.4.」「ステップ3.5.」のみを再度実行して新しいイメージを再pushします。

その後、Lambdaの設定画面で「新しいイメージをデプロイ」を選択して、最新のイメージを再選択すれば完了です。

![update-image.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3981597/ef3b90ce-1d60-4962-91d6-1f7c6220d499.jpeg)


お疲れ様でした！