mk-mode BLOG

このブログは自作の自宅サーバに構築した Debian GNU/Linux で運用しています。
PC・サーバ構築等の話題を中心に公開しております。(クローンサイト: GitHub Pages

ブログ開設日2009-01-05
サーバ連続稼働時間
Reading...
Page View 合計
Reading...
今日
Reading...
昨日
Reading...

Ruby - スクリプトをデーモン化!

[ プログラミング ] [ Ruby ]

こんばんは。

Ruby スクリプトをデーモン化する方法についてです。

デーモン(Daemon)とは、マルチタスク OS におけるバックグラウンドで動作するプロセスのことです。
Windows のようなシングルタスク OS では、同様な処理を擬似的に実現するサービスに当たるでしょうか。

以下、Ruby スクリプト作成例を掲示して簡単に説明し、起動スクリプトも紹介します。

0. 前提条件

  • Linux Mint 14 (64bit) での作業を想定。
  • Ruby 2.0.0-p247 での作業を想定。
  • デーモン処理を行う Ruby スクリプトを起動・停止・再起動するスクリプトも作成する。

1. Ruby スクリプト作成

以下のような Ruby スクリプトを作成してみた。(説明は次項で)

test_daemon.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#*********************************************
# デーモン化テスト
#*********************************************
#
class TestDaemon
  def initialize
    # 割り込みフラグ
    @flag_int = false
    # ファイル名
    @pid_file = "./test_daemon.pid"  # PID ファイル
    out_file  = "./test_daemon.txt"  # 出力ファイル
    # 出力ファイル OPEN
    @out_file = File.open(out_file, "w")
  end

  # 起動
  def run
    begin
      # 開始メッセージ
      @out_file.puts "[START]"

      # デーモン化
      daemonize

      # トラップ(割り込み)設定
      set_trap

      # 処理実行
      execute

      # 終了メッセージ
      @out_file.puts "[E N D]"
    rescue => e
      STDERR.puts "[ERROR][#{self.class.name}.run] #{e}"
      exit 1
    end
  end

private

  # デーモン化
  def daemonize
    begin
      # デーモン化
      # ( RUBY_VERSION < 1.9 )
      # exit!(0) if Process.fork
      # Process.setsid
      # exit!(0) if Process.fork
      # ( RUBY_VERSION >= 1.9 )
      Process.daemon(true, true)

      # PID ファイル生成
      open(@pid_file, 'w') {|f| f << Process.pid} if @pid_file
    rescue => e
      STDERR.puts "[ERROR][#{self.class.name}.daemonize] #{e}"
      exit 1
    end
  end

  # トラップ(割り込み)設定
  def set_trap
    begin
      Signal.trap(:INT)  {@flag_int = true}  # SIGINT  捕獲
      Signal.trap(:TERM) {@flag_int = true}  # SIGTERM 捕獲
    rescue => e
      STDERR.puts "[ERROR][#{self.class.name}.set_trap] #{e}"
      exit 1
    end
  end

  # 処理実行
  def execute
    begin
      count = 0
      loop do
        break if @flag_int
        sleep 1
        @out_file.puts count += 1
        @out_file.flush
      end
    rescue => e
      STDERR.puts "[ERROR][#{self.class.name}.execute] #{e}"
      @out_file.close
      exit 1
    end
  end
end

obj_proc = TestDaemon.new
obj_proc.run

2. Ruby スクリプト説明

  1. 処理概要
    • 1 から順に1秒間隔でカウントアップし、テキストファイルにカウントを永遠に出力し続けるスクリプトである。
    • システム割り込みを検出した場合に終了処理を行う。
  2. デーモン化処理について
    • Ruby 1.9 以上では、Process.fork を使用する方法以外に、Process.daemon(true, true) を使用する方法が利用できる。
      • daemon の第1引数を true にすると、カレントディレクトリを移動しない。(デフォルトは、"/“ ディレクトリへ移動する。)
      • daemon の第2引数を true にすると、標準入力・標準出力・標準エラーを “/dev/null” へリダイレクトしない。(デフォルトは false なので、"/dev/null" へリダイレクトする。)
    • プロセス ID(PID)を “test_daemon.pid” に出力(退避)する。
  3. トラップ(割り込み)設定について
    • “SIGINT”, “SIGTERM” のシステム割り込みを検出した場合に、割り込みフラグを立てる。
    • 割り込みフラグを検知したら、ループ中の処理から抜ける。

3. Ruby スクリプト実行

作成した Ruby スクリプトを実行してみる。

1
$ ruby test_daemon.rb

コンソールには特に何も出力されないはずである。
この Ruby スクリプトと同じディレクトリに、"test_daemon.pid" という PID ファイルと、"test_daemon.txt" という出力ファイルが作成される。

また、プロセスを確認してみると以下のようになる。 ? の表示が、ターミナルから離れてデーモンとしてバックグラウンドで動作しているということを意味している。

1
2
$ ps aux | grep test_daemon.rb | grep -v grep
masaru   19940  0.0  0.1  35492  4668 ?        Sl   16:48   0:00 ruby test_daemon.rb

4. Ruby スクリプト停止

デーモンとして動作している Ruby スクリプトを停止するには以下のようにする。(もしくは、システムモニタ等で終了させる。)

1
$ kill `cat ./test_daemon.pid`

kill コマンドのオプションはデフォルトで -TERM なので、これにより Ruby スクリプト内のトラップ処理部分で SIGTERM を検知して終了処理が動く。
ただし、PID ファイルは残ったままなので、残っていて問題になるようなら削除する。

5. シェルスクリプト作成

起動したデーモン(Ruby スクリプト)を停止するのに毎回 PID ファイルを指定して kill コマンドを実行するのは面倒である。
そこで、容易に起動・終了・再起動できるように、以下のようなシェルスクリプトを作成する。

test_daemon
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#!/bin/sh

NAME="[ TEST DAEMON ]"
PID="./test_daemon.pid"
CMD="ruby test_daemon.rb"

start()
{
  if [ -e $PID ]; then
    echo "$NAME already started"
    exit 1
  fi
  echo "$NAME START!"
  $CMD
}

stop()
{
  if [ ! -e $PID ]; then
    echo "$NAME not started"
    exit 1
  fi
  echo "$NAME STOP!"
  kill -INT `cat ${PID}`
  rm $PID
}

restart()
{
  stop
  sleep 2
  start
}

case "$1" in
  start)
    start
    ;;
  stop)
    stop
    ;;
  restart)
    restart
    ;;
  *)
    echo "Syntax Error: release [start|stop|restart]"
    ;;
esac

6. 起動スクリプト実行

作成した起動スクリプトで、デーモンを起動・停止・再起動してみる。
(PID ファイルの有無で起動中か停止中かを判定するので、PID ファイルが残っていて起動できない場合は一旦 PID ファイルを削除する)

1
2
3
$ ./test_daemo start
$ ./test_daemo restart
$ ./test_daemo stop

参考サイト

Ruby の Process については以下を参照。

ログ出力に Logger を使用してもよいが、Ruby 2.0 では、うまく機能しない部分があるので注意!


これで、Ruby スクリプトのデーモン化についての基本的なことは理解できました。
色々と応用できそうです。

以上。

Comments