明日はじめるCapistrano

Railsのdeployに使われることで知られるようになったCapistrano。でもその実態はRailsにとらわれているわけではありません。Capistranoは何かというと、こう言えます。SSHを使って多数のホストに同時並列に接続して、実行させたいコマンドを一斉に送信、実行結果を受け取って問題なければ次のコマンドを、というのを行うためのフレームワークです。

Capistranoを使い始めるのは簡単です。受け側に必要なものがあまりなく、一般的なUNIX系環境であればすでに準備が整っている状態です。Capistranoを実行するホストにはRuby環境が必要ですが、それ以上のものは基本的には必要ありません。コンパイラも不要ですし、ちょっと試してみるだけならインストールしてなくても大丈夫。

環境作り

とはいえ、何もないところでCapistranoを試せるわけではありませんから、実行環境を作っておかねばなりません。いくつかのやり方があります。

  • OS環境で用意されたCapistranoを使う
  • RubyGemsでインストールする
  • RubyGemsは避けたいので自力でインストールする
  • ちょっと試したいだけなので一時的に使えるようにする

一つめはややバージョンが古くなりがちではありますが、動きを知る、動作を試す、あまり深入りしない範囲で使い始めるといったところであればそれほど問題なかろうと思います。どういったOS環境でCapistranoが用意されているのかというと、知っているなかでは以下の通りです。おそらく他にもあるでしょう。

  • Debian GNU/Linux - capistranoパッケージがあります。ただし少し古めのバージョン2.5.5。
  • Mac OS X Lepard - 最初から使えます。ただしも少し古めの2.5.2。

二つめ。RubyGems(Ruby系ソフトウェアのための簡易パッケージシステム)でインストールするのはごく一般的なやり方です。Capistrano自身と、Capistranoが使っているライブラリ類はすべてRubyだけで書かれていますからコンパイラなどのいわゆる開発環境は必要ありません。すでにRubyGemsが使える状態なら特別な準備は必要ないでしょう。(細いことを言えば、CapistranoのバージョンやRubyのバージョンによって多少異なることもあります。)

gem install capistrano

三つめは、こういったことに慣れていないと手間がかかります。必要なソフトウェアのインストールはどれも難しいわけではありませんが、少々数が多いのです。以下に必要なソフトウェアをあげておきます。インストール方法は各ドキュメントにあたってください。

最後の方法ですが、これは必ずしもおすすめるわけではありませんので後で述べることにします。

初めて書くレシピ

Capistranoは他の多数のホストに対して一括してコマンド実行をしかけます。したがって、どのホストを対象とするのかをあらかじめ示しておかねばなりません。そのためにはロールというものを定義します。

ロールはホスト名のリストのようなものです。ロールというくらいなので、ホストの用途に応じて複数のロールを定義できます。たとえばHTTPサーバを動作させているホスト群、RDBMS用のホスト群、などのように。具体的な定義方法は以下の通りです。

role :http_server, "www01", "www02", "www03"
role :db_server, "db1", "db2"

「httpserver」「dbserver」という二つのロールを定義しています。それぞれにホストwww01〜03とdb01〜02が所属します。定義の上で、ロール名の前に「:」がついているのがちょっとしたポイントです。これはRubyに由来する記法ですが、ここではそんなものだと考えおくくらいで構いません。

これでロール定義は完了です。Capistranoは特に指定しなければ定義されているロールに所属している全ホストを対象にしますので、ロール定義=対象ホスト指定を終えたことにもなります。

次に、Capistranoを通じて行いたいコマンド実行の内容を決めます。具体的なコマンド実行の内容を考えます。ここでは…… Passengerがインストールされているかどうかを確認してみましょう。手作業で実行するならこんなところでしょう。

gem list passenger -i

gemでインストールしていることを前提としています。バージョンを指定してもよいでしょう。

gem list passenger -i -v 2.2.5

これをCapistranoが理解できる書式に書き換えると次のようになります。

task :check_passenger_gem do
  run "gem list passenger -i"
end

いかがでしょうか。ちょっとばかり呪文が入ってきましたが、コマンド実行の記述そのものに変わりないことを見てとれます。

Capistranoではコマンド実行の指示をタスクという単位で扱っています。タスクの定義には「task〜do〜end」という記法を使います。「task」の直後にタスクの名前を書きますが、ここでも「:」を付けるのがポイントです。「do」と「end」の間には、そのタスクの内容を記述します。

「do〜end」の中で頻繁に使うことになるのがコマンド実行させるための「run」命令です。

「run」に続くのが具体的なコマンド実行内容で、手作業で実行するときとおおむね同じ要領で記述できます。ワイルドカードやパイプ、リダイレクトも利用できます。もっといえばシェルスクリプトと呼ばれるようになる実行制御のための記述も可能ですから、この部分にはシェルスクリプトを記述すると言ってもよいでしょう。

レシピを実行する

決めたCapistranoへの指示内容はレシピとしてまとめます。まとめるといっても単にファイルに記述するだけのことです。Capistranoは「Capfile」という名前のファイルを自身への指示書として扱いますので、Capfileというファイルを作成します。makeコマンドに対するMakefileの関係と同じです。今のところ簡単な内容ですが全体を見ておきましょう。

role :http_server, "www01", "www02", "www03"
role :db_server, "db1"
task :check_passenger_gem do
  run "gem list passenger -i"
end

これでレシピの完成です。いよいよCapistranoの実行です。

Capistranoでレシピを実行するためにはcapというコマンドを使います。Capfileを置いたディレクトリで「cap checkpassengergem」のようにタスク名を指定して実行します。ここで指定するタスク名には「:」がないことに注意してください。実際にやってみましょう。

$ cap check_passenger_gem
  * executing `check_passenger_gem'
  * executing "gem list passenger -i"
servers: ["www01", "www02", "www03", "db1"]
[www01] executing command
[www02] executing command
 ** [out :: www01] false
   [db1] executing command
 ** [out :: db1] false
(略)
command finished
failed: "sh -c 'gem list passenger -i'" on www01,db1

やあ、実行できました。「servers:」に続く部分がコマンド実行対象となったホストのリストで、これらに一斉にコマンド送信しています。ですがよく見るといくつかのホストについて「failed」と表示されています。

「[out :: ○○]」という部分は、コマンド実行をした各ホストからの出力を表しています。ここでfalseという出力があったことがわかります。これは指定したgemがインストールされていないときの表示です。gemが見付からなかったことでgemコマンドが正常終了せず、それを検知したCapistranoが「failed」と通知してきていたのです。

つまりCapistrano自体の動作に問題があったわけではなく、Capistranoを通じたコマンド実行結果に問題があったということです。

ところで、これらの失敗ホストのうちのいくつかではgemのインストール先を個別に指定して運用しています。そのため実行するコマンドラインを少し変えて、次のようにしてみました。

task :check_passenger_gem do
  run "GEM_HOME=/var/www/apps/GEM gem list passenger -i"
end

これを実行すると次のように結果が変わりました。

$ cap check_passenger_gem
  * executing `check_passenger_gem'
  * executing "GEM_HOME=/var/www/apps/GEM gem list passenger -i"
servers: ["www01", "db1"]
[www01] executing command
[db1] executing command
 ** [out :: www01] true
 ** [out :: db1] false
(略)
command finished
failed: "sh -c 'GEM_HOME=/var/www/apps/GEM gem list passenger -i'" on db1

ホストのうちの一つでは依然としてgemを発見できていませんが、それは少なくとも想定している場所にはインストールされていないためです。実際、RDBMS用として運用しているdb1にはPassengerをインストールしていないのです。

ロールでわける

このように、一括してコマンド実行するときには、ホストの用途に応じて実行内容が変わるのが普通です。Capistranoはロールすべてにコマンド実行しようとしますが、タスク定義のやり方を少し変えることで、特定のロールにだけ適用されるタスクを作れます。

たとえば先のPassengerの例はHTTPサーバ用のホストでだけ実行できればよいわけですから、ロールwww_serverを対象としたタスク定義を行います。

task :check_passenger_gem, :roles => [:www_server] do
  run "GEM_HOME=/var/www/apps/GEM gem list passenger -i"
end

こんなふうに「:roles => [〜]」というタスク定義に記述を加えます。「[〜]」の部分では複数のロールを指定でき、その場合にはカンマで区切ります。ここでの記述には「:」が必要です。

定義を書き換えても実行方法はこれまでと変わりません。

$ cap check_passenger_gem
  * executing `check_passenger_gem'
  * executing "GEM_HOME=/var/www/apps/GEM gem list passenger -i"
servers: ["www01", "www02", "www03"]
[www01] executing command
(略)
 ** [out :: www01] true
command finished

結果が変わりました。「servers:」の部分が変わっていますし、ここでは省略していますが「out :: ○○」の数も変わっているのを確認できるはずです。

複数のコマンドを実行する

さて、実際の場面について考えると、ここでの例のようなgemがインストールされているかどうかの確認だけして終わりということはなく、そこからさらにプロセスが動作しているかとか、設定が正しいかといった作業が続きます。このように一連のコマンド実行がセットにしたのが現実世界のタスクですが、Capistranoのタスクでも複数のコマンド実行を記述することが可能です。それには単純に必要なだけのrunを書き連ねればよいのです。

では次の例としてApache HTTPサーバの設定を確認し、問題なければリスタートするというのを考えてみます。手作業で行うと次のようになるでしょうか(Debian GNU/Linuxの場合)。

apache2ctl configtest
sudo apche2ctl graceful

これをタスクに書き起こすとこうなります。

task check_and_restart_apache do
  run "apache2ctl configtest"
  sudo "apache2ctl graceful", :pty => true
end

レシピの側でも「sudo」が出てきました。Capistranoでは特定の権限でのコマンド実行のためにはsudoコマンドを使用していて、sudoコマンドを利用したコマンド実行には「run」の代わりに「sudo」を使います。もちろん、これを行うためには、コマンド実行の対象となる各ホスト側でsudoコマンドが利用可能でなければなりません。(コマンドライのおしりに「:pty => true」という新たな呪文が付きましたが、これもここでは「そういうものだ」と考えておいてください。)

Capistranoを通じたsudoコマンドの利用では認証のためのパスワード入力の省略が可能です。たくさんのホストへ相手にしなければならないとき、ホストごとにパスワードを入力するのは一苦労です。そのため、Capistranoでは、全ホストのパスワードが同一であることを前提に、一度入力されたパスワードを内部で保持しておいて、必要に応じて代理でパスワード入力を行っています。

ですから、上のタスクの実行に際して、ロール定義されたホストが10個あっても20個あってもパスワード入力を求められるのは一度だけです。

さて、このタスクを実行する前に気になってくるのは、configtestに失敗したらgracefulを実行したくないということです。configtestに失敗するようならその時点で実行を中止してほしいのですが…… 実際のところ、特に気にかける必要はありません。Capistranoは、ちょうとmakeコマンドがそうするように、一連のコマンド実行の途中で失敗するようなことがあればその時点で全体の実行を中止します。

ですから、この例のように特に何の記述もないようであっても、目的はかなえられているのです。少し注意が必要なのは、対象ホストのうちのどれか一つででも引っかかることがあれば、全ホストでその実行を中止するという点です。この例なら、どれか一つのホストで設定が間違っていれば、すべてのホストでgracefulは実行されません。(ただし、これには回避方法も用意されています。)

これにて終了、の前に

これでひとまずの説明は終わりです。ですがその前に。

環境作りの中であげた四つの方法の最後に、インストールせずに試すというのを入れていました。この方法は、結局のところ各種のソフトウェアが必要になるのは変わりありません。ただ、これらはインストールするまでもなく利用できますので、たとえばこんなふうにすると試せるようになります。

$ mkdir tmp
$ cat <<E | while read url; do wget -qO- $url | tar zx -C tmp; done
http://github.com/capistrano/capistrano/tarball/76488f18268bfb34a54d7bb82ee8286d65ac4e00
http://github.com/net-ssh/net-ssh/tarball/v2.0.19
http://github.com/net-ssh/net-sftp/tarball/v2.0.4
http://github.com/net-ssh/net-scp/tarball/v1.0.2
http://rubyforge.org/frs/download.php/51134/net-ssh-gateway-1.0.1.tar.gz
http://rubyforge.org/frs/download.php/68882/highline-1.5.2.tgz
E
$ cp -a tmp/*/{bin,lib} .
$ rm -rf tmp
$ RUBYLIB=$(pwd)/lib bin/cap invoke HOSTS=db1 COMMAND='echo Hello, World!'
  * executing `invoke'
  * executing "echo Hello, World!"
servers: ["db1"]
[db1] executing command
 ** [out :: db1] Hello, World!
command finished

実行のテストを行っているところではまた新たな呪文が増えていますが、ここでは実行できているということだけ確認できれば十分です。もし続きを書くことがあれば、またそのときに説明できるかもしれません。

なお、このようなやり方は、Capistranoや各ライブラリのバージョン変わってもいつでも使える方法というわけではありません。あくまでお試し、限定的なものと考えてください。

では今日これまで。明日がんばりましょう。