D. J. Bernstein
UNIX
daemontools
Frequently asked questions
(原文: Service creation)

サービスの作成


/serviceinittabinit.drc.local よりも良いのはどうして? 自分のパッケージを svscansupervise に従わせるとどんないいことがあるの?

回答: いくつかの理由があります。

サービスの導入や除去が簡単にできること。 /serviceinit.d を使えば、 いくつかのファイルを一元化されたディレクトリにリンクするだけで 新しいサービスを作成したことになります。 また、これらのファイルを削除すると、そのサービスを 除去したことになります。 これによって作業は簡単に自動化できます。

これとは逆に、 inittabrc.local を使った場合、 新しいサービスを作成するには一元化されたファイルに いくつかの行を追加することになります。 またサービスを除去するときは、それらの追加された行をつきとめ 削除しなくてはいけません。 こうなると、作業の自動化はずっと難しくなります: ファイルを編集するための移植性の高いツールは存在していません。 そのため、ほとんどのパッケージでは、この編集作業は ユーザに任される形になっています。

最初のサービス立ち上げが簡単にできること。 /service を使うと 一度サービスを作成すれば、それは 5秒以内に自動的に開始されます。

これとは逆に、 inittabinit.drc.local を使った場合、 サービスを開始するには余計なコマンドを実行しなくてはいけません。

確実に再起動されること。 /serviceinittab を使った場合、 もしお使いのデーモンが死んでも、それは自動的に再起動されます。

これとは逆に、 init.drc.local を使った場合は デーモンは監視されません。 たとえば前回起動したデーモンが一時的にシステムのメモリを ほとんど使い果たしてしまった場合、新しいデーモンは起動に失敗するでしょう。 このような場合、システム管理者は問題発生に気づいてから それを手動で再起動させる必要があります。

簡単、確実なシグナル送信。 /service を使えば、 システム管理者はデーモンを制御するのに svc を 使うことができます。たとえば:

これとは逆に、 inittabinit.drc.local を 使った場合、デーモンにシグナルを送るにはそのプロセス ID を つきとめるという余分な作業が必要になります。 これを信頼性あるやり方で自動化するのは簡単ではありません。

「きれいな」プロセス状態。 /serviceinittab を使えば、 システム管理者がサービスを再起動させても そのサービスをシステムブート時と同様の「きれいな」状態から 始めることができます。

これとは逆に、 init.drc.local を使った場合は、 状態を元に戻すために多大な労力が必要です。 環境変数や使用できるリソースの上限、端末制御など。 プログラマーは、いつもこれらのことに苦しめられています。 たとえ (異なるプロセス状態をもつ) 別システムへの移植を 考えなくてもです。 そしてシステム管理者はデーモンを再起動するときに 不可解な事故に出くわすことになります。

移植性。 /service を使えば、 あなたのプログラムはあらゆるシステム上で同じやり方を 使って動かすことができます: Linux, BSD, Solaris など。

これとは逆に、 inittabinit.drc.local を 使っている場合、やり方はシステムによって違います。 たとえばいくつかのシステムでは init.d がなかったり、 あっても場所がまちまちだったりします。 これは複数のプラットフォームを管理している システム管理者にとって、たいへん腹立たしいことです。


サービス用のディレクトリを作るには?

回答: サービス用のディレクトリ中に唯一必要なコンポーネントは ひとつの実行可能ファイル ./run です。 これはデーモンをフォアグラウンドで走らせるもので、 そのデーモンが終了すればこれも終了します (訳注: この説明はおかしい、正確にはプロセスが オーバーライドされるので、./run がデーモン自身になる)。 ふつう ./run のパーミッションは 755 であり、 そのディレクトリ自身のパーミッションも 755 になっています。

典型的な ./run はシェルスクリプトです。たとえば:

     #!/bin/sh
     echo starting
     exec clockspeed
ここでの exec は、 自分自身を clockspeed によって置き換えるよう sh に指示します。 これによって、システム管理者は svc を使うことで clockspeed にシグナルを直接送ることができます。

指定されたディレクトリ中にあるファイルに従って環境変数を設定するには、 envdir が使えます。通常このディレクトリは ./env です:

     #!/bin/sh
     exec envdir ./env ./run2
これによって、デーモンの設定を簡単かつ自動化されたやり方で おこなうことができます。

その他の ./run 用の役に立つツールとしては envuidgid, setuidgid, softlimit, および setlock があります。

一般に、シェルのパイプラインを使うのはいい方法ではありません:

     #!/bin/sh
     generate-crucial-data | log-crucial-data
この場合、もし log-crucial-data が起動に失敗したら、 generate-crucial-data によってすでにパイプに 吐かれているデータはすべて捨てられてしまいます。 この問題を解消するため ログの分離 を使いましょう。
サービス用のディレクトリをログつきで作るには?

回答: まず、お使いのサービス用ディレクトリを sticky (パーミッション 1755) にします。これはバージョン 0.75 以前の daemontools との互換性のためです。 つぎに、サービス用ディレクトリの中に ./log サブディレクトリを 作ります。そしてロギング用プログラムを走らせる ./log/run スクリプトを置きます。

svscan はお使いのデーモンからロギング用プログラムへと 流れるパイプを作成します。たとえば、./run が 以下のようになっていたとしましょう:

     #!/bin/sh
     exec generate-crucial-data
いっぽう ./log/run はこうなっていたとします:
     #!/bin/sh
     exec log-crucial-data
すると、 generate-crucial-data からの 標準出力は log-crucial-data の標準入力に流れます。 また、それぞれのプログラムは独立に監督 (supervise) されます: つまり log-crucial-data が死んでも自動的に再起動されるし、 generate-crucial-data が死んでも自動的に再起動されるわけです。

./log/run で使うロギング用プログラムの一般的な 選択としては、 multilog があります:

     #!/bin/sh
     exec multilog t ./main
この例ではログの各行の行頭にタイムスタンプが追加され、 結果は自動的にローテートされるログ用ディレクトリ ./log/main に保存されます。

ロギング用プログラムを root 以外のアカウントで走らせるには、 setuidgid を使います:

     #!/bin/sh
     exec setuidgid cruxlog multilog t ./main
ただし、この場合 ./log/main ディレクトリはその ユーザの所有にしなければいけません。 multilog には ./log/main を作成できる権限は ないからです。

お使いのデーモンがログメッセージをファイル記述子 2 (標準エラー出力) に 吐き出す場合は、デーモン用の ./run スクリプト中でこれを ファイル記述子 1 (標準出力) にリダイレクトする必要があります:

     #!/bin/sh
     exec 2>&1
     exec envuidgid tinydns envdir ./env softlimit -d300000 /usr/local/bin/tinydns

自分自身をバックグラウンドに移行させてしまう デーモンはどう監督 (supervise) したらいいの? inetd を走らせると、私の書いたシェルスクリプトは すぐに終了してしまう。だから supervise が何度もこれを 再起動させようとするんだけど。

回答: 最良の解はデーモンを修正することです。 デーモンをバックグラウンドに移行させるのは、どれも悪い ソフトウエアデザインです。

fghack を使うと、デーモンによっては強制的にフォアグラウンドで走らせることが できます:

     #!/bin/sh
     echo starting
     exec fghack inetd
でも fghack を使った場合は、 supervise がシグナルを送ることはできません。気をつけてください。

fghack はそのデーモンからのパイプを作成し、 そのパイプが閉じられるまでデータを読みつづけます。 通常、デーモンの子プロセスはすべてそのデーモンが開いた パイプを継承しますから、パイプが閉じられた時はそれらが すべて終了したときだ、ということになります。

しかしながら、そのデーモンが余分なファイル記述子をすべて 閉じてしまうといったことをする場合、fghack は すぐに終了してしまいます。 これらのデーモンのうちいくつかはファイル記述子 0 を (使わないにもかかわらず) 開いたままにしておくので、

     #!/bin/sh
     exec fghack baddaemon <&-
こうすればうまくいくかもしれません。
自分のプロセスグループをすべて kill するような デーモンはどう監督 (supervise) したらいいの? pppd は TERM シグナルを受けると、 svscan やその他のデーモンを含め、そのプロセスグループを まるごとすべて殺してしまうんだけど。

回答: 最良の解はデーモンを修正することです。 自分が作成してもいないプロセスグループにシグナルを送るプログラムは、 許されるものではありません。

pgrphack を使えば、デーモンを新しいプロセスグループで走らせることができます:

     #!/bin/sh
     echo starting
     exec pgrphack pppd nodetach call myisp

サービス用ディレクトリをアップグレードするには どうするの? ./run スクリプトを変更したいんだけど。

Answer: まず ./run.new を作ります。 つぎにそれをアトミックに ./run.new から ./run へ リネームします。そのあと

     svc -t .
を実行して、デーモンに TERM シグナルを送ってください。 デーモンが終了すると supervise は新しい ./run を 実行します。

決まった ./run を直接編集するやり方は安全ではありません。 supervise はいつでも ./run を再実行する 可能性があるからです。システムも、いつリブートするかわかりませんしね。

多くのロギング用プログラムは、 TERM を受けとった際に未処理の入力データを ぜんぶ捨ててしまうので気をつけてください。 multilog はこのあたりを心得ていて、 終了する前に読み込んだすべてのものを処理するようになっています。


新しいサービスを導入するにはどうやるの? サービス用のディレクトリが /etc/sshd にあるんだけど、 どうすればこのサービスを立ち上げることができる?

回答: あなたのサービス用ディレクトリへのシンボリックリンクを、 /service 中に作成してください:

     ln -s /etc/sshd /service/sshd
svscan は 5秒以内に、自動的 sshd をスタートさせます。
サービスを除去するにはどうやるの? /service/telnetd をやめたいんだけど。

回答:

     cd /service/telnetd
     rm /service/telnetd
     svc -dx . log

日本語訳: 新山 祐介 yusuke @ cs . nyu . edu