PythonかければWebアプリぐらい作れる。

# はじめに
　最近、自分の作ったWebアプリ(特に機械学習系)を公開する人が増えてきた気がしますが、<b>「お前ら論文読んで理解して、データ集めてモデル作って検証して精度上げて…ってどこにそんな趣味のアプリ作る時間あるねん、なんだ？１日48時間でもあるのか？？」</b>と思ったので調べてみたところ、<b>「とりあえず動くもの」なら簡単に作れそう</b>だとわかったので、その作り方を紹介します。

　個人的には、機械学習の各種モデルを色々な形に応用し、公開している方を見ると刺激を受けるので、ぜひ今まで敷居が高いと思ってWebアプリを作ってこなかった機械学習エンジニアの方に、Webアプリを作っていただきたいなと思います。

※コードは全て [GitHub](https://github.com/iwasakishuto/Webapp-for-Python) に上げていますが、一つずつコピペした方が勉強になると思います。

# 使うもの
　以下のものを使います。

| 名前                                        | 役割                                                                                                                                       |
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| [Nginx](https://nginx.org/en/)              | ロードバランシングやSSL暗号化通信などを提供する、安全性の高いWebサーバー。                                                                 |
| [Flask](http://flask.pocoo.org)             | RESTful APIを構築するのに最小限な python のフレームワーク。大規模な物を作る場合には [Django](https://www.djangoproject.com) などが使われる。 |
| [uWSGI](https://uwsgi-docs.readthedocs.io/) | Webサーバーとアプリケーションをつなぐインターフェースの役割を担う。                                                                        |
　これらの関係性をまとめると、以下のようになります。Nginx はクライアントの要求を受け取り、ソケットを介して uWSGI サーバーと通信します。その後、uWSGI サーバーが Flask のアプリケーションを呼び起こします。
<img width="723" alt="スクリーンショット 2019-04-21 23.33.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308700/2959b26b-a092-758d-669f-5e0da06e1714.png">
引用元：[A Guide to Scaling Machine Learning Models in Production](https://hackernoon.com/a-guide-to-scaling-machine-learning-models-in-production-aa8831163846)

# 全体像
　上記のものを駆使して今回構築する構造は、以下のようになります。

```
├── etc
│   ├── nginx
│   │   ├── conf.d
│   │   │   ├── default.conf
│   │   │   └── uwsgi.conf
│   │   ├── nginx.conf
│   │   └── uwsgi_params
│   └── systemd
│       └── system
│           └── uwsgi.service
└── usr
    └── local
        └── app
            ├── main.py
            ├── templates
            │   └── index.html
            ├── tmp
            └── uwsgi.ini
```
　それでは、実際に作っていきましょう！

# AWS
　Webアプリを公開するのに欠かせないのはサーバーですが、普通の人は個人サーバーなんて持ってません。そこで、クラウドに頼りましょう。[AWS（AmazonWebService）](https://aws.amazon.com/en/)をはじめとする各種サービスは<b>初期費用がかからず従量課金</b>のため、使った分だけ料金を払えば良いサービスです。今回のように趣味程度で利用することができるので、ありがたく使わせていただきましょう。
　登録の仕方や各種サービスの使い方を説明していると本題に入れないのでここでは割愛しますが、[「AWS EC2 インスタンス作成」](https://www.google.co.jp/search?q=AWS+EC2+%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E4%BD%9C%E6%88%90)などで検索すれば鬼のように記事が出てきます。それだけ愛されているサービスなのです。

## インスタンスの作成
　今回は、<b>「ubuntu Server 18.04 LTS (HVM), SSD Volume Type」</b> を使うことにします。
<img src="https://qiita-image-store.s3.amazonaws.com/0/308700/e8e8b7f3-2232-5842-e814-8d94e61207d3.png" width=80%>
　インスタンスは、基本的にAWSの指示通りに作成していきます。注意しなくてはいけないところは、
<b><font color="Red">「セキュリティグループにHTTP接続を追加すること」</font></b>と<b><font color="Red">「Elastic IPアドレスを設定し、インスタンスを起動するたびにIPアドレスが変わらないようにすること」</font></b>です。これらについてもたくさんの解説記事が出ているので、やり方がわからない場合は調べて見てください！
<img src="https://qiita-image-store.s3.amazonaws.com/0/308700/6b33b303-a401-ecd8-a780-4e614ef7a323.png" width=80%>

## 接続
　AWS の EC2 インスタンスへは ssh を用いて接続します。ここも最初はつまづくポイントですが、いたるところに解説記事が出ているので割愛させていただきます。

## 環境構築
### pip
　無事 AWS の EC2 インスタンスに接続することができたら、まず始めに環境構築を行います。試しに python 利用者はお馴染みのコマンド `pip` を打ってみましょう。すると、以下の絶望的なメッセージが表示されます。

```
$ pip
> Command 'pip' not found, but can be installed with:
sudo apt install python-pip
```

　まずは、`pip` をインストールしましょう！！今回は OS が Ubuntu なので、`apt-get` コマンドを使用します。`apt-get` コマンドは、Debian や Ubuntu など、Debian 系のパッケージ管理システムである APT(Advanced Package Tool) ライブラリを利用してパッケージを操作・管理するコマンドです。まずは `apt-get` を最新の状態にアップデートしておきましょう。

```
$ sudo apt-get update
```

　さて、それでは python に欠かせない `pip` をインストールします。先ほど言われたコマンドを素直に打ち込みましょう。

```
$ sudo apt install python-pip
```
　これで無事に `pip` はインストールされました。続いて、パッケージをインストールしていきましょう。
### Nginx

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

　ここで、念のため実際に動かして正しくインストールできているかを確かめます。

```
$ sudo systemctl start nginx.service
$ sudo systemctl status nginx.service
```

　この時、<b>「Active: <font color='Green'>active (running)</font> 」</b>と表示されていれば正しく動作しています。`q` と入力し、ステータス画面を閉じましょう。

### Flask
　Flask は python 系のパッケージなので、`pip` コマンドを使います。

```
$ sudo pip install flask
```

### uwsgi
　最後に、uwsgi をインストールします。この時 C compiler が必要なので、[clang](https://clang.llvm.org) を用います。

```
$ sudo apt install clang
$ CC=`which clang` pip install uwsgi # コンパイラを指定
$ sudo apt install uwsgi-plugin-python
$ sudo apt-get install libpcre3 libpcre3-dev
```
　これで、最低限必要な材料は揃いました！

# 設定
　それでは各種設定ファイルを編集していきましょう。

## Clients-Nginx 間
　Nginx の設定ファイル（設定値が書いてあるファイル）は、全て `/etc/nginx/` ディレクトリに配置します。デフォルトの設定では、`/etc/nginx/nginx.conf` がその設定ファイルの役割をしています。そこで、まずはそのファイルを見てみましょう。

```
$ sudo vi /etc/nginx/nginx.conf
```

　なお、`vim` を使用する際は、コマンドモードで `:set number` と打つと行数が表示されるので見やすくなります。

```/etc/nginx/nginx.conf
11 http{
12
：
：
61        include /etc/nginx/conf.d/*.conf;
62        include /etc/nginx/sites-enabled/*;
63 }
```

　ここで、上記の `http` ブロックが見つかると思います。このブロック内の `include` は、Nginx に、どこに website の設定ファイルがあるかを教えています。
　ちなみに、上の `nginx.conf` はどのようにして Nginx をインストールしたかによって決まっており、今回は Debian 系のパッケージからインストールしているので、上のように書かれています。
　また、 `../sites-enabled/` フォルダは、シンボリックリンク(symbolic link)と呼ばれるファイルやフォルダの代用ファイル（中継ファイル）を保持しており、`/etc/nginx/sites-available/` に存在するファイルに中継をしています。<b>とりあえずめんどくさそうなので62行目はコメントアウトしておいて、ここでは/etc/nginx/conf.d/*.conf で website の設定をしていきます。</b>

　以下のファイルを作成します。

```
$ sudo vi /etc/nginx/conf.d/default.conf
```

```/etc/nginx/conf.d/default.conf
server {
    listen       80;

    location / {
        include uwsgi_params;
        uwsgi_pass unix:///usr/local/app/tmp/uwsgi.sock;
    }
}
```

　ここでは色々な設定ができるようですが、ここでは<b>「最低限動くこと」</b>を目指して必要な部分だけを書きます。上記の `listen` は、Nginx が外部からリクエストを受け付けるポート番号を指定しています。また、`location` は、Nginx が各リソース (/hoge) にアクセスしてきた時にどのように対応するかを設定します。例えば、以下のように設定したとします。

```hoge.conf
location / { }
location /images/ { }
location /blog/ { }
location /planet/ { }
location /planet/blog/ { }
```

　この時、`http://example.com/` にアクセスが来た場合は`location /` が対応し、`http://example.com/planet/blog/` や `http://example.com/planet/blog/about/` などにアクセスが来た時には `location /planet/` がどちらも対応します。
　つまり、今回の `/etc/nginx/conf.d/default.conf` では、全てのリソースへのアクセスに対して同じ対応をするように設定されていることがわかります。

　その対応とは、<b>「uWSGI に sokect で繋げる」</b>ということです。これを実現するには、`include uwsgi_params` を書き、`uwsgi_pass` に uWSGI socket のアドレスを指定するだけで大丈夫です。

## Nginx-uwsgi 間
```
$ sudo vi /etc/nginx/conf.d/uwsgi.conf
```

```/etc/nginx/conf.d/uwsgi.conf
server {
  listen 5050;
  error_log  /var/log/nginx/error.log warn;

  location / {
    include uwsgi_params;
    uwsgi_pass unix:///usr/local/app/tmp/uwsgi.sock;
    uwsgi_ignore_client_abort on;
  }
}
```

## uwsgi-Flask 間
　まず、Flask アプリケーションを作成（配置）するディレクトリを作成しましょう。

```
$ sudo mkdir /usr/local/app
```

　続いて、`uwsgi` を起動する際のパラメータをまとめたファイルを作成します。

```
$ sudo vi /usr/local/app/uwsgi.ini
```

```/usr/local/app/uwsgi.ini
[uwsgi]
# plugin
plugins-dir = /usr/lib/uwsgi/plugins
plugin = python27
chdir = /usr/local/app

# application's base folder
base = /usr/local/app

# python module to import
app = main
module = %(app)

# socket file's location
socket = /usr/local/app/tmp/uwsgi.sock

# permissions for the socket file
chmod-socket = 666

# the variable that holds a flask application inside the module imported at line #6
callable = app

# location of log files
logto = /var/log/uwsgi/%n.log

master = true
processes = 5
vacuum = true
die-on-term = true                 
```

　このファイルを引数にとることにより、以下の指定方法で各種パラメータを指定でき、スッキリとします。

```
$ sudo uwsgi --plugins-dir /usr/lib/uwsgi/plugins --plugin python27 --chdir… # 本来
$ sudo uwsgi --ini uwsgi.ini # 上のファイルゆえ
```

　また、以下のファイルを用いて uwsgi を起動できるようにします。

```
$ sudo vi /etc/systemd/system/uwsgi.service
```

```/etc/systemd/system/uwsgi.service
[Unit]
Description=uWSGI instance to serve uwsgi
After=network.target

[Service]
User=root  # 好ましくない
Group=root # 好ましくない
WorkingDirectory=/usr/local/app
Environment="LD_LIBRARY_PATH=/usr/lib"
ExecStart=/usr/bin/uwsgi  --ini uwsgi.ini

[Install]
WantedBy=multi-user.target
```

　この時、正しいファイルパーミッションが含まれるようにします。

```
$ sudo touch /etc/systemd/system/uwsgi.service
$ sudo chmod 664 /etc/systemd/system/uwsgi.service
```

　EC2 を再起動した時などに毎回起動させるのは面倒なので、自動起動を登録しておくと楽です。

```
$ sudo systemctl enable uwsgi
```

　なお、手動で行うときは以下の通りです。

```
$ sudo systemctl stop uwsgi.service
$ sudo systemctl start uwsgi.service
$ sudo systemctl status uwsgi.service
```

# Flask アプリケーション作成
　ここまでで必要なもののインストール・設定は終わったので、Flask アプリケーションを作成（配置）します。まずは、作成したディレクトリの所有者やグループを変えます。そのために、以下のコマンドを打ちます。

```
$ chown [オプション] [ユーザー:グループ] ファイル
```

　なお、ユーザー名は

```
$ ps faux | grep nginx
```

　と入力した時に `master_process` と書かれているものを記録し、そのユーザー名を用いて

```
$ sudo id ユーザー名
```

　と入力すればグループ名がわかります。デフォルトの設定ではユーザー名、グループ名ともに `root` になっています。（このまま `root` を使用するのは好ましくありませんが、ここでは割愛します。）
※ なお、ここでのユーザー名、グループ名を `/etc/systemd/system/uwsgi.service` の該当箇所に書いています。

　上記の内容に従って、ディレクトリの所有者やグループを変更していきます。

```
$ sudo mkdir -p /usr/local/app/tmp
$ sudo chmod 777 /usr/local/app
$ sudo chown root:root /usr/local/app
$ sudo mkdir /var/log/uwsgi
$ sudo chown root:root /var/log/uwsgi
```

　それでは、アプリを作りますが、大体の場合一度ローカル環境で動かしていると思うので、ローカルのデータをそのまま AWS に送りましょう。

```
# [注意]ローカル環境で行う！
> scp -r -i ~/.ssh/key path/to/app/directory username@aws.example.com:~/
```

　すると、`/home/ubuntu/` にデータが移動されるので、これを目的の位置まで移動させます。これで必要なものは揃いました！なお、ここで `main.py` や `index.html` に何を書けば良いのかわからない場合は、以下のものを貼り付けてください。（機械学習のモデルを用いたい場合は [Github](https://github.com/iwasakishuto/Webapp-for-Python) を参考にしてください！タグでいくつかの種類に分かれています。）

```main.py
# coding: utf-8
from flask import Flask,render_template

# 初期化
app = Flask(__name__)

# ルートアクセス時の処理
@app.route('/')
def index():
    return render_template('index.html')

if __name__ == "__main__":
    app.run()
```
```index.html
<!doctype html>
<html>
  <head>
    <title>sample</title>
    <body>
      <h1>Hello world.</h1>
      <!-- メインコンテンツ -->
    </body>
  </head>
</html>
```


# その他
　ここまでで設定は完了しましたが、動かない場合は

```
$ sudo cat /var/log/uwsgi/uwsgi.log
```

　でエラーの原因を見てみてください。おそらく Flask のアプリケーションで用いている各種モジュールが見つからない、などのことだと思いますので、適宜必要なものをインストールしてください。
　また、以下の nginx のデフォルトページが表示される場合は、
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308700/e1ac74cf-a1ef-de65-e66f-789b8f996481.png" width=80%>

```
$ sudo rm -v /etc/nginx/sites-enabled/default
$ sudo service nginx restart
```

　によって、デフォルトページへの参照を完全に削除しましょう。

# ドメインの取得
　Webアプリケーションが作成できたら、IPアドレスのまま公開するのではなく、ドメインを取得して公開したいと考える人が多いと思います。そこで、AWSでのドメインの取得方法(Route 53)を簡単にだけ記します。
<img src="https://qiita-image-store.s3.amazonaws.com/0/308700/13a08ebd-e6c3-587b-5399-bc030bc45ce2.png" width=80%>
　上記の場所を選択して、以下で自分の取得したいドメイン名を検索して購入します。もちろんですが、同じドメインは取得できないので、世界中で唯一のドメイン名であることが必要です。（ちなみに「令和」にちなんだドメインはすぐに売り切れてしまったようです。）
<img src="https://qiita-image-store.s3.amazonaws.com/0/308700/9379673b-a851-2e22-2c54-77a86d780a77.png" width=80%>
　しばらく待ち、自分の購入したドメイン名が表示されたら選択し、「Go to Record Sets」を押します。
<img src="https://qiita-image-store.s3.amazonaws.com/0/308700/d5ac866b-cd71-0ec8-c51d-4a2f6add2226.png" width=80%>
　画面右側の Name に「www」、valueに ElasticIPアドレス を入力します。後は結果が反映されるまで少し待てばドメイン名からアクセスすることが可能になります！！
<img src="https://qiita-image-store.s3.amazonaws.com/0/308700/349743a9-da66-3de8-85bd-bc13d0919700.png" width=80%>

# 参考
- [A Guide to Scaling Machine Learning Models in Production](https://hackernoon.com/a-guide-to-scaling-machine-learning-models-in-production-aa8831163846)
- [Quickstart for Python/WSGI applications — uWSGI 2.0 documentation](https://uwsgi-docs.readthedocs.io/en/latest/WSGIquickstart.html)
- [Django uwsgi nginxがデフォルトページ「Welcome to NGINX」にリダイレクトする - コードログ](https://codeday.me/jp/qa/20190414/629501.html)
- [ちゃんと運用するときのuWSGI設定メモ - Qiita](https://qiita.com/yasunori/items/64606e63b36b396cf695#etcsystemdsystemuwsgiservice-uwsgi-emperor起動用-centos7用)