Skip to content

daemontools HOW-TO (α版)

Copyright 2000 滝澤 隆史 <taki@cyber.email.ne.jp>

Last modified: Sun Nov 19 19:29:10 2000

この文書はDJB氏のdaemontoolsパッケージに興味を持たれる方やこれから導入・運用しようとするかたに向けて書かれたものです。daemontoolsパッケージの概要、導入・設定方法、使用例などをまとめています。しかし、各ツールを詳細に説明するものではありません。そのため、この文書を読んだ後に、マニュアルを読んでください。日本語訳もあります。 また、新山さんの daemontools FAQもありますのでそちらもご覧ください。

daemontoolsはサービスを安全・確実かつ容易に管理するためのツール集です。 主にサービスの制御、ログの収集、環境変数・資源制限を行います。

daemontoolsパッケージに含まれているプログラムには次のものがあります。

プログラム名説明
superviseサービスを開始させ、監視します。何らかのトラブルでサービスが停止したら、自動的に再起動させます。
svcsupervise により監視されているサービスを制御します。
svoksupervise が起動しているかを調べます。
svstatsupervise により監視されているサービスの状態を出力します。
svscanサービスの集まりを開始させ、監視します。
fghack自信をバックグランドに移すサービスがバックグランドに移るのを防ぐツールです。
multilog標準入力から一続きの行を読み、任意の数のログに選択された行を追加します。
tai64n各行に TAI64N 形式の正確なタイムスタンプを付けます。
tai64nlocalTAI64N 形式のタイムスタンプを人が読める形式に変換します。
setuidgid指定されたアカウントの uid と gid で別のプログラムを起動します。
envuidgid指定されたアカウントの uid と gid を示す環境変数を設定して別のプログラムを起動させます。
envdir指定したディレクトリにあるファイルによって修正された環境を設定して別のプログラムを起動させます。
softlimit新しい資源制限を伴って別のプログラムを起動させます。
setlockファイルをロックして別のプログラムを起動させます。

1.3. 既存の他のプログラムとの違い

Section titled “1.3. 既存の他のプログラムとの違い”

既存の他のプログラムとに違いは次のとおりです。

  • 自身が常駐するサービスの場合、何らかのトラブルが生じてサービスが死んだとき、通常は死んだまま。しかし、daemontoolsのsuperviseを使うと、サービスが突然死んでも、自動的に起動し直してくれる。
  • サービスの中にはそのプロセスIDをservice.pidのようなファイルに格納するものもあるが、そうでないものもある。一方、daemontoolsではサービスのプロセスIDをsuperviseが一括して管理している。そのため、その制御は svc を使って簡単にできる。
  • SVR4形式のinitスクリプトでも起動/停止/再起動の指令は出せるが、daemontoolsのsvcではさらに多くの種類のシグナルを送ることができる。
  • syslogd は負荷が高くなるとデータの取りこぼしがある。daemontoolsのmultilogでは取りこぼしは一切ない。また、ログのサイズの制限/循環、パターンマッチによりログの取捨選択ができるので効率のよいログの管理ができる。
  • syslogd のタイムスタンプは一般に秒単位である。一方、multilogではTAI64N形式のタイムスタンプで管理していて、その精度はナノ秒(ただし、現状のマシンクロックの制限により実質マイクロ秒の精度)。

次のサイトからdaemontoolsのパッケージdaemontools-0.70.tar.gzを入手します。

ファイルを展開し、そのディレクトリに移動します。

$ gzip -dc daemontools-0.70.tar.gz | tar xvf -
$ cd daemontools-0.70

コンパイルして、インストールします。

$ make
# make setup check

試験します。何も出力しなかったら正常です。

$ ./rts > rts.out
$ cmp rts.out rts.exp

タイムスタンプを確認します。各行の前の日時と後ろの日時は同じになります。ただし、端数の関係で1秒違うかもしれません。

$ date | ./tai64n | ./tai64nlocal
2000-05-05 11:16:53.959932500 Fri May 5 11:16:53 JST 2000
$ date | sh -c './multilog t e 2\>&1' | ./tai64nlocal
2000-05-05 11:17:11.739003500 Fri May 5 11:17:11 JST 2000

以上、何も問題が生じなければ、インストールは終了です。

svscan が監視するディレクトリを作成します。svscan に関する説明は次章で行います。

# mkdir /service
# chmod 755 /service

ブートスクリプトに svscan を起動するコマンドを登録します。BSD形式の起動スクリプトであれば、rc.local などに、次のコマンドを追加してください。PATHは必要なものを設定してください。

Terminal window
env - PATH=/usr/local/bin:/usr/bin:/bin csh -cf 'svscan /service &'

SVR4形式の起動スクリプトであれば、次のようなスクリプト svscan を作成し、登録してください。OSにより少々修正する必要があります。

#!/bin/sh
PATH=/usr/local/bin:/usr/bin:/bin
case "$1" in
start)
echo -n "Starting svscan: "
exec env - PATH="$PATH" \
csh -cf 'svscan /service &; echo $! > /var/run/svscan.pid'
touch /var/lock/subsys/svscan
;;
stop)
if [ -f /var/run/svscan.pid ]; then
echo -n "Stopping svscan: "
kill `cat /var/run/svscan.pid`
svc -dx /service/*
svc -dx /service/*/log
rm -f /var/run/svscan.pid
rm -f /var/lock/subsys/svscan
fi
;;
*)
echo "Usage: $0 {start|stop}"
exit 1
esac
exit 0

なお、RedHat Linux 7.x用の起動スクリプトは svscan にあります。次のようにして登録してください。

# cd /etc/rc.d/init.d
# cp /tmp/svscan .
# chmod +x svscan
# chkconfig --add svscan

起動スクリプトの登録ができたら、起動スクリプトを個別に実行したり、再起動したりして、svscan を起動してください。

svscanは監視対象のディレクトリ/serviceにサブディレクトリsubがあるとき、そのディレクトリ名を引数にしてsuperviseを起動させます。superviseは引数で渡されたディレクトリsubに移動し、./runスクリプトを起動させ、監視します。この./runにはサービスを実行するスクリプトを記述します。さらに、subにsticky bitが立っていれば、svscansubに移動し、logを引数にしてsuperviseを起動させ、sub/runの出力とsub/log/runの入力をパイプでつなぎます。このときのsuperviseは引数で渡されたディレクトリlogに移動し、./runスクリプトを起動させ、監視します。この./runにはログを記録するプログラム(multilog)を実行するスクリプトを記述します。このように supervisesvscanにより起動させられるので、スクリプト中にsuperviseを明示して記述する必要はありません。 それぞれのプログラムとその引数および作業するディレクトリを整理すると次の表のようになります。

プログラム作業ディレクトリ
svscan /service/service
supervisesub/service/sub
supervise log/service/sub/log

上記で述べたことに加えて、svscan には次のような特徴があります。

  • 起動したディレクトリを監視する。引数(ディレクトリ名)が与えられたら、起動後にそのディレクトリに移動し、そのディレクトリを監視する。
  • 5秒毎にサブディレクトリを調べる。
    • 新しいサブディレクトリがあれば、superviseプロセスを開始させる。
    • supervise プロセスが終了しているサブディレクトリを見つけたら、そのsuperviseプロセスを再開させる。
    • ドットで始まるサブディレクトリは無視する。

上記で述べたことに加えて、supervise には次のような特徴があります。

  • ./run が終了したら、./runを再起動させる。ループを防ぐため、再起動まで1秒間待つ。
  • superviseの起動時に ./downが存在すれば、./run を起動させない。
  • supervise自身は終了の指示がない限り終了しない。

起動させたいサービスのためのディレクトリ/path/to/fooを適当な場所に作成します。

# mkdir /path/to/foo

次に、./run を作成し、サービスを実行するスクリプトを記述します。

/service/sub から /path/to/foo へのシンボリックリンクを作ります。

# ln -s /path/to/foo /service/sub

5秒以内に supervise が起動するでしょう。svok を使って起動が成功しているか確認できます。また、svstat でもその起動の状態を確認できます。

# svok /service/sub; echo "$?"
0
# svstat /service/sub
/service/sub: up (pid 1234) 20 seconds

起動させたいサービスのためのディレクトリ/path/to/fooを適当な場所に作成します。ログ用のディレクトリ /path/to/foo/log も作成します。さらに foo に対してsticky bitを立てます。ログを出力するUIDとGIDを変える場合は、logの所有者も変えます。

# mkdir /path/to/foo
# mkdir /path/to/foo/log
# chmod +t /path/to/foo
# chown uid.gid /path/to/foo/log

次に、./run を作成し、サービスを実行するスクリプトを記述します。また、log/run を作成し、ログを保存するスクリプトを記述します。

/service/sub から /path/to/foo へのシンボリックリンクを作ります。

# ln -s /path/to/foo /service/sub

5秒以内に supervise が起動するでしょう。svok を使って起動が成功しているか確認できます。また、svstat でもその起動の状態を確認できます。

# svok /service/sub; echo "$?"
0
# svok /service/sub/log; echo "$?"
0
# svstat /service/sub /service/sub/log
/service/sub: up (pid 1234) 20 seconds
/service/sub/log: up (pid 1235) 20 seconds

まず、qmailの起動プログラム qmail-start 用のディレクトリを作成します。場所はどこでもかまいませんが、ここでは /var/qmail/supurvise/qmail-send とします。ログを保存するために、このディレクトリにsticky bitを立て、そのディレクトリの下にqmail-sendのログ保存用ユーザqmaill所有のディレクトリlogを作成します。

# mkdir /var/qmail/supervise/qmail-send
# chmod +t /var/qmail/supervise/qmail-send
# mkdir /var/qmail/supervise/qmail-send/log
# chown qmaill.nofiles /var/qmail/supervise/qmail-send/log

起動スクリプト ./runlog/run を作成します。このスクリプトの例は次節に述べます。

/service/qmail からのシンボリックリンクを作ります。

# ln -s /var/qmail/supervise/qmail-send /service/qmail

5秒以内にsuperviseが起動するはずなので、svokあるいはsvstatで起動を確認してください。また、qmailのマニュアルに記述してある配送試験を行ってください。

次に、qmailのSMTPデーモンqmail-smtpd用のディレクトリを作成します。ここでは/var/qmail/supervise/qmail-smtpdとします。ログを保存するために、このディレクトリにsticky bitを立て、そのディレクトリの下にqmail-sendのログ保存用ユーザsmtplog所有のディレクトリlogを作成します。

# mkdir /var/qmail/supervise/qmail-smtpd
# chmod +t /var/qmail/supervise/qmail-smtpd
# mkdir /var/qmail/supervise/qmail-smtpd/log
# chown smtplog.nofiles /var/qmail/supervise/qmail-smtpd/log

起動スクリプト ./runlog/run を作成します。このスクリプトの例は次節に述べます。

/service/smtpd からのシンボリックリンクを作ります。

# ln -s /var/qmail/supervise/qmail-smtpd /service/smtpd

5秒以内に supervise が起動するはずなので、svok あるいは svstat で起動を確認してください。

関連リンク

ここでは、サービスの起動スクリプト./runの作成例を示します。ログの収集スクリプトlog/runに関してはここでは典型的な例しか示しません。応用例は次章に記述します。

./run を作成するに当たっての注意事項

Section titled “./run を作成するに当たっての注意事項”
  • シグナルをサービスに直接送るために、execを使ってsh./runのプロセス)を置き換える必要がある。
  • & を付けてバックグランドで走らせてはいけない。
  • 自身をforkしてバックグランドに移してしまうプログラムはfghackを使うことができる。ただし、制御は行えない。
  • サービスによってはsuperviseからのシグナルでは制御できないものもある。

qmailのメール配送のプログラムを起動させ、配送のログを取る場合の例を示します。ログはqmail-sendのログ保存専用のユーザqmaillにより保存されます。すべてのログはタイムスタンプを付けて./log/main/に保存されます。さらに現在の接続数は./log/statusに保存されます。

#!/bin/sh
exec env - PATH="/var/qmail/bin:$PATH" \
qmail-start ./Maildir/
#!/bin/sh
exec \
setuidgid qmaill \
multilog t ./main '-*' '+* status: *' =status

qmailのSMTPデーモンqmail-smtpdtcpserverで起動させて接続制御を行い、接続状況のログを取る場合の例を示します。この場合はログ保存専用のユーザsmtplogによりログを保存します。また、接続制御ファイルtcp.cdbは同じディレクトリにあるものとします。

#!/bin/sh
exec env - PATH="/var/qmail/bin:$PATH" \
tcpserver -vR -c40 -x./tcp.cdb -u7791 -g2108 0 smtp qmail-smtpd 2>&1
#!/bin/sh
exec \
setuidgid smtplog \
multilog t ./main '-*' '+* * status: *' =status

svc を使って次のことができます。

  • サービスの起動・停止
  • サービスへのシグナルの送信
  • supervise の終了

svc の使い方は次の通りです。

svc opts services

optsはgetopt形式のオプションです。複数のオプションを指定でき、前から順番に実行されます。servicesは制御対象のディレクトリ名です。複数のディレクトリを同時に指定できます。ここで、svcのオプションの一覧を示します。

オプション意味動作
-uUpサービスが起動していなければ、開始します。サービスが停止していれば、再開します。
-dDownサービスが起動していれば、TERM シグナルを送り、それから CONT シグナルを送ります。停止した後は再開しません。
-oOnceサービスが起動していなければ、開始します。サービスが停止していれば、再開しません。
-pPauseサービスに STOP シグナルを送ります。
-cContinueサービスに CONT シグナルを送ります。
-hHangupサービスに HUP シグナルを送ります。
-aAlarmサービスに ALRM シグナルを送ります。
-iInterruptサービスに INT シグナルを送ります。
-tTerminateサービスに TERM シグナルを送ります。
-kKillサービスに KILL シグナルを送ります。
-xExitサービスがダウンしたらすぐに supervise は終了します。

シグナルの意味は次のとおりです(値は環境により異なることがあります)。詳細はsignal(7)を読んでください。

シグナル意味
SIGSTOP17プロセスの停止
SIGCONT19停止状態からの再開
SIGHUP1制御している端末のハングアップの検出。制御しているプロセスの死。
SIGALRM14alarm(2)からのタイマーシグナル
SIGINT2キーボードからの割り込み
SIGTERM15終了シグナル
SIGKILL9Killシグナル

ここで、いくつかの使用例を示します。

qmail-sendの設定の変更を有効にするためのサービスの再起動

Section titled “qmail-sendの設定の変更を有効にするためのサービスの再起動”
# svc -t /service/qmail
# mv ./run.new ./run; svc -t /service/smtpd

サービスの一時的な停止および再開

Section titled “サービスの一時的な停止および再開”
# svc -d /service/ftpd
# svc -u /service/ftpd

サービスの停止後、superviseの終了(ただし、5秒以内に superviseは再起動する)

Section titled “サービスの停止後、superviseの終了(ただし、5秒以内に superviseは再起動する)”
# svc -dx /service/ftpd /service/ftpd/log

サービスの停止後、superviseの終了(superviseを再開させない)

Section titled “サービスの停止後、superviseの終了(superviseを再開させない)”
# mv /service/ftpd /service/.ftpd; svc -dx /service/.ftpd /service/.ftpd/log

svscanを終了させるとき(全てのサービスの停止後、superviseを終了し、svscanのプロセスを終了する)

Section titled “svscanを終了させるとき(全てのサービスの停止後、superviseを終了し、svscanのプロセスを終了する)”
# svc -dx /service/*
# svc -dx /service/*/log
# kill pid_of_svscan

前章で述べたログの収集スクリプトlog/runには収集プログラムとしてmultilogを使います。ここでは、そのmultilogの使い方について説明します。

multilog の使い方は次のとおりです。

multilog script

scriptは動作(action)の集まりで、次の表に記述した動作の行の選択から出力までの組合わせを繰り返して指定できます。(恐らくシェルの限界まで)

動作の概要動作備考
タイムスタンプt各行の先頭にTAI64N形式のタイムスタンプを付ける。(最初の動作として記述した場合のみ有効)
行の選択-patternpatternが行に合えばその行の選択が解除される。
+patternpattern が行に合えばその行は選択される。
自動切り替えの動作ssize最大ファイルサイズの設定。デフォルト99999。
nnumログファイルの最大数。デフォルト10。
!processorプロセッサの設定
ログdirログ dir へ選択された行を追加する。ドットやスラッシュで始まる必要がある。
警告e標準エラーに選択された各行(の最初の200バイト)を出力する。
Statusファイル=filefile の中身を選択された各行(の最初の1000バイト)で置き換える。

最初は各行が選択されています。行の選択指定はいくつでも記述でき、前から順番にそのパターンの選択・解除が追加されていきます。

patternの仕様は次のとおりです。

  • *とそれ以外の文字からなる文字列。
  • *は直後に現れる文字を含まない任意の文字列に一致。
  • 最後にある*は任意の文字列に一致。
  • シェルのメタ文字を含めることができるが、引用符で囲む必要がある。

想定外の動作を防ぐために、pattern全体を引用符で囲んだほうが無難でしょう。

ここで、いくつか行の選択指定の例を示します。multilogの詳しい使用例に関しては次節をご覧ください。

+hellohelloを選択します。hello worldは選択しません。

-'* * > *'@400000003879ded713291cfc 4357 > -ERR authorization failedの選択を解除します。1つ目と2つ目の*はその直後のスペースを含まない任意の文字列に一致します。

'-*'はすべての行の選択を解除します。特定のパターンのみを選択する場合は通常、最初にこの動作を記述してすべての選択を解除してから、選択するパターンを追加していきます。

-'*' +'* status: *' =status@400000003914053c22ab2904 status: local 0/10 remote 0/20を選択し、statusというファイルの内容を置き換えます。つまり、statusというファイルには常に最新のstatus:行が格納されることになります。

qmailのメール配送プログラムqmail-sendのログを取る場合の例を示します。ログはqmail-sendのログ保存専用のユーザqmaillにより保存されます。このスクリプトは次の3つの部分に分けることができます。

  • タイムスタンプを付けてすべてのログを./log/main/に保存する。
  • 通常よく出てくるパターンに一致しないものは./log/alert/に保存する。
  • 現在の同時配送数を./log/statusに保存する。

ちなみに、この例はDJB氏がメイリングリストに投稿した例を修正したものです。

#!/bin/sh
exec \
setuidgid qmaill \
multilog t ./main \
-'* status: *' \
-'* starting delivery *' \
-'* delivery * success*' \
-'* delivery * failure*' \
-'* new msg *' \
-'* info msg *' \
-'* end msg *' \
-'* bounce msg *' \
-"* delivery * deferral: Sorry,_I_couldn't_find_any_host_by_that_name*" \
-"* delivery * deferral: Sorry,_I_wasn't_able_to_establish_an_SMTP_*" \
./alert \
-'*' \
+'* status: *' \
=status

qmailのSMTPデーモンqmail-smtpdを途中にrecordioを挟んでtcpserverで起動させて接続制御を行い、接続状況のログとコマンドの応答を取る場合の例を示します。この場合はログ保存専用のユーザsmtplogによりログを保存します。このスクリプトは次の3つの動作に分けることができます。

  • recordioによって記録されたもの以外のログをすべて./log/main/に保存する。
  • recordioによって記録されたもの中でサーバへ送られる主要なコマンドとその応答を./log/tcp/に保存する。
  • 現在の接続数を./log/statusに保存する。

ちなみにrecordioとは、プログラムの入出力を記録するプログラムで、ucspi-tcpパッケージに含まれています。これを用いれば、ログの出力を持たないサービスであっても、セッションのコマンドの応答から、不正中継の試みなどの様々な情報を記録できます。ただし、選択する行によってはプライバシの問題にもなるので扱いには注意が必要です。

#!/bin/sh
exec \
setuidgid smtplog \
multilog t \
-'* * > *' \
-'* * < *' \
s200000 \
./main \
-'*' \
+'* * < HELO *' \
+'* * < EHLO *' \
+'* * < MAIL *' \
+'* * < RCPT *' \
+'* * < DATA*' \
+'* * < QUIT*' \
+'* * < RSET*' \
+'* * < NOOP*' \
+'* * > 1*' \
+'* * > 2*' \
+'* * > 3*' \
+'* * > 4*' \
+'* * > 5*' \
s200000 \
./tcp \
-'*' \
+'* * status: *' \
=status

Thanks to M.Sugimoto.