NGINX + NGINX Unit + Flask で PythonのWeb アプリを動かす

# NGINX + NGINX Unit + Flask でPythonのWebアプリ開発

Python で Web 開発をしようと思い、Python の Web アプリの環境構築を行いました。
これまでは、`NGINX <-> uwsgi <-> Flask`という構成が主流でしたが、2018 年 4 月に NGINX 社が軽量な AP サーバー`NGINX Unit`をリリースしました。
今回は NGINX Unit を使用し、`NGINX <-> NGINX Unit <-> Flask`という構成で Web アプリケーションの環境を構築していきます。

## 環境

Web サーバーとして`NGINX`を、AP サーバーとして`NGINX Unit`を、Python の Web フレームワークに`Flask`を使用します。

- OS : CentOS7
- Python : 3.7.4
- NGINX : 1.16.1
- NGINX Unit : 1.10.0
- Flask : 1.1.1

## 備考

サーバーは Vagrant と VirtualBox の仮想環境を使用します。
プライベートネットワークを有効にしてください。

```bash
$ vagrant init centos/7
$ vi Vagrantfile
- # config.vm.network "private_network", ip: "192.168.33.10"
+ config.vm.network "private_network", ip: "192.168.33.10"
$ vagrant up
$ vagrant ssh
```

`vagrant ssh`で仮想環境に接続し、selinux を無効にしてください。

```bash
$ sudo vi /etc/selinux/config
- SELINUX=enforcing
+ SELINUX=disabled
$ sudo reboot
```

## Python の開発環境構築

仮想環境に Python の開発環境を構築します。
CentOS にはデフォルトで`Python 2.7.5`がインストールされています。

```bash
$ python --version
Python 2.7.5
```

今回は`Python 3.7.4`を利用します。

### CentOS に Python3 をインストール

Python の[公式ページ](https://www.python.jp/install/centos/index.html)の手順を参考にして CentOS に Python をインストールします。
まずは必要なツールをインストールします。

```bash
$ sudo yum -y groupinstall "development tools"
$ sudo yum install -y bzip2-devel gdbm-devel libffi-devel libuuid-devel ncurses-devel openssl-devel readline-devel sqlite-devel tk-devel wget xz-devel zlib-devel
```

インストーラーを Python の[公式ページ](https://www.python.org/downloads/release/python-374/)から`wget`でダウンロードします。
ダウンロード後に解凍します。

```bash
$ wget https://www.python.org/ftp/python/3.7.4/Python-3.7.4.tgz
$ tar xzf Python-3.7.4.tgz
```

ソースコードを解凍後にビルドします。

```bash
$ cd Python-3.7.4
$ ./configure --enable-shared
$ make
$ sudo make install
$ sudo sh -c "echo '/usr/local/lib' > /etc/ld.so.conf.d/custom_python3.conf"
$ sudo ldconfig
```

ビルドしたコマンドは`/usr/local/bin`配下ににインストールされます。

```bash
$ which python
/usr/bin/python
$ which python3
/usr/local/bin/python3
$ python3 --version
Python 3.7.4
$ which pip3
/usr/local/bin/pip3
```

最後に`pip`をアップグレードします。

```bash
$ pip3 install --upgrade pip --user
```

ローカルの`bin`に最新版の`pip`コマンドがインストールされます。

```bash
$ which pip
~/.local/bin/pip
$ pip --version
pip 19.2.3 from /home/vagrant/.local/lib/python3.7/site-packages/pip (python 3.7)
```

Python の環境構築は以上になります。

### Python の仮想環境の作成

プロジェクトごとにパッケージを切り替えて管理するために、Python の仮想環境を構築します。
Python には仮想環境の作成をサポートしている標準モジュールとして`venv`が用意されています。
`venv`を使用して仮想環境を作成します。

```bash
# flask_sampleという仮想環境を作成
$ python3 -m venv flask_sample
$ ls
venv
```

venv で作成した仮想環境を有効化します。

```bash
$ source flask_sample/bin/activate
(flask_sample)$
```

`venv`モジュールで作成されたディレクトリ内の`activate`スクリプトを実行することで、仮想環境を有効化することができます。
仮想環境を有効化するとプロンプトの先頭に`(仮想環境名)`が表示されます。
作成した仮想環境のディレクトリに移動して構成を確認します。

```bash
(flask_sample)$ cd flask_sample
(flask_sample)$ ls
bin  include  lib  lib64  pyvenv.cfg
```

`bin`ディレクトリには仮想環境を有効化する`activate`スクリプトや、Python のパッケージ管理ツール`pip`コマンドが格納されています。
仮想環境内で `pip` コマンドを実行する際は、このディレクトリ内のコマンドが実行されます。
インストールされたパッケージは`lib/python3.7/site-packages`と`lib64/python3.7/site-packages`内にインストールされます。
仮想環境内では、Python 本体の`site-packages`ディレクトリを無視してこちらのディレクトリ内のパッケージを使用します。
Python 本体のパッケージを汚すことなくアプリ開発を行うことができます。

## Flask でサンプルプロジェクトの作成

`Flask`は Python の小規模なマイクロフレームワークです。標準で掲載されている機能は最小限になっており、小規模なアプリケーションを開発する際に用いられます。
プラグインが提供されているため、機能を拡張することができます。
初めから大規模なアプリケーションを開発する場合は`Django`を使用します。

### Flask のインストール

venv 仮想環境で、`Flask` をインストールします。

```bash
(flask_sample)$  pip install Flask
```

### アプリケーションの作成

サンプルプロジェクトの構成は以下のようにします。

```bash
/sample
  |- app.py
  |- templates
        |- index.html
        |- post.html
```

それぞれのコードは以下のように実装します。

```python:app.py
from flask import Flask ,render_template,request

application = Flask(__name__)

@application.route('/')
def index():
    return render_template('index.html')

@application.route('/sample',methods=['POST'])
def sample():
    message = request.form['message']
    return render_template('post.html',message=message)

if __name__=="__main__":
    application.run(host='0.0.0.0')
```

```html:index.html
<html>
  <head>
    <meta charset="utf-8" />
    <title>Flask Sample</title>
  </head>
  <body>
    <h1>Flask Smaple</h1>
    <form action="sample" method="post">
      <input type="text" name="message" />
      <input type="submit" value="送信" />
    </form>
  </body>
</html>
```

```html:post.html
<html>
  <head>
    <meta charset="utf-8" />
    <title>Flask Sample</title>
  </head>
  <body>
    <h1>Flask Smaple</h1>
    メッセージ：{{message}}
  </body>
</html>
```

実装後に`app.py`を実行し、Flask のアプリケーションを起動します。

```bash
(flask_sample)$ python /sample/app.py
 * 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://0.0.0.0:5000/ (Press CTRL+C to quit)
```

起動後にブラウザで`http://仮想環境のプライベートIP:5000/`にアクセスすると、`index.html`が表示されます。
確認できれば`Flask`の動作確認は終了です。次は、`NGINX Unit` で AP サーバーを構築します。

## NGINX Unit

[NGINX Unit](http://unit.nginx.org/) は NGINX 社が開発した、軽量なアプリケーションサーバーです。
複数の言語のアプリケーションを実行することができるため、同じサーバー上で異なる言語のアプリケーションを運用できます。
言語は`Python`,`PHP`,`Go`,`Perl`,`Ruby`,`Node.js`,`Java`に対応しています。
さらに、言語内の異なるバージョンを共存させて実行することができます。(Python2.7 と Python3 など)
API や JSON 経由で、サーバーを停止することなくリアルタイムに設定を変更することができるため、サービスを停止させることなくシームレスに設定の変更を行うことができます。

### インストール

[公式ドキュメント](https://unit.nginx.org/installation/)に各 OS ごとのインストール方法が公開されています。

venv 仮想環境から出て、yum のリポジトリを追加します。

```bash
(flask_sample)$ deactivate
$ sudo vi /etc/yum.repos.d/unit.repo
[unit]
name=unit repo
baseurl=https://packages.nginx.org/unit/centos/$releasever/$basearch/
gpgcheck=0
enabled=1
```

インストールします。

```bash
$ sudo yum install -y unit
$ sudo yum install -y unit-devel unit-go unit-jsc8 unit-perl unit-php unit-python
```

NGINX Unit の自動起動を設定します。

```bash
$ sudo systemctl enable unit
Created symlink from /etc/systemd/system/multi-user.target.wants/unit.service to /usr/lib/systemd/system/unit.service.
$ sudo service unit start
Redirecting to /bin/systemctl start unit.service
$ sudo service unit status
Redirecting to /bin/systemctl status unit.service
● unit.service - NGINX Unit
   Loaded: loaded (/usr/lib/systemd/system/unit.service; enabled; vendor preset: disabled)
   Active: active (running) since 火 2019-11-05 15:42:47 UTC; 7h ago
 Main PID: 5714 (unitd)
   CGroup: /system.slice/unit.service
           ├─5714 unit: main v1.12.0 [/usr/sbin/unitd --log /var/log/unit/unit.log --pid /var/run/unit/unit.pid --no-daemon]
           ├─5716 unit: controller
           └─5717 unit: router
```

`service unit status`で NGINX Unit が起動できていることが確認できます。

### Flask 用の設定を行う

`NGINX Unit` で Flask のアプリケーションを実行する設定を行います。
設定ファイルを NGINX Unit のソケットから `GET` して、Flask のアプリケーションを `NGINX Unit` にロードする設定を行います。
`NGINX Unit` のコントロール用の Unix ソケットは、`/var/run/unit/control.sock`にあります。(OSによって場所は異なります)

```bash
$ cd /sample
$ sudo curl --unix-socket /var/run/unit/control.sock http://localhost/config/ > config.json
$ vi config.json
```

```json
{
  "listeners": {
    "*:8080": {
      "pass": "applications/flask_app"
    }
  },

  "applications": {
    "flask_app": {
      "type": "python",
      "processes": 2,
      "path": "/sample/",
      "module": "app"
    }
  }
}
```

- listeners : ポートの設定を行います。`*:8080`の`pass`の値が、`applications`の階層と紐ずいています。
- applications :
  - type : モジュール名(言語)を指定
  - processes : worker 数
  - path : Python プログラムを配置しているパス
  - module : 起動するファイル。ファイル名から拡張子を除いた形で指定

作成した設定ファイルを NGINX Unit のソケットに対して PUT して、設定の反映をします。

```bash
$ sudo curl -X PUT --data-binary @config.json --unix-socket /var/run/unit/control.sock http://localhost/config
{
	"success": "Reconfiguration done."
}
```

`success`が帰ってくると設定が完了しています。設定内容の確認もできます。

```bash
$ sudo curl --unix-socket /var/run/unit/control.sock http://localhost/
{
	"certificates": {},
	"config": {
		"listeners": {
			"*:8080": {
				"pass": "applications/flask_app"
			}
		},

		"applications": {
			"flask_app": {
				"type": "python",
        "processes": 2,
				"path": "/sample/",
				"module": "app"
			}
		}
	}
}
```

仮想環境を起動し、`http://localhost:8080/`にアクセスします。

```bash
$ source venv/bin/activate
(flask_sample)$ curl http://localhost:8080/
<html>
  <head>
    <meta charset="utf-8" />
    <title>Flask Sample</title>
  </head>
  <body>
    <h1>Flask Smaple</h1>
    <form action="sample" method="post">
      <input type="text" name="message" />
      <input type="submit" value="送信" />
    </form>
  </body>
</html>
```

`index.html`が表示されれば構築完了です。ブラウザでアクセスしても`index.html`が表示されます。
`NGINX Unit` の構築は以上です。次は Web サーバーの `NGINX` を構築します。

## NGINX

軽量・高速が特徴のオープンソースの Web サーバー で、Apache の代替になりうるサーバーとして注目されています。
Apache と比べるとスケーラビリティの面で優れており、処理性能・並列処理・メモリ使用量の小ささなどが特徴として挙げられます。今回はプロキシサーバーとして利用します。

### NGINX を CentOS にインストールする

`NGINX` は公式に yum のリポジトリを公開しているため、そのリポジトリからインストールします。
[公式ページ](http://NGINX.org/en/linux_packages.html#RHEL-CentOS)の記述を参考にリポジトリを追加してインストールを行います。

### リポジトリの追加

一度 Python 仮想環境から出ます。
yum リポジトリの追加を行います。`/etc/yum.repos.d/NGINX.repo`を追加します。

```bash
$ sudo vi /etc/yum.repos.d/NGINX.repo
[NGINX]
name=NGINX repo
baseurl=http://NGINX.org/packages/centos/7/$basearch/
gpgcheck=0
enabled=1
```

### インストール

リポジトリの確認を行います。

```bash
$ sudo yum info nginx
Available Packages
Name        : NGINX
Arch        : x86_64
Epoch       : 1
Version     : 1.16.1
Release     : 1.el7_4.ngx
Size        : 754 k
Repo        : NGINX/x86_64
Summary     : High performance web server
URL         : http://NGINX.org/
License     : 2-clause BSD-like license
Description : NGINX [engine x] is an HTTP and reverse proxy server, as well as
            : a mail proxy server.
```

`yum info`コマンドでリポジトリに NGINX があるとこが確認できたらインストールを行います。

```bash
$ sudo yum install nginx
```

自動起動を設定し、
NGINX を起動します。

```bash
$ sudo systemctl enable nginx
$ sudo service start nginx
$ ![nginx.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239179/0d818e0a-7aee-8a07-af95-72d34dfaa824.png)
service status nginx
● NGINX.service - NGINX - high performance web server
   Loaded: loaded (/usr/lib/systemd/system/NGINX.service; enabled; vendor preset: disabled)
   Active: active (running)
```

NGINX を起動後にブラウザで`http://仮想環境のプライベートIP`にアクセスします。
以下のうような画面が表示されれば起動が完了です。

![nginx.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239179/7f3b2f58-4d83-d912-3578-d7dcbc5c5186.png)


## NGINX と NGINX Unit の連携

### NGINX の設定ファイル

`NGINX` は `NGINX Unit` の前の WEB サーバーやプロキシサーバーとして運用します。
アプリケーションへのリクエストを Nginx Unit にプロキシします。
`NGINX` の設定ファイルは`/etc/nginx/conf.d`にあります。

```bash
$ sudo vi /etc/nginx/conf.d/app.conf
upstream unit-python {
        server 127.0.0.1:8080;
}
server {
    listen 80;
    server_name localhost;

    location ~ / {
      proxy_pass http://unit-python;
      proxy_set_header Host $host;
    }
}
```

NGINX を再起動し、`http://localhost/` にアクセスします。

```bash
$ sudo service restart nginx
$ curl http://localhost/
<html>
  <head>
    <meta charset="utf-8" />
    <title>Flask Sample</title>
  </head>
  <body>
    <h1>Flask Smaple</h1>
    <form action="sample" method="post">
      <input type="text" name="message" />
      <input type="submit" value="送信" />
    </form>
  </body>
</html>
```

'index.html'が表示されればプロキシ設定が完了です。ブラウザで`http://仮想環境のプライベートIP`にアクセスし`index.html`を確認することができます。
以上でPythonのWebアプリの環境構築は終了です。

## まとめ

`NGINX` + `NGINX Unit` + `Flask` で WEB アプリケーションの環境構築を行いました。
`NGINX Unit` はマイクロサービスの運用に特化しているため、今後も注目されて行くと思います。
継続して触れていきたいと思います。