どうも、cloudpack の 夜の Player かっぱ(@inokara)です。
はじめに
Play Framework のアプリケーションをデプロイする方法について思いを巡らせていて、capistrano かなー、シェルかなー…Rails だったら capistrano でいいかなーと思った次第ですが、単にシェルよりもリモートへのデプロイ等が関数化されていて簡単に呼び出すことが出来たりするので fabric を選んでみました。
参考
- FabricでPlay! frameworkアプリのデプロイを自動化してみた
- スタンドアロンで実行可能なアプリケーションのビルド
- Welcome to Fabric’s documentation!
- 今日からすぐに使えるデプロイ・システム管理ツール Fabric 入門
- Python製デプロイツール Fabricを初めて使う際に役立つTips
- Fabric 1.8.1 よくある質問 (FAQ)
fabric とは…
fabric とは…
すごくザックリで恐縮ですが、 Python で実装されたデプロイツールです。(※自分自身は Python は Hello world レベルなので全く書けません。)ライバルは Capistrano ではないかと勝手に思っていますので Chef や Ansible とはちょっちレイヤーが違うかなと思っています。
尚、自分はシェルがやっと解る程度ですが、数時間で下記のような fab ファイルを作成することが出来たので学習コストは低いのではと考えています。
fabric 自体の導入
pip install fabric
以上です。
fabric をちょっと触る
ローカルホストでコマンド実行
ローカルホストでのコマンド実行は local
の後に続けて ()
内に実行したいコマンドを書きます。
from fabric.api import local def hoge(): local('echo "hoge"')
リモートホストでコマンド実行
リモートホストでのコマンド実行は run
の後に続けて ()
内に実行したいコマンドを書きます。
from fabric.api import run def hoge(): run('echo "hoge"')
リモートホストにファイルアップ
リモートホストへのファイルアップは put
コマンドを利用します。
from fabric.api import local, run, put def hoge(): put('/path/to/file','/path/to/hoge/')
別の関数を呼び出して実行する
別の関数を実行させたい場合には execute
コマンドを利用します。
from fabric.api import local, run, put def hoge(): execute(huga) put('/path/to/file','/path/to/hoge/')
その他
その他にも cd
や lcd
等のコマンドがありますが、うんちくは抜きにして実践で使い方を学んでいきたいと思います…。
fab ファイル
どんな風に動かしたいか
やりたいことをざっくり箇条書き。
- play dist コマンドでアプリケーションをスタンドアロンで動作させる状態にパッケージング
- パッケージされた zip ファイルを prod ディレクトリに展開
- 最新のパッケージを current ディレクトリとしてシンボリックリンクを設定
- current ディレクトリ以下にてアプリケーションを起動
- 任意のタイミングでアプリケーションを起動出来るようにしたい
- また、任意のタイミングでアプリケーションを停止出来るようにもしたい
尚、今回はローカルホストのみアプリケーションを展開するようにしています。
とりあえず
ちょっと冗長な書き方もあるかもしれませんが、fab ファイルを以下のように書きました。
以下、一応、全文掲載
deploy.py
from fabric.api import local, run, env, cd, lcd, execute, put
def set_path():
env.app = "hoge"
env.snap_shot = "%(app)s-1.0-SNAPSHOT" % { 'app':env.app }
env.base_dir = "/path/to/prod"
env.deploy_dir = "/path/to/deploy"
env.current_path = "%(base_dir)s/current" % { 'base_dir':env.base_dir }
env.releases_path = "%(base_dir)s/releases" % { 'base_dir':env.base_dir }
env.releases = sorted(local('ls -x %(releases_path)s' % { 'releases_path':env.releases_path }).split())
if len(env.releases) >= 1:
env.current_revision = env.releases[-1]
env.current_release = "%(releases_path)s/%(current_revision)s" % { 'releases_path':env.releases_path, 'current_revision':env.current_revision }
if len(env.releases) > 1:
env.previous_revision = env.releases[-2]
env.previous_release = "%(releases_path)s/%(previous_revision)s" % { 'releases_path':env.releases_path, 'previous_revision':env.previous_revision }
def setup():
execute(set_path)
with lcd('%(deploy_dir)s/%(app)s/' % { 'deploy_dir':env.deploy_dir, 'app':env.app }):
local('rm %(deploy_dir)s/%(app)s/target/universal/*.zip' % { 'deploy_dir':env.deploy_dir, 'app':env.app })
local('play dist')
def app_stop():
execute(set_path)
with lcd('%(current_path)s/%(snap_shot)s/' % { 'current_path':env.current_path, 'snap_shot':env.snap_shot }):
local('kill MARKDOWN_HASH9ccceec02e465e71cf18b7000681da21MARKDOWN_HASH
')
local('rm -f RUNNING_PID')
def app_start():
execute(set_path)
with lcd('%(current_path)s/%(snap_shot)s/' % { 'current_path':env.current_path, 'snap_shot':env.snap_shot }):
local('if [ -f RUNNING_PID ]; then rm RUNNING_PID ; fi')
with lcd('%(current_path)s/%(snap_shot)s/bin/' % { 'current_path':env.current_path, 'snap_shot':env.snap_shot }):
local('./hoge &')
local('sleep 5')
def deploy():
execute(set_path)
execute(setup)
from time import time
env.current_release = "%(releases_path)s/%(time).0f" % { 'releases_path':env.releases_path, 'time':time() }
local('cp %(deploy_dir)s/%(app)s/target/universal/%(snap_shot)s.zip %(base_dir)s/' % { 'deploy_dir':env.deploy_dir, 'base_dir':env.base_dir, 'snap_shot':env.snap_shot, 'app':env.app })
with lcd ("%(base_dir)s/" % { 'base_dir':env.base_dir }):
local('unzip %(snap_shot)s.zip -d %(current_release)s/' % { 'current_release':env.current_release, 'snap_shot':env.snap_shot })
local('ln -nfs %(current_release)s %(current_path)s' % { 'current_release':env.current_release , 'current_path':env.current_path })
可読性はあまりよくないかもしれませんが、基本的にはシェルコマンドを並べているだけです。env.**
を関数を超えて使いまわせたら良さそうなのですが、変数を利用している行の後に % { 'base_dir':env.base_dir }
な感じで書くのがちょっと面倒でした。
そもそも関数という呼び方が適切かどうかは判りませんが、def xxxx()
で機能毎に書けるのと、任意の関数から呼び出せるというのは嬉しいですなあね。
使い方
スタンドアロン起動のファイルを作成
fab -f deploy.py deploy
実行すると def deploy()
が呼び出されます。
アプリケーションを起動
fab -f deploy.py app_start
実行すると def app_start()
が呼び出されます。
アプリケーションを停止
fab -f deploy.py app_stop
実行すると def app_stop()
が呼び出されます。
さいごに
次にやりたいこと
- ソースを git から取得して play dist させる
- そもそもリモートホスト対応
- deploy の度にパッケージファイルが残り続けるのでちゃんと後始末をさせる
- ロールバック機能を付ける
初めての fabric でしたが…
- 思った以上に簡単でした
- ちゃんと fabric のコマンドを覚えて fab ファイルを簡素化したい
元記事はこちらです。
「PlayFramework – Play2 アプリケーションをデプロイを fabric でやろうと思ったのでメモ」