まだ手動でリリースしていた頃、”リリース職人”みたいな扱いになって作業押し付けられてばかりでカッとなって自動化した時の遺物を載せます。
実際使っているものを所々端折って書いていますが、だいたいこんな感じ。
パスは適宜読み替えてください。
前置き
準備するもの
- Fabric ビルド・デプロイの自動化に使う。
- daemontools プロセス管理に使う。ここではdaemontoolsの設定については特に触れません。
ディレクトリ構成
/[root]
├ /service
| └ run
└ /product
├ current
└ /versions
└ [version_number]
├ /bin
├ /conf
└ /lib
/service/run
daemontoolsのサービス起動スクリプト/product/current
任意の /product/versions/[version_number] へのシンボリックリンク/procduct/versions/[version_number]
Play2アプリケーションが展開される場所
play stageコマンドで生成されるものが展開されると思ってもらえればOK
サービス起動スクリプト
daemontoolsのサービス起動スクリプトはこんな感じ。
exec [PATH_TO_ROOT]/product/current/bin/[MY_APP_NAME] -Dconfig.file=[PATH_TO_ROOT]/product/current/conf/application.conf
ビルドの自動化
以前はJenkinsのシェルで実行していたものを、gitで管理できるようFabricのスクリプトにしています。
実際のスクリプトはこんな感じ。(これも所々端折ってます)
from fabric.api import local, lcd, settings
from datetime import datetime
def build():
play_stage()
remove_non_deploy_files()
make_tar_file()
# play stageコマンドを実行。Jenkins環境ならsbtを、開発環境ならローカルのplayコマンドを直で使う。
def play_stage():
build_command = ("java -Dsbt.log.noformat=true -XX:MaxPermSize=256M "
"-jar [PATH_TO_SBT]/sbt-launch.jar "
"clean compile scalastyle stage") if is_jenkins() is True \
else "play stage"
local(build_command)
# 実行環境の判定
def is_jenkins():
return '[JENKINS_HOSTNAME]' in local('hostname')
# READMEとshareは使わないので削除
def remove_non_deploy_files():
with settings(warn_only=True):
local('rm -r ./target/universal/stage/README*')
local('rm -r ./target/universal/stage/share')
# stageで生成されたファイル郡を.tar.gz化。Jenkins環境なら、ビルド番号名に、それ以外なら日付をファイル名にする。
def make_tar_file():
with lcd("./target/universal"):
with settings(warn_only=True):
local('pwd')
build_number = get_build_number()
local('rm -r ./%s' % build_number)
local('mv ./stage ./%s' % build_number)
local('tar cvzf %s.tar.gz ./%s' % (build_number, build_number))
# Jenkinsのビルド番号を取得。無ければ日付文字列を返す。
def get_build_number():
with settings(warn_only=True):
build_number = local('printenv BUILD_NUMBER', capture=True)
if build_number.succeeded is True:
return build_number
else:
return datetime.now().strftime('%Y%m%d')
これを fab_build.pyとして、以下のコマンドで実行できます。
fab -f fab_build.py build
デプロイの自動化
ビルドと同じようにFabricスクリプト化してます。
from fabric.api import run, put, env, cd, sudo
def release(file_path):
env.hosts = ["xxx.xxx.xxx.xxx", "yyy.yyy.yyy.yyy"]
env.user = '[USER]'
env.key_filename = '[PATH_TO_PRIVATE_KEY]'
deploy(file_path)
# デプロイの実行
def deploy(file_path):
env.use_ssh_config = True
build_number = file_name.split('.')[0]
upload(file_path)
expand(file_name)
update_symbolic_link(target, build_number)
restart_service(get_service(target))
# .tar.gzファイルをアップロード
def upload(file_path):
put(file_path, "./product/versions/")
# .tar.gzファイルを展開
def expand(file_name):
with cd("~/product/versions"):
run("tar zxvf %s" % file_name)
# シンボリックリンクを更新
def update_symbolic_link(number):
with cd("~/product"):
run("ln -sfn [PATH_TO_ROOT]/product/versions/%s current" % (number))
# daemontoolsでプロセスを再起動
def restart_service():
stop_service()
start_service()
# プロセス停止
def stop_service():
manage_service("down")
# プロセス起動
def start_service():
manage_service("up")
# daemontoolsの実行
def manage_service(command):
svc_opt = "-u" if command == "up" else "-d"
sudo("[PATH_TO_DAEMONTOOLS]/command/svc %s [PATH_TO_DAEMONTOOLS]/service/[MY_APP_NAME]" % (svc_opt), pty=True, shell=False)
これも fab_deploy.pyとして、以下のコマンドで実行できます。
fab -f fab_deploy.py release:"./target/universal/[BUILD_NUMBER].tar.gz"
Jenkinsの設定
ビルド・デプロイ用のジョブを作ったら、シェルでFabricのスクリプトを実行するようにします。
基本的には上のコマンドを並べただけですが、ビルド番号をJenkinsの環境変数から取得するようになっています。
fab -f fab_build.py build
fab -f fab_deploy.py release:"../target/universal/${BUILD_NUMBER}.tar.gz"
最後に
実際はもっと細かい制御も入れているのですが、以下略。
さらに、Slack + HubotでJenkinsのジョブを叩くコマンドを定義して、Slack経由でビルド・デプロイするようにしています。これで、Slackが使えれば、どこでも誰でもリリースができる!
もう「誰にも”リリース職人”なんて呼ばせないッ!!」