FLINTERS Engineer's Blog

FLINTERSのエンジニアによる技術ブログ

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

わんばんこ、しもむらです。
現在は、つぶやきGANMA!というサービスを開発・運用してます。

つぶやきGANMA!のデプロイ自動化するにあたって、

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

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のプロセスをバックグラウンドで起動

@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 ファイルを使ってプロセス削除します。

@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
https://github.com/boto/boto

botoをインポート

import boto.ec2.elb

AWSに接続して、env.elb_nameの名前のELBを取得

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しないと変数に入らないので注意

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(自分)を外す

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

ELBから EC2(自分)を付ける

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

f:id:taketor:20150722131728p:plain

$ fab deploy

f:id:taketor:20150722131756p:plain

f:id:taketor:20150722131811p:plain

おわりに

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

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

Qiita原文

Qiitaの自己投稿の転載になります。
Qiitaの記事はこちら