Friday, March 18, 2016

Python + Django + uWSGIをセットアップ

Python + Djangoの最新版(Python 3.5.1、Django 1.9.4)を使って動かしてみます。簡単なDjangoのサンプルプログラムを用意して、ブラウザで見れるようにします。
WebサーバにはuWSGI + nginxをセットアップします。uWSGIは、Upstartを使ってデーモンを起動するようにします。
OSは、EC2の Ubuntu Server 14.04 LTS (HVM), SSD Volume Type - ami-a21529cc です。

個人的に、PythonでWebサービスを書いたことがなかったので、どういうふうになっているのか興味があって調べてみた。というのが今回の動機です。
以下、セットアップ方法です。ステップバイステップで説明していきます。

Python 3.5をインストール
システムにインストールします。
複数バージョンのPythonを扱うpythonzというプロダクトもありますが、ここでは使わないでおきます。
fkrull's repository (https://launchpad.net/~fkrull/+archive/ubuntu/deadsnakes)を使います。
$ sudo add-apt-repository ppa:fkrull/deadsnakes
$ sudo aptitude update
$ sudo apt-get install python3.5 python3.5-dev python3-pip python-pip
/usr/bin/pythonへシンボリックリンクを貼る。
$ sudo rm /usr/bin/python
$ sudo ln -s /usr/bin/python3.5 /usr/bin/python
バージョン確認。
$ python --version
Python 3.5.1
$ pip --version
pip 1.5.4 from /usr/lib/python3/dist-packages (python 3.5)

Djangoのサンプルアプリを準備
テスト用ディレクトリを作って、DjangoとuWSGIをインストール。
$ mkdir test-app
$ cd test-app/
$ emacs requirements.txt
Django==1.9.4
uWSGI==2.0.12
$ sudo pip install -r requirements.txt
テスト用プロジェクトを作成。
$ django-admin startproject myweb
サンプルアプリを作成。
$ cd myweb
$ python manage.py startapp hello
$ emacs myweb/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'hello',
]

$ emacs hello/views.py
from django.http import HttpResponse
def main(request):
        return HttpResponse("Hello!")

$ emacs myweb/urls.py
urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'^hello/', 'hello.views.main'),
]
マイグレーションを実行。
$ python manage.py migrate
簡易Webサーバを立てて、HTTPでアクセスしてみます。
$ python manage.py runserver 0.0.0.0:8000
$ curl http://127.0.0.1:8000/hello/
Hello!
サンプルアプリの準備ができました。

nginxのインストール
nginxをインストールします。
UNIXドメインソケットを通じてWSGIへ繋ぐように設定します。
uwsgi_passは都合よく変更してください。
$ sudo aptitude install nginx-full
$ sudo emacs /etc/nginx/sites-available/default
server {
    listen 80;
    location / {
        include uwsgi_params;
        uwsgi_param Host $host;
        uwsgi_param X-Real-IP $remote_addr;
        uwsgi_param X-Forwarded-For $proxy_add_x_forwarded_for;
        uwsgi_param X-Forwarded-Proto $http_x_forwarded_proto;
        uwsgi_pass unix:/home/yako/test-app/myweb/uwsgi.sock;
    }
}

$ sudo service nginx restart

uWSGI設定ファイルを編集
uWSGIは、スレッドをサポートしていますが、スレッドを有効にすると graceful restart がうなく動かなかったので、ここでは有効にしませんでした。
$ cd /home/yako/test-app/myweb
$ emacs uwsgi_staging.ini
[uwsgi]
base = /home/yako/test-app/myweb
chdir           = %(base)
socket          = %(base)/uwsgi.sock
pidfile         = %(base)/exchange-server.pid
touch-reload    = %(base)/uwsgi_touch_reload.trigger
touch-logreopen = %(base)/uwsgi_touch_logreopenr.trigger
daemonize       = %(base)/manage.log
module = myweb.wsgi
master = True
vacuum = True
processes = 4
max-requests = 1000
harakiri = 10
chmod-socket = 666
die-on-term = true

Upstart設定ファイルを編集
uWSGIスタート前に、touch-logreopenファイルを削除します。このファイルが存在する状態で、uWSGIをスタートすると、touch-logreopen機能が動きませんでした。
(さらに、uWSGIをreloadするとtouch-logreopen機能が動かなくなりました。良い回避方法があったら教えてください。)
Djangoでファイル名に日本語が入っているファイルのアップロードできるように、export LC_ALL=en_US.UTF-8を入れておきます。
$ sudo emacs /etc/init/uwsgi.conf
description "myweb"

start on runlevel [2345]
stop on runlevel [!2345]

setuid yako
setgid yako

expect daemon
respawn

pre-start script
    # remove touch files
    base=/home/yako/test-app/myweb/
    touch_reload_file=$base/uwsgi_touch_logreopenr.trigger
    touch_logreopen_file=$base/uwsgi_touch_logreopenr.trigger
    if [ -e $touch_reload_file ]; then
        rm $touch_reload_file
    fi
    if [ -e $touch_logreopen_file ]; then
        rm $touch_logreopen_file
    fi
end script

script
    export LC_ALL=en_US.UTF-8
    chdir /home/yako/test-app/myweb
    exec uwsgi --ini uwsgi_staging.ini
end script

サンプルアプリをスタート
$ sudo service uwsgi start
プロセスチェック。
$ ps auxf | grep uwsgi
yako     16870  0.0  0.0  10460   936 pts/0    S+   06:02   0:00  |           \_ grep --color=auto uwsgi
yako     16850  2.8  0.6  97708 26948 ?        S    06:02   0:00 uwsgi --ini uwsgi_staging.ini
yako     16853  0.0  0.6 318904 23324 ?        Sl   06:02   0:00  \_ uwsgi --ini uwsgi_staging.ini
yako     16854  0.0  0.6 318904 23136 ?        Sl   06:02   0:00  \_ uwsgi --ini uwsgi_staging.ini
yako     16855  0.0  0.6 318904 23336 ?        Sl   06:02   0:00  \_ uwsgi --ini uwsgi_staging.ini
yako     16859  0.0  0.6 318904 23324 ?        Sl   06:02   0:00  \_ uwsgi --ini uwsgi_staging.ini
ログを確認。
$ tail -f /home/yako/test-app/myweb/manage.log
HTTPでアクセスしてみます。
curl -> nginx -> uWSGI -> Django と、繋がる形になります。
$ curl 127.0.0.1/hello/
Hello!
うまくいきました。

uWSGIの運用
ログファイルをローテートするときは、touch-logreopenで設定したファイルを更新します。
$ touch /home/yako/test-app/myweb/uwsgi_touch_logreopenr.trigger


See also)
Django Under The Hood Photo by Bartek Pawlik
https://www.flickr.com/photos/137962885@N08/22793957097/in/photolist-AJdZji-rJ1Av8-9RzYXi-cabjcj-Dgv6rN-rKK85W-s3kxtP-r6x6Gx-5KNeRh-s3kxre-rKK8dG-s13tgs-5kYmmW-5jMxyf-6cBmZt-djfBr4-6eVGGw-ekyaUg-dUbX9V-4ptVd-B9As9d-81dkcd-4JvxJL-B9AB59-5Sz4Gp-s3cT3G-r6x6Mn-r6kjvU-rKK8p3-rKTa8D-s3kxt8-r6kjou-rJ1Anc-r6x6Ra-5jDb1t-ASV1Fw-dKUbwX-BhKcMS-s3kxvc-s3cSwb-rKLg1L-rJ1ABa-s3gZ3P-rJ1A7c-s3ky8e-s13tq5-rKK8aA-FEgA9-cYohhb-AnvLtC
The Web framework for perfectionists with deadlines | Django
https://www.djangoproject.com/
The uWSGI project — uWSGI 2.0 documentation
http://uwsgi-docs.readthedocs.org/en/latest/index.html
Running uWSGI via Upstart — uWSGI 2.0 documentation What is –die-on-term?
http://uwsgi-docs.readthedocs.org/en/latest/Upstart.html#what-is-die-on-term
Managing the uWSGI server — uWSGI 2.0 documentation
http://uwsgi-docs.readthedocs.org/en/latest/Management.html#signals-for-controlling-uwsgi
NGINX as an Application Gateway with uWSGI and Django
https://www.nginx.com/resources/admin-guide/gateway-uwsgi-django/
EC2上で Django + Nginx + uWSGI を試す - Qiita
http://qiita.com/melos/items/91dc3b45a589ea5a369b