仕事でCapistranoを使っている。使い始めて半年経ったので,使い方をまとめておく。
Capistranoとは
Capistranoは複数のサーバー上でスクリプトを実行するためのオープンソースのツールであり,その主な用途はウェブアプリケーションのソフトウェアデプロイメントである。1つ以上のWebサーバ上のアプリケーションを新しいバージョンにする作業を自動化出来たり,データベースを変更するといった作業もできる。
Capistranoをインストールする
Capistranoはrubyで書かれているので,インストールするにはRubyとRubygemsとBundlerが必要である。
Gemfileという名前のファイルを作成し,以下の記述を追加してbundle installを実行することでインストールすることが出来る。
gem 'capistrano', '~> 3.0.1'
Capistranoの設定ファイルを作成する
Capistranoのインストールが終わったら,Capistranoの設定ファイルを作成する。以下のコマンドを実行する。
bundle exec cap install
実行するとCapfileとconfigディレクトリとlibディレクトリが作成される。
config/deploy/ステージ名.rbを記述する方法
ステージ名.rbにはステージ毎の設定を書くことが出来る。今回はtest.rbという名前で簡単な例を作成する。
サーバはlocalhostを対象にし,そのサーバへはvagrantというユーザでログインし,そのサーバに「Webサーバ」というロールを与えるということを設定している。
server 'localhost', user: 'vagrant', roles: %w{web}
紹介までに留めるが,このファイルには以下のような内容を設定することが出来る。
- ホスト名
- ログインユーザ
- サーバロール
- SSH設定
- その他、そのサーバに紐づく任意の設定
config/deploy.rbを記述する方法
config/deploy.rbにはステージ間で共通の設定を記述する。
よくあるのは以下のような設定である。
- アプリケーション名
- レポジトリ名
- 利用するSCM
- タスク
- それぞれのタスクで実行するコマンド
上記の項目をconfig/deploy.rbにDSL(Capistrano固有の言語)で記述する。DSLには文法と語彙が存在するので,以下よりこれらについて説明する。
設定値の変更と取得
これを定義するとグローバル変数のようにdeploy.rbやステージ.rbの全域で設定値を取り出すことが出来る。set :名前, 値で設定し,fetch :名前で設定を取り出している。
set :repo_url, 'git@github.com:whohu/sample_app'fetch :repo_url #=> "git@github.com:whohu/sample_app"
タスクの定義
実行したいコマンドなどをタスクとして定義することが出来る。task :タスク名 do; … endのブロックでタスクを設定することが出来る。
task :uptime do ここにタスクの内容end
さらにtaskブロックの中にはrun_locally do; … endもしくはon 対象サーバ do; … endブロックを記述することが出来る。run_locallyブロック内にはローカルマシン上で実行するコマンドを記述する。onブロック内にはサーバ上で実行するコマンドを記述する。
task :uptime do run_locally do #ここにローカルマシン上で実行するコマンド end on roles(:web) do #ここにサーバ上で実行するコマンド endend
上記の書式内ではonブロックにてWebサーバ(:web)のロールが与えられているサーバのみを作業対象とする設定を行っていることに注意してほしい。
主なコマンド
run_locallyブロックとonブロックで使用することが出来る主なコマンドとして以下の3つがある。
- executeコマンド
コマンドを実行することが出来る。
task :uptime do run_locally do execute "uptime" end on roles(:web) do execute "uptime" endend
- captureコマンド
標準出力の内容を受け取ることが出来る。outputに標準出力の内容が代入される。
task :uptime do run_locally do output = capture "uptime" end on roles(:web) do output = capture "uptime" endend
- infoコマンド
ログを出力することが出来る。
task :uptime do run_locally do output = capture "uptime" info output end on roles(:web) do output = capture "uptime" info output endend
ログレベルに応じてdebug, info, warn, error, fatalなど文法を使うことが出来る。
実際にレシピを書いてみる
config/deploy/test.rb
server 'localhost', user: 'vagrant', roles: %w{web}
config/deploy.rb
set :application, 'finalge_sample_app'set :repo_url, 'git@github.com:whohu/sample_app.git'#updateタスクを実行する。Gitからソースコードを取得する。task :update do run_locally do application = fetch :application if test "[ -d #{application} ]" #test.rbで記述した環境下にfinalge_sample_appディレクトリがあれば,ディレクトリ内に移動してgit pullする。 execute "cd #{application}; git pull" else #test.rbで記述した環境下にfinalge_sample_appディレクトリが無ければgit cloneする。 execute "git clone #{fetch :repo_url} #{application}" end endend#上記のupdateタスクが終わったらarchiveタスクを実行する。task :archive => :update do run_locally do #sbtコマンドでビルドする(sbtはScalaのビルドコマンド)。 sbt_output = capture "cd #{fetch :application}; sbt pack-archive" #パスを取得する処理が続く…。 sbt_output_without_escape_sequences = sbt_output.lines.map { |line| line.gsub(/\e\[\d{1,2}m/, '') }.join archive_relative_path = sbt_output_without_escape_sequences.match(/\[info\] Generating (?<archive_path>.+\.tar\.gz)\s*$/)[:archive_path] archive_name = archive_relative_path.match(/(?<archive_name>[^\/]+\.tar\.gz)$/)[:archive_name] archive_absolute_path = File.join(capture("cd #{fetch(:application)}; pwd").chomp, archive_relative_path) info archive_absolute_path info archive_name set :archive_absolute_path, archive_absolute_path set :archive_name, archive_name endend#上記のarchiveタスクが終わったらdeployタスクを実行する。task :deploy => :archive do #archive タスクで設定したパスを取得する。 archive_path = fetch :archive_absolute_path archive_name = fetch :archive_name release_path = File.join(fetch(:deploy_to), fetch(:application)) on roles(:web) do #前回デプロイした際に実行したアプリケーションのプロセスをkillする(2回目以降のデプロイを考慮して) begin old_project_dir = File.join(release_path, capture("cd #{release_path}; ls -d */").chomp) if test "[ -d #{old_project_dir} ]" running_pid = capture("cd #{old_project_dir}; cat RUNNING_PID") execute "kill #{running_pid}" end rescue => e info "No previous release directory exists" end unless test "[ -d #{release_path} ]" #test.rbで記述した環境下にrelease_pathに格納したパスが存在しない場合は新しくディレクトリを作成する。 execute "mkdir -p #{release_path}" end #archive_path変数が参照しているディレクトリをrelease_path変数が参照しているディレクトリへコピーする。 upload! archive_path, release_path #release_path変数が参照しているディレクトリへ移動し,tarコマンドでアーカイブを作成する。 execute "cd #{release_path}; tar -zxvf #{archive_name}" project_dir = File.join(release_path, capture("cd #{release_path}; ls -d */").chomp) launch = capture("cd #{project_dir}; ls bin/*").chomp #nohupコマンドで起動スクリプトを実行する。 execute "cd #{project_dir}; ( ( nohup #{launch} &>/dev/null ) & echo $! > RUNNING_PID)" endend
capコマンドでタスクを実行する
必要な設定が記述し終わったらcapコマンドによってタスクを実行する。capコマンドの第1引数がconfig/deploy/test.rbのtestの部分に対応する。第2引数がタスク名でtask :deployのdeployの部分に対応する。
bundle exec cap test deploy
参考
capistrano/capistrano
Capistrano – Wikipedia
入門 Capistrano 3 ~ 全ての手作業を生まれる前に消し去りたい | GREE Engineers’Blog
【入門】Capistrano3で自動デプロイ – Qiita
capistrano 3 をできるだけシンプルにサーバーにコマンドを流し込むツールとして使いこなす – Qiita