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


はじめに

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

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

※コードは全て GitHub に上げていますが、一つずつコピペした方が勉強になると思います。


使うもの

 以下のものを使います。

名前
役割

Nginx
ロードバランシングやSSL暗号化通信などを提供する、安全性の高いWebサーバー。

Flask
RESTful APIを構築するのに最小限な python のフレームワーク。大規模な物を作る場合には Django などが使われる。

uWSGI
Webサーバーとアプリケーションをつなぐインターフェースの役割を担う。

 これらの関係性をまとめると、以下のようになります。Nginx はクライアントの要求を受け取り、ソケットを介して uWSGI サーバーと通信します。その後、uWSGI サーバーが Flask のアプリケーションを呼び起こします。

スクリーンショット 2019-04-21 23.33.58.png

引用元:A Guide to Scaling Machine Learning Models in Production


全体像

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

├── 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)をはじめとする各種サービスは初期費用がかからず従量課金のため、使った分だけ料金を払えば良いサービスです。今回のように趣味程度で利用することができるので、ありがたく使わせていただきましょう。

 登録の仕方や各種サービスの使い方を説明していると本題に入れないのでここでは割愛しますが、「AWS EC2 インスタンス作成」などで検索すれば鬼のように記事が出てきます。それだけ愛されているサービスなのです。


インスタンスの作成

 今回は、「ubuntu Server 18.04 LTS (HVM), SSD Volume Type」 を使うことにします。



 インスタンスは、基本的にAWSの指示通りに作成していきます。注意しなくてはいけないところは、

「セキュリティグループにHTTP接続を追加すること」「Elastic IPアドレスを設定し、インスタンスを起動するたびにIPアドレスが変わらないようにすること」です。これらについてもたくさんの解説記事が出ているので、やり方がわからない場合は調べて見てください!


接続

 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

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


Flask

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

$ sudo pip install flask


uwsgi

 最後に、uwsgi をインストールします。この時 C compiler が必要なので、clang を用います。

$ 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/ に存在するファイルに中継をしています。とりあえずめんどくさそうなので62行目はコメントアウトしておいて、ここでは/etc/nginx/conf.d/*.conf で website の設定をしていきます。

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

$ 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;
}
}


 ここでは色々な設定ができるようですが、ここでは「最低限動くこと」を目指して必要な部分だけを書きます。上記の 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 では、全てのリソースへのアクセスに対して同じ対応をするように設定されていることがわかります。

 その対応とは、「uWSGI に sokect で繋げる」ということです。これを実現するには、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.pyindex.html に何を書けば良いのかわからない場合は、以下のものを貼り付けてください。(機械学習のモデルを用いたい場合は Github を参考にしてください!タグでいくつかの種類に分かれています。)


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 のデフォルトページが表示される場合は、

$ sudo rm -v /etc/nginx/sites-enabled/default

$ sudo service nginx restart

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


ドメインの取得

 Webアプリケーションが作成できたら、IPアドレスのまま公開するのではなく、ドメインを取得して公開したいと考える人が多いと思います。そこで、AWSでのドメインの取得方法(Route 53)を簡単にだけ記します。



 上記の場所を選択して、以下で自分の取得したいドメイン名を検索して購入します。もちろんですが、同じドメインは取得できないので、世界中で唯一のドメイン名であることが必要です。(ちなみに「零和」にちなんだドメインはすぐに売り切れてしまったようです。)



 しばらく待ち、自分の購入したドメイン名が表示されたら選択し、「Go to Record Sets」を押します。



 画面右側の Name に「www」、valueに ElasticIPアドレス を入力します。後は結果が反映されるまで少し待てばドメイン名からアクセスすることが可能になります!!


参考