2011年1月17日月曜日

Solaris 10のSMFの話をしよう

このエントリーをはてなブックマークに追加
このエントリはカーネル/VM Advent Calendarの42日目のために書きました。

Solaris 9ではOSの起動時や終了時の処理は、/etc/init.dに格納されたシェルスクリプトによって行われます。実行される順序は、/etc/rc?.dに作成されたシェルスクリプトへのリンクの名前で決まります。リンクの名前は、開始のための処理はS、終了のための処理はKで始めて、次に2桁の数字を続けて処理の順序を制御します。

他のSystem V系のUNIXや、System V互換のinitを採用しているLinuxディストリビューションでも同様の方法が用いられています。この方法では処理の依存関係の管理が困難ですし、依存関係のない処理を並列に実行して起動時間を短縮することもできません。

最近のUnix系のOSではinitを置換したり補完したりして、OSの起動時の処理を依存関係に基づいて並列に実行できるようにしています。たとえば、FedoraやUbuntuはinitをupstartに置き換えています。Gentoo Linuxでは、initを補完するスクリプトで依存関係に基づく実行を可能にしています。Mac OS Xはinitをlaunchdに置き換えています。

Solaris 10では、initを補完する形でService Management Facility (SMF)を導入しています。SMFでは、サービスの起動方法やサービスの依存関係などを記述したマニフェストをリポジトリに登録します。OSの起動時にsvc.startdがマニフェストに基づいて、依存関係のないサービスを並列に起動します。svc.startdが扱うサービスの種類は、一時的に実行する処理か、継続して実行する子プロセスか、デーモンです。svc.startdは子プロセスやデーモンが異常終了したときに自動的に再起動します。

マニフェストはXMLで記述します。記述すべき内容は、「Solaris Service Management Facility (SMF) - 予測的自己修復」や、/var/svc/manifestの下にあるリポジトリに登録済みのマニフェストを参考にすればわかります。ftp.jaist.ac.jpのApache httpd 2.2.17に関するマニフェストは以下の通りです。
 1: <?xml version='1.0'?>
 2: <!DOCTYPE service_bundle SYSTEM '/usr/share/lib/xml/dtd/service_bundle.dtd.1'>
 3: <service_bundle type='manifest' name='export'>
 4:   <service name='network/http' type='service' version='1'>
 5:     <instance name='apache2-2-17' enabled='false'>
 6:       <dependency name='network' grouping='require_all' restart_on='error' type='service'>
 7:         <service_fmri value='svc:/milestone/network'/>
 8:       </dependency>
 9:       <dependency name='fs-local' grouping='require_all' restart_on='none' type='service'>
10:         <service_fmri value='svc:/system/filesystem/local'/>
11:       </dependency>
12:       <exec_method name='start' type='method' exec='%{config/apache_home}/bin/apachectl start' timeout_seconds='60'/>
13:       <exec_method name='stop' type='method' exec='%{config/apache_home}/bin/apachectl stop' timeout_seconds='60'/>
14:       <exec_method name='refresh' type='method' exec='%{config/apache_home}/bin/apachectl graceful' timeout_seconds='60'/>
15:       <property_group name='config' type='application'>
16:         <propval name='apache_home' type='astring' value='/opt/httpd-2.2.17'/>
17:       </property_group>
18:       <property_group name='startd' type='framework'>
19:         <propval name='ignore_error' type='astring' value='core,signal'/>
20:       </property_group>
21:       <template>
22:         <common_name>
23:           <loctext xml:lang='C'>Apache HTTP Server 2.2.17</loctext>
24:         </common_name>
25:       </template>
26:     </instance>
27:   </service>
28: </service_bundle>

このマニフェストでは依存するサービスとして、ネットワークとローカルファイルシステムを指定しています(6-11行目)。また、ネットワークにエラーが生じたときにサービスを再起動するよう指定しています(6行目のrestart_on)。関連するプロセスがコアを吐いたりシグナルを受信したりしたときに、デフォルトではサービスを再起動しますが、この例では無視するように指示しています(18-20行目)。

ここまでの話は一見すると全部ユーザランドの話に見えます。しかし、よく考えてみてください。上で述べた「デーモンが異常終了したときに自動的に再起動」というのは、どうやって実現しているのでしょうか? 子プロセスならいいのですが、デーモンが終了しても普通はsvc.startdにはわかりません。マニフェストに死活監視に関するものはありません。それでもhttpdが全部死ぬとsvc.startdはhttpdを再起動します。

Solaris 10では、SMFのためにプロセスのcontractという概念を追加しています。contractを利用することでプロセスをグループ化して、グループ内のプロセスに関するイベントの監視が可能になります。httpdに関するプロセスとcontractの関連を表示すると以下のようになります。
$ ptree -c `pgrep httpd`
  1     /sbin/init
    [process contract 4]
      7     /lib/svc/bin/svc.startd
        [process contract 345439]
          10752 /opt/httpd-2.2.17/bin/httpd -k start
            17663 /opt/httpd-2.2.17/bin/rotatelogs /var/opt/httpd/logs/error_lo
            17664 /usr/bin/perl /dev/fd/3
              17666 /usr/bin/perl /dev/fd/3
            17665 /opt/httpd-2.2.17/bin/rotatelogs /var/opt/httpd/logs/access_l
            17672 /opt/httpd-2.2.17/bin/httpd -k start
            17673 /opt/httpd-2.2.17/bin/httpd -k start
            17674 /opt/httpd-2.2.17/bin/httpd -k start
            17675 /opt/httpd-2.2.17/bin/httpd -k start
            17676 /opt/httpd-2.2.17/bin/httpd -k start
            17677 /opt/httpd-2.2.17/bin/httpd -k start
            17678 /opt/httpd-2.2.17/bin/httpd -k start
            17679 /opt/httpd-2.2.17/bin/httpd -k start
            17680 /opt/httpd-2.2.17/bin/httpd -k start
            17681 /opt/httpd-2.2.17/bin/httpd -k start
            17682 /opt/httpd-2.2.17/bin/httpd -k start
            17683 /opt/httpd-2.2.17/bin/httpd -k start
            17684 /opt/httpd-2.2.17/bin/httpd -k start
            17685 /opt/httpd-2.2.17/bin/httpd -k start
            17686 /opt/httpd-2.2.17/bin/httpd -k start
            17687 /opt/httpd-2.2.17/bin/httpd -k start
httpdに関するプロセスがcontract 345439に含まれていることがわかります。

contractの状態はctstatで表示することができます。
$ ctstat -v -i 345439
CTID    ZONEID  TYPE    STATE   HOLDER  EVENTS  QTIME   NTIME
345439  0       process owned   7       0       -       -
        cookie:                0x20
        informative event set: none
        critical event set:    hwerr empty
        fatal event set:       none
        parameter set:         inherit regent
        member processes:      10752 17663 17664 17665 17666 17672 17673 17674 17675 17676 17677 17678 17679 17680 17681 17682 17683 17684 17685 17686 17687
        inherited contracts:   none
このcontractはプロセスID 7のsvc.startdが所有していて、メモリエラーでプロセスが異常終了した場合(hwerr)と、contract内にプロセスが存在しなくなった場合(empty)にクリティカルイベントが発生することがわかります。svc.startdは、このcontractのイベントを監視することで、必要なときにhttpdを再起動できます。

contractを利用するには/system/contractにマウントされたcontractファイルシステムと、libcontractの提供するAPIを利用する必要があります。簡単にcontractを利用する方法として、新たなcontractを作成してコマンドを実行するctrunユーティリティと、contractのイベントを監視するctwatchユーティリティも提供されています。

ctrunはコマンドを実行すると同時にイベントを監視することもできます。イベントを監視できるようにしてctrunでシェルを実行すると以下のようになります。
$ ctrun -V -i exit,signal -l contract bash
ctrun(17311): created contract id 388816
$ ls >/dev/null
ctrun(17311): event from contract 388816: process 17459 exited
        wait status: 0x0 (exited, code 0)
$ sleep 1000000
ctrun(17311): event from contract 388816: process 18186 received a fatal signal
        signal: 15 (SIGTERM)
        sender pid: 18015
        sender ctid: 388819
ctrun(17311): event from contract 388816: process 18186 exited
        wait status: 0xf (signal 15 (SIGTERM))
Terminated
$ exit
exit
ctrun(17311): event from contract 388816: process 17312 exited
        wait status: 0x8f00 (exited, code 143)
ctrun(17311): event from contract 388816: contract empty
lsの実行例を見ると、孫プロセスのlsの終了ステータスをctrunが取得できていることがわかります。sleepには他の端末からSIGTERMを送りました。contractのシグナルイベントは、他のcontractからシグナルが来たときに発生します。この例では、孫プロセスのsleepに飛んだシグナルをctrunが認識できていることがわかります。最後にシェルを終了するとcontractに属しているプロセスがいなくなるので、それを表すemptyイベントが発生します。

Solaris 10ではデーモンの死活監視をするためにcontractという新たな概念を導入しました。その一方でMac OS XのlaunchdやFedora 15で採用されるsystemdでは、サービスの死活監視のためにデーモン化しないことを求めていくアプローチを取っています。今後Unixのサービスは、これらに対応するためにデーモン化しない方向に進んでいき、やがてcontractは不要になるのかもしれません。

0 件のコメント:

コメントを投稿