CLOVER🍀

That was when it all began.

uWSGIを試してみる

これは、なにをしたくて書いたもの?

PythonでWebアプリケーションを書くためのインターフェースとして、WSGIというものがあるそうです。

Web Server Gateway Interface - Wikipedia

PEP 3333: Python Web Server Gateway Interface v1.0.1 — knzm.readthedocs.org 2012-12-31 documentation

第1回 WSGIの概要:WSGIとPythonでスマートなWebアプリケーション開発を|gihyo.jp … 技術評論社

WSGIって、「ウィズギー」って読むんですね…。

で、このWSGIに対応しているサーバーのひとつとして、uWSGIというものがあるらしいので、今回試してみることにしました。

The uWSGI project — uWSGI 2.0 documentation

GitHub - unbit/uwsgi: uWSGI application server container

uWSGI

WSGIに対応したサーバーで、すごく多機能らしいです。いわゆるアプリケーションサーバーですね。前段に、nginxなどを置くことが
多いようです。

The uWSGI project — uWSGI 2.0 documentation

uwsgi-docker-pycon2015 - Speaker Deck

どうやら、Python以外にもいろいろ動かせるらしく…。

Supported languages and platforms — uWSGI 2.0 documentation

また、マルチプロセス、マルチスレッドでも動作させられるようです。

Adding concurrency and monitoring

サポートしているプラットフォームは、こちら。基本的に、UnixLinux系みたいですね。

Supported Platforms/Systems — uWSGI 2.0 documentation

とりあえず、Quick Startに沿って試してみましょう。

環境

今回の環境は、こちらです。

$ python3 -V
Python 3.6.7

インストールと簡単なWSGIアプリケーションの作成。

まずは、venvで環境を作ってからuWSGIのインストール。

$ python3 -m venv venv
$ . venv/bin/activate
$ pip3 install uwsgi

バージョン。

$ pip3 freeze
...
uWSGI==2.0.18

確認。

$ uwsgi --version
2.0.18


$ uwsgi --help
Usage: /patht/to/venv/bin/uwsgi [options...]
    -s|--socket                             bind to the specified UNIX/TCP socket using default protocol
    -s|--uwsgi-socket                       bind to the specified UNIX/TCP socket using uwsgi protocol
    --http-socket                           bind to the specified UNIX/TCP socket using HTTP protocol
    --http-socket-modifier1                 force the specified modifier1 when using HTTP protocol
    --http-socket-modifier2                 force the specified modifier2 when using HTTP protocol
    --http11-socket                         bind to the specified UNIX/TCP socket using HTTP 1.1 (Keep-Alive) protocol
    --fastcgi-socket                        bind to the specified UNIX/TCP socket using FastCGI protocol
    --fastcgi-nph-socket                    bind to the specified UNIX/TCP socket using FastCGI protocol (nph mode)
    --fastcgi-modifier1                     force the specified modifier1 when using FastCGI protocol
    --fastcgi-modifier2                     force the specified modifier2 when using FastCGI protocol
    --scgi-socket                           bind to the specified UNIX/TCP socket using SCGI protocol
    --scgi-nph-socket                       bind to the specified UNIX/TCP socket using SCGI protocol (nph mode)
    --scgi-modifier1                        force the specified modifier1 when using SCGI protocol
    --scgi-modifier2                        force the specified modifier2 when using SCGI protocol
    --raw-socket                            bind to the specified UNIX/TCP socket using RAW protocol
    --raw-modifier1                         force the specified modifier1 when using RAW protocol
    --raw-modifier2                         force the specified modifier2 when using RAW protocol
    --puwsgi-socket                         bind to the specified UNIX/TCP socket using persistent uwsgi protocol (puwsgi)
    --protocol                              force the specified protocol for default sockets
    --socket-protocol                       force the specified protocol for default sockets
    --shared-socket                         create a shared socket for advanced jailing or ipc
    --undeferred-shared-socket              create a shared socket for advanced jailing or ipc (undeferred mode)
    -p|--processes                          spawn the specified number of workers/processes
    -p|--workers                            spawn the specified number of workers/processes
    --thunder-lock                          serialize accept() usage (if possible)
    -t|--harakiri                           set harakiri timeout

〜省略〜

これで、インストールは完了です。

簡単なWSGIアプリケーションを作成してみます。 simple-wsgi-app.py

def application(env, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return [b'Hello WSGI!!']

このスクリプトを使って、uWSGIを起動してみます。

$ uwsgi --http :8000 --wsgi-file simple-wsgi-app.py

確認。

$ curl -i localhost:8000
HTTP/1.1 200 OK
Content-Type: text/plain

Hello WSGI!!

なにかパスを見ているわけではないので、どんなアクセスをしても同じ内容が返ってきます。

$ curl -i localhost:8000/foobar
HTTP/1.1 200 OK
Content-Type: text/plain

Hello WSGI!!


$ curl -i localhost:8000/hoge
HTTP/1.1 200 OK
Content-Type: text/plain

ここで、uWSGIが利用するスレッドおよびプロセスを増やしてみましょう。

Adding concurrency and monitoring

デフォルトでは、以下の状態でした。

$ ps aux -L | grep uwsgi | grep -v grep
xxxxx 26484 26484  2.0    1  0.0  57380 14188 pts/4    S+   22:45   0:00 uwsgi --http :8000 --wsgi-file simple-wsgi-app.py
xxxxx 26485 26485  0.0    1  0.0  35428   788 pts/4    S+   22:45   0:00 uwsgi --http :8000 --wsgi-file simple-wsgi-app.py

とりあえず、ドキュメントどおりにプロセス数4、スレッド数2で起動してみましょう。

$ uwsgi --http :8000 --wsgi-file simple-wsgi-app.py --master --processes 4 --threads 2

起動時に、こんな表記が出るようになりました。

spawned uWSGI master process (pid: 26561)
spawned uWSGI worker 1 (pid: 26562, cores: 2)
spawned uWSGI worker 2 (pid: 26563, cores: 2)
spawned uWSGI worker 3 (pid: 26564, cores: 2)
spawned uWSGI worker 4 (pid: 26565, cores: 2)
spawned uWSGI http 1 (pid: 26566)

プロセスあたり2つのスレッドができ、その状態のプロセスが4つありますね。スレッドは、計8つ。

$ ps aux -L | grep uwsgi | grep -v grep
xxxxx 26561 26561  0.0    1  0.0  49760 11084 pts/4    S+   22:47   0:00 uwsgi --http :8000 --wsgi-file simple-wsgi-app.py --master --processes 4 --threads 2
xxxxx 26562 26562  0.0    2  0.0 131568 12860 pts/4    Sl+  22:47   0:00 uwsgi --http :8000 --wsgi-file simple-wsgi-app.py --master --processes 4 --threads 2
xxxxx 26562 26568  0.0    2  0.0 131568 12860 pts/4    Sl+  22:47   0:00 uwsgi --http :8000 --wsgi-file simple-wsgi-app.py --master --processes 4 --threads 2
xxxxx 26563 26563  0.0    2  0.0 131568 12840 pts/4    Sl+  22:47   0:00 uwsgi --http :8000 --wsgi-file simple-wsgi-app.py --master --processes 4 --threads 2
xxxxx 26563 26567  0.0    2  0.0 131568 12840 pts/4    Sl+  22:47   0:00 uwsgi --http :8000 --wsgi-file simple-wsgi-app.py --master --processes 4 --threads 2
xxxxx 26564 26564  0.0    2  0.0 131568 12772 pts/4    Sl+  22:47   0:00 uwsgi --http :8000 --wsgi-file simple-wsgi-app.py --master --processes 4 --threads 2
xxxxx 26564 26570  0.0    2  0.0 131568 12772 pts/4    Sl+  22:47   0:00 uwsgi --http :8000 --wsgi-file simple-wsgi-app.py --master --processes 4 --threads 2
xxxxx 26565 26565  0.0    2  0.0 131568 12756 pts/4    Sl+  22:47   0:00 uwsgi --http :8000 --wsgi-file simple-wsgi-app.py --master --processes 4 --threads 2
xxxxx 26565 26569  0.0    2  0.0 131568 12756 pts/4    Sl+  22:47   0:00 uwsgi --http :8000 --wsgi-file simple-wsgi-app.py --master --processes 4 --threads 2
xxxxx 26566 26566  0.0    1  0.0  49760  3524 pts/4    S+   22:47   0:00 uwsgi --http :8000 --wsgi-file simple-wsgi-app.py --master --processes 4 --threads 2

先の表示を見ると、残りはmasterプロセスとhttpプロセスになりますね。

ここで「master」というのは、用語を見るとPrefork+マルチスレッドなワーカーモードのようです。

Glossary — uWSGI 2.0 documentation

This will spawn 4 processes (each with 2 threads), a master process (will respawn your processes when they die) and the HTTP router (seen before).

Adding concurrency and monitoring

ドキュメントを見ると、masterプロセスというのは他のプロセスが終了した時にプロセスを再生成する役割を担うようです。

このプロセス、スレッドをどのようにチューニングするかとかは、あんまり書いていないんですよねぇ…。

There is no magic rule for setting the number of processes or threads to use. It is very much application and system dependent. Simple math like processes = 2 * cpucores will not be enough. You need to experiment with various setups and be prepared to constantly monitor your apps. uwsgitop could be a great tool to find the best values.

Things to know (best practices and “issues”) READ IT !!! — uWSGI 2.0 documentation

ちなみに、なにも指定しなかった時はこういう表示でした。

## $ uwsgi --http :8000 --wsgi-file simple-wsgi-app.py
spawned uWSGI worker 1 (and the only) (pid: 26891, cores: 1)

ここで、先ほどのアプリケーションを少し修正して、関数の第1引数に入っていた情報を標準出力に書き出してみましょう。

def application(env, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    print(env)
    return [b'Hello WSGI!!']

結果、こんな情報が得られました。

{'REQUEST_METHOD': 'GET', 'REQUEST_URI': '/', 'PATH_INFO': '/', 'QUERY_STRING': '', 'SERVER_PROTOCOL': 'HTTP/1.1', 'SCRIPT_NAME': '', 'SERVER_NAME': 'xxxxx', 'SERVER_PORT': '8000', 'UWSGI_ROUTER': 'http', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': '58540', 'HTTP_HOST': 'localhost:8000', 'HTTP_USER_AGENT': 'curl/7.58.0', 'HTTP_ACCEPT': '*/*', 'wsgi.input': <uwsgi._Input object at 0x7f454d960780>, 'wsgi.file_wrapper': <built-in function uwsgi_sendfile>, 'wsgi.version': (1, 0), 'wsgi.errors': <_io.TextIOWrapper name=2 mode='w' encoding='UTF-8'>, 'wsgi.run_once': False, 'wsgi.multithread': False, 'wsgi.multiprocess': False, 'wsgi.url_scheme': 'http', 'uwsgi.version': b'2.0.18', 'uwsgi.node': b'xxxxx'}

リクエストの情報や、実行環境の情報が入るようですね。

Djangoで作ったアプリケーションをuWSGIで動かしてみる

それでは、最後にDjangoをuWSGIで動かしてみましょう。

ドキュメントにも、Djangoをデプロイする方法が書いてあります。

Deploying Django

$ pip3 install Django
$ pip3 freeze
...
Django==2.2
...
$ django-admin startproject mysite
$ cd mysite
$ django-admin startapp myapp

以下のように、アプリケーションを作成します。
mysite/urls.py

from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('myapp/', include('myapp.urls')),
    path('admin/', admin.site.urls),
]

mysite/settings.pyより抜粋。

INSTALLED_APPS = [
    'myapp.apps.MyappConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

myapp/urls.py

from django.urls import path

from . import views

urlpatterns = [
    path('', views.index, name = 'index'),
]

myapp/views.py

from django.shortcuts import render
from django.http import HttpResponse

# Create your views here.
def index(request):
    return HttpResponse('Hello Django!!')

まずは、Djangoの開発用サーバーで確認。

$ python3 manage.py runserver


$ curl -i localhost:8000/myapp/
HTTP/1.1 200 OK
Date: Sat, 13 Apr 2019 14:13:16 GMT
Server: WSGIServer/0.2 CPython/3.6.7
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 14

Hello Django!!

では、今度はuWSGIを使ってDjangoアプリケーションを起動します。

$ uwsgi --http :8000 --wsgi-file mysite/wsgi.py

プロジェクト側に、「wsgi.py」というファイルがあるので、こちらを使用します。

確認。

$ curl -i localhost:8000/myapp/
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 14

Hello Django!!

OKそうですね!

こんなところで、今回はおしまいです。