Amplify CLIでPython REST APIを設定する

# はじめに
業務の一環でAmplifyにホストしたTypeScript ReactベースのwebアプリからPython実装のAPIを使いたかった。初手として、Amplify CLIを用いてLambda+API Gatewayを使った実装を試行したので、備忘をここに残す。
この記事のゴールはAmplify CLIでapiを追加してpushするところまで。

# 環境
- OS macOS Sonoma 14.4.1
- AWS Amplify CLI 12.11.1
- Node.js v20.11.0
- npm 10.2.4
- TypeScript 5.3.3
- React 18.2.0
- Python 3.12.1

# 前提
AmplifyにAuthとStorageを追加済み。ここからPython実装のAPIを追加する。
```shell
inakason@buz web-app % amplify status

    Current Environment: dev
    
┌──────────┬─────────────────┬───────────┬───────────────────┐
│ Category │ Resource name   │ Operation │ Provider plugin   │
├──────────┼─────────────────┼───────────┼───────────────────┤
│ Auth     │ webapp          │ No Change │ awscloudformation │
├──────────┼─────────────────┼───────────┼───────────────────┤
│ Storage  │ ffdwebappstrage │ No Change │ awscloudformation │
└──────────┴─────────────────┴───────────┴───────────────────┘
```

# APIの追加
まず、```amplify add api```から要件に併せてapiの設定を行っていく。
```shell
inakason@buz web-app %  amplify add api          
? Select from one of the below mentioned services: REST
✔ Provide a friendly name for your resource to be used as a label for this category in the project: · ffdexecutorapi
✔ Provide a path (e.g., /book/{isbn}): · /coffee
Only one option for [Choose a Lambda source]. Selecting [Create a new Lambda function].
? Provide an AWS Lambda function name: ffdexecutorapi
? Choose the runtime that you want to use: Python
⚠️ You must have pipenv installed and available on your PATH as "pipenv". It can be installed by running "pip3 install --user pipenv".
You must have virtualenv installed and available on your PATH as "venv". It can be installed by running "pip3 install venv".
Only one template found - using Hello World by default.

✅ Available advanced settings:
- Resource access permissions
- Scheduled recurring invocation
- Lambda layers configuration
- Environment variables configuration
- Secret values configuration

? Do you want to configure advanced settings? Yes
? Do you want to access other resources in this project from your Lambda function? Yes
? Select the categories you want this function to have access to. auth, storage
? Select the operations you want to permit on webapp80c33697 read
? Select the operations you want to permit on ffdwebappstrage create, read, update, delete

You can access the following resource attributes as environment variables from your Lambda function
	AUTH_WEBAPP80C33697_USERPOOLID
	ENV
	REGION
	STORAGE_FFDWEBAPPSTRAGE_BUCKETNAME
? Do you want to invoke this function on a recurring schedule? No
? Do you want to enable Lambda layers for this function? No
? Do you want to configure environment variables for this function? No
? Do you want to configure secret values this function can access? No
? Do you want to edit the local lambda function now? Yes
Edit the file in your editor: /Users/***mask***/amplify/backend/function/ffdexecutorapi/src/index.py
? Press enter to continue 
✅ Successfully added resource ffdexecutorapi locally.

✅ Next steps:
Check out sample function code generated in <project-dir>/amplify/backend/function/ffdexecutorapi/src
"amplify function build" builds all of your functions currently in the project
"amplify mock function <functionName>" runs your function locally
To access AWS resources outside of this Amplify app, edit the /Users/***mask***/amplify/backend/function/ffdexecutorapi/custom-policies.json
"amplify push" builds all of your local backend resources and provisions them in the cloud
"amplify publish" builds all of your local backend and front-end resources (if you added hosting category) and provisions them in the cloud
✅ Succesfully added the Lambda function locally
✔ Restrict API access? (Y/n) · yes
✔ Who should have access? · Authenticated and Guest users
✔ What permissions do you want to grant to Authenticated users? · create, read, update, delete
✔ What permissions do you want to grant to Guest users? · create, read, update, delete
✔ Do you want to add another path? (y/N) · no
✅ Successfully added resource ffdexecutorapi locally

✅ Some next steps:
"amplify push" will build all your local backend resources and provision it in the cloud
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud
```

## 設定の更新
ここで、Lambda Layerの追加はNoとしているが、実行時にはLayerを作成してなかったため、Layer作成後に再度```amplify update function```でLambda LayerのARNを指定した。
```shell
inakason@buz web-app % amplify update function
? Select the Lambda function you want to update ffdexecutorapi
General information
- Name: ffdexecutorapi
- Runtime: python

Resource access permission
- webapp (read)
- ffdwebappstrage (create, read, update, delete)

Scheduled recurring invocation
- Not configured

Lambda layers
- Not configured

Environment variables:
- Not configured

Secrets configuration
- Not configured

? Which setting do you want to update? Lambda layers configuration
? Do you want to enable Lambda layers for this function? Yes
? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:ap-northeast-1:***mask***:layer:trimesh:1,arn:aws:lambda:
ap-northeast-1:***mask***:layer:PyGeM:1
✔ Modify the layer order (Layers with conflicting files will overwrite contents of layers earlier in the list): · arn:aws:lambda:ap-northeast-1:***mask***:layer:trimesh:1, arn:aws:lambda:ap-northeast-1:***mask***:layer:PyGeM:1
? Do you want to edit the local lambda function now? Yes
Edit the file in your editor: /Users/***mask***/amplify/backend/function/ffdexecutorapi/src/index.py
? Press enter to continue 
```

# pipenvとvenvのインストール
先ほど```amplify add api```とした時に以下の警告が出現していた。
```shell
You must have pipenv installed and available on your PATH as "pipenv". It can be installed by running "pip3 install --user pipenv".
You must have virtualenv installed and available on your PATH as "venv". It can be installed by running "pip3 install venv".shell
```
実はこれはpip3ではなく、brewでインストールすることで解決できた。したがって、以下のコマンドを実行。
```shell
inakason@buz web-app % brew install pipenv 
inakason@buz web-app % brew install virtualenv
```

# ランタイム設定の変更
このまま、```amplify push```としてもPythonのバージョン違いでエラーが出てしまう。
```shell
inakason@buz web-app % amplify push           
✔ Successfully pulled backend environment dev from the cloud.

    Current Environment: dev
    
┌──────────┬─────────────────┬───────────┬───────────────────┐
│ Category │ Resource name   │ Operation │ Provider plugin   │
├──────────┼─────────────────┼───────────┼───────────────────┤
│ Function │ ffdexecutorapi  │ Create    │ awscloudformation │
├──────────┼─────────────────┼───────────┼───────────────────┤
│ Api      │ ffdexecutorapi  │ Create    │ awscloudformation │
├──────────┼─────────────────┼───────────┼───────────────────┤
│ Auth     │ webapp          │ No Change │ awscloudformation │
├──────────┼─────────────────┼───────────┼───────────────────┤
│ Storage  │ ffdwebappstrage │ No Change │ awscloudformation │
└──────────┴─────────────────┴───────────┴───────────────────┘
✔ Are you sure you want to continue? (Y/n) · yes
Courtesy Notice: Pipenv found itself running within a virtual environment, so it will automatically use that environment, instead of creating its own for any project. You can set PIPENV_IGNORE_VIRTUALENVS=1 to force pipenv to ignore that environment and create its own instead. You can set PIPENV_VERBOSITY=-1 to suppress this warning.
Pipfile.lock not found, creating...
Locking [packages] dependencies...
Building requirements...
Resolving dependencies...
✔ Success!
Locking [dev-packages] dependencies...
Updated Pipfile.lock (20a5580e79d8b5a7a360dfdbab4ee3c05dce7388bd6c4f2e7c30c18288e02f68)!
Installing dependencies from Pipfile.lock (e02f68)...
To activate this project's virtualenv, run pipenv shell.
Alternatively, run a command inside the virtualenv with pipenv run.
🛑 Could not find a pipenv site-packages directory at /Users/***mask***/.pyenv/versions/3.12.1/envs/myenv/lib/python3.8/site-packages

Learn more at: https://docs.amplify.aws/cli/project/troubleshooting/
```
どうやら、Python3.12で実装しようとしているのに対して、CI/CDでインストールされるPythonのバージョンが3.8('24年5月時点)であることが問題のようだ。
そこで、以下の変更を加える必要がある。参考は[こちら](https://github.com/aws-amplify/amplify-hosting/issues/2983)。

## amplify.ymlのバックエンドビルドコマンドの変更
AWSマネジメントコンソールから開発中のアプリを選択、左のツールバーからホスティング→ビルドの設定から```amplify.yml```でビルドの設定ができる。```update-alternatives```コマンドをバックエンドのビルドコマンドに追加する。フロントエンドの箇所は元からあったものなので、変更は加えていない。
```yaml
version: 1
backend:
  phases:
    build:
      commands:
        - echo "Installing Python 3.12..."
        - pyenv install 3.12.0
        - pyenv global 3.12.0
        - python --version
        - echo "Python 3.12 installed and configured."
frontend:
  phases:
    preBuild:
      commands:
        - npm ci --cache .npm --prefer-offline
    build:
      commands:
        - npm run build
  artifacts:
    baseDirectory: build
    files:
      - '**/*'
  cache:
    paths:
      - .npm/**/*
```

## Pipfileのrequire pythonバージョンを変更
```amplify/backend/function/ffdexecutorapi/Pipfile```内にあるpython_versionを3.8から3.12に変更する。(ffdexecutorapiは作成したLambda関数名)
```text
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]

[packages]
src = {editable = true, path = "./src"}

[requires]
python_version = "3.12"
```

## cloudformation-templateのランタイムを変更
```amplify/backend/function/ffdexecutorapi/ffdexecutorapi-cloudformation-template.json```内にあるPythonランタイムを3.8から3.12に変更
```text
(略)
        "Runtime": "python3.12",
        "Layers": [
          "arn:aws:lambda:ap-northeast-1:***mask***:layer:trimesh:1",
          "arn:aws:lambda:ap-northeast-1:***mask***:layer:PyGeM:1"
        ],
        "Timeout": 25
(略)
```
このファイルでLambdaの諸々設定を触ることができる。今回、開発環境はMacなので、Lambdaアーキテクチャはarm64にしたいところ。この場合上記にさらに```Architectures```の値を設定する。（Resources->Properties下にネスト）
```text
(略)
        "Architectures": ["arm64"]
(略)
```

# Amplify push
```amplify push```を実行する前に、環境変数```PIPENV_IGNORE_VIRTUALENVS=1```を設定する。こうすることで、Pipenvが現在の仮想環境を無視して、新しい環境を作成してくれる。これを設定せずに```amplify push```すると、ローカルにインストールしたライブラリ全てを含めてLambdaへデプロイしてしまうので、許容上限（≒250MB）を超えてエラーとなってしまう。
```shell
inakason@buz web-app % export PIPENV_IGNORE_VIRTUALENVS=1
```
これで、ようやく```amplify push```できるようになる。
```
inakason@buz web-app % amplify push
✔ Successfully pulled backend environment dev from the cloud.

    Current Environment: dev
    
┌──────────┬─────────────────┬───────────┬───────────────────┐
│ Category │ Resource name   │ Operation │ Provider plugin   │
├──────────┼─────────────────┼───────────┼───────────────────┤
│ Function │ ffdexecutorapi  │ Create    │ awscloudformation │
├──────────┼─────────────────┼───────────┼───────────────────┤
│ Api      │ ffdexecutorapi  │ Create    │ awscloudformation │
├──────────┼─────────────────┼───────────┼───────────────────┤
│ Auth     │ webapp          │ No Change │ awscloudformation │
├──────────┼─────────────────┼───────────┼───────────────────┤
│ Storage  │ ffdwebappstrage │ No Change │ awscloudformation │
└──────────┴─────────────────┴───────────┴───────────────────┘
✔ Are you sure you want to continue? (Y/n) · yes
Creating a virtualenv for this project...
Pipfile: /Users/***mask***/amplify/backend/function/ffdexecutorapi/Pipfile
Using /opt/homebrew/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/3.12/bin/python3 (3.12.3) to create virtualenv...
⠹ Creating virtual environment...created virtual environment CPython3.12.3.final.0-64 in 886ms
  creator CPython3macOsBrew(dest=/Users/inakason/.local/share/virtualenvs/ffdexecutorapi-GykZc1Qo, clear=False, no_vcs_ignore=False, global=False)
  seeder FromAppData(download=False, pip=bundle, via=copy, app_data_dir=/Users/inakason/Library/Application Support/virtualenv)
    added seed packages: pip==23.3.1
  activators BashActivator,CShellActivator,FishActivator,NushellActivator,PowerShellActivator,PythonActivator

✔ Successfully created virtual environment!
Virtualenv location: /Users/inakason/.local/share/virtualenvs/ffdexecutorapi-GykZc1Qo
Installing dependencies from Pipfile.lock (4a2311)...
To activate this project's virtualenv, run pipenv shell.
Alternatively, run a command inside the virtualenv with pipenv run.

Deployment completed.
Deployed root stack webapp [ ======================================== ] 5/5
	amplify-webapp-dev-fa745       AWS::CloudFormation::Stack     UPDATE_COMPLETE                Mon May 13 2024 
	storageffdwebappstrage         AWS::CloudFormation::Stack     UPDATE_COMPLETE                Mon May 13 2024 
	authwebapp80c33697             AWS::CloudFormation::Stack     UPDATE_COMPLETE                Mon May 13 2024 
	functionffdexecutorapi         AWS::CloudFormation::Stack     CREATE_COMPLETE                Mon May 13 2024 
	apiffdexecutorapi              AWS::CloudFormation::Stack     CREATE_COMPLETE                Mon May 13 2024 
Deployed function ffdexecutorapi [ ======================================== ] 4/4
	LambdaExecutionRole            AWS::IAM::Role                 CREATE_COMPLETE                Mon May 13 2024 
	AmplifyResourcesPolicy         AWS::IAM::Policy               CREATE_IN_PROGRESS             Mon May 13 2024 
	LambdaFunction                 AWS::Lambda::Function          CREATE_IN_PROGRESS             Mon May 13 2024 
Deployed api ffdexecutorapi [ ======================================== ] 5/5

Deployment state saved successfully.

REST API endpoint: https://***mask***.execute-api.ap-northeast-1.amazonaws.com/dev

Browserslist: caniuse-lite is outdated. Please run:
  npx update-browserslist-db@latest
  Why you should do it regularly: https://github.com/browserslist/update-db#readme
No AppSync API configured. Please add an API
```

# おわりに
結局、Lambdaデプロイのサイズ上限250MBをクリアできず、```amplify remove api```と```amplify remove function```して別途手動でimageタイプのLambdaを組みました。
この案件がうまくいくことを心より願っています。