PyPI を使わないでデプロイする方法
pip、buildoutなどを使うとデプロイする時にPythonライブラリの依存関係はややこしいことがあります。普段はデプロイスクリプトで、pipにrequirements.txtを指定して、もしくは、buildoutを実行して、依存ライブラリを落としてインストールしますが、
PyPIがダウンしている場合、環境によって、PyPIにアクセス出来ない場合もありますので、デプロイが止まってしまって困ります。PyPIはダウンしている時にpipはPyPIのミラーを使うことができますが、ミラーに必要がパッケージバージョンが入っていない、ミラーの最後のIDのDNSがちゃんと動いていないときに、pipは当然ちゃんと動かない場合も。Bitbucketや、GitHubからのリポジトリに依存している場合、接続できなかったら、ミラーがないので、当然インストールできます。
つもり、デプロイは外部サイトに依存していて、デプロイを邪魔する問題が出てくる可能性が高いです。
ローカルで必要なライブラリは既にインストールしているので、それを使えばいいじゃん!と自然に思います。実は、情報がなくて、あまり使われてないみたいですが、pipはバンドルを作成する機能があります。バンドルはrequirements.txtの依存ライブラリをzipに固めて、そして、installコマンドで、バンドルからライブラリをインストールしてくれる機能です。つもり、バンドルさえあれば、PyPIにアクセスしたくても良い。
やったぜ! これを使おう
バンドルを作成するのが簡単:
pip bundle -r requirements.txt mybundle.pybundle
インストールも簡単:
pip install mybundle.pybundle
もちろん、virtualenvと組み合わせて使えます。
注意:ファイルの拡張子はpybundleじゃないとinstallコマンドがバンドルを認識してくれない。
バンドルは単のzipファイル:
$ unzip mybundle.pybundle
...
$ ls
build/ pip-manifest.txt mybundle.pybundle src/
$ cat pip-manifest.txt
# This is a pip bundle file, that contains many source packages
# that can be installed as a group. You can install this like:
# pip this_file.zip
# The rest of the file contains a list of all the packages included:
# These packages were installed to satisfy the above requirements:
Django==1.3
django-debug-toolbar==0.8.4
South==0.7.3
...
デプロイ
デプロイはFabricを使います。ローカルで、バンドルを一回作っておけば、依存ライブラリを修正しない限り、そのまま使えます。
まずは、バンドルを作成するコマンドを作る。runs_once()デコレータで一回しか実行しないようにします。local()メソッドでローカルコマンドを叩きます。
from fabric.decorators import runs_once
from fabric.api import local
@runs_once
def create_bundle():
u""" 依存ライブラリのバンドルを作り直す """
local('pip bundle mybundle.pybundle -r requirements.txt')
print 'Created bundle mybundle.pybundle'
次は、コード自体をアップするコマンドを作ります。下記は、Mercurialをsshでpushしています。
from fabric.api import sudo, cd, local
def _hg_pull():
local("hg push -r %(revision)s ssh://%(host_string)s/%(base_path)s" % env
def _hg_update():
with cd(env.base_path):
sudo("hg update -C -r %(revision)s" % env, user=env.deploy_user)
def push():
u""" 最新バージョンに更新 """
_hg_pull(rev)
_hg_update(rev)
次は依存関係の更新
import os
from fabric.api import sudo, cd, put, local
def update_deps():
if not os.path.exists('mybundle.pybundle'):
create_bundle()
put('mybundle.pybundle', '%(base_path)s/mybundle.pybundle' % env, use_sudo=True)
with cd(env.base_path):
sudo('chown %(deploy_user)s:%(deploy_user)s mybundle.pybundle' % env)
sudo('pip install -E %(venv_path)s mybundle.pybundle' % env)
Mercurialの代わりにrsyncを使う場合はこんな感じで、一発でできる。
from fabric.contrib.project import rsync_project
RSYNC_EXCLUDE=[".hg"]
def push():
if not os.path.exists('mybundle.pybundle'):
create_bundle()
rsync_project(
env.base_path,
exclude=RSYNC_EXCLUDE,
delete=True,
)
with cd(env.base_path):
sudo('chown %(deploy_user)s:%(deploy_user)s mybundle.pybundle' % env)
sudo('pip install -E %(venv_path)s mybundle.pybundle' % env)
最後は、deployコマンドを作る
def deploy():
push()
update_deps()
# DB を更新
migrate_db()
# ウェブサーバーを再起動
reboot_server()
こういう感じで、ローカルとサーバーの接続さえできれば、デプロイできます。外部サイトに依存したいのが楽過ぎて、逆にいい意味で困ります。