Python 3 + mod_wsgi + Django

CentOS 6.5, Python 3.6.1, Django 1.11.1

私がPythonについて持っている知識というのはこの程度のことしかない。
にわかというレベルですらないが、このたび、PythonでちょっとしたWebアプリを作ってみる機会があった。

以下は記事タイトルの環境を構築した時のメモである。

Python 3.6をインストールする

私が対象にした環境にはPython 2.6がインストールされており、これはyumが内部的に使用しているらしい。
そこで、2.6は残したまま、3.6をインストールすることにした。

$ wget https://www.python.org/ftp/python/3.6.1/Python-3.6.1.tgz
$ tar zxf Python-3.6.1.tgz
$ cd Python-3.6.1
# 64bit 環境で Python3 と mod_wsgi の組み合わせの場合、-fPIC が必要みたい
$ ./configure CFLAGS=-fPIC --enable-shared --prefix=/usr/local
$ make
$ sudo make install
# Python の .so を読み込むパスを通しておく
$ export LD_LIBRARY_PATH=/usr/local/lib
$ /usr/local/bin/python3.6 --version
Python 3.6.1

virtualenvを使ってPython Webアプリの稼働環境を構築する

virtualenvはRubyのrbenv的なもの。
pipはgemみたいなもんかな?
という認識。

ここでは、/var/www/python/ 以下にPythonのWebアプリをデプロイするつもり。
この環境を venv という仮想環境として、venv内では Python 3.6 を使うこととする。

$ curl -kLO https://bootstrap.pypa.io/get-pip.py
$ sudo LD_LIBRARY_PATH=/usr/local/lib /usr/local/bin/python3.6
$ sudo LD_LIBRARY_PATH=/usr/local/lib /usr/local/bin/pip3.6 install virtualenv

$ sudo mkdir -p /var/www/python
$ sudo chown myuid:mygid /var/www/python
$ cd /var/www/python
$ virtualenv venv --python=/usr/local/bin/python3.6 --no-site-packages
$ source venv/bin/activate
(venv) $ which python
/var/www/python/venv/bin/python

Djangoをインストールする

Djangoってのは、Python Webアプリのフレームワーク。RoRみたいなもん。
という認識。

# pipでDjangoをインストール
(venv) $ pip install Django

# test_siteというプロジェクトを新規作成
(venv) $ django-admin.py startproject test_site
(venv) $ cd test_site

# 雛形プロジェクトがSQLiteを使うようになっているが、今は使わないのでコメントアウト
(venv) $ vim test_site/settings.py

DATABASES = {
#     'default': {
#         'ENGINE': 'django.db.backends.sqlite3',
#         'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
#     }
}

# ひとまず内包のHTTPサーバーを起動してみる(バックグラウンド起動させる)
(venv) $ python manage.py runserver &
Performing system checks...

System check identified no issues (0 silenced).

You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.

May 5, 2017 - 00:00:00
Django version 1.11.1, using settings 'myproject.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
^M

# ローカルからHTTP経由でアクセスしてみる
$ curl -s http://localhost:8000/ | grep "<h1>"
  <h1>It worked!</h1>

# 動いているみたいなので、シャットダウン
$ fg 1
python manage.py runserver
^C(venv) $ 

mod_wsgiを使ってApache経由でアクセスできるようにする

mod_wsgiはよくあるApacheの拡張モジュール。
ApacheへのリクエストをPythonに処理させるために必要。
ソースから直接コンパイルするより、pipでインストールした方が良いらしい。

(venv) $ pip install mod_wsgi

これでvenv仮想環境内にmod_wsgiがインストールされたはずなので、ファイルパスを検索する。

$ find /var/www/python/venv -name 'mod_*.so'
/var/www/python/venv/lib/python3.6/site-packages/mod_wsgi/server/mod_wsgi-xxx.so

このパスをコピペして、httd.confに設定する。

$ sudo vim /etc/httpd/conf/httpd.conf
LoadModule wsgi_module /var/www/python/venv/lib/python3.6/site-packages/mod_wsgi/server/mod_wsgi-xxx.so

WSGIPythonHome /var/www/python/venv
WSGIPythonPath /var/www/python/venv/lib/python3.6/site-packages
WSGIScriptAlias /test-site /var/www/python/test_site/test_site/wsgi.py
  • WSGIPythonHomeは、Apacheに使わせるPythonのホームディレクトリー
  • WSGIPythonPathは、実行中にPythonに通させるパス
  • WSGIScriptAliasは引数を2つ取り、1つめのURLがアクセスされたら、2つめのwsgiスクリプトに移譲するという設定

と思われる。

さて、これでhttpd.confの設定確認を行ったところ、下記のようなエラーが発生した。

$ apachectl configtest
httpd: Syntax error on line 9999 of /etc/httpd/conf/httpd.conf: Cannot load /var/www/python/venv/lib/python3.6/site-packages/mod_wsgi/server/mod_wsgi-xxx.so into server: /var/www/python/venv/lib/python3.6/site-packages/mod_wsgi/server/mod_wsgi-xxx.so: undefined symbol: forkpty

どうも、環境によってなのか、Python3でmod_wsgiを使うとこのようなエラーが発生するそうな。
事前にApacheにとあるライブラリーをロードさせておくと良いらしい。

$ sudo vim /etc/sysconfig/httpd
# 以下2行を追加
export LD_PRELOAD=/usr/lib/libutil.so  # これはいらない場合もある
export LD_LIBRARY_PATH=/usr/local/lib  # Python を -fPIC でコンパイルした場合は .so が読み込まれるようにしておく(設定箇所はここじゃなくても良い)

$ sudo apachectl restart

この libutil.so ってやつは、私の環境には既に入っていた。

Apache経由でDjangoにアクセスしてみる

さて、お楽しみの時間だ。
みんな大好きコーディング。

まず、プロジェクト初期化時に自動生成されたsettings.py内のALLOWED_HOSTSという設定項目に、開発中(デバッグモード時)にどこからアクセスされるのか、という設定を行う。

$ vim /var/www/python/test_site/test_site/settings.py

ALLOWED_HOSTS = ['127.0.0.1', 'localhost']

ALLOWD_HOSTSの設定はおそらく、実際にアクセスされた時のHTTPヘッダーのホスト名と比較されるものと思われる。
上記設定ではローカルホストからしかアクセスできないので、実際には、クライアントからアクセスされる時のサーバーのホスト名を設定してやらなければならないだろう。

次に、自動生成されたwsgi.pyをカスタマイズする。
と言っても、プロジェクトのパスを追加するだけである。

$ vim /var/www/python/test_site/test_site/wsgi.py

import os
import sys  # 追加

from django.core.wsgi import get_wsgi_application

sys.path.append('/var/www/python/test_site')             # 追加
sys.path.append('/var/www/python/test_site/test_site')   # 追加

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_site.settings")

application = get_wsgi_application()

テスト用にview.pyという名のコントローラーを作ってみる。
ここでは、動作確認用に、単にHelloテキストを返すだけとする。

$ touch /var/www/python/test_site/test_site/view.py
$ vim /var/www/python/test_site/test_site/view.py

from django.http import HttpResponse

def hello(request):
	return HttpResponse('Hello Django\n', content_type='text/plain')

最後に、urls.py(こちらも自動生成)をカスタマイズして、URLマッピングを行う。
urlpatternsには、urlレコードを配列で登録する。
urlは引数を3つ取る。
1つ目は、アクセスされるURLだが、ここでは正規表現 ^$ として、トップページがパスなしでアクセスされた時を指定している。
2つ目は、そのURLがアクセスされた時に実行される関数を指定する。ここでは、カレントディレクトリーからimportしたview.pyの中のhello関数を指定している。

$ vim /var/www/python/test_site/test_site/urls.py

from django.conf.urls import url
from django.contrib import admin
from . import view  # 追加

urlpatterns = [
	url(r'^$', view.hello, name='hello')  # 元々あった設定を削除して追加
]

動作確認。

$ curl localhost/test-site/
Hello Django