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

はじめに

昨晩「Play2 アプリケーションをデプロイを fabric でやろうと思ったのでメモ」の続きです。リモートホストへのデプロイをやりたいと思います。

参考

リモートホストへのデプロイ(1)

fab ファイルの改修

以下、手抜きの全文掲載。

from fabric.api import local, run, env, cd, execute, put, sudo

def default_path():
  env.app = "app_name"
  env.app_repo = "https://github.com/user/app_name.git"
  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 }

def set_path():
  execute(default_path)
  env.releases = sorted(run('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(default_path)
  run('mkdir -p %(base_dir)s/releases' % { 'base_dir':env.base_dir} )
  run('mkdir -p %(deploy_dir)s' % { 'deploy_dir':env.deploy_dir })

def dist_package():
  execute(set_path)
  with cd('%(deploy_dir)s/' % { 'deploy_dir':env.deploy_dir }):
    run('if [ -d %(app)s ]; then rm -rf %(app)s ; fi' % { 'app':env.app })
    run('git clone %(app_repo)s' % { 'app_repo': env.app_repo })
  with cd('%(deploy_dir)s/%(app)s' % { 'deploy_dir':env.deploy_dir, 'app':env.app }):
    run('play dist')

def deploy():
  execute(set_path)
  execute(dist_package)
  from time import time
  env.current_release = "%(releases_path)s/%(time).0f" % { 'releases_path':env.releases_path, 'time':time() }
  run('cp %(deploy_dir)s/%(app)s/target/universal/%(snap_shot)s.zip %(base_dir)s/' % { 'deploy_dir':env.deploy_dir, 'app':env.app, 'base_dir':env.base_dir, 'snap_shot':env.snap_shot })
  with cd ("%(base_dir)s/" % { 'base_dir':env.base_dir }):
    run('unzip %(snap_shot)s.zip -d %(current_release)s/' % { 'current_release':env.current_release, 'snap_shot':env.snap_shot })
  run('ln -nfs %(current_release)s %(current_path)s' % { 'current_release':env.current_release , 'current_path':env.current_path })

def app_status():
  execute(set_path)
  sudo('supervisorctl status %(app)s' % { 'app':env.app }, pty=True, shell=False)

def app_start():
  execute(set_path)
  with cd('%(current_path)s/%(snap_shot)s/' % { 'current_path':env.current_path, 'snap_shot':env.snap_shot }):
    run('if [ -f RUNNING_PID ]; then rm RUNNING_PID ; fi')
  sudo('supervisorctl start %(app)s' % { 'app':env.app }, pty=True, shell=False)

def app_stop():
  execute(set_path)
  sudo('supervisorctl stop %(app)s' % { 'app':env.app }, pty=True, shell=False)

def default_path() だけ弄れば良いようになっています。

動作イメージ(処理の流れ)

play2 を fabric からでプロイする: 動作イメージ
図を書きながら気付いたのは play dist までの処理はデプロイ用のホストで実行、生成された zip ファイルを put するという流れでも良かったのかなってこと…。まあ、いいや。

ポイント

  • def default_path() だけ弄れば良いようになっています(デプロイ先のパス、git のリポジトリ URL 等)
  • fabric を介して sudo を実行させる場合には pty=True, shell=False を付ける必要がある
  • アプリケーションの起動(app_start)、停止(app_stop)、ステータス確認(app_status)は後述の通り supervisord 依存です(すいません)

リモートホストへのデプロイ(2)〜リモートホスト側の準備〜

すいません

自分の実力では fabric 単体では実現することは出来ませんでした。

当初はバックグラウンドでアプリケーションを起動させる為に以下のように書いていましたがリモートホストでは正常に起動しませんでした。

def app_start():
  execute(set_path)
  with cd('%(current_path)s/%(snap_shot)s/' % { 'current_path':env.current_path, 'snap_shot':env.snap_shot }):
    run('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 }):
    run('./hoge &')

Fabric ドキュメントでも以下のように言及されています。

Fabric はリモート側で run や sudo (see also) の呼び出しでそれぞれシェルを起動します。run or sudo (see also)。シェルを通してのバックグランドプロセスは期待通りに動きません。バックグランドプロセスが終了するまで呼び出されたシェルから抜けられないかもしれません。これでは Fabric が順番に実行することができません。

うう、そうなんですな…ってことで、supervisord 等のプロセスマネージャを利用を推奨されているので supervisord を利用してみたいと思います。

supervisor の設定

sudo pip install supervisor

以前「朝の 3 分ハッキング(1) Supervisor で Sinatra アプリケーションをデーモン閣下する」に supervisor の設定については書いているのでそちらを参考にします。アプリケーションの起動設定については以下のように設定します。

[program:play-test-app]
command = /path/to/current/play-test-app-1.0-SNAPSHOT/bin/play-test-app
process_name = play-test-app
user = app-user
autostart   = true
autorestart = true

設定したら /etc/init.d/supervisord start を実行して supervisord を起動しておきましょう。

sudo の設定

アプリケーション用のユーザーが supervisorctl を実行する必要があるので /etc/sudoers.d/ の下に以下のように設定したファイルを置きます。

app-user ALL=(ALL) NOPASSWD:/usr/bin/supervisorctl

デモ

とりあえず静止画で…。

deploy

fab -H ${APP_HOST} -u ${APP_USER} -f remote_deploy.py deploy

まずは…
play2 を fabric からでプロイする: 動作デモ(1)

git clone からの play dist を実行。
play2 を fabric からでプロイする: 動作デモ(2)

play dist が終わったら zip を展開して最新のリリースディレクトリを current ディレクトリとして設定(シンボリックリンクを設定)。

app_start

fab -H ${APP_HOST} -u ${APP_USER} -f remote_deploy.py app_start

以下のように出力されます。
play2 を fabric からでプロイする: 動作デモ(3)

app_status

fab -H ${APP_HOST} -u ${APP_USER} -f remote_deploy.py app_status

以下のように出力されます。
play2 を fabric からでプロイする: 動作デモ(4)

app_stop

fab -H ${APP_HOST} -u ${APP_USER} -f remote_deploy.py app_stop

以下のように出力されます。
play2 を fabric からでプロイする: 動作デモ(5)

お疲れ様でした

fabric を利用して play2 のアプリケーションをリモートホストへのデプロイをひと通り試してみました。基本的にはコマンドの羅列なので悩むことはなかったのですが、アプリケーションの起動に関してはバックグラウンドで起動させるのが fabric 単体ではちょっと実現出来なかった(実現方法を見つけることが出来なかった)のがちょい残念でしたが、実際の運用を想定するとアプリケーションのプロセス管理を何らかの方法で必要となるので、supervisord を fabric から利用するのも悪くはないのかなって考えています。

ということで、おやすみなさい。

元記事はこちらです。
Play2 アプリケーションをデプロイを fabric でやろうと思ったのでメモ(2)