Unix Domain SocketによるuWSGIとNginxの通信

本記事は[東京学芸大学 櫨山研究室 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/hazelab)の16日目の記事になります．

# はじめに

本記事はPythonのWebフレームワークであるflaskで作成したアプリケーションをuWSGIサーバを使って動作させる内容を通してUnix Domain Socket通信について学ぶという内容になります．

# Unix Domain Socketとは

Unix Domain SocketはPOSIX系のOSに搭載されている機能でTCP/UDPによるソケット通信とは異なり，カーネル内部で完結する**高速なネットワークインタフェース**を作成します．
カーネル内部で完結するため外部のコンピュータとは接続することはできません．

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

エンドポイント`/`でアクセスして`Hello, World`とだけ返す簡単なアプリケーションを作成します．

```python:app.py
from flask import Flask
app = Flask(__name__)

@app.route("/")
def index():
    return "Hello World"

if __name__ == "__main__":
    app.run(host="0.0.0.0", debug=True)
```

そして以下のコマンドを実行することでアプリケーションが起動します．
この時，起動には開発用の組み込みサーバが立ち上がります．

```bash
python app.py
```

http://localhost:5000/
にアクセスして`Hello, World`と表示されれば成功です．

# uWSGIを使って動かす

先ほどの例では開発用の組み込みサーバを使用していました．
Pythonにはアプリケーションサーバとして`uWSGI`があります．
`uWSGI`ではUnix Domain Socketでの通信がサポートされています．

まずは`uWSGI`を使って先ほどのアプリケーションを動作させてみます．

```bash
pip install uWSGI
```

```python:app.py
from flask import Flask
app = Flask(__name__)

@app.route("/")
def index():
    return "Hello World"

if __name__ == "__main__":
    app.run()
```

`uWSGI`で先ほどのアプリケーションを起動するには以下のコマンドを入力します．

```bash
uwsgi --http=0.0.0.0:4000 --wsgi-file=app.py --callable=app
```

http://localhost:4000/
にアクセスして`Hello, World`と表示されれば成功です．

`uwsgi.ini`という設定ファイルを作成することでより短いコマンドで起動することも可能です．

```ini:uwsgi.ini
[uwsgi]
wsgi-file=app.py
callable=app

http=0.0.0.0:4000
```

```bash
uwsgi uwsgi.ini
```

# Nginxの導入

さてここからが本題のUnix Domina Socketでのアクセスです．これまでの起動方法ではHTTP通信(TCPソケット)でのアクセスでした．

実際に運用する場合は同一サーバで複数のアプリケーションを動作させることを考慮しNginxをリバースプロキシとする場合が多いでしょう．
この時，同一サーバ内の通信であればUnix Domain Socketを使ってHTTP通信よりも高速にアクセスすることができます．

今回は便宜的にDockerを用いて動作させてみます．

## `uwsgi.ini`の編集

`uwsgi.ini`を編集してUnix Domain Socketで通信するようにします．
Unix Domain Socketではファイルシステム上の指定した位置にソケットファイルができます．
クライアントはソケットファイルにアクセスを試みます．
つまりIPアドレスとポート番号ではなくファイルパスによって通信相手を決めます．

今回は`/var/app/app.sock`というソケットファイルを作成するようにします．

```ini:uwsgi.ini
[uwsgi]
wsgi-file=app.py
callable=app

http=0.0.0.0:4000
# 追記
socket=/var/app/app.sock
```

## Docker環境の準備

まずは先ほどのflaskのアプリケーションを動作させる環境を用意する`Dockerfile`を用意します．

```docker:Dockerfile
FROM python:3.7

ENV LANG C.UTF-8
ENV TZ Asia/Tokyo

WORKDIR /app
ADD app.py ./
ADD uwsgi.ini ./

RUN pip install flask uWSGI

CMD ["uwsgi", "uwsgi.ini"]
```

`nginx`も動かしたいので`docker-compose.yml`を用意します．

```yml:docker-compose.yml
version: '3.7'

services:

  app:
    container_name: uds_app
    build:
      context: .
      dockerfile: ./Dockerfile
    volumes:
      - ./tmp/:/var/app/
    tty: true

  nginx:
    container_name: uds_nginx
    image: nginx
    volumes:
      - ./tmp:/var/app/
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
    ports:
      - 80:80
    tty: true
```

ここで重要なのはホストマシン上の`./tmp`ディレクトリをflaskアプリケーションコンテナとNginxコンテナの両方と共有していることです．

1. ホストマシン上の`./tmp`ディレクトリ ←→ flaskアプリケーションコンテナの`/var/app`ディレクトリ
2. ホストマシン上の`./tmp`ディレクトリ ←→ Nginxコンテナの`/var/app`ディレクトリ

これによりNginxコンテナからもソケットファイルを参照できます．

Nginxの設定ファイルを記述し`/`へのアクセスをflaskのアプリケーションを呼び出すようにします．

```conf:nginx.conf
server {
    listen       80 default_server;
    listen       [::]:80 default_server;
    server_name  _;

    location / {
    include uwsgi_params;
    uwsgi_pass unix:/var/app/app.sock;
    }

    error_page 404 /404.html;
    location = /404.html {
    }

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    }
}
```

`docker-compose up`コマンドで起動してみましょう．

```bash
docker-compose up
```

http://localhost/
にアクセスして先ほどと同様に`Hello, World`の文字列が確認できれば成功です．

# おわりに

本記事ではflaskで作成したアプリケーションにリバースプロキシのNginxからUnix Domain Socketで通信するということを扱いました．

TCP通信と異なりファイルパスによって通信相手を決めるという部分が伝わればと思います．
また余裕があれば性能も調査してきたいですね．