AWSを利用したサーバーレスWebAPI開発環境の構築（zappaでAWSLambdaにデプロイする）

#概要
AWSを利用してシンプルなサーバーレスWebAPIを作成します:fist:
※大雑把に構築の流れだけ追っていくので、細かいことは都度調べてください:joy_cat:

#目次
1. ローカルで仮想環境の構築（キーワード：pipenv）
1. 開発環境の構築（VSCode）
1. プログラムの作成（Flask）
1. IAMユーザーの作成（AWS）
1. ローカル環境をAWSにデプロイ（zappa）
1. おまけ
1. 参考サイト

#ローカルで仮想環境の構築（pipenv）
pipenvを使用して仮想環境を構築します。


:slight_smile:　**環境変数に以下を追加します。**
仮想環境で使用するインタープリターや参照パッケージを、作業フォルダに入れる設定です。（.venv）

PIPENV_VENV_IN_PROJECT = TRUE

:slight_smile:　**pipenvをインストールします。**
コマンドラインで以下を実行します。

```
pip install pipenv
```
バージョンが表示されればインストールできています。

```
pipenv --version
```

:slight_smile:　**作業フォルダを作成して、初期化を行います。**
コマンドラインで作成した作業フォルダに移動して、以下を実行します。

```
pipenv --three
```
「three」はpython3系のインストールコマンドです。バージョンを指定する場合は、以下のように実行します。:thinking:

```
pipenv --python 3.7
```
初期化に成功すると、作業フォルダに以下のフォルダとファイルが作成されます。
.venv
Pipfile

#開発環境の構築（VSCode）
VSCodeで開発環境を構築します。（VSCodeは良いぞ...:relieved:）

:slight_smile:　**VSCodeで作業フォルダを開きます。**
「ファイル」→「フォルダーを開く」
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/53b5a31d-c255-14d1-d92c-b74260c98ee3.png)
VSCodeは、pythonファイルを扱えるようにするために、拡張機能を入れる必要があります。:thinking:
「表示」→「拡張機能」から「python」で検索してインストールしましょう。

:slight_smile:　**pythonファイルを作成します。**
テキトーに「app.py」などのファイルを作成します。作成したpythonファイルを選択すると、エディターの左下に実行する環境が表示されます。
ここから、作業フォルダのpipenvを指定します。
![無題2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/a83fdad2-5d6a-0680-aedd-64888391c60d.png)
:slight_smile:　**設定ファイルを編集します。**
以下のファイルで、仮想環境等のパス設定、フォーマッター、Lintの設定を行います。[参考](https://qiita.com/youkidkk/items/b674e6ace96eb227cc28)
![無題3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/4ce0d265-eeb0-b75d-d32a-8673f0cb3723.png)

```[ワークスペースフォルダ]\.vscode\settings.json
{
    "python.pythonPath": "[ワークスペースフォルダ]\\.venv\\Scripts\\python.exe",

    // 拡張機能のロード時にターミナルでPython環境をアクティブにする。
    "python.terminal.activateEnvInCurrentTerminal": true,
    // 仮想環境のパス。作成した仮想環境を指定する。
    "python.venvPath": "{$workspaceFolder}/.venv",
    "python.autoComplete.extraPaths": [
        "{$workspaceFolder}/.venv/Lib/site-packages",
    ],
    // フォーマッターの設定。autopep8 を指定する。
    "python.formatting.provider": "autopep8",
    "python.jediEnabled": false,
    // Lintの設定
    "python.linting.mypyEnabled": true,
    "python.linting.pylintEnabled": false,
}
```
:slight_smile:　**パッケージをインストールします。**
setting.json の設定を行うと、使用している依存パッケージをインストールするように促されます。

まず、ターミナルを表示して、仮想環境がアクティブになっているか確認します。
「表示」→「ターミナル」
以下のように「（qiita_test）」のように出ていればOK

```
(qiita_test) C:\~\qiita_test>
```

以下を実行して、パッケージをインストールします。

```
pipenv install autopep8 mypy --dev
```
また、サンプルソースように「Flask」「numpy」もインストールします。

```
pipenv install Flask numpy
```
「--dev」は、開発環境のみで使用するパッケージを指定するものです。つまり、AWSにデプロイするときは含まれないパッケージとなります。:thinking:

#プログラムの作成（Flask）
簡単なサンプルプログラムを作成します。
リクエストに対して、json形式のレスポンスを返すWebAPIです。
作成したら実行してエラーが出ないか確認しましょう。:spider:

```app.py
from flask import Flask, jsonify, request
import numpy as np

app = Flask(__name__)
app.config["JSON_AS_ASCII"] = False

@app.route("/")
def index():
    return jsonify({"language": "パイソン"})

# exp：http://127.0.0.1:5000/sqrt?val=4
@app.route("/sqrt")
def getSqrt():
    param = request.args.get('val')
    val = int(param)
    return jsonify({"sqrt": np.sqrt(val)})

# exp：http://127.0.0.1:5000/sqrt/4
@app.route("/sqrt/<value>")
def getSqrt2(value):
    val = int(value)
    return jsonify({"sqrt": np.sqrt(val)})

if __name__ == "__main__":
    app.run(debug=True)
```

折り返し地点です。:rabbit::turtle:

#IAMユーザーの作成（AWS）
ここからは、ローカルで作成したWebAPIを、AWSにデプロイしていきます。

AWSアカウントには、ルートユーザーとIAMユーザーの2つがあります。
:chicken:(親です)：ルートユーザーは、すべての権限を持つアカウントです。IAMユーザーの管理も行います。
:hatched_chick:(子です)：IAMユーザーは、プロジェクト単位や開発者単位で、個別に権限を持つアカウントです。

まず、ルートユーザーを作成し、AWSコンソールにログインした後に、IAMユーザーを作成します。
IAMユーザーは以下の2つを作成します。

:hatched_chick:　zappaデプロイ用のアカウント
:hatched_chick:　AWSコンソールで管理する用のアカウント

:slight_smile:　**zappaからデプロイする用のポリシーを作成します。**
ポリシーとはIAMユーザーに付与する権限のことです。:cop:

AWSコンソールにルートユーザーでログインし、画面上の「サービス」→「IAM」を選択します。
「ポリシー」を選択して、［ポリシーの作成］を押します。
「JSON」タブを選択して、ポリシーを書き込みます。
参考：[Pythonで作るはじめてのサーバレスアプリケーション Kindle版](https://www.amazon.co.jp/dp/B07JD6NDS7/keywords=Python%E3%81%A7%E4%BD%9C%E3%82%8B%E3%81%AF%E3%81%98%E3%82%81%E3%81%A6%E3%81%AE%E3%82%B5%E3%83%BC%E3%83%90%E3%83%AC%E3%82%B9%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3)

```zappa-simple-policy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "iam:AttachRolePolicy",
                "iam:CreateRole",
                "iam:GetRole",
                "iam:PutRolePolicy"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "iam:PassRole"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "apigateway:DELETE",
                "apigateway:GET",
                "apigateway:PATCH",
                "apigateway:POST",
                "apigateway:PUT",
                "events:DeleteRule",
                "events:DescribeRule",
                "events:ListRules",
                "events:ListTargetsByRule",
                "events:ListRuleNamesByTarget",
                "events:PutRule",
                "events:PutTargets",
                "events:RemoveTargets",
                "lambda:AddPermission",
                "lambda:CreateFunction",
                "lambda:DeleteFunction",
                "lambda:GetFunction",
                "lambda:GetFunctionConfiguration",
                "lambda:GetPolicy",
                "lambda:ListVersionsByFunction",
                "lambda:RemovePermission",
                "lambda:UpdateFunctionCode",
                "lambda:UpdateFunctionConfiguration",
                "cloudformation:CreateStack",
                "cloudformation:DeleteStack",
                "cloudformation:DescribeStackResource",
                "cloudformation:DescribeStacks",
                "cloudformation:ListStackResources",
                "cloudformation:UpdateStack",
                "cloudfront:updateDistribution",
                "logs:DescribeLogStreams",
                "logs:FilterLogEvents",
                "route53:ListHostedZones",
                "route53:ChangeResourceRecordSets",
                "route53:GetHostedZone",
                "s3:CreateBucket",
                "dynamodb:*"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:DeleteObject",
                "s3:GetObject",
                "s3:PutObject",
                "s3:CreateMultipartUpload",
                "s3:AbortMultipartUpload",
                "s3:ListMultipartUploadParts",
                "s3:ListBucketMultipartUploads"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}
```
[ポリシーの確認]を押して、「名前」を付けます。ここでは「zappa-simple-policy」としました。
[ポリシーの作成]を押して、zappa用のポリシーが出来ました。

:slight_smile:　**zappa用のIAMユーザーを作成します。**
（AWSコンソールにルートユーザーでログインし、画面上の「サービス」→「IAM」を選択します。）

1. 「ユーザー」を選択して、［ユーザーを追加］を押します。
1. 「ユーザー名」を指定します。ここでは、「zappa-simple-user」としました。
1. 「アクセスの種類」で「プログラムによるアクセス」に☑します。
1. [次のステップ：アクセス権限]を押します。
1. [既存のポリシーを直接アタッチ]を選択して、検索欄で「zappa-simple-policy」と入力し、☑します。
1. [次のステップ：タグ]を押します。（飛ばします）
1. [次のステップ：確認]を押します。
1. [ユーザーの作成]を押します。
1. 作成に成功すると、アクセスキーIDとシークレットアクセスキーが表示されるので、メモっておきます。:writing_hand:

:slight_smile:　**AWSコンソールで管理する用のIAMユーザーを作成します。**

1. 「ユーザー」を選択して、［ユーザーを追加］を押します。
1. 「ユーザー名」を指定します。ここでは、「qiita-test」としました。
1. 「アクセスの種類」で「AWS マネジメントコンソールへのアクセス」に☑します。
1. [次のステップ：アクセス権限]を押します。
1. [既存のポリシーを直接アタッチ]を選択して、以下を選択します。
   -  AmazonAPIGatewayAdministrator
   -  AWSLambdaFullAccess
   -  AmazonDynamoDBFullAccess（今回は利用していないが、DynamoDBを利用する場合）
   -  IAMUserChangePassword（3で初回ログイン時にパスワードを変更する設定にした場合、自動で選択されます）
1. [次のステップ：タグ]を押します。（飛ばします）
1. [次のステップ：確認]を押します。
1. [ユーザーの作成]を押します。
1. 作成に成功すると、アクセスキーIDとシークレットアクセスキーが表示されるので、メモっておきます。:writing_hand:

:angry:もう少し:bangbang:

#ローカル環境をAWSにデプロイ（zappa）
zappaというpythonの拡張機能を使って、作成したプロジェクトをAWSにデプロイします。

:slight_smile:　**AWS CLIを設定します。**
IAMユーザーの情報をプロファイル単位で管理するために、AWS CLIをインストールします。
参考：[AWS CLIのインストール](https://qiita.com/yuyj109/items/3163a84480da4c8f402c)
pipからもインストールできるみたいです。参考：https://www.suzu6.net/posts/4/

コマンドプロンプトで以下を実行して、[default]のプロファイルを設定します。
とりあえずzappa用のIAMユーザー（zappa-simple-user）情報を設定しましょう。

```
aws configure

AWS Access Key ID [********************]:
AWS Secret Access Key [********************]:
Default region name [ap-northeast-1]:
Default output format [json]:
```
regionは東京です。

インストールフォルダ以下の credentials ファイルに、作成したzappa用のIAMユーザー（zappa-simple-user）情報を設定します。
メモ帳か何かで開いて編集しましょう。

```~\.aws\credentials
[zappa-simple-user]
aws_access_key_id = ********************
aws_secret_access_key = ********************
```
:slight_smile:　**zappaをインストールします。**
ターミナルにて、仮想環境がアクティブになっていることを確認してから、以下を実行します。

```
pipenv install zappa
```
次に初期設定を行います。

```
zappa init
```
1. デプロイ環境の名前の指定。（デフォルトでも可）
2. AWSプロファイルの指定。先ほどcredentialsファイルに設定した「zappa-simple-user」を指定します。
3. S3バケットを利用するための仮の名前の指定。（デフォルトでも可）
4. アプリケーションの起動ファイルの指定。.appファイルとして指定します。今回は「app.py」を起動したいので、「app.app」を指定します。
5. リージョンごとの最適化設定。特にパフォーマンスが必要なければデフォルトにします。
6. 作成される設定情報が表示されます。問題なければ、Enterで初期設定を終了します。

:slight_smile:　**デプロイします。**
ターミナルにて、仮想環境がアクティブになっていることを確認してから、以下を実行します。

```
zappa deploy
```
成功すると以下のようにエンドポイントが表示されます。
リンクに飛ぶと、json形式の値が表示されます。

```
Deployment complete!: [リンク]
```

:slight_smile:　**AWSで確認してみます。**
AWSコンソールで管理する用のIAMユーザーで、AWSコンソールにログインします。
「サービス」→「API Gateway」または「Lambda」を選択すると、デプロイしたアプリケーションを確認することができます。

ハイ、オツカレー( ￣▽)_:tea:

#おまけ
:slight_smile:　**zappaコマンド**

```デプロイ済みのアプリケーションを更新する.
zappa update dev
```
```デプロイ済みのアプリケーションを削除する.
zappa undeploy dev
```
```ログの確認.
zappa tail dev
```

:rolling_eyes:　**課題**
AWSのポリシーがいまいちわかっていない。（ので、本稿の設定はあくまで参考程度に）
githubを使ってバージョン管理も絡めてみる。

:thinking:　**なんで？**

- なんで、WebAPI作るの？

これから5G技術が普及することによって、オンライン環境は今より格段に拡がり、よりストレスレスな通信が可能になるから。
ユーザーの環境に依存しない開発ができ、色々なプラットフォームでリソースを共有できるから。

- なんで、Pythonなの？

機械学習を勉強する上で、パッケージが豊富で、スクリプトもわかりやすく、参考事例も多いから。
参考書やサイトでコーディングしたリソースを形にしたくて、色々考えた結果 WebAPI という技術に至ったから。
（参考書のほとんどは、colaboratory等でデータ分析して「はいできましたね。～終わり～」となっていて、じゃあ具体的にどう活用するの？ってずっとモヤモヤしてました）

- なんで、サーバーレスなの？

サーバーの構築技術にはあまり興味がなく、自分でサーバー立てるよりAmazonやMicrosoftのサービスを利用したほうが早そうだなって思ったから。
自前でサーバー運用するよりもコストパフォーマンスに優れているから。

#参考サイト
[Windows + Python + PipEnv + Visual Studio Code でPython開発環境](https://qiita.com/youkidkk/items/b674e6ace96eb227cc28)
[Zappaを使ってPythonのコードをAWS Lambdaにデプロイする](https://www.suzu6.net/posts/4/)

参考書籍です。こちらの筆者の環境はOSXでエディターは特に使ってません。
[Pythonで作るはじめてのサーバレスアプリケーション Kindle版](https://www.amazon.co.jp/dp/B07JD6NDS7/keywords=Python%E3%81%A7%E4%BD%9C%E3%82%8B%E3%81%AF%E3%81%98%E3%82%81%E3%81%A6%E3%81%AE%E3%82%B5%E3%83%BC%E3%83%90%E3%83%AC%E3%82%B9%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3)