Capistrano 3 で複数のサーバーを同時に SSH 経由で操作する
[履歴] [最終更新] (2016/07/04 02:02:24)
最近の投稿
注目の記事

概要

Capistrano は複数のサーバーに ssh して何らかの処理を実行するための汎用ツールです。こちらのページでは Rails のデプロイツールとしての Capistrano 3 の使用方法をまとめました。Rails のデプロイに関しては Capistrano の設定が gem で提供されているため、独自に Capistrano の設定をする必要はありませんでした。ここでは Rails に限定されない一般的な使用方法をまとめます。

参考ページ

インストール

bundler はインストール済みであるとします。Gemfile で capistrano をインストールします。

Gemfile

source 'https://rubygems.org'

group :development do
  gem "capistrano", "~> 3.4"
end

現在有効な ruby に対して bundler で capistrano を vendor/bundle にインストールします。

bundle install --path vendor/bundle

以下のファイルが生成されました。git プロジェクトの場合は必要に応じて .gitignore に追記します。

  • Gemfile.lock (git レポジトリに含めます)
  • .bundle (.gitignore で除外します)
  • vendor/bundle (.gitignore で除外します)

Gemfile.lock はレポジトリに含めるようにしてください。

It is recommended to fix the version number when using Capistrano, and is therefore recommended to use an appropriate bundler.
http://capistranorb.com/documentation/getting-started/installation/

.gitignore

.bundle
bundle

以下のコマンドで動作確認できます。実行可能なタスク一覧が表示されます。

bundle exec cap -T

設定の雛形を生成

以下のコマンドで設定ファイルの雛形を生成します。公式ドキュメントではこれを "Capify" と表現しています。

$ bundle exec cap install

不要ですので "no" としました。

Enhance Capistrano with awesome collaboration and
automation features? (Yes/no): no

以下のファイルが生成されました。すべて git レポジトリに含めます。

Capfile
lib/capistrano/tasks/
config/deploy.rb
config/deploy/staging.rb
config/deploy/production.rb

複数サーバーに SSH ログインしてコマンドを発行

Consolerequire すると console タスクが利用できるようになります。Capfile に以下の一行を追記します。

require 'capistrano/console'

複数サーバーに対して一括して同じコマンドを発行できます。

config/deploy/staging.rb

server 'xxx.xxx.xxx.xxx', user: 'ec2-user'
server 'yyy.yyy.yyy.yyy', user: 'ec2-user'

実行例

$ bundle exec cap staging console

staging> uptime
      03 uptime
      03  22:50:13 up  2:12,  1 user,  load average: 0.03, 0.02, 0.00
    ✔ 03 ec2-user@xxx.xxx.xxx.xxx 0.632s
      03  22:50:13 up  2:12,  1 user,  load average: 0.03, 0.02, 0.00
    ✔ 03 ec2-user@yyy.yyy.yyy.yyy 0.669s

任意のタスクを定義して実行するサンプル

Capify すると deploy タスクに関してのみ予め定義されたタスク群が利用できるようになります。

Capfile

# Include default deployment tasks
require "capistrano/deploy"

デプロイとは関係のない任意のタスクを用意するためには以下のようにします。

lib/capistrano/tasks/my_task.rake

# ユーザー入力を求めます。パスワード等については echo: false として内容が表示されないようにします。
set :my_variable, ask('Enter something:', '既定値', echo: false)
set :my_variable2, "you typed #{fetch(:my_variable)}"

# 設定が前後する場合はラムダ `{}` を利用して遅延評価します。
set :my_variable4, -> { "my_variable3 == #{fetch(:my_variable3)}" }
set :my_variable3, 123

desc "\"desc\" を記載すると cap -T に表示されるようになります。"
task :my_task do

  run_locally do
    execute "hostname"
  end

  on roles(:all) do |host|
    execute "echo \"#{fetch(:my_variable2)}\""
    execute "echo \"#{fetch(:my_variable4)}\""
  end
  on roles(:my_role1) do |host|
    execute "echo \"#{host} has my_role1\""
  end
  on roles(:my_role2) do |host|
    execute "echo \"#{host} has my_role2\""
  end

end

config/deploy/staging.rb

server '127.0.0.1', user: 'user1', roles: [:my_role1]
server 'localhost', user: 'user2', roles: [:my_role1, :my_role2]

実行例

$ bundle exec cap staging my_task
Please enter Enter something: (既定値): 
00:00 my_task
      01 hostname  ← 実行コマンド
      01 vagrant  ← 実行結果
    ✔ 01 vagrant@localhost 0.004s  ← run_locally が完了
      02 echo "you typed abc"  ← on roles(:all) 開始
      02 you typed abc
    ✔ 02 user1@127.0.0.1 0.570s
      03 echo "my_variable3 == 123"
      02 you typed abc
    ✔ 02 user2@localhost 0.617s
      03 my_variable3 == 123
    ✔ 03 user1@127.0.0.1 0.622s
      03 my_variable3 == 123
    ✔ 03 user2@localhost 0.611s  ← on roles(:all) 完了
      04 echo "localhost has my_role1"  ← on roles(:my_role1) 開始
      05 echo "127.0.0.1 has my_role1"
      04 localhost has my_role1
    ✔ 04 user2@localhost 0.100s
      05 127.0.0.1 has my_role1
    ✔ 05 user1@127.0.0.1 0.100s  ← on roles(:my_role1) 完了
      06 echo "localhost has my_role2"  ← on roles(:my_role2) 開始
      06 localhost has my_role2
    ✔ 06 user2@localhost 0.045s  ← on roles(:my_role2) 完了

ファイルアップロード

upload! でローカルマシンのファイルを各リモートホストにアップロードできます。

task :my_task do
  on roles(:all) do |host|
    upload! 'Capfile', "/tmp/Capfile_#{host}"
  end
end

コマンド実行結果の取得およびログへの出力

capture でコマンド実行結果を変数に格納できます。info, warn, error でログ出力できます。

task :my_task do
  run_locally do
    output = capture "whoami"
    info output
    warn output
    error output
  end
end

タスク間の連携

invoke による連携

lib/capistrano/tasks/my_task.rake

task :my_task01 do
  on roles(:all) do |host|
    info "my_task01"
  end
end

task :my_task02 do
  on roles(:all) do |host|
    invoke 'my_task01' # 他のタスクを実行
    info "my_task02"
  end
end

実行例

$ bundle exec cap staging my_task01
00:00 my_task01
      my_task01
$ bundle exec cap staging my_task02
00:00 my_task01
      my_task01
      my_task02

before, after による連携

lib/capistrano/tasks/my_task.rake

task :my_task01 do
  on roles(:all) do |host|
    info "my_task01"
  end
end

task :my_task02 do
  on roles(:all) do |host|
    info "my_task02"
  end
end

before :my_task02, :my_task01
# after :my_task02, :my_task01

before, after による連携 (ブロック形式)

lib/capistrano/tasks/my_task.rake

task :my_task02 do
  on roles(:all) do |host|
    info "my_task02"
  end
end

before :my_task02, :my_task03 do
  on roles(:all) do |host|
    info "my_task03"
  end
end

実行例

$ bundle exec cap staging my_task03
00:00 my_task03
      my_task03
      my_task03
$ bundle exec cap staging my_task02
00:00 my_task03
      my_task03
      my_task03
00:00 my_task02
      my_task02
      my_task02

並列処理数の制御

サーバー数が多い場合、例えば git pull などが同時に実行されるとホスティングサーバーに負荷がかかります。また、デプロイ後のプロセス再起動を全ホストで一斉に行うとダウンタイムが発生するため 1 ホストずつプロセス再起動 (rolling restarts) するのが好ましいです。これを実現するためには以下の設定を利用します

on(in: :sequence, wait: 5) { ... }
on(in: :groups, limit: 2, wait: 5) { ... }

無制限の並列処理

デフォルト値です。parallel 実行されます。

task :my_task do
  on roles(:all), in: :parallel do |host|
    execute "whoami && date"
    execute "whoami && date"
  end
end

実行例

$ bundle exec cap staging my_task
00:00 my_task
      01 whoami && date
      01 user1  ← リモートサーバー1
      01 user2  ← リモートサーバー2
      01 2016年  7月  4日 月曜日 01:33:55 JST
      01 2016年  7月  4日 月曜日 01:33:55 JST
    ✔ 01 user2@localhost 0.630s
    ✔ 01 user1@127.0.0.1 0.614s
      01 user2  ← リモートサーバー2
      01 user1  ← リモートサーバー1
      01 2016年  7月  4日 月曜日 01:33:56 JST
    ✔ 01 user2@localhost 0.661s
      01 2016年  7月  4日 月曜日 01:33:56 JST
    ✔ 01 user1@127.0.0.1 0.643s

一台ずつの直列処理

task :my_task do
  on roles(:all), in: :sequence, wait: 5 do |host|
    execute "whoami && date"
    execute "whoami && date"
  end
end

実行例

$ bundle exec cap staging my_task
00:00 my_task
      01 whoami && date
      01 user1  ← リモートサーバー1
      01 2016年  7月  4日 月曜日 01:35:01 JST
    ✔ 01 user1@127.0.0.1 0.546s
      01 user1  ← リモートサーバー1
      01 2016年  7月  4日 月曜日 01:35:02 JST
    ✔ 01 user1@127.0.0.1 0.579s  ← ここで `wait: 5` (秒) が始まります。
      01 user2  ← リモートサーバー2
      01 2016年  7月  4日 月曜日 01:35:07 JST
    ✔ 01 user2@localhost 0.531s
      01 user2  ← リモートサーバー2
      01 2016年  7月  4日 月曜日 01:35:08 JST
    ✔ 01 user2@localhost 0.557s  ← このあとすぐに終了します。

数台までに限定した並列処理

limit: 1 とすると、ほぼ sequence の場合と同じ処理方法になります。

task :my_task do
  on roles(:all), in: :groups, limit: 1, wait: 5 do |host|
    execute "whoami && date"
    execute "whoami && date"
  end
end

実行例

$ bundle exec cap staging my_task
00:00 my_task
      01 whoami && date
      01 user1  ← リモートサーバー1
      01 2016年  7月  4日 月曜日 01:38:53 JST
    ✔ 01 user1@127.0.0.1 0.541s
      01 user1  ← リモートサーバー1
      01 2016年  7月  4日 月曜日 01:38:54 JST
    ✔ 01 user1@127.0.0.1 0.593s  ← ここで `wait: 5` (秒) が始まります。
      01 user2  ← リモートサーバー2
      01 2016年  7月  4日 月曜日 01:38:59 JST
    ✔ 01 user2@localhost 0.526s
      01 user2  ← リモートサーバー2
      01 2016年  7月  4日 月曜日 01:39:00 JST
    ✔ 01 user2@localhost 0.541s  ← ここで `wait: 5` (秒) が始まります (sequence との違い)

タスク間の連携がある場合の例

task :my_task01 do
  on roles(:all), in: :groups, limit: 1, wait: 5 do |host|
    execute "whoami && date"
    execute "whoami && date"
  end
end

task :my_task02 do
  on roles(:all) do |host|
    execute "whoami && date"
    execute "whoami && date"
  end
end

after :my_task01, :my_task02

実行例

$ bundle exec cap staging my_task01
00:00 my_task01
      01 whoami && date
      01 user1  ← リモートサーバー1
      01 2016年  7月  4日 月曜日 01:58:28 JST
    ✔ 01 user1@127.0.0.1 0.661s
      01 user1  ← リモートサーバー1
      01 2016年  7月  4日 月曜日 01:58:28 JST
    ✔ 01 user1@127.0.0.1 0.606s  ← ここで `wait: 5` (秒) が始まります。
      01 user2  ← リモートサーバー2
      01 2016年  7月  4日 月曜日 01:58:34 JST
    ✔ 01 user2@localhost 0.482s
      01 user2  ← リモートサーバー2
      01 2016年  7月  4日 月曜日 01:58:34 JST
    ✔ 01 user2@localhost 0.501s  ← ここで `wait: 5` (秒) が始まります。
00:12 my_task02  ← my_task01 が完了してから、my_task02 は全サーバーでほぼ同時に実行されます↓
      01 whoami && date
      01 user2
      01 2016年  7月  4日 月曜日 01:58:39 JST
    ✔ 01 user2@localhost 0.103s
      01 user2
      01 2016年  7月  4日 月曜日 01:58:40 JST
    ✔ 01 user2@localhost 0.326s
      01 user1
      01 2016年  7月  4日 月曜日 01:58:40 JST
    ✔ 01 user1@127.0.0.1 0.630s
      01 user1
      01 2016年  7月  4日 月曜日 01:58:40 JST
    ✔ 01 user1@127.0.0.1 0.054s
関連ページ
    概要 Capistranoはサーバの遠隔操作を自動化する多目的なツールです。以下では特に Rails4 を Capistrano3 でデプロイする基本的な方法をまとめます。Rails の場合は Capistrano の設定が gem で提供されているため、Capistrano の知識がなくても基本的なデプロイはできます。独自にカスタマイズしたい場合など、本ページの内容を越えるものは