#きょうのsystemd : (翻訳) どうやったらSysV initのスクリプトをsystemdのサービスファイルに換えられるのか? (systemd for Administrators, Part 3)

概要

これは

systemd for Administrators, Part III

の翻訳。

どうやったらSysV initのスクリプトをsystemdのサービスファイルに換えられるのか?

伝統的にUnixLinuxのサービス(デーモン)はSysV init スクリプト経由で起動されている。 これらはBourne Shellスクリプトであり、通常は/etc/rc.d/init.dといったディレクトリに置かれている。 これらのスクリプトstartstoprestartといった数個の標準化された引数(動詞)を与えられて呼び出されたときに、件のサービスの制御、すなわち、起動、停止、再起動をおこなう。 起動の場合、スクリプトは通常、デーモンのバイナリーの呼び出しに関わり、バックグラウンドのプロセスをフォークする(より正確にはデーモン化させる)。 これらは非常に柔軟性がある(なんといったって、これらは単なるコードである)が、いくつかのことはシェルスクリプトでは適切におこなうことが非常に難しい。 具体的には、実行をパラレル化するように並べる、適切にプロセスを監督する、あるいは、非常に細かいところまで実行のコンテクストを設定するといったことである。 systemdはこれらのシェルスクリプトとの互換性を提供しているが、ここで指摘した短所ゆえに、インストールされているすべてのデーモンがネイティブなsystemdサービスファイルをインストールすることが推奨される。 また、ディストリビューションに合うよう調整しなければいけないSysV initスクリプトと比べると、systemdのサービスファイルはsystemdを動かしているディストリビューション(最近どんどん増えている)ならどの種類でも互換性がある。 となると、次に続くのは、どのようにSysV initスクリプトを取り上げ、ネイティブなsystemdサービスファイルに翻訳するかの完結なガイドということになる。 理想的には、上流のプロジェクトがそれらのtarballにsystemdサービスファイルを同梱しインストールすることだ。 もし、SysVスクリプトガイドラインに従ってうまく変換しているならば、そのファイルを上流にパッチとして送ってしまうのはいい考えだ。 このようなパッチを準備する方法は、後の投稿で論じるつもりだが、現時点で十分に言えることは、systemdに同梱されているdaemon(7)のマニュアルページには、これに関する有益な情報が多く含まれているということだ。

閑話休題。 一例として、ここではABRTデーモンのinitスクリプトをsystemdサービスファイルに変換することにしよう。 ABRTは各Fedoraのインストールの標準的なコンポーネントであり、Automatic Bug Reporting Toolの略だ。 何をするかはその名のとおりで、クラッシュダンプを集めるサービスである。 これのSysVスクリプトはもうここに上げてある。

こういったスクリプトを変換するときの第一歩はこれを読むこと(驚け、驚け!)で、たいていは長いこのスクリプトから有益な情報を抽出することだ。 ほとんどすべてのケースで、スクリプトはほとんど決まり文句のコードで構成されている。 このコードはすべてのinitスクリプトで一致ないしは少なくともかなり似ており、通常はあっちからこっちへのコピペである。 さて、上でリンクを張ったスクリプトから興味深い情報を抽出してみよう。

  • サービスの説明文は「クラッシュしたアプリを検出するデーモン」である。 見たらわかるとおり、ヘッダーのコメントには余分な数の説明の文字列が含まれている。 これらのいくつかは実際のサービスを説明しているというよりかは、それをスタートさせるinitスクリプトについての説明をしている。 systemdサービスも説明文は含んではいるが、それが説明すべきなのはサービスのことであり、サービスファイルのことではない。

  • LSBヘッダー*1には依存関係の情報が含まれている。 systemdはそのソケットベースのアクティベーションの設計のため、通常は手動で依存関係を設定する必要はまったく(ないしはほんの少しだけしか)ない(ソケットアクティベーションの詳細はオリジナルのアナウンスのブログ投稿を見てほしい)。 今回の場合は$syslogに依存(これはabrtdがsyslogデーモンを必要としているということがエンコードされている)することが唯一、価値のある情報である。 ヘッダーがその他の依存関係($local_fs)を挙げているが、これはsystemdには余分な情報である。 というのも、通常のシステムサービスは常にすべてのローカルファイルシステムが利用可能な状態で起動されるためだ。

  • LSBヘッダーはこのサービスがランレベル3(マルチユーザー)と5(グラフィカル)で起動するべきことを示唆している。

  • デーモンのバイナリーは/usr/sbin/abrtdである。

準備はととのった。 この115行のシェルスクリプトの残りの行は全部、単なる決まり文句か余分なコードだ。 このコードで、スタートアップの同期やシリアル化(つまり、ロックファイルを検討するコード)を取り扱ったり、状態メッセージを出力(つまり、echoを呼び出すコード)したり、単に動詞をパース(つまり大きなcaseブロック)したりしている。

上で抽出した情報から、もう、systemdサービスファイルを書くことができる。

[Unit]
Description=Daemon to detect crashing apps
After=syslog.target

[Service]
ExecStart=/usr/sbin/abrtd
Type=forking

[Install]
WantedBy=multi-user.target

このファイルの中身についてちょっと説明しよう。 [Unit]セクションにはサービスに関する汎用的な情報が含まれている。 systemdはシステムサービスを管理するだけでなく、デバイスやマウントポイント、タイマー、あるいはその他のシステムのコンポーネントも管理している。 systemdでぇあこれらのオブジェクトすべてに汎用的な用語を与えており、それはユニットという。 そして[Unit]セクションにはユニットに関して、サービスだけでなく、systemdが維持しているその他のユニットタイプでも適用可能かもしれない情報をまとめている。 今回の場合、次のようなユニットの設定をおこなっている。 説明の文字列を設定し、デーモンがSyslog*2の後に開始されるように設定した。これはオリジナルのinitスクリプトのLSBヘッダーに書かれているのと同じようなことだ。 このsyslog依存関係のために、After=という種類の依存関係をsystemdのユニットであるsyslog.targetに対し作った。 後者はsystemdでは特別なターゲットユニットであり、これはsyslog実装を制御するための標準化された名前である。 これら標準化された名前についての詳細はsystemd.special(7)を見てほしい。 After=という種類の依存関係は示唆された順番をエンコードしているにすぎず、実際にabrtdが起動しするときにsyslogを起動させようとするものではないことに注意してほしい。 そしてこれが実際に我々が求めていることあることにも注意してほしい。abrtdは実際にsyslogが動いていなかったとしてもうまく動くためだ。 とはいえ、2つとも起動していれば(そして、普通はそうだ)、この2つを含む順序はこの依存関係によって制御される。

次のセクションは[Service]であり、これにはサービス自身の情報が含まれている。 これにはサービスにだけ適用される、すべての情報が含まれており、systemdが維持している他の種類のユニット(マウントポイント、デバイス、タイマー、……)にはない。 ここでは2つの設定が使われている。 ExecStart=はサービスが起動したときに実行されるべきバイナリのパスが書かれている。 Type=では、サービスがどのようにしてinitシステムに自身が起動を終えたことを伝えるかが設定されている。 伝統的なUnixデーモンではバックグラウンドデーモンのフォークと初期化が終わってから、親プロセスに戻ることでこれを実現しているため、ここではforkingに設定している。 これはsystemdに対し、スタートアップバイナリーが戻ってくるまで待ち、その後、デーモンプロセスを動かしているプロセスを考慮するように伝えるのである。

最後のセクションは[Install]である。 これは示唆されるインストレーションがどのように見えるべきかについての情報を含んでいる。 つまり、どの環境とどのトリガーに寄ってサービスがスタートすべきかである。 今回の場合、簡単に、このサービスはmulti-user.targetユニットがアクティブになったら開始されるべきと言っている。 これは特別なユニット(既述)であり、基本的には、古典的なSysVのランレベル3*3の役割を引き受ける。 WantedBy=という設定は実行時のデーモンにはほとんど効果がない。 これはsystemctl enableコマンドによってのみ読み込まれる。 このコマンドはsystemdでサービスを有効にするのに推奨されている方法だ。 このコマンドは我々の小さいサービスが、すべての通常起動*4でオンになるmulti-user.targetが要求されればすぐに自動的にアクティブになるようにするものである。

中身の説明はここまでだ。 さて、もう機能する最小限のsystemdサービスファイルを手に入れた。 テストするため、これを/etc/systemd/system/abrtd.serviceにコピーし、systemctl daemon-reloadを実行しよう。 これでsystemdにこのサービスファイルの存在を気づかせ、systemctl start abrtd.serviceを使ってサービスを起動させられるようになる。 systemctl status abrtd.serviceを使って状態を確かめることができる。 systemctl stop abrtd.serviceでサービスを再び停止させられる。 最後に、systemctl enable abrtd.serviceこれを有効にし、次回以降の起動時にデフォルトでこのサービスがアクティブになるようにすることができる。

上のサービスファイルは、十分かつ、基本的な1対1のSysV initスクリプトの翻訳(機能その他)なのだが、まだ改善の余地がある。 ここで、ちょっとだけアップデートしてみる。

[Unit]
Description=ABRT Automated Bug Reporting Tool
After=syslog.target

[Service]
Type=dbus
BusName=com.redhat.abrt
ExecStart=/usr/sbin/abrtd -d -s

[Install]
WantedBy=multi-user.target

さて、何が変わっただろうか? 答えは2つだ。説明の文字列をちょっと改善した。 しかしもっと重要なのは、サービスの種類をdbusに変えて、サービスのD-Busバス名を設定したことだ。 なぜこうしたのか? すでに述べたとおり、伝統的なSysVサービスはスタートアップ後にデーモン化する。 これは通常ダブルフォークとすべてのターミナルからのデタッチが起こる。 これはデーモンがスクリプト経由で起動されるときには便利であり、必須なのだが、systemdのような適切なプロセスのベビーシッターが使われている場合、逆効果なのに加え、不必要(で遅い)のだ。 その理由はフォークされたデーモンプロセスは通常、systemdによって起動されたオリジナルのプロセスとほとんど関係を持たないからであり、それ故、systemdが、フォークが終わった後に、サービスに属しているプロセスのどれがメインプロセスでどれが単なる補助のサービスなのかの見分けるのが難しいためだ。 だが、この情報は高度なベビーシッター、つまり、プロセスの監督、異常終了時の自動的な再スポーン、クラッシュおよび終了コード情報の収集、などを実装するには重要なことだ。 systemdがデーモンのメインプロセスを見つけるのが簡単になるようにサービスタイプをdbusへ変更した。 このサービスタイプのセマンティクスは、サービスの初期化の最終段階でD-Busシステムバス上に名前を取るすべてのサービスに適切なものである*5。 ABRTはその1つである。 この設定によりsystemdはABRTプロセスをスポーンするが、(訳注: 今度の)そのプロセスはもはやフォークしない(これはデーモンに与えられた-dsスイッチで設定されている)し、systemdはバス上にcom.redhat.abrt`が現れたら、すぐにサービスが完全に起動したとみなすようになる。 この方法でsystemdがスポーンしたプロセスはデーモンのメインプロセスであり、systemdはデーモンが完全にスタートアップした時を知る信頼できる方法を手に入れ、systemdはそれを簡単に監督することができるのだ。

それだけのことだ。 オリジナルのSysV initスクリプトでは115行でエンコードされていた以上の情報を10行でエンコードした、簡単なsystemdのサービスファイルを手に入れた。 そして、実はまだsystemdが提供するより多くの機能を使って、さらに改善する余地がたくさんある。 例えば、Restart=restart-alwaysを設定し、systemdにサービスが死んでしまったときにこれを自動的に再起動するように伝えることができる。 あるいはOMMScoreAdjust=0500を使うことで、カーネルにお願いだから、このプロセスをOOM Killerが大損害を与えるときにこのプロセスだけは見逃してくれるよう頼むことができる。 あるいはCPUSchedulingPolicy=idleを使うことで、abrtdプロセスのクラッシュダンプをバックグラウンドでのみ取得し、カーネルに何であれ他に動いていて、CPU時間を必要としているものにパフォーマンスを与えることができる。

ここで言及した設定オプションについての詳細情報は対応するmanページ、systemd.unit(5)systemd.service(5)systemd.exec(5)を見てほしい。 あるいはsystemdのmanページすべてをブラウズするのでも良い。

もちろん、すべてのSysVスクリプトがこれみたいに簡単に変換できるわけではない。 だが、嬉しいことに、実際には大半が簡単に変換できることがわかっている。

今日はここまで。次回の投稿ですぐまた会おう。

*1:原文注: initスクリプトのLSBヘッダーはSysV initスクリプトのトップにあるコメントブロックでサービスについてのメタデータを含ませる慣習であり、Linux Standard Baseで定義されている。これはディストリビューション間でinitスクリプトを標準化することを意図したものである。ほとんどのディストリビューションはこのスキームを採用しているが、ヘッダーの取り扱い方はディストリビューション間でかなり違っており、実際、各ディストリビューションごとにinitスクリプトを調整する必要がある。このようにLSBの仕様は自分がした約束を決して守っていないのである。

*2:原文注: 厳密なことをいうと、この依存関係は実はここで書く必要はなかった。というのも、Syslogデーモンがソケットアクティベーション可能なシステムなら、これは余分なのだ。モダンなsyslogシステム(例えばv5)はソケットアクティベーション可能なよう上流でパッチが当てられている。もしinitシステムがAfter=syslog.targetの設定を使っているなら、依存関係は余分であり、暗黙のものとなる。だが、アップデートされていないsyslogサービスとの互換性維持のために、ここではこの依存関係を含ませた

*3:原文注: 少なくともFedoraではそのように定義されていたものだ。

*4:原文注: systemdではグラフィカルなブートアップ(graphical.targetつまり、SysVランレベル5の役目を引き受ける)はコンソールのみのブートアップ(multi-user.targetつまりランレベル3のようなもの)の暗黙的なスーパセットである。これは後者でサービスを起動させれば、前者でもそのサービスが上がってくることを意味する。

*5:実際、デフォルトのFedoraインストールのサービスの大多数はいまやスタートアップ後にバス上に名前を取る