Flask + uwsgi + NginxでAPIを実行する

# やりたいこと

- Flask + uwsgi + Nginxの構成でAPIを動作させる

## 環境・MWのバージョン

- Ubuntu20.04
- Python 3.8.10
- Flask 1.0.2
- Nginx 1.18.0
- uwsgi 2.0.20

## 今回使用する技術の概要

## Flaskとは

Pythonの軽量フレームワークです。機能が少ない分リリース用のアーティファクトが軽量になる、習得が簡単であるなどのメリットがあります。

 Djangoが中規模以上の開発に適しているのに対してFlaskは小規模な開発に向いています。(機能が限られたAPIをサブシステムとして構築する際に選択肢になるでしょう。)

## uwsgiとは

Pythonのアプリケーションを動作させるためのアプリケーションサーバ用MWです。
機能が豊富で動作が速いことがメリットとして挙げられます。

uwsgiを使うにあたってはWSGIを理解しておく必要があります。

WSGIとは(Web Server GateWay Interface)の略で、Webサーバとアプリケーションサーバを繋ぐインタフェースの規格です。

uwsgiはこのWSGIに準拠したミドルウェアです。

## Nginxとは

オープンソースのWebサーバ用MWです。
Apacheと比較して静的リソースの大量処理が得意な事やメモリ消費量が小さいことが特徴です。
(逆にApacheよりも動的リソースの処理が苦手です。)

## 作業手順

それぞれのMWが単体で動作することを確認した後、疎通確認をします。

- Flaskの動作確認
  - インストールする
  - ビルトインサーバで動作確認する

- Nginxの動作確認
  - インストールする
  - 静的ページを表示する

- uwsgiの動作確認
  - インストールする
  - 動作確認する

- Flask ⇔ uwsgi ⇔ Nginxの疎通確認
  - Nginxの設定
  - uwsgiの設定
  - 疎通確認

### Flaskの動作確認

#### インストールする

※ Python3系がインストールされていることは前提とします。

以下のコマンドでFlaskをインストールします。
(Jinja2, itsdangerous, requests, WerkzeugはFlaskを動作させるために必要なパッケージです。)

```bash
$ pip install Flask==1.0.2 Jinja2==3.0.1 itsdangerous==2.0.1 requests==2.21.0 Werkzeug==2.0.1
```

正しくインストールされていることが確認できればOKです。

```bash
$ pip show flask

Name: Flask
Version: 1.0.2
Summary: A simple framework for building complex web applications.
Home-page: https://www.palletsprojects.com/p/flask/
Author: Armin Ronacher
Author-email: armin.ronacher@active-4.com
License: BSD
Location: /usr/local/lib/python3.8/dist-packages
Requires: Werkzeug, click, Jinja2, itsdangerous
Required-by: Flask-Testing, Flask-SQLAlchemy, Flask-RESTful, Flask-Migrate, flask-marshmallow, Flask-Mail, Flask-Login, Flask-JWT, Flask-JWT-Extended, Flask-Cors, Flask-Bcrypt, Bcrypt-Flask
```

#### ビルトインサーバで動作確認をする

サンプルプログラムのディレクトリ構成は以下の通りです。


```
/tmp/flask_sample/ # プロジェクトのルートディレクトリ
    ├── app
    │   └── __init__.py # アプリケーション本体とエンドポイントを記述する
    └── manage.py # アプリケーションを読み込んで起動する
```


```python:/tmp/flask_sample/app/__init__.py

# Flaskで利用するパッケージをインポートする
from flask import Flask, jsonify

# アプリケーションオブジェクトを生成する
application = Flask(__name__)

# エンドポイントを作る(http://host/hello にリクエストするとJSONが表示される)
@application .route("/hello")
def hello():
    return jsonify({
        "message": "Hello World!"
    })
```

```python:/tmp/flask_sample/manage.py
# appディレクトリからアプリケーションオブジェクトをインポートする。
from app import application

# Pythonスクリプトとして実行された場合、アプリケーションを実行する。
if __name__ == "__main__":
    application.run()
```

ここで、Flask単体の動作確認をします。

``` bash
# プロジェクトのルートディレクトリに移動する
$ cd /tmp/flask_sample 
# ビルトインサーバを起動する
$ flask run

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

※ ビルトインサーバ起動時に警告が表示されますが一旦無視してOKです。

ブラウザから ``` http://localhost:5000/hello ``` にアクセスして以下のように表示されれば確認完了です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598747/67336470-1164-5f23-6336-984c489606b8.png)

### Nginxの動作確認

#### インストールする

以下のコマンドでインストールします。

```
$ sudo apt-get update
$ sudo apt-get -y install nginx
```

#### 静的ページを表示する

インストール後、Nginxを起動してブラウザで動作確認します。

```
$ service nginx start
```

デフォルトページが表示されれば完了です。

### uwsgiの動作確認

#### インストールする

```:bash
$ apt-get update

# 各種依存パッケージをインストールする
$ apt-get install -y build-essential python-dev pip

# pipでuwsgiをインストールする
$ pip install uwsgi
```

#### 動作確認する

```bash
$ cd /tmp/flask_sample
$ uwsgi --http=0.0.0.0:8000 --wsgi-file=/tmp/flask_sample/manage.py --callable=application
```

--wsgi-fileオプションでwsgiから動作させるファイルを指定し、--callableオプションでアプリケーションオブジェクトを指定します。

このとき必ずプロジェクトのルートディレクトリ(今回の場合は/tmp/flask_sample)に移動します。
理由は、manage.pyが相対インポートでapplicationオブジェクトを参照できるようにするためです。

ブラウザから ```http://localhost:8000/hello ```にアクセスして画像のように表示されれば成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598747/e56abb19-75b3-0a94-c1f9-a57cb468410f.png)

ここまで完了したら一旦各MWの動作確認は完了です。

### Flask ⇔ uwsgi ⇔ Nginxの疎通確認

ここからはいよいよNginx・uwsgi・Flaskを組み合わせて動かします。

#### Nginxの設定

まずはクライアントのリクエストを受け付けるWebサーバとしてNginxの設定を変更します。

/etc/nginx/conf.d下にuwsgi.confを作成し、以下の内容を記述します。

```bash:/etc/nginx/conf.d


server {
   # 80番ポートを開放する
   listen 80;
   
   # "/"から始まるURIに{}内の設定を適用する
   location / {
       
       # uwsgiのパラメータを読み込む
       include uwsgi_params;
       # uwsgiとの通信に用いるソケットファイルを指定する(パスは任意)
       uwsgi_pass unix:///tmp/uwsgi.sock;
   }
}
```

続いて、uwsgi.confの設定がNginxに読み込まれるよう ```/etc/nginx/nginx.confの設定を変更します。

```bash:/etc/nginx/nginx.conf
# ファイルの真ん中あたりにあります

    # /etc/nginx/conf.d下の拡張子.confのファイルを全て読み込む
    # ※ 前項のuwsgi.confもここで読み込まれます
    include /etc/nginx/conf.d/*.conf;

    # この記述をコメントアウトする
    #include /etc/nginx/sites-enabled/*;
```

#### uwsgiの設定

続いてuwsgiの設定をします。
先ほどはuwsgiコマンドにオプションを指定して実行していました。しかし、設定項目が多くなると
毎回オプションを指定するのが面倒ですし、設定ミスも増えます。

そこで、設定ファイルでオプションを指定する方法を取りましょう。

/tmp/flask_sample下にwsgi.iniを作成します。
ディレクトリ構成は以下のようになります。

```
/tmp/flask_sample/
├── app
│   └── __init__.py
├── manage.py
└── wsgi.ini # 新規追加ファイル
```

/tmp/flask_sample/wsgi.iniを次のように編集します。

```ini
[wsgi]

# wsgiの実行時にプロジェクトのルートディレクトリに移動する
chdir = /tmp/flask_sample

# wsgiで動作させるファイルのパス
wsgi-file =  /tmp/flask_sample/manage.py

# アプリケーションオブジェクトの変数名
callable = application

# ソケットファイル
socket = /tmp/uwsgi.sock

# ソケットファイルの権限
chmod-socket = 666

# root権限で実行する
master = true
```

chdirでプロジェクトのルートディレクトリに移動しないと相対インポートが正しく動かない可能性があるので忘れずに設定します。(インターネット上の記事では全てのファイルを1つのディレクトリに配置しているため、chdirが省略されていることが多いです。しかし、実際のプロジェクトではそのようなケースは稀なのでchdirを指定するのが無難でしょう。)

#### 疎通確認

ここまでの設定で、サーバのルートディレクトリにリクエストが来ると/tmp/uwsgi.sockに繋げる⇒
uwsgi.sockからmanage.pyが呼び出されてFlaskアプリケーションが実行される という処理が行われるようになりました。

以下のコマンドでwsgiを起動しましょう。

```bash
$ uwsgi --ini /tmp/flask_sample/wsgi.ini
```

ブラウザから ``` http://localhost:8000/hello ``` にアクセスしてレスポンスが表示されれば動作確認完了です。


### 参考資料

[Nginx support](https://uwsgi-docs.readthedocs.io/en/latest/Nginx.html)

[Flask+uWSGI+NginxでWSGIを試してみる](https://amateur-engineer-blog.com/flask-uwsgi-nginx/)

[Install | NGINX](https://www.nginx.com/resources/wiki/start/topics/tutorials/install/)

[The uWSGI project](https://uwsgi-docs.readthedocs.io/en/latest/)