岩本隆史の日記帳(アーカイブ)

はてなダイアリーのサービス終了をうけて移行したものです。更新はしません。

Rack用HTTPデーモンの複数起動を制御するスクリプト

RailsでいうところのMongrelClusterのような、複数のHTTPデーモンを起動したり停止したりする仕組みがRackにもあったら便利だなーと思い、Daemonsを使う方法を考えてみました。

以下、サンプルコードに注釈をまじえながら。

まずはrequire

require 'rubygems'
require 'rack'
require 'daemons'

コマンドラインオプションで渡しそうなものをとりあえず変数で定義

app_dir = '/path/to/app'
port    = 3000
number  = 3
command = 'start'  # or 'stop' or 'status' or 'run'

この例では、3つのポート(3000、3001、3002)でlistenします。

commandは、「start」なら起動、「stop」なら停止、「status」ならステータス表示。「run」ならデーモンにせずに1プロセスのみ起動します(開発時に使う)。「restart」は必要性を感じないので実装しません。

なお、実際のコードではOptionParserなどでコマンドラインオプションをパースすることになるでしょう。

Daemonsのオプション

daemon_options = {
  :ARGV     => [command],
  :dir_mode => :normal,
  :dir      => app_dir,
  :multiple => true
}

:ARGV はDaemonsに渡すパラメータ配列で、1番目の要素が起動や停止をつかさどるコマンドになります。「start」「stop」「status」「run」などが有効な値なので、commandをそのまま渡しています。

:dir_mode と :dir は、ざっくりいうと、pidファイルの作成先を規定する組み合わせです。:dir_mode が「:normal」かつ :dir がディレクトリの絶対パスの場合、そのディレクトリにプロセスファイル(「アプリケーション名+番号」.pid)が作られます。

:dir_mode は「:normal」の他に「:script」と「:system」が選べます(Daemons::Pid.dirのドキュメントを参照)。が、後述の理由のため「:normal」にしました。

:multiple は、同じデーモンの複数起動を許可するかどうかのフラグです。当然、trueにします。

Daemonsの呼び出し

case command
when 'start'
  number.times do |n|
    Daemons.call(daemon_options) do
      Rack::Handler::Mongrel.new(MyApp.new, :Port => port + n)
    end
  end
when 'run', 'stop', 'status'
  Daemons.run_proc('proc', daemon_options) do
    Rack::Handler::Mongrel.new(MyApp.new, :Port => port)
  end
end

commandが「start」のとき、複数デーモンを起動するために Daemons.call しています(READMEの「3. Control a bunch of daemons from another application」にあたる)。

commandがそれ以外の場合は、 Daemons.run_proc しています(同「2. Create wrapper scripts that include your server procs」にあたる)。第1引数がアプリケーション名です。これがプロセスファイル名に使われます。

このアプリケーション名を「proc」にしているのには理由があります。Daemons.call の場合、アプリケーション名を渡せません。Daemons内部で「proc」に固定されます(プロセスファイル名が「proc0.pid」「proc1.pid」のようになる)。そこで、Daemons.run_proc 側で渡すアプリケーション名を「proc」にし、Daemons.call に合わせました。

また、Daemons.run_proc のオプションでは、:dir_mode を「:script」にできません。もし「:script」にすると、Daemons内部で「:normal」に置き換えられてしまいます。「:system」にはできますが、そうすると、複数のアプリケーションを動かしたい場合に、プロセスファイルが「/var/run/proc0.pid」「/var/run/proc1.pid」のように一箇所にまとめられてしまい、別々に停止できなくなります。というわけで「:normal」にしました。

改善したい点

  • 「start」や「run」で既に開いているポートが指定された場合にエラー表示されない(「daemon_options[:log_output] = true」や「daemon_options[:backtrace] = true」にすれば、ログには残る)

というか、これ

Rackじゃなくても使えますよね。

あ、もしかして

それCapistranoだったのかな? ま、いいか。