FastAPIで作ったアプリをApp ServiceとAzure Database for MySQL上にデプロイする

# はじめに
こちらの記事は[求ム！Pythonを使ってAzureで開発する時のTips！【PR】日本マイクロソフト Advent Calendar 2020](https://qiita.com/advent-calendar/2020/azure-python)の16日目の記事です。
最近触り始めたFastAPI.このフレームワークで作成したサンプルアプリをAzure上にデプロイすべく手順をまとめてみます。
サンプルアプリと言いつつもHelloWorldだけだと少し味気ないのでDBが絡むような簡単なコードを実装してみました。

# 何ができるようになるのか
* 開発環境からAzureへのFastAPIを利用したアプリのデプロイ
* Python(SQLAlcemy)を利用してAzure Database for MySQLへのSSL/TLS接続構成

# 動作環境
以下のシステム要件を想定しております。

* Docker最新版　※ローカルで動作確認を行うため
* App Service on Linux（Python3.7系）
* Azure Database for MySQL5.7
* VSCodeの[App Service拡張機能](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azureappservice)

# 手順
### ローカルでの動作確認
* [ここ](https://github.com/hiro110/fastapi-jwt.git)からローカルにソースをクローンします。
* `.env.sample` から `.env`をコピーする。※編集はこの後行います。
* `docker-compose up --build`で起動します。
* 起動したら `http://localhost:8000/` にアクセスし `{"message":"Hello World"}` が表示されたらOKです。


### App Serviceを作成
* [こちら](https://docs.microsoft.com/ja-jp/azure/developer/python/tutorial-deploy-app-service-on-linux-03)の手順を元にApp Service、WebAppsを作成します。ランタイムは `python3.7` としてください。

### Azure Database for MySQLを作成
* Azure Potalにアクセスします。
* 左上のハンバーガーメニューから[リソースの作成]をクリックし、[データベース]を選択ください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23761/39863c56-cb9c-376e-f24d-93a65474780b.png)
* Azure Database for MySQLをクリックし、[単一サーバー] or [フレキシブルサーバー]の選択となります。今回はサンプルということもあるので[フレキシブルサーバー]にしたいと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23761/8135cebb-421d-db1f-e314-868140d4e455.png)

* 基本設定は下記の通りとしてます。
  * サブスクリプション[任意]
  * リソースグループ[任意]
  * サーバ名[任意] ※こちらは控えておいてください
  * リージョン[先ほど作成したApp Serviceと同一リージョン]
  * ワークロードの種類[実稼働（小規模/中規模）]
  * MySQLバージョン[5.7]
  * 管理者アカウント[任意] ※こちらは控えておいてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23761/c43c28b5-69d2-4834-50ed-8319df25c57e.png)


* SKUは下記の通りとしてます。※今回はミニマムとしてます。
  * Computer Tier[バースト可能]
  * コンピューティングサイズ[Standart_B1s]
  * ストレージサイズ[10GB]
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23761/5713a444-c4f7-8eac-61b2-4d1f0c5c5704.png)


* ネットワーク設定ですが、今回は[パブリックアクセス]とし、 **AppServiceからの接続とローカル（開発端末）** からのみ許可しました。要件によってはVNET IntegrationとPrivate Endpointを組み合わせてセキュアにしても良いかもしれません。送信元IPは[こちら](https://docs.microsoft.com/ja-jp/azure/app-service/overview-inbound-outbound-ips#when-outbound-ips-change)のページを参考に確認ください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23761/bd4db8fc-4033-6b13-cc5c-3b18cf25f65b.png)

### DB初期設定
* 下記の手順で初期設定を行います。

```
# 接続
mysql -h fastapi.mysql.database.azure.com -u <ユーザ名> -p

# DB作成 ※こちらは控えておいてください。
create database <DB名>;

# DB指定
use <DB名>;

# テーブル作成
CREATE TABLE users (
    id INT NOT NULL AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL DEFAULT '',
    email VARCHAR(255) NOT NULL DEFAULT '',
    password VARCHAR(255) NOT NULL DEFAULT '',
    token TEXT,
    created_at datetime DEFAULT CURRENT_TIMESTAMP,
    updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id)
);

# 初期データ追加
INSERT INTO users (name, email, password) VALUES ("admin", "admin@example.com", "<hash password>");
※passwordの作成方法は後述します。
```

### 環境変数の設定とアップロード
* ローカルでの動作確認の項で作成した `.env` を下記の通り更新します。

```:.env
...
ENVIRONMENT="production"
SECRET_KEY="<任意の文字列>"
ALGORITHM="HS256"
ACCESS_TOKEN_EXPIRE_MINUTES=60

# MySQL
MYSQL_SERVER="<Azure Database for MySQLのサーバー名>"
MYSQL_USER="<Azure Database for MySQLのユーザ名>"
MYSQL_PASSWORD="<Azure Database for MySQLのパスワード>"
MYSQL_DB="<Azure Database for MySQLのスキーマ名>"
...

※フレキシブルサーバの場合はユーザ名は単一サーバー版とは異なり、hoge@hostnameではなく、hogeのみで良さそうです。
```
更新後、VSCodeからアップロードします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23761/88ced9e6-1c9a-4f5c-d211-7ad226173abd.png)


### スタートアップファイルの設定
Webappsの[構成]ブレードの全般設定タブのスタートアップコマンドに `startup.txt` を登録しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23761/9ffd966b-83ab-a3f0-5847-3d3b5f0b4716.png)

startup.txtの中身はこのような感じです。必要に応じてオプションを付け足してみてください。 オプションの詳細は[こちら](https://www.uvicorn.org/settings/)

```:startup.txt
python -m uvicorn src.main:app --host 0.0.0.0
```



### ソースのデプロイ
VSCodeからアップロードします。赤囲みの矢印ボタンをクリックし
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23761/6d4b91a3-4269-e178-b648-4b87c7f1fe0b.png)
デプロイ中は[output]タブにて進行状況が確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23761/6443f4b7-1377-5e23-f4e8-ff2697cfb00a.png)


デプロイが正常終了するとこのような表示になります。
TOPページ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23761/0654ab4e-dafc-f103-a51f-ae4640463be7.png)

docsページ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23761/de60f220-085c-4a4a-6a98-6d42b12f8312.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23761/8c95d7fc-d7dd-92fa-5556-485d4237b4e9.png)

試しにJWTを取得するAPIを叩いても問題なくレスポンスが返ってきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23761/1ce504ee-31fd-18b7-5cc2-cb005ec79381.png)

# 補足
* パスワードの作成について  
passlibを用いハッシュしてます。
pipで`passlib`, `bcrypt`をそれぞれインストールした上で下記を実行すると作成できます。

```
import passlib.hash

secret = "plain text"
salt = "saltsalt"
hashed = passlib.hash.sha512_crypt.hash(secret, salt=salt)
print(hashed)
```

# 最後に
FlaskやDjangoをApp Serviceにデプロイする記事は見かけることはありましたが、FastAPIの記事はまだ少ない印象でしたので、参考になれば幸いです。
サンプルコードがまだまだ成熟し切っていないので、さわりだけ掴んでもらえればと思います。
雛形にできるよう今後も少しずつ改善してきます。

# 参考
[3:Visual Studio Code から App Service を作成する](https://docs.microsoft.com/ja-jp/azure/developer/python/tutorial-deploy-app-service-on-linux-03)
[IPアドレスを見つける](https://docs.microsoft.com/ja-jp/azure/app-service/overview-inbound-outbound-ips#find-outbound-ips)