Python + Flask + MongoDB を利用した Web API の作成と Azure VM + Nginx への配置（ホロライブの動画配信予定を収集 その３）

## はじめに

前回（[https://qiita.com/kerobot/items/41f266dd52988f9bb68e](https://qiita.com/kerobot/items/41f266dd52988f9bb68e)）は、ホロライブの配信予定や動画情報を収集する仕組みを Azure VM の Ubuntu に配置して定期的に実行されるようにしました。

今回は、Azure VM の Ubuntu の MongoDB から、ホロライブの配信予定や動画情報を取得する Web API を作成し、Azure VM (Ubuntu) + Nginx + gunicorn へ配置してみます。

まずは、ローカル環境で Web API を作成して動作を確認したうえで、Azure VM (Ubuntu) + Nginx + gunicorn へ配置します。

* Nginx : Webサーバー（今回はリバースプロキシとして利用）
* gunicorn : WSGI アプリケーションを起動させる WSGI サーバー
* WSGI（ウィスギー） : Web サーバーとWeb アプリケーション（フレームワーク）を接続するためのインターフェース

## 最終的にやりたいこと

ここ最近、毎日のように閲覧している バーチャル YouTuber プロダクション「ホロライブ」などの、動画配信予定を定期的にデータベースに格納し、それを Web API で参照して閲覧する Android アプリを作りたい。

1. 動画配信予定を収集するためのプログラムの作成（完了）
2. 収集した動画配信予定を格納するためのデータベースの作成と収集の自動化（完了）
3. 格納した動画配信予定を参照するための Web API の作成（今回）
4. Web API を参照して動画配信予定を閲覧する Android アプリの作成

## やったこと

今回は、やりたいこと 3 の「格納した動画配信予定を参照するための Web API の作成」を行いました。

AppService を利用して Web API を配置したかったのですが、いろいろ（後述）あって断念し、結果として Azure VM (Ubuntu) に、Nginx + gunicorn を導入して配置しました。

![img04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/e6ba027f-3a4a-5bd5-af20-d09910a07240.jpeg)

1. ローカル環境の準備
    * pyenv (pyenv-win) が利用できることを確認
    * Poetry が利用できることを確認
    * MongoDB の接続確認

2. プロジェクトの準備
    * Poetry を利用してプロジェクトの作成とパッケージのインストール
    * pyenv を 利用して Python のバージョンをセット
    * git の初期化
    * .gitignore の作成
    * .env の作成

3. プログラムの作成
    * .env からの設定情報の取得
    * ホロジュールから取得した情報を保持
    * Flask を利用した Web API モジュールの作成

4. ローカル環境での実行
    * lounch.json の作成
    * プログラムのデバッグ実行
    * Postman を利用した Web API の動作確認
    * Nginx のコンフィグファイルの作成
    * ファイル構成

5. Azure AppService の利用
    * 今回は断念

6. Azure VM 環境の準備
    * pyenv が利用できることを確認
    * Poetry が利用できることを確認
    * MongoDB の接続確認

7. プログラムの配置
    * プログラムの配置と設定
    * .env の作成
    * プログラムの動作確認

8. Nginx の導入と Web API の公開
    * Nginx の導入
    * コンフィグファイルを指定してリバースプロキシの実行
    * リバースプロキシ経由の動作確認
    * ローカル環境からの動作確認

## ローカル環境の準備

下記構成の環境を用意しました。

* Windows 10 Pro 1909 x64
* Python 3.8.5 x64
* PowerShell 7.1 x64
* Visual Studio Code 1.51.1 x64
* Git for Windows 2.27.0 x64
* MongoDB 4.4.1 x64

### pyenv (pyenv-win) が利用できることを確認

参考：[Windows 10 で Python のインストールから Poetry と pyenv の利用](https://qiita.com/kerobot/items/3f4064d5174676080585)

```powershell
> pyenv --version
pyenv 2.64.2
```

### Poetry が利用できることを確認

参考：[Windows 10 で Python のインストールから Poetry と pyenv の利用](https://qiita.com/kerobot/items/3f4064d5174676080585)

```powershell
> poetry --version
Poetry version 1.1.0
```

### MongoDB の接続確認

参考：[Windows 10 に MongoDB をインストールして Python から利用する](https://qiita.com/kerobot/items/e566559940b2c517188d)

```powershell
> mongo --version
MongoDB shell version v4.4.1
Build Info: {
    "version": "4.4.1",
    "gitVersion": "ad91a93a5a31e175f5cbf8c69561e788bbc55ce1",
    "modules": [
        "enterprise"
    ],
    "allocator": "tcmalloc",
    "environment": {
        "distmod": "windows",
        "distarch": "x86_64",
        "target_arch": "x86_64"
    }
}
```

ホロライブの配信予定や動画情報を格納している holoduledb への接続確認

参考：[Python3でWebスクレイピングとYouTube Data APIとMongoDB（ホロライブの動画配信予定を収集 その２）](https://qiita.com/kerobot/items/fa728f665d559b969e1c)

```powershell
> mongo localhost:27017/holoduledb -u owner -p
MongoDB shell version v4.4.1
> show collections
holodules
> db.holodules.find().sort({"datetime":-1});
{ "_id" : ObjectId("5f85996f7fc0cba6f1309217"), "key" : "HL0204_20201014_193000", "video_id" : "LiItELTkkko", "datetime" : "20201014 193000", "name" : "癒月ちょこ", "title" : "【#カレーメシWEEK】ちょこ先生が味変えアレンジカレーメシ作ってみ たら【ホロライブ/癒月ちょこ】", "url" : "https://www.youtube.com/watch?v=LiItELTkkko", "description" : "サブ垢→https://www.youtube.com/channel/UCp3tgHXw_HI0QMk1K8qh3gQ◎2周年記念グッズ期間限定発売決定！ご購入はコチラ⇊https://hololi" }
...
> exit
```

## プロジェクトの準備

### Poetry と pyenv を利用してプロジェクトの作成やパッケージのインストール

poetry を利用してプロジェクトを作成します。

```powershell
> poetry new holoapi
```

プロジェクトのディレクトリに移動します。

```powershell
> cd holoapi
```

ディレクトリとファイルの構成を下記のようにしておきます。

```text
holoapi
├ config
├ models
├ pyproject.toml
└ README.md
```

念のため、pyenv をアップデートしておきます。

```powershell
> pyenv update
```

インストール可能な Python のバージョンを確認します。

```powershell
> pyenv install -l
```

インストールされている Python を確認します。

```powershell
> pyenv versions
```

Python 3.8.5 がインストールされていない場合はインストールします。

```powershell
> pyenv install 3.8.5
```

ローカル（プロジェクトディレクトリ内）で利用する Python をセットします。

```powershell
> pyenv local 3.8.5
```

切り替わったかを確認します。

```powershell
> python -V
Python 3.8.5
```

セットした Python に切り替わらない場合は、rehash で shim コマンドを更新します。

```powershell
> pyenv rehash
```

これにより、$HOME/.pyenv/shims/ 配下のコマンドが更新され、セットした Python に切り替わります。

poetry を利用してパッケージを pyproject.toml に追加しインストールします。

> gunicorn は Windows 環境で動作しませんが、後ほど Azuru VM の Ubuntu に配置した際に利用するため、ここでパッケージを追加しておきました。

```powershell
> poetry add pylint
> poetry add python-dotenv
> poetry add pymongo
> poetry add Flask
> poetry add gunicorn
```

パッケージがインストールされるとともに、pyproject.toml が下記のように更新されました。

pyproject.toml にパッケージを記述したうえで poetry install を実行しても良いです。

```text
[tool.poetry.dependencies]
python = "^3.8"
pylint = "^2.6.0"
python-dotenv = "^0.15.0"
pymongo = "^3.11.1"
Flask = "^1.1.2"
gunicorn = "^20.0.4"
```

poetry を利用して Python のバージョンを確認してみます。

```powershell
> poetry run python -V
Python 3.8.5
```

### git の初期化

git を初期化しておきます。

```powershell
> git init
```

### .gitignore の作成

.gitignore ファイルを作成し、除外対象を設定しておきます。

```powershell
> ni .gitignore
```

```text
__pycache__/
*.egg-info/
*.egg
.env
.venv
env/
venv/
.python-version
```

### .env の作成

MongoDB の接続情報などを設定しておくためのファイルを作成します。

```powershell
> ni .env
```

```text
MONGODB_USER = "<user>"
MONGODB_PASSWORD = "<password>"
MONGODB_HOST = "<localhost:27017>"
```

## プログラムの作成

### .env から取得した設定情報を保持するクラス

config/settings.py ファイルを作成します。

今回は Flask の config 関連を利用せず、これまでに利用してきたクラスを使いまわしています。

```python
import os
from dotenv import load_dotenv

class Settings:
    def __init__(self, envpath):
        # .env ファイルを明示的に指定して環境変数として読み込む
        self.__dotenv_path = envpath
        load_dotenv(self.__dotenv_path)
        # 環境変数から設定値を取得
        self.__mongodb_user = os.environ.get("MONGODB_USER")
        self.__mongodb_password = os.environ.get("MONGODB_PASSWORD")
        self.__mongodb_host = os.environ.get("MONGODB_HOST")

    # mongodb の ユーザー
    @property
    def mongodb_user(self):
        return self.__mongodb_user

    # mongodb の パスワード
    @property
    def mongodb_password(self):
        return self.__mongodb_password

    # mongodb の ホスト:ポート
    @property
    def mongodb_host(self):
        return self.__mongodb_host
```

### ホロジュールから取得した情報を保持するクラス

models/holodule.py ファイルを作成します。

こちらも Flask-MongoEngine やモデルの仕組みを利用せず、これまでに利用してきたクラスを使い回し、オブジェクトと辞書を変換するメソッドを追加しています。

```python
import datetime

class Holodule:
    codes = {
        "ときのそら"  : "HL0001",
        "ロボ子さん" : "HL0002",
        "さくらみこ" : "HL0003",
        "星街すいせい" : "HL0004",
        "夜空メル" : "HL0101",
        "アキ・ローゼンタール" : "HL0102",
        "赤井はあと" : "HL0103",
        "白上フブキ" : "HL0104",
        "夏色まつり" : "HL0105",
        "湊あくあ" : "HL0201",
        "紫咲シオン" : "HL0202",
        "百鬼あやめ" : "HL0203",
        "癒月ちょこ" : "HL0204",
        "大空スバル" : "HL0205",
        "大神ミオ" : "HL0G02",
        "猫又おかゆ" : "HL0G03",
        "戌神ころね" : "HL0G04",
        "兎田ぺこら" : "HL0301",
        "潤羽るしあ" : "HL0302",
        "不知火フレア" : "HL0303",
        "白銀ノエル" : "HL0304",
        "宝鐘マリン" : "HL0305",
        "天音かなた" : "HL0401",
        "桐生ココ" : "HL0402",
        "角巻わため" : "HL0403",
        "常闇トワ" : "HL0404",
        "姫森ルーナ" : "HL0405",
        "獅白ぼたん" : "HL0501",
        "雪花ラミィ" : "HL0502",
        "尾丸ポルカ" : "HL0503",
        "桃鈴ねね" : "HL0504",
        "魔乃アロエ" : "HL0505"
    }

    def __init__(self, video_id="", datetime=None, name="", title="", url="", description=""):
        self.__video_id = video_id
        self.__datetime = datetime
        self.__name = name
        self.__title = title
        self.__url = url
        self.__description = description

    # キー
    @property
    def key(self):
        _code = Holodule.codes[self.name] if self.name in Holodule.codes else ""
        _dttm = self.datetime.strftime("%Y%m%d_%H%M%S") if self.datetime is not None else ""
        return _code + "_" + _dttm if ( len(_code) > 0 and len(_dttm) > 0 ) else ""

    # video_id
    @property
    def video_id(self):
        return self.__video_id

    @video_id.setter
    def video_id(self, video_id):
        self.__video_id = video_id

    # 日時
    @property
    def datetime(self):
        return self.__datetime

    @datetime.setter
    def datetime(self, datetime):
        self.__datetime = datetime

    # 名前
    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self, name):
        self.__name = name

    # タイトル（Youtubeから取得）
    @property
    def title(self):
        return self.__title

    @title.setter
    def title(self, title):
        self.__title = title

    # URL
    @property
    def url(self):
        return self.__url

    @url.setter
    def url(self, url):
        self.__url = url

    # 説明（Youtubeから取得）
    @property
    def description(self):
        return self.__description

    @description.setter
    def description(self, description):
        self.__description = description

    # ドキュメントから変換
    @classmethod
    def from_doc(cls, doc):
        holodule = Holodule(doc['video_id'], 
                            datetime.datetime.strptime(doc['datetime'], '%Y%m%d %H%M%S'), 
                            doc['name'], 
                            doc['title'], 
                            doc['url'], 
                            doc['description'])
        return holodule

    # ドキュメントへ変換
    def to_doc(self):
        doc = { 'key': str(self.key),
                'video_id': str(self.video_id),
                'datetime' : str(self.datetime.strftime("%Y%m%d %H%M%S")),
                'name' : str(self.name),
                'title' : str(self.title),
                'url' : str(self.url),
                'description' : str(self.description) }
        return doc
```

### Flask を利用した Web API モジュールの作成

app.py ファイルを作成します。

今回は、Web API を作成するためのフレームワークとして、軽量なウェブアプリケーションフレームワークである Flask を利用してみました。
Web API として動作させるための最低限の実装としています。

```python
from flask import Flask, jsonify, request, abort, make_response
from pymongo import MongoClient
from os.path import join, dirname
import json
from urllib.parse import quote_plus
from config.settings import Settings
from models.holodule import Holodule

# Settings インスタンス
settings = Settings(join(dirname(__file__), '.env'))

# MongoDB 接続情報
mongodb_user = quote_plus(settings.mongodb_user)
mongodb_password = quote_plus(settings.mongodb_password)
mongodb_host = "mongodb://%s/" % (settings.mongodb_host)

# MongoDB 接続認証
client = MongoClient(mongodb_host)
db = client.holoduledb
db.authenticate(name=mongodb_user,password=mongodb_password)

# Flask
app = Flask(__name__)
# JSONのソートを抑止
app.config['JSON_SORT_KEYS'] = False

# 動作確認用
@app.route('/')
def index():
    return 'holoapi'

# ホロジュール配信予定の取得
@app.route('/Holodules/<string:date>', methods=['GET'])
def get_Holodules(date):
    if len(date) != 8:
        abort(500)

    # MongoDB から年月日を条件にホロジュール配信予定を取得してリストに格納
    holodule_list = []
    for doc in db.holodules.find({"datetime": {'$regex':'^'+date}}).sort("datetime", -1):
        holodule = Holodule.from_doc(doc)
        holodule_list.append(holodule)

    if len(holodule_list) == 0:
        abort(404)

    # オブジェクトをもとに辞書を構築してJSONとして返却
    data = {}
    for holodule in holodule_list:
        doc = holodule.to_doc()
        data[doc['key']] = doc
    result = {
        "result":len(holodule_list),
        "data":data
    }
    # UTF-8コード、Content-Type は application/json
    return make_response(jsonify(result))
    # UTF-8文字、Content-Type は text/html; charset=utf-8
    # return make_response(json.dumps(result, ensure_ascii=False))

# エラーハンドラ：404
@app.errorhandler(404)
def not_found(error):
    return make_response(jsonify({'error': 'Not found'}), 404)

# エラーハンドラ：500
@app.errorhandler(500)
def internal_server_error(error):
    return make_response(jsonify({'error': 'Internal Server Error'}), 500)

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

## ローカル環境での実行

### lounch.json の作成

Visual Studio Code で launch.json を作成しておきます。

```json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Current File",
            "type": "python",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal"
        }
    ]
}
```

### プログラムのデバッグ実行

Visual Studio Code でデバッグ実行します。

コマンドラインから実行する場合は、下記のように実行します。

```powershell
> poetry run python app.py
```

ローカルサーバーが既定のポート 5000 で起動します。

```powershell
 * Serving Flask app "app" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
```

Ctrl + C で終了できます。

### Postman を利用した Web API の動作確認

Postman を利用して、作成した Web API にアクセスしてみます。
GET の URL のみ指定し、Query Params は指定なしです。

```text
http://127.0.0.1:5000/Holodules/20201011
```

JSON レスポンスボディが取得できました。

![img01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/54acdb35-b92f-eb95-7475-dd4e1fbe7eb5.jpeg)

jsonify を利用しているため、Content-Type は application/json となっています。

![img02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/fa860bb6-785a-3b87-4631-a3b7ccce3cba.jpeg)

このような感じです。

```json
{
    "result": 26,
    "data": {
        "HL0501_20201011_230000": {
            "key": "HL0501_20201011_230000",
            "video_id": "NKFFXwZx8hw",
            "datetime": "20201011 230000",
            "name": "獅白ぼたん",
            "title": "【アソビ大全】るしあ先輩と今日はアソビつくす大全！【ホロライブ/#るししし】",
            "url": "https://www.youtube.com/watch?v=NKFFXwZx8hw",
            "description": "るしあ先輩と遊びつくすよ！感想は #るししし でつぶやいてね！この動画およびライブは、任天堂著作物の利用許諾を受けて配信しています。🦋潤羽るしあhttps://twitter.com/uruharus"
        },
        "HL0002_20201011_223800": {
            "key": "HL0002_20201011_223800",
            "video_id": "XFO_4O-aBGM",
            "datetime": "20201011 223800",
            "name": "ロボ子さん",
            "title": "【耐久SP 】３５万人記念にみんなで出すぞ３５万ダメージ！！APEX参加型#2【ホロライブ/ロボ子さん】",
            "url": "https://www.youtube.com/watch?v=XFO_4O-aBGM",
            "description": "＃RBC350K耐久SP  #APEX　#ホロライブ３４・３５万人記念をまとめて耐久すぺしゃる！！カジュアルでみんなとの通算ダメージで３５万を目指すよ！クロスプレイとPCのみで時間分けます！＃RBC3"
        },
        ...
    }
}
```

ここまでで、ローカル環境での Web API の作成が完了しました。

### Nginx のコンフィグファイルの作成

Azure VM (Ubuntu) + Nginx + gunicorn の環境へ配置するため、事前に Nginx のコンフィグファイルを作成しておきました。
config/nginx.conf ファイルを作成し、ポート 8080 を http://127.0.0.1:8888 へ転送するためのリバースプロキシを設定しているだけです。

```text
worker_processes  1;

events {
    worker_connections 512;
}

http {
    server {
        listen  8080;
        server_name HOLOAPI;
        charset UTF-8;

        proxy_set_header    Host    $host;

        location / {
            proxy_pass http://127.0.0.1:8888;
        }
    }
}
```

### ファイル構成

```text
holoapi
├ .venv
├ .vscode
|  └ launch.json
├ config
|  ├ __init__.py
|  ├ nginx.conf
|  └ settings.py
├ models
|  ├ __init__.py
|  └ holodule.py
├ .env
├ .gitignore
├ .python-version
├ app.py
├ poetry.lock
├ pyproject.toml
└ README.md
```

## Azure AppService の利用

Azure AppService を利用して Flask アプリケーションを配置し、Azure VM (Ubuntu) の MongoDB にアクセスしようとしたのですが、Azure AppService と Azure VM をネットワーク接続するためには VNet統合 を利用する必要があり、VNet統合 は Azure AppService のスタンダードプラン以上でなければいけないため今回は断念しました。

* [Azure App Service on Linux で Python アプリを作成する](https://docs.microsoft.com/ja-jp/azure/app-service/quickstart-python?tabs=bash&pivots=python-framework-flask)

とはいえ、Azure AppService と VNet統合 を利用するために、仮想ネットワークのサブネットを切り替えたり、実際に試してみたりもしたので、あらためて挑戦してみたいと思います。

* [アプリを Azure 仮想ネットワークと統合する](https://docs.microsoft.com/ja-jp/azure/app-service/web-sites-integrate-with-vnet)

## Azure VM 環境の準備

参考：[Azure VM の Ubuntu 20.04(LTS) で Python3 と MongoDB を利用する（ホロライブの動画配信予定を収集 その２の続き）](https://qiita.com/kerobot/items/41f266dd52988f9bb68e)

下記構成の環境を用意しました。

* Azure VM の Ubuntu 20.04(LTS)
* Python 3.8.6 x64
* Git 2.25.1 x64
* MongoDB 4.4.2 x64

ここからは、Azure VM の Ubuntu に SSH で接続して作業を行っています。

### pyenv が利用できることを確認

```bash
$ pyenv --version
pyenv 1.2.21
```

### Poetry が利用できることを確認

```bash
$ poetry --version
Poetry version 1.1.4
```

### MongoDB の接続確認

```bash
$ mongo --version
MongoDB shell version v4.4.2
Build Info: {
    "version": "4.4.2",
    "gitVersion": "15e73dc5738d2278b688f8929aee605fe4279b0e",
    "openSSLVersion": "OpenSSL 1.1.1f  31 Mar 2020",
    "modules": [],
    "allocator": "tcmalloc",
    "environment": {
        "distmod": "ubuntu2004",
        "distarch": "x86_64",
        "target_arch": "x86_64"
    }
}
```

ホロライブの配信予定や動画情報を格納している holoduledb への接続確認

```bash
> mongo localhost:27017/holoduledb -u owner -p
MongoDB server version: 4.4.2
> show collections
holodules
> db.holodules.find().sort({"datetime":-1});
{ "_id" : ObjectId("5fcee53448b99985d55d7346"), "key" : "HL0304_20201208_200000", "video_id" : "sjKsUZ8wM-8", "datetime" : "20201208 200000", "name" : "白銀ノエル", "title" : "【#白銀ノエル謎肉牛丼】新発売『カップヌードル謎肉牛丼』の食レポ 配信します！", "url" : "https://www.youtube.com/watch?v=sjKsUZ8wM-8", "description" : "▼カップヌードル 謎肉牛丼　商品情 報https://www.nissin.com/jp/products/items/10245▼カップヌードル 謎肉牛丼　オンラインストアhttps://store." }
...
> exit
```

## プログラムの配置

github からプログラムを clone して配置と設定を行います。

### プログラムの配置と設定

```bash
$ cd ~
$ git clone https://github.com/kerobot/holoapi.git holoapi
$ cd holoapi
```

pyenv を利用して、ローカル環境の Python のバージョンを切り替えます。

```bash
$ pyenv local 3.8.6
```

Poetry を利用して、Python 仮想環境やパッケージをインストールします。

```bash
$ poetry install
```

Python 仮想環境の pip を更新しておきます。

```bash
$ /home/mcuser/holoapi/.venv/bin/python -m pip install --upgrade pip
Collecting pip
  Downloading pip-20.2.4-py2.py3-none-any.whl (1.5 MB)
     |████████████████████████████████| 1.5 MB 5.4 MB/s
Installing collected packages: pip
  Attempting uninstall: pip
    Found existing installation: pip 20.2.2
    Uninstalling pip-20.2.2:
      Successfully uninstalled pip-20.2.2
Successfully installed pip-20.2.4
```

### .env の作成

MongoDB の接続情報などを設定しておくためのファイルを作成します。

```bash
$ cp .env.sample .env
```

```text
MONGODB_USER = "<user>"
MONGODB_PASSWORD = "<password>"
MONGODB_HOST = "<localhost:27017>"
```

### プログラムの動作確認

まずは、Flask の開発用サーバーを利用してプログラムを実行してみます。

```bash
$ poetry run flask run
```

開発用サーバーが起動しました。

```bash
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
```

curl コマンドで GET してみます。

```bash
$ curl "http://127.0.0.1:5000/"
holoapi
```

Web API が応答してくれました。
Ctrl + C で終了しておきます。

この方法はあくまでも開発用なので、WSGI サーバーのひとつである gunicorn を利用してプログラムを実行してみます。

```bash
$ poetry run gunicorn -w 1 -b 0.0.0.0:8888 app:app
```

WSGI サーバーが起動しました。

```bash
[2020-12-08 13:53:52 +0900] [35797] [INFO] Starting gunicorn 20.0.4
[2020-12-08 13:53:52 +0900] [35797] [INFO] Listening at: http://0.0.0.0:8888 (35797)
[2020-12-08 13:53:52 +0900] [35797] [INFO] Using worker: sync
[2020-12-08 13:53:52 +0900] [35833] [INFO] Booting worker with pid: 35833
```

curl コマンドで GET してみます。

```bash
$ curl "http://127.0.0.1:8888/"
holoapi
```

gunicorn でも、Web API が応答してくれました。

## Nginx の導入と Web API の公開

Nginx を導入して、リバースプロキシを設定してみました。

### Nginx の導入

Nginx をインストールします。

```bash
$ sudo apt install nginx
```

Nginx のバージョンを確認します。

```bash
$ nginx -v
nginx version: nginx/1.18.0 (Ubuntu)
```

サーバーが起動していることを確認します。

```bash
$curl 'http://localhost'
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
...
```

プロセスはこのような状態で、daemon として起動しています。

```bash
$ ps ax | grep nginx
  10278 ?        Ss     0:00 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
  11918 ?        S      0:00 nginx: worker process
  11919 ?        S      0:00 nginx: worker process
```

daemon の停止などはこのように行います。

```bash
$ sudo nginx –s stop    すぐに停止
$ sudo nginx -s quit    リクエスト処理が完了した後に停止
$ sudo nginx -s reopen  ログファイルの再オープン
$ sudo nginx -s reload  設定の再読み込み
```

### コンフィグファイルを指定してリバースプロキシの実行

既定の daemon はこのままにしておき、Web API のためのリバースプロキシを実行します。

まずは、Web API の config ディレクトリにある設定ファイルを確認します。

```bash
$ sudo nginx -t -c ~/holoapi/config/nginx.conf
nginx: the configuration file /home/mcuser/holoapi/config/nginx.conf syntax is ok
nginx: configuration file /home/mcuser/holoapi/config/nginx.conf test is successful
```

設定ファイルは問題なさそうです。

Nginx をリバースプロキシとして実行します。

```bash
$ sudo nginx -c ~/holoapi/config/nginx.conf
```

プロセスはこのような状態になりました。

```bash
$ ps ax | grep nginx
  10278 ?        Ss     0:00 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
  11918 ?        S      0:00 nginx: worker process
  11919 ?        S      0:00 nginx: worker process
  14863 ?        Ss     0:00 nginx: master process nginx -c /home/mcuser/holoapi/config/nginx.conf
  14864 ?        S      0:00 nginx: worker process
```

参考までに pid を指定した停止方法

```bash
$ cat /var/run/nginx.pid
14863
$ sudo kill -QUIT $( cat /var/run/nginx.pid )
```

### リバースプロキシ経由の動作確認

gunicorn で Web API が実行され、Nginx のリバースプロキシが起動している状態で、動作を確認します。

```bash
$ curl 'http://10.1.1.4:8080/'
holoapi
```

リバースプロキシ経由で Web API が応答してくれました。

### ローカル環境からの動作確認

Azure のネットワークセキュリティゲートウェイに、受信セキュリティ規則を追加しています。

ローカル環境の Postman を利用して、Azure VM の Web API にアクセスしてみます。
GET の URL のみ指定し、Query Params は指定なしです。

```text
http://test.example.co.jp:8080/Holodules/20201208
```

Azure VM の Web API から JSON レスポンスボディが取得できました！

![img03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/0359e756-969a-02f3-7452-6552a4e79911.jpeg)

## おわりに

Azure AppServcie を利用する予定が、Nginx + gunicorn になるなど変更はありましたが、なんとか Web API を公開することができました。
課題もたくさんあるので、継続して取り組んでいきます。

* Flask アプリケーションとしてあるべき作り込みが甘いので作り直したい。
* Flask-JWT などを利用して、Web API のトークン認証を行いたい。
* SSL無しは早めにどうにかしたい。
* Azure AppService (Azure Functions とかも) をあらためてチャレンジしたい。