これは、なにをしたくて書いたもの?
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
サポートしているプラットフォームは、こちら。基本的に、Unix、Linux系みたいですね。
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をデプロイする方法が書いてあります。
$ 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そうですね!
こんなところで、今回はおしまいです。