nginx unitでDjangoアプリケーションを実行してみる

# はじめに

この記事は JSL (日本システム技研) Advent Calendar 2020 - Qiita 24日目の記事です。

先日の[アドベントカレンダー記事](https://qiita.com/katekichi/items/9ca7a8e185ea1460c87c)で、年一回のノルマは達成したものの、記録への挑戦をしたい年頃のため以前より興味のあった`nginx unit`について書きました。

# nginx unitとは

ざっくり言うと、以下のような特徴があります（[公式ページ](https://www.nginx.co.jp/products/nginx-unit/)）。

+ NGINX社製のアプリケーションサーバーです。
+ 多言語対応したアプリケーションサーバーです。
    + Go、Node.js、Perl、PHP、Python、Ruby
    + 言語の異なるバージョンでも共存可能
+ REST API経由でサーバー無停止で設定変更が可能です。

# 今回のゴール
+ EC2（Amazon Linux2）に`nginx unit`をインストールしてDjangoアプリケーションのデプロイするまでを書きます。

## 書かないこと
+ Djangoアプリケーションの一般的な環境設定のこと
+ `uWSGI`や`Gunicorn`との比較

# 各種バージョン
+ Amazon Linux2
+ Python 3.7.9
+ Django 3.1.1
+ MariaDB 15.1
+ Nginx Unit 1.21.0

# インストール

[公式ページ](http://unit.nginx.org/installation/#amazon-20lts)を確認しながら進めます。

`/etc/yum.repos.d/unit.repo`を作成し、リポジトリの追加をします。

```
[unit]
name=unit repo
baseurl=https://packages.nginx.org/unit/amzn2/$releasever/$basearch/
gpgcheck=0
enabled=1
```

```
# nginx unitのインストール
$ sudo yum install unit 

# 必要なモジュールをインストール
$ sudo yum install unit-devel unit-python37

# 自動起動の設定
$ sudo systemctl enable unit

# nginx unitの起動
$ sudo systemctl start unit

# helpオプションで各設定のデフォルト値の確認ができます。
$ unitd --help 

unit options:

  --version            print unit version and configure options

  --no-daemon          run unit in non-daemon mode

  --control ADDRESS    set address of control API socket
                       default: "unix:/var/run/unit/control.sock"

  --pid FILE           set pid filename
                       default: "/var/run/unit/unit.pid"

  --log FILE           set log filename
                       default: "/var/log/unit/unit.log"

  --modules DIRECTORY  set modules directory name
                       default: "/usr/lib64/unit/modules"

  --state DIRECTORY    set state directory name
                       default: "/var/lib/unit"

  --tmp DIRECTORY      set tmp directory name
                       default: "/var/tmp"

  --user USER          set non-privileged processes to run as specified user
                       default: "nobody"

  --group GROUP        set non-privileged processes to run as specified group
                       default: user's primary group

```

# 環境の確認

`nginx unit`は、REST API経由で環境確認や設定を行います。

```
$ sudo curl --unix-socket /var/run/unit/control.sock http://localhost/
{
	"certificates": {},
	"config": {
		"listeners": {},
		"applications": {}
	}
}
```
現状は、なにも設定されていない状態です。

# Djangoアプリケーションのデプロイ

Djangoアプリケーションのデプロイも[公式ページ](http://unit.nginx.org/howto/django/)に沿って進めます。
デプロイ先を`/var/www`配下と想定して設定用ファイル（config.json）を作成します。
config.jsonの格納先は悩みましたが、今回はDjangoアプリケーション配下に置くようにしました。


```config.json
{
    "listeners": {
        "*:80": {
            "pass": "routes"
        }
    },
    "routes": [
        {
            "match": {
                "uri": "/static/*"
            },

            "action": {
                "share": "/var/www/my_djangoapp/"
            }
        },
        {
            "action": {
                "pass": "applications/django"
            }
        }
    ],
    "applications": {
        "django": {
            "type": "python 3.7",
            "user": "ec2-user",
            "group": "ec2-user",
            "path": "/var/www/my_djangoapp/",
            "home": "/var/www/my_djangoapp/env/",
            "module": "my_djangoapp.wsgi",
        }
    }
}
```

以下のように`config.json`を適用します。

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

以上で`nginx unit`の設定は終了です。Djangoアプリケーションの`migrate`なり、`collectstatic`をして環境と整えることは別途必要です。


## 設定上のポイント
* config.jsonの`type`で指定する名称は、`--modules`で指定しているディレクトリのモジュール名を指定するのですが、バージョンの間に*空白*を入れる必要があるようです。

* Djangoアプリケーションのディレクトリは、config.jsonで指定する`user`と`group`で指定した値で適切に所有権を設定してあげる必要があります。

# 所感
+ マイクロサービスで複数の言語やバージョンを扱うWebアプリケーションを運用する場合は、`nginx unit`のみで保守運用出来そうなため選択肢の一つとして検討しても良いように思いました。

+ REST API経由での設定は新鮮ではあったが、調査範囲では構文チェックする機能が見当たらずログファイルを確認しながらエラーチェックをするのが少々厳しかったです。

+ PythonのみWebアプリケーションを想定している場合は、`uWSGI`や`Gunicorn`でも充分かなと思いました。