fabric で playをスタンドアロンデプロイしてみた 　　【botoでAWS操作】【Playデプロイ】

fabricでplayアプロケーションのデプロイ自動化したり 
fabric内でAmazon EC2, ELBを操作したりしたので、そこらへんをかいつまんで紹介したいと思います。


### fabricって何？
http://fabric-ja.readthedocs.org/en/latest/tutorial.html

Fabricとは:
> ・コマンドライン 経由で 任意の Python 関数 を実行するツールです。
> ・(低レベルライブラリの上に構築された)サブルーチンのライブラリで、
SSH経由で簡単に、かつPython風に シェルコマンドを実行します。

公式ドキュメントだとわかりにくく言ってますが、
この2つを組み合わせると、リモートサーバへの処理を自動化できますよってことです。
本番デプロイ手作業とか怖すぎるし、面倒なのでどんどん自動化しちゃいましょう。

Capistranoとの比較は下記の記事を参考にしました。
http://dekokun.github.io/posts/2013-05-21.html

### 前提
○ デプロイの流れはこんな感じ
ソースの更新
↓
ELBからEC2を外す
↓
play stop
↓
playでデプロイ
↓
play start
↓
ELBにEC2をつける
↓
デプロイおわり


### 環境
Play framework 2.3.x
activator 1.2.10
デプロイ先のOSはいずれも Amazon Linux
Fabric 1.8.1

## fabric インストール
centOS
sudo yum -y install python python-devel python-setuptools gcc
sudo easy_install pip
sudo pip install fabric==1.8.1

Mac
sudo easy_install fabric



### 事前に押さえておきたい内容
local   -> ローカルのシェルコマンドを呼び出し
env     -> env変数(グローバルなシングルトン)、タスク間の情報共有に使う
lcd     -> ローカルのカレントディレクトリ指定
execute -> taskを実行

デコレータ
@task 	　 -> 有効なタスクとして読み込ませる
@runs_once -> ラップされた関数が複数回実行されないようにする




とりあえずfabfile をはっちゃいます。 下に部分的に説明書いてます。
### fabfile
```py
# -*- coding: utf-8 -*-
from fabric.api import *
from fabric.decorators import runs_once
import boto.ec2.elb

env.hosts = ['localhost']
env.user = "username"

env.project_root = "path/to/project"
env.build_path = "target/universal/stage/bin/project_name"
env.config_path = env.project_root + '/conf/application.conf'
env.branch = 'master'

env.region = "ap-northeast-1"
env.elb_name = 'your_elb_name'
env.aws_access_key = 'your_aws_access_key'
env.aws_secret_key = 'your_aws_secret_key'


## ビルド
@task
@runs_once
def build_play():
    """ activator clean stage"""
    with lcd(env.project_root):
        local("activator clean stage")


## activator起動系
@task
def play_start():
    """ activatorを本番モードで起動"""
    with lcd(env.project_root):
        local("%(bin)s -Dconfig.file=%(conf)s &" % {"bin":env.build_path, "conf":env.config_path})

@task
def play_stop():
    """ 本番モードで起動中のプロセスをkill"""
    if local('ps -ef | grep "%(root)s/target/universal/stage" | grep -v grep | wc -l' % {"root":env.project_root}, capture=True) == "1":
        local("kill `cat %(root)s/target/universal/stage/RUNNING_PID`" % {"root":env.project_root})

@task
def play_restart():
    """ 本番モードでの再起動"""
    execute(play_stop)
    execute(play_start)


## AWS ELB操作
def get_ec2_id():
    """ ec2のinstanceID取得"""
    env.ec2_id = local("curl http://169.254.169.254/latest/meta-data/instance-id", capture=True)

def conn_elb():
    env.conn_elb = boto.ec2.elb.connect_to_region(
        env.region,
        aws_access_key_id = env.aws_access_key,
        aws_secret_access_key = env.aws_secret_key)

def get_elb():
    execute(conn_elb)
    env.elb = env.conn_elb.get_all_load_balancers(env.elb_name)[0]

def remove_ec2():
    """ ec2(自分)をELBから外す"""
    execute(get_elb)
    execute(get_ec2_id)
    env.elb.deregister_instances(env.ec2_id)

def add_ec2():
    """ ec2(自分)をELBに追加する"""
    execute(get_elb)
    execute(get_ec2_id)
    env.elb.register_instances(env.ec2_id)


@task
@runs_once
def update(branch=env.branch):
    """ env.branchに同期する。ブランチ指定場合-> fab update:branch=branch_name"""
    with lcd(env.project_root):
        local("git fetch")
        local("git reset --hard HEAD")
        local("git checkout " + branch)
        local("git pull")


## デプロイ
@task
@runs_once
def deploy(branch=env.branch):
    """ ブランチ指定-> fab deploy:branch=branch_name"""
    execute(remove_ec2)
    execute(play_stop)
    execute(update,branch=branch)
    execute(build_play)
    execute(play_start)
    execute(add_ec2)

```

## Playデプロイ

### playのプロセスをバックグラウンドで起動
```py
@task
def play_start():
    """ activatorを本番モードで起動"""
    with lcd(env.project_root):
        local("%(bin)s -Dconfig.file=%(conf)s &" % {"bin":env.build_path, "conf":env.config_path})
```

### playのプロセス停止
playアプリケーションでプロセスを管理してるRUNNING_PID ファイルを使ってプロセス削除します。

```py
@task
def play_stop():
    """ 本番モードで起動中のプロセスをkill"""
    if local('ps -ef | grep "%(root)s/target/universal/stage" | grep -v grep | wc -l' % {"root":env.project_root}, capture=True) == "1":
        local("kill `cat %(root)s/target/universal/stage/RUNNING_PID`" % {"root":env.project_root})
```

## ELB操作

### boto
fabricから ELBを操作するのに boto(AWS SDK for Python) というライブラリを使ってます。
API document: http://boto.readthedocs.org/en/latest/
https://github.com/boto/boto


### botoをインポート
```py
import boto.ec2.elb
```

### AWSに接続して、env.elb_nameの名前のELBを取得
```py
def conn_elb():
    env.conn_elb = boto.ec2.elb.connect_to_region(
        env.region,
        aws_access_key_id = env.aws_access_key,
        aws_secret_access_key = env.aws_secret_key)

def get_elb():
    execute(conn_elb)
    env.elb = env.conn_elb.get_all_load_balancers(env.elb_name)[0]
````

### EC2(自分)のインスタンスIDを取得
EC2は色々と自分のメタ情報を取得できるみたいですね。知っとくとベンリ
http://d.hatena.ne.jp/rx7/20100605/p1

localの場合は、capture=Trueしないと変数に入らないので注意

```py
def get_ec2_id():
    """ ec2のinstanceID取得"""
    env.ec2_id = local("curl http://169.254.169.254/latest/meta-data/instance-id", capture=True)
```


### ELBから EC2(自分)を外す
```py
def remove_ec2():
    """ ec2(自分)をELBから外す"""
    execute(get_elb)
    execute(get_ec2_id)
    env.elb.deregister_instances(env.ec2_id)
```

### ELBから EC2(自分)を付ける
```py
def add_ec2():
    """ ec2(自分)をELBに追加する"""
    execute(get_elb)
    execute(get_ec2_id)
    env.elb.register_instances(env.ec2_id)
```


## demo

fabfileを設置してるカレントディレクトリで fabコマンドを実行します。
ブロジェクルートに置いても、/home/user配下でもいいと思います。

@taskデコレータを付けてるものが listに表示されます。
$ fab --list 
![スクリーンショット 2015-02-24 20.46.00.png](https://qiita-image-store.s3.amazonaws.com/0/1698/adc9663a-38fa-dc8f-e93d-3b3d78f322d9.png)

$ fab deploy

![スクリーンショット 2015-02-24 20.55.23.png](https://qiita-image-store.s3.amazonaws.com/0/1698/3d2bb5b7-b7e7-eb4c-f031-8faac39d4afb.png)

![スクリーンショット 2015-02-24 20.53.54.png](https://qiita-image-store.s3.amazonaws.com/0/1698/a1e0ca81-d0e8-cbf9-6bb6-de1edf1d530b.png)




## おわりに
今回 fabricも botoも初めて触りましたが学習コストが低い割に便益が高いと思いました。
デプロイサーバからリモートホストへのデプロイ版も作ったので、時間のあるときにそちらも
blogにおこしたいと思います。

最後まで読んでいただき、ありがとうございました。