この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。
ときどき、「rubyでちょっとしたデーモンを動かしたい」かつ、「出力されるログファイルもローテーションさせたい」ということたびたび発生します。 そんなとき、どのようにスクリプトを組むのが良いかという点についてまとめてみました。
TL;DR
ポイントは以下です。
- ふつうにRubyのスクリプト書いて systemd でデーモン化する
- logrotate の postscript の中で実行中のスクリプトに対して
USR1
を送出する - スクリプト側では
USR1
シグナルを受け取ったらログファイルをreopen
する
スクリプト本体
デーモンとなるスクリプト本体です。
log_file = File.expand_path("hoge.log", File.dirname(__FILE__))
pid_file = File.expand_path("hoge.pid", File.dirname(__FILE__))
# pid 作成
File.write(pid_file, Process.pid)
# logger 初期化
logger = Logger.new(log_file)
logger.level = Logger::INFO
$reopen_log = false
# USR1シグナルでログのreopenフラグを立てる
trap("USR1") do
$reopen_log = true
end
# 終了時に pid ファイルを削除
END {
File.delete(pid_file)
}
loop do
# フラグ立ってたらログを reopen する
if $reopen_log
logger.reopen(log_file)
$reopen_log = false
end
logger.info "hogehoge---"
sleep 10
end
スクリプトのポイント
いくつかポイントとなる箇所があるので解説。
プロセスIDをファイルに出力する
理由は、logrotate の postrotate のスクリプトで kill -USR1 でプロセスに対してシグナルを送出するためです。
# pid 作成
File.write(pid_file, Process.pid)
# 終了時に pid ファイルを削除 END { File.delete(pid_file) }
シグナルを受け取ってログを再オープンする
普通にログローテートすると、対象ファイルがローテーションした際、既存のファイルがリネームされて新しいファイルが作られるが、reopen
しないとずっと古いファイル(リネームされた方)に出力されてしまう。
そこで、USR1
(たいていUSR1
が使われるらしい)のシグナルを捕捉して、シグナルを受け取ったらreopen
する処理を組み込む。
# USR1シグナルでログのreopenフラグを立てる
trap("USR1") do
$reopen_log = true
end
# フラグ立ってたらログを reopen する
if $reopen_log
logger.reopen(log_file)
$reopen_log = false
end
ちなみに、trap()
のブロック内(シグナルハンドラと呼ぶらしい)でreopen
してみましたが、Threadがなんちゃらというエラーが出て落ちてしまいました。
一般的にシグナルハンドラの中では入出力を伴う処理等を行うのはよくないらしく、フラグを立てるくらいしたほうが良いらしいです。
systemd に登録するやつ
特に解説はしません。
Systemdを使ってさくっと自作コマンドをサービス化してみる – Qiita などを参考に。
[Unit]
Description = xxxx daemon
[Service]
ExecStart = /home/xxxx/.rbenv/shims/ruby /path/to/hoge.rb
Restart = always
Type = simple
User = hoge
Group = hoge
[Install]
WantedBy = multi-user.target
このファイル名 hoge.service
がサービス名(hoge
と省略可能)となります。
logrotate
日毎のローテーションで、履歴を10件保持する設定です。
この設定のポイントは、postrotate
のブロックに記述されたスクリプトで、kill -USR1 $(cat $pid)
の部分です。
これによってローテーション完了後、スクリプトにUSR1
シグナルが送出され、それによってスクリプト側ではログファイルがreopen
され、結果としてローテーション後の新しいログファイルにログが出力されるようになるという仕組みになっています。
/path/to/hoge.log {
daily
rotate 10
missingok
su hoge hoge
create 0600 hoge hoge
postrotate
pid=/path/to/hoge.pid
test -e $pid && kill -USR1 $(cat $pid) || true
endscript
}
デーモンの起動/停止
以下のコマンドでデーモンが起動します。
$ sudo systemctl start hoge
停止は以下
$ sudo systemctl stop hoge
さいごに
こんな感じで、意外なほど簡単にRubyで気軽にデーモンが作れるのでbotとかいろいろ作ってみたらどうでしょうか。