デプロイメイントTips

Last Change: 04-Sep-2015.
author : qh73xe

このページでは Django のデプロイメントに関する情報を記述します.

注釈

デプロイメントとは?

Django は基本的に WEB アプリを作成するためのライブラリです. WEB アプリということは サーバー で機能するアプリケーションということです. デプロイメントとは,ようはローカルで作成していたアプリケーションを実際にアプリケーションが機能するサーバーに 設置するという意味です.

ここでサーバーは LINUX であることを前提とします. WINDOWS サーバーは問題にしません.

CGI を使用する

Django の主要なデプロイプラットフォームは, Web サーバと Web アプリケーション に関して Python の標準である WSGI です。 しかし,これを設定していくには,どうしても appach の設定を弄る必要があり, 開発者がサーバーの SUDO 権限を持っている必要があります.

というか,SUDO 権限を持たないサーバーを使用している場合には,そもそも Django のインストールですら 困難な場合も多いかと思います.

このような場合でも CGI なら動く場合が多いのでその対策を記述します. 多分デプロイメント環境の作成はこれが一番てっとり速いかと思います.

サーバーに Django 環境を作成する

さて,ではまずサーバーに Django 環境を構築していきましょう. 幸いなことに python は仮想環境が充実しています. 要は home 以下に サーバーネイティブとは異なる Python 環境を構築し, ここからゴニョゴニョしていけば,みんな幸せになれるという話です.

これを行うには pyenv というものを使用します.

git clone https://github.com/yyuu/pyenv.git ~/.pyenv
git clone https://github.com/yyuu/pyenv-virtualenv.git ~/.pyenv/plugins/pyenv-virtualenv

警告

サーバーの条件に関して

上記の設定を行うには git が必要になります. これは多分,もともと入っている環境とそうではないものがありますので, 入っていない場合,ちょっとごめんなさいです

これで,~/.pyenv 以下に python 環境が構築されます. ただし,そのままではパスが通っていないので,サーバーの方の python が実行されてしまいます.

各人が使用する python を指定するには以下の設定を ~/.bash_profile というファイルに書き込みます.

.bash_profile
 export PYENV_ROOT="$HOME/.pyenv"
 export PATH="$PYENV_ROOT/bin:$PATH"
 export TMPDIR="$HOME/tmp"
 export PYTHON_PATH=./
 eval "$(pyenv init -)"
 eval "$(pyenv virtualenv-init -)"

ざっくり内容を説明すると, ~/.pyenv を python 用のライブラリとか入っているディレクトリとして登録し, ~/.pyenv/bin 以下にあるコマンドを bash から呼びだせるようにし(path に追加し), ~/tmp を一時ファイル置き場とし(ライブラリの導入等には必要で,これがhomeよりうえではsudoが必要になるので) ~/ 以下も python から使用できるようにし,pyenv init, pyenv virtualenv-initというコマンドを先に実行しておくという意味です.

注釈

.bash_profile とは

bash の設定ファイルの一つです. ということは当然このページの内容はログインシェルが bash であることを前提にしています.

  • zsh とか使用している人は当然適宜設定を変えてくれますよね.

要は,サーバーにログインした際に読み込まれる設定で, Path の管理等を行います.

bash 自身にはいくつかの設定ファイルがありますが,詳しくは以下のページが分かりやすいかと思います.

上記の設定が終了すると 次回から pyenv の環境を使用する準備ができました. でもまだ,pyenv 上に python 環境を構築していませんし, Django の導入も行っていません. これらを行うには以下のコマンドを入力していきます.

pyenv install 2.7.9
pyenv virtualenv 2.7.9 django-example-python2
pyenv activate django-example-python2
pip install django

これもざっくり解説していくと, まず,一行目で ~/.pyenv 以下に python 環境を作成しています. ここでは python 2 系を導入しています.

  • 現在の最新版を導入していますが,時期が経てばバージョンはアップデートされるかもです.
    • その時には適当に修正してください.

二行目では一行目で導入した python を使用して, django-example-python2 という名前の virtualenv 環境を作成しています.

  • これは python のライブラリをおいておく,入れ物だと思っていただければ問題無いです.

で 三行目では django-example-python2 をアクティブにしています. こうすることで,上記の入れ物の中に入っているライブラリを使用することができます.

  • これは複数のプロダクトを作成する場合には便利な機能です.
    • このコマンドに関しては一度ログアウトした後では無効になってしまう(というかそうでないと意味がない)ので,ログイン時には毎回入力する必要があります

最後の行で Django の導入をしています. Django 以外に必要なライブラリがある場合には,ここで追加を行います.

これでサーバー内に Django の導入ができました.

デプロイメント環境の作成

以下ではいよいよ Django のデプロイメントを行っていきます. まずは cgi が許可されているディレクトリに(私の使用している環境では~/public_html 以下にCGIを置くことが可能です)Django の CGI を置くための ディレクトリ を作成します.

警告

Django のデプロイメント環境に関して

ここで,上記のディレクトリはあくまでも CGI を置くためのディレクトリであることに注意してください. 何か言いたいかというと Django の場合,CGI と Django アプリケーションは別のものです. CGI は,あるURLにアクセスしていた場合にどの Django アプリケーションを実行するのかを指示するための窓口に過ぎません.

mkdir -p ~/public_html/django_test
cd /path/to/django-app/
pyenv local django-example-python2

まず CGI が設置できるディレクトリ以下に Django アプリケーション用の CGI を置くためのディレクトリを用意します.

  • ここで CGI を置くのではなく,ディレクトリを用意するのは css や js 等の静的なファイルをこのディレクトリ以下の設置する必要があるからです

二行目の /path/to/django-app/ には,公開したい Django のプロジェクトルートディレクトリを指定してください. また 3行目の django-example-python2 ですが,これは Django 用に作成した pyenv 環境名を指定します.

Django の設定ファイルを変更する

ここで Django の設定ファイルを変更します. おそらくはローカルで Django app を開発している際に色々と設定ファイルも変更していると思うので, 同じ設定ファイルを編集してしまうよりも,別の設定ファイルを用意し,今までの設定を継承して置くほうが 何かと楽な気がします.そのため Django の設定ファイル(settings.py)と同じディレクトリに production_settings.py というファイルを用意して これを使用します.

以下に私が使用した production_settings.py の内容を記述しておきます.

production_settings.py
# encoding=utf-8
from .settings import *
DEBUG = False
ALLOWED_HOSTS = ["*"]

STATIC_URL = '/static/'
STATIC_ROOT = '/path/to/Django/cgi/directory/static/'

from .settings import * の部分で今までの 設定ファイルを継承しています. その上で DEBUG = False を指定します. これはサーバーで公開する際の流儀だと思ってください. ローカル環境では何らかのエラーが起きた際にはデバック情報を伝えてくれた方が便利ですが, この情報を公開してしまうのはあまりに危険です.

ALLOWED_HOSTS ではサイトをホストするドメイン名を設定します. 面倒くさい場合上記の用にワイルドカードをおいておけばいいです(ごめんなさい,よくわかっていない) STATIC_URL, STATIC_ROOT は静的なファイルの置き場設定です. デプロイメント環境では STATIC_ROOT で設定された場所に静的なファイルを公開します.

では上記の設定を反映させて行きましょう

cd /path/to/django-app/
python manage.py migrate --settings=<app-name>.production_settings
python manage.py createsuperuser --settings=<app-name>.production_settings
python manage.py collectstatic --settings=<app-name>.production_settings

とりあえず,一行目では Django 本体に移動しています. 二行目以降 <app-name> の部分は 作成した Django 本体のプロジェクト名にしてください.

  • パスのつなぎは / ではなく . なので注意

CGI を作成する

では最後に CGI を作成していきます. テンプレートは以下の通りです.

django.cgi
#!/path/to/pyenv/directory/bin/python/
# encoding: utf-8
import os, sys

# Change this to the directory above your site code.
sys.path.append("/path/to/django/app/directory")

def run_with_cgi(application):

    environ = dict(os.environ.items())
    environ['wsgi.input'] = sys.stdin
    environ['wsgi.errors'] = sys.stderr
    environ['wsgi.version'] = (1,0)
    environ['wsgi.multithread'] = False
    environ['wsgi.multiprocess'] = True
    environ['wsgi.run_once'] = True

    if environ.get('HTTPS','off') in ('on','1'):
        environ['wsgi.url_scheme'] = 'https'
    else:
        environ['wsgi.url_scheme'] = 'http'

    headers_set  = []
    headers_sent = []

    def write(data):
        if not headers_set:
             raise AssertionError("write() before start_response()")

        elif not headers_sent:
             # Before the first output, send the stored headers
             status, response_headers = headers_sent[:] = headers_set
             sys.stdout.write('Status: %s\r\n' % status)
             for header in response_headers:
                 sys.stdout.write('%s: %s\r\n' % header)
             sys.stdout.write('\r\n')

        sys.stdout.write(data)
        sys.stdout.flush()

    def start_response(status,response_headers,exc_info=None):
        if exc_info:
            try:
                if headers_sent:
                    # Re-raise original exception if headers sent
                    raise exc_info[0], exc_info[1], exc_info[2]
            finally:
                exc_info = None     # avoid dangling circular ref
        elif headers_set:
            raise AssertionError("Headers already set!")

        headers_set[:] = [status,response_headers]
        return write

    result = application(environ, start_response)
    try:
        for data in result:
            if data:    # don't send headers until body appears
                write(data)
        if not headers_sent:
            write('')   # send headers now if body was empty
    finally:
        if hasattr(result,'close'):
            result.close()

# Change to the name of your settings module
os.environ['DJANGO_SETTINGS_MODULE'] = '<app-name>.production_settings'
from django.core.wsgi import get_wsgi_application
run_with_cgi(get_wsgi_application())

さて,このテンプレートの修正箇所は以下の三点です. 一行目 #!/path/to/pyenv/directory/bin/python/ ですが,この行は,このスクリプトを 実行する python を指定します.そのため pyenv で作成したディレクトリを指定してください. このページに記述した通りの設定を行っている場合には,以下の設定で問題無いはずです.

  • #!/home/<user-name>/.pyenv/versions/django-example-python2/bin/python
    • <user-name>: サーバーのアカウント名です.

続いて, # Change this to the directory above your site code. の行の後にDjangoのプロジェクトのパスを追加します.

  • sys.path.append(“/home/<user-name>/path/to/django/app”)

末尾のほうの application.settings を今回利用する <app-name>.production_settings に変更します.

  • <app-name>: 作成したDjangoアプリケーション名

これで該当のディレクトリにブラウザでアクセスすれば Djnago が起動すると思います.

sqlite3 を使用する

sqlite3 では データベースがファイル単位で作成されます. このため,ファイルの権限の問題が生じうることに注意してください. 具体的には db ファイルが存在しているディレクトリ 及び db ファイル自身に書き込み権限が必要です.

とりあえず公開用の setting.py を以下のように変更してアプリケーションと DB ファイルの場所を変えます.

公開用 setting.py
 DATABASES = {
     'default': {
         'ENGINE': 'django.db.backends.sqlite3',
         'NAME': '/path/to/django-app/db/db.sqlite3',
     }
 }

注釈

setting.py の変更について

多分,これは任意です. ただ,アプリケーション本体があるディレクトリそのものに対して他人に書き込み権限を与えるのは生理的に嫌なので

sudo chmod u+w+x,g+w+x django-app/db
sudo chmod u+w+x,g+w+x django-app/db/db.sqlite3'

ログイン, ログアウトの設定

アプリケーション側でログイン,ログアウトを設定している場合, サーバーとの相性が色々あると思います. 特に私の場合,サクラサーバーのように有名なコトロではなく,研究室のサーバーを使用 しているものですから(管理者権限なしで),.htaccess がうまく機能しなかったりします.

このような場合 setteing.py の方で制御してしまう方が楽な場合もあります. 具体的に以下の設定を使用します.

公開用 setting.py
 LOGIN_URL = '/~username/path/to/cgi/accounts/login/'
 LOGIN_REDIRECT_URL = '/~username/path/to/cgi/'
 LOGOUT_URL = '/~username/path/to/cgi/accounts/logout/'

注釈

ここで前提にするサーバー設定について

ここで前提にする appach の設定では各ユーザーのホームディレクトリに,公開用ディレクトリが存在しているものとします. そこへアクセスするには http://サーバーのURL/~username とすると前提にしています.

git による自動デプロイメント

さて上記の環境で,大体のものは動きます. 後足りないものといえば自動デプロイメントではないでしょうか?

要はいちいち,更新したスクリプトをサーバーにあげて,指定の場所に指定のものをおいて... というのは面倒なので, 自動化しましょうというお話です.

私の場合,大抵のプロジェクトは git を使って(githubではなく)管理をしているので, この機能を利用します.

具体的には git の hook という機能を使用します. これは git レポジトリに何かが起きたとき,それに対応するシェルスクリプトを実行するという 機能です.

目標は,django を管理している git レポジトリに向けて push をすれば,そのサーバー の中で自動デプロイメントを行ってくれ,その結果を示してくれるという感じです.

まず,設定をしていくファイルですが, Djnago を管理している git レポジトリ内にある hook というディレクトリに作成します. ここにはいくつかのサンプルが用意されています.

hook ディレクトリに post-update というファイルを作成してみましょう.

post-update
#!/bin/sh
# An example hook script to prepare a packed repository for use over
# dumb transports.
#
# To enable this hook, rename this file to "post-update".

# pyenv を使用できるようにする
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
export TMPDIR="$HOME/tmp"
export PYTHON_PATH=./
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"
pyenv activate django-example-python2
# pyenv が active になっているのかを確認
if [ $? -eq 0 ];
    then
    echo 'pyenv active'
else
    echo 'can not activate for pyenv'
fi

cd <<django-app>>
git --git-dir=.git pull
# pull が成功したのかを確認
if [ $? -eq 0 ];
    then
    echo 'pull OK'
else
    echo 'can not pull'
fi
cd <django-cgi>
python manage.py collectstatic --noinput --settings=<django-app-name>.production_settings
# static ディレクトリを配置できたのかを確認
if [ $? -eq 0 ];
    then
    echo 'All GREEN'
else
    echo 'can not deployment of static files'
fi

ほぼ,愚直に今までやっていたことを記述しただけです. 途中,<django-app> 等の記述はご自分の環境に合わせてください.