どうも、cloudpack の 夜の Player かっぱ@inokara)です。

はじめに

Play Framework のアプリケーションをデプロイする方法について思いを巡らせていて、capistrano かなー、シェルかなー…Rails だったら capistrano でいいかなーと思った次第ですが、単にシェルよりもリモートへのデプロイ等が関数化されていて簡単に呼び出すことが出来たりするので fabric を選んでみました。

参考

fabric とは…

fabric とは…

すごくザックリで恐縮ですが、 Python で実装されたデプロイツールです。(※自分自身は Python は Hello world レベルなので全く書けません。)ライバルは Capistrano ではないかと勝手に思っていますので ChefAnsible とはちょっちレイヤーが違うかなと思っています。

尚、自分はシェルがやっと解る程度ですが、数時間で下記のような 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/')
その他

その他にも cdlcd 等のコマンドがありますが、うんちくは抜きにして実践で使い方を学んでいきたいと思います…。

fab ファイル

どんな風に動かしたいか

やりたいことをざっくり箇条書き。

  • play dist コマンドでアプリケーションをスタンドアロンで動作させる状態にパッケージング
  • パッケージされた zip ファイルを prod ディレクトリに展開
  • 最新のパッケージを current ディレクトリとしてシンボリックリンクを設定
  • current ディレクトリ以下にてアプリケーションを起動
  • 任意のタイミングでアプリケーションを起動出来るようにしたい
  • また、任意のタイミングでアプリケーションを停止出来るようにもしたい

尚、今回はローカルホストのみアプリケーションを展開するようにしています。

とりあえず

ちょっと冗長な書き方もあるかもしれませんが、fab ファイルを以下のように書きました。
以下、一応、全文掲載

・inokappa/fab-deploy-play

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 でやろうと思ったのでメモ