systemd: (翻訳)PID 1 を考え直す

レナートがsystemdの構想について語ったブログエントリーの翻訳。

原文はこちら:

Rethinking PID 1

投稿は2010年4月30日とかなり古いものではあるが、この時点で現在に至るまでのsystemdの設計思想がしっかり示されており、いまもなお色あせていないと思う。

レナートからブログエントリーの翻訳の許可を取り付けたので、せっかくだから一番最初に訳すのはこの記事にしたい。

  • 一気に訳すだけの気力と時間はないので、何回かに分けて訳す。
  • 記事を分割すると検索性が落ちるので、この記事に加筆修正していく。
  • 最初からきちんと訳せるという気はしておらず、誤字・脱字・誤訳がたぶんいっぱいある。
  • なので、見つけた方はご指摘ください。徐々にいいものにしていきたい。 *1

PID 1 を考え直す

十分な関わりがあったり、行間を読むのが得意であったりすれば、このブログ投稿が何に関することかはもう察しがついているであろう。 だが、そうであったとしても、このストーリーは面白いと思うかもしれない。 なので、1杯のコーヒーを手に取り、座って、何が来るのか読んでほしい。

このブログストーリーは長い。私としては長くてもこのストーリーを読むことしかおすすめできないのだが、1文でまとめるとこうである。 我々は新しいinitを試しており、それはとても楽しい。

コードはここ *2にある。ストーリーはここにある。

プロセス識別子 1

すべてのUnixシステムで、特別なプロセス識別子である1を与えられる1つのプロセスが存在する。 これは他のすべてのプロセスよりも前にカーネルによって起動され、他に親となるプロセスを持たない他のプロセスすべての親となるものである。 そのため、このプロセスは他のプロセスにはできないようなたくさんのことをすることができる。 そして、ブート時にユーザースペースを立ち上げ、維持するといった、他のプロセスが責任を負えないようなことに対しても責任を負うのである。

歴史的に、LinuxではPID 1として振る舞うソフトウェアは由緒あるsyvinitパッケージであった。一方で、sysvinitはかなりの時間、衰えを感じさせていた。 多くの代替案が提案されてきたが、そのうちのたった一つだけが実際に成功した。それはUpstartであり、これは今のところすべてのメジャーなディストリビューションにまで到達することができた。

すでに述べたとおり、initシステムの主たる責任はユーザースペースの立ち上げにある。 そして、優れたinitシステムはそれを速くおこなうものである。 不幸なことに、伝統的なSysV initシステムは特に速いというわけではない。

素早く、そして、効果的なブートアップには2つのことが重要になる - 小さくスタート - よりパラレルにスタート

これは何を意味するのか?小さくスタートは、より少ないサービスを起動させる、あるいは、サービスが実際に必要とされるまではその起動を遅らせることを意味する。 サービスの中には、すでに知られているように、早かれ遅かれ必要とされるものもある(syslog, D-Busシステムなどである)が、その他の多くはこのケースには当てはまらない。 例えば、bluetoothdはBluetoothドングルが実際にプラグインされるか、アプリケーションがD-Busインターフェイストークしたいと思わない限りは、実行されている必要はない。 同じことはプリンティングシステムにも言える。つまり、マシンに物理的にプリンターが接続されているか、アプリケーションが何かを印刷したいと思うまでは、CUPSといったプリンティングデーモンが起動している必要はないのである。 Avahiもそうである。もしマシンがネットワークに繋がれていない場合、アプリケーションがAPIを使いたいと思わない限りは、Avahiが起動している必要がない。 SSHにだって同じことが言える。誰もマシンに接続しようとしなければ、SSHが起動している必要はなく、最初に接続が来たときに起動させればよい。(そして、sshdがリスンしているかもしれないマシンの殆どは誰かが数カ月にたった1度といった感じで接続していると認めよう。)

よりパラレルにスタートは何かを起動させなければならないなら、スタートアップを(sysvinitがするように)シリアルでおこなうべきではなく、すべてを同時に実行すべきであるということを意味する。こうすることで、使用可能なCPUやディスクIOの帯域を織り交ぜて、全体としてのスタートアップ時間を最小化できる。

ハードウェアとソフトウェアが動的に変化する

モダンなシステム(特に汎用OS)は設定と利用においてかなり動的である。それらはモバイルであり、様々なアプリケーションが起動・停止し、様々なハードウェアが脱着される。 initシステムはサービスの維持に責任があり、ハードウェア・ソフトウェアの変化に注意を向ける必要がある。 initシステムはプログラムを実行したり、ハードウェアを有効にしたりするのに必要なサービスを動的に起動(そして、ときには停止)させる必要がある。

ブートアップをパラレル化しようとしているほとんどの流通しているシステムは、まだ、関連する様々なデーモンの起動を同期させている。 つまり、AvahiがD-Busを必要とするため、D-Busを最初に起動させ、D-Busが準備ができたとシグナルを送らないと、Avahiはスタートしない。 類似のことは他のサービスにも言える。 libvirtdとX11はHAL(ここではFedora13を念頭に置いているので、HALが推奨されないことは無視してほしい)を必要とする。なので、libvirtdとX11が起動する前に、HALが最初に起動する。 そして、libvirtdもまたAvahiを必要とするため、libvirtdはAvahiも待つ。 そして、これらすべてがsyslogを必要とするため、全部が全部syslogが完全にスタートアップし、初期化されるのを待つのである。他もこんな感じだ。

ソケットサービスをパラレル化する

この類のスタートアップの同期で、ブートプロセスの重要な部分がシリアル化してしまう。 もし、同期とシリアル化とのコストを取り除くことができたら、素晴らしくないか? あぁ、実際はできるんだよ。 そのためには、デーモン同士が正確に何を要求しあっているか、スタートアップが遅れるのはなぜかについて理解する必要がある。 伝統的なUnixデーモンにとって、これに対する一つの答えがある。それは、他のデーモンが提供するサービスのソケットが接続の準備ができるまで、デーモンが待っているからである。 ふつう、それはファイルシステムにあるAF_UNIXソケットだが、AF_INET[6]というのもありうる。 例えばD-Busのクライアントは/var/run/dbus/system_bus_socketに接続できるようになるまで待つ。 syslogのクライアントは/dev/logを待つ。 CUPSのクライアントは/var/run/cups/cups.sockを待つ。 NFSマウントは/var/run/rcpbind.sockとportmapper IPポートを待つ、など。 こう考えると、実はデーモンが待っている唯一のものはこれなのである。

さて、もしデーモンが待っているものがこれだけなのであれば、我々が、もっと早く接続できるようにこれらのソケットを管理し、デーモンの完全な起動ではなく、実際にはソケットの起動を待つようにすれば、ブート全体をスピードアップさせ、より多くのプロセスをパラレルに起動させることができる。 さて、これはどうやったら可能なのだろうか? 実際はUnixライクなシステムではかなり簡単に可能である。 つまり、我々はリスンするソケットを、デーモンが実際に起動するに作ること、そして、ソケットをexec()している中でデーモンに渡すだけということが可能なのである。 この方法で、我々はすべてのデーモンのためのすべてのソケットをinitシステムの1ステップで作成することができ、2つ目のステップですべてのデーモンを一度に起動させることができる。 もし、あるサービスがその他のサービスを必要としており、それが完全にスタートアップしていなかったとしても、それはまったく問題はない。 というのも、それで起こることは、接続は提供側のサービスにキューイングされ、クライアント側はその一つのリクエストで潜在的にそれ以上進めなくなるということなのである。 しかし、一つのクライアントがそれ以上進めなくなるだけであり、それは、一つのリクエストでしか起こらないのである。 また、サービス間の依存関係も、もはや、より適切にパラレル化されたスタートアップをするために設定される必要はなくなるのである。 つまり、一度にすべてのソケットを起動し、あるサービスが他のサービスを必要とするのであれば、そのサービスがそのソケットに接続できることを確実なものにすることができるからである。

これはこの後に続くことの核心となるものなので、違う言葉で例を使ってもう一度これを説明させてほしい。 syslogと様々なsyslogサービスを同時に起動させると、上で説明したようなスキームの場合、何が起こるかといえば、クライアントのメッセージは/dev/logソケットバッファーに追加されるということが起こるのである。 バッファーが満杯にならない限りは、クライアントは待つ必要はまったくないし、自身のスタートアップをすぐに続けることが可能なのである。 syslog自身がスタートアップを完了したらすぐに、syslogはすべてのメッセージをデキューし、それらを処理する。 もし、同期的なバスのリクエストが送られ、リプライが期待される場合、何が起こるかといえば、そのクライアントはそれ以上先に進めないということである。 ただ、それは、1つのクライアントでのみ起こることであり、D-Busがそれをキャッチアップし、処理するように管理されるまでのことである*3

基本的に、カーネルソケットバッファーを使えば、パラレル化を最大化することができる。そして、序列化と同期はカーネルによっておこなわれるので、ユーザースペースからの管理は不要なのである! そして、デーモンが実際に起動する前にソケットがすべて利用可能になるのであれば、依存関係の管理も不要なものに(あるいは、少なくとも二の次)となるのである。 つまり、もし、あるデーモンが他のデーモンを必要とするのであれば、そのデーモンはただ単に必要とするデーモンに接続すれば良いのである。 もし、必要としているデーモンがすでに起動しているのであれば、これはすぐに成功するであろう。 もしそうでなく、起動中であれば、そのデーモンが同期的なリクエストを発行しない限りは、必要とされているデーモンを待つ必要はないのである。 そして、必要とされているデーモンがまったく起動していなかったとしても、自動的にスポーンさせることができるのである。 他のデーモンを必要とするデーモンからみると、違いは存在せず、依存関係の管理はほとんど不必要か、すくなくとも二の次となる。そして、このすべては最適なパラレル化の状態であり、任意にオンデマンドでロードする状態にある。 その上、もっと堅牢でもある。というのも、実際のデーモンが一時的に(おそらくクラッシュしているために)利用不可能であったとしても、ソケットは利用可能な状態に保つからである。 実際、これを使って我々は簡単に、起動し、終了(ないしはクラッシュ)し、また起動させて、また終了する(以下続く)ようなデーモンを作ることができ、この起動終了のすべてをクライアントが気づかなかったり、リクエストを1つも取りこぼさないようにすることができるのである。

さて、そろそろ休憩の時間だろう。席を立って、コーヒーをカップに補充しよう。安心してくれ。面白いことはまだ続く。

だが、まず、いくつかの点についてクリアにしておこう。この類のロジックは新しいものなのか?いや、そうではない。 このように動作するもっとも有名なシステムはAppleのlaunchdシステムである。MacOSではソケットのリスンはすべてのデーモンから切り離され、launchdにより実行されている。 したがって、サービス自体はパラレルに起動することができ、それらの依存関係は設定する必要がない。 そしてこれは本当に巧妙なデザインで、MacOSの夢のような起動時間を実現することができる主な理由なのである。 私はlaunchd関係者が、自分たちが何をやっているかを説明しているこの動画を非常におすすめする。 残念ながら、このアイディアはApple陣営の外では決して人気を得ることはなかった。

このアイディア自体はlaunchdよりもさらに古い。 launchd以前の、由緒あるinetdはかなりこのように動作していた。ソケットは実際のサービスデーモンを起動し、ソケットのファイル記述子をexec()の中で渡すデーモンを中心につくられたのである。 しかし、inetdのフォーカスははっきりとローカルサービスにはなく、インターネットサービスにあった(後の再実装ではAF_UNIXソケットもサポートされたが)。 inetdはブートアップをパラレル化するツールでもなかったし、潜在的な依存関係を正しく把握するのに役立つものでもなかった。

TCPソケットに対し、inetdが主に使われていた方法は、入ってくる接続それぞれに対し、1つの新しいデーモンインスタンスをスポーンさせるというものであった。 これは、それぞれの接続に対し、1つのたらしいプロセスがスポーンされ、初期化されるというものであり、ハイパフォーマンスのサーバーのためのレシピではなかった。 しかし、当初より利口なことに、inetdは別のモードもサポートしていた。このモードでは、最初の接続で1つのデーモンがスポーンし、その後、このインスタンスが残り続けて後続の接続の受付もするというものである(これはinetd.confにあるwaitおよびnowaitオプションで設定されるが、残念なことにまったく適切に説明されていないオプションである)。 接続ごとにデーモンがスタートすることで、おそらく、inetdは遅いという悪評を得てしまった。だが、これはまったくフェアではない。

バスサービスをパラレル化する

Linuxのモダンなデーモンは素のAF_UNIXソケットではなくD-Busを通じてサービスを提供する傾向がある。さて、ここで問題がある。これらのサービスに、伝統的なソケットサービスの場合と同一の、ブートロジックをパラレル化は適用可能なのだろうか? 答えはイエス、できる。D-Busはこのための正しいフックをすべて、すでに持っている。バスアクティベーションを使うことで、サービスは最初にアクセスされたときに起動させることができる。 ただ、D-Busサービスの提供側と利用側とを同時に起動させるのに我々はリクエストごとの同期が必要なのだが、このアクティベーションは最小限しか提供しない。 つまり、AvahiとCUPS(注: CUPSはmDNS/DNS-SDプリンターをブラウズするためにAvahiを利用する)を同時に起動させようとすると、我々は単純にこの2つを同時に起動させれば良い。 もし、バスアクティベーションロジックを通じて、CUPSがAvahiより速く起動すれば、Avahiが自身のサービス名を確立するまでD-Busに(訳注: CUPSの)リクエストをキューさせておけば良い。

なので、要点はこうである。 ソケットベースのサービスアクティベーションとバスベースのサービスアクティベーションは、ともに、さらなる同期なしに、すべてのデーモンをパラレルに起動させることを可能にする。 アクティベーションはサービスの遅延読み込みも可能にする。 もし、あるサービスがめったに使われないのであれば、ブート時にそれを起動させる代わりに、誰かがソケットやバス名に初めてアクセスしてきたときにそれを読み込めばよい。

これが素晴らしくなければ、一体何が素晴らしいというのだろう!

ファイルシステムのジョブをパラレル化する

現在のディストリビューションのブートプロセスをシリアル化したグラフ*4をみれば、デーモンのスタートアップよりも多くの同期ポイントがある。 もっとも目立つのがファイルシステムに関連するジョブである。つまり、マウント、fsck、クォーターである。 今まさに、ブート時には多くの時間を消費して、/etc/fstabに記述されているすべてのデバイスがデバイスツリーに現れ、fsckされ、マウントされ、(有効であれば)クォーターがチェックされるのをアイドリングして待つことになる。 これが完全に終わってからようやく、後続作業にとりかかり、実際のサービスをブートさせるのである。

これは改善できるのであろうか?できることがわかっている。Herald Hoyerは由緒あるautofsシステムをこれに使うというアイディアに至った。

connect()コールがサービスが他のサービスに関心があることを示すのとちょうど同じように、open()(ないしは類似のコール)で、サービスが特定のファイルやファイルシステムに関心があることを示す。 なので、どれだけパラレル化の度合いを改善するために、これらのアプリが探しているファイルシステムがマウントされていなかったり、すぐに利用できなかったりする場合は、これらのアプリに待たせるようにすることができる。 つまり、autofsのマウントポイントをセットアップし、その後、通常のブートアップで実行されることになっているファイルシステムfsckやクォーターを終えたら、本物のマウントとautofsのマウントとを置き換えるのである。 ファイルシステムがまだ準備できていなければ、アクセスはカーネルによりキューに入れられ、アクセスしてきたプロセスは待たされる。しかし、その1つのデーモンと1つのアクセスに限る。 この方法で、ファイルシステムが完全に利用可能になる前にでも、デーモンを起動させることが可能になる。デーモンは1つもファイルを失わないし、パラレル化も最大化される。

ファイルシステムジョブとサービスジョブのパラレル化は/には当てはまらない。サービスのバイナリーが通常はそこに格納されているからである。 しかし、/homeのようなファイルシステムについて言えば、通常は、/よりも大きかったり、ひょっとすると暗号化されていたり、リモートであるかもしれなかったり、通常のブートでデーモンがめったにアクセスしなかったりする。これ(訳注: パラレル化)によりブート時間をかなり改善できる。 言うまでもないかもしれないが、procfsやsysfsといった仮想のファイルシステムはautofsでマウントすべきではない。

initシステムにautofsを組み込むことを、少し危ういと思ったり、異様だと思ったり、もしかしたら、「狂った*5」ものの見方だと思ったりする読者がいても驚きはしない。 しかし、これをいじくり回してみて、広く言えることとしては、これは確実に、かなり正しいと感じるということだ。 autofsをここで使うということは、単純に、マウントポイントをそれが紐づくファイルシステムを提供する必要なしに、マウントポイントを作成できるということを意味する。 実際にはアクセスを遅らせているだけに過ぎない。 アプリケーションがautofsのファイルシステムにアクセスしようとしており、それを本物のファイルシステムに置き換えるのに時間がかかってしまえば、そのアプリケーションは割り込み可能なスリープ状態でハングすることになる。これは、例えばC-cなどで安全にキャンセルすることができることを意味している。 また、留意してほしいのは、任意のポイントで、マウントポイントが最終的にマウントすべきでない場合(たぶん、fsckが失敗したからだ)は、autofsに(ENOENTのような)クリーンなエラーコードを返すように伝えればいいということだ。 なので、私が言いたいと思っていることは、initシステムにautofsを組み込むことが、一見、冒険的に見えたとしても、我々の書いたコードはこのアイディアが--正しい理由で正しい方法で行われていれば--実際には驚くほどうまく動くことを示しているということだ。

さらに、これらはダイレクトなautofsマウントであるべきだということにも留意してほしい。これはアプリケーションから見ると、古典的なマウントポイントとautofsベースのマウントポイントには実質的な差異はほとんどないことを意味する。

最初のユーザーPIDを小さく保つ

MacOSのブートアップロジックから学んだもう1つのことはシェルスクリプトは有害であるということである。 シェルは速くもあり、遅くもある。 つまり、ハックするのは早いが、実行は遅いのである。 シェルが/bin/bashであろうが、(シェルスクリプトをより速く実行できるように書かれた)その他のシェルであれ、結局、このアプローチは遅くなる運命にある。 私のシステムで/etc/init.dにあるスクリプトgrepを少なくとも77回呼び出している。 awkは92回、cutは23回、sedは74回である。 これらのコマンドやその他のコマンドが呼び出されるたびにプロセスがスポーンし、ライブラリは検索され、i18nのようなものがいくつかセットアップされ、その他もろもろがおこなわれる。 そして、一部の例外を除き、ちょっとした文字列を操作する程度のことを行った後、プロセスは再び終了する。 もちろん、これは間違いなく、驚くほど遅い。 ほかでもないシェルがこのように物事を実行するのである。 これにくわえて、シェルスクリプトは非常に壊れやすくもあり、環境変数やその種のもの、予測と統制のしづらい要素によって、劇的にその挙動を変化させるものなのである。

なので、ブートプロセスからシェルスクリプトを取り除こう! それをする前に、我々はシェルスクリプトが現時点で正確には何のために使われているかを把握する必要がある。 あぁ、全体像としては、ほとんどの時間、シェルスクリプトがやっていることは実際、かなりつまらないことであろう。 スクリプトでやっていることのほとんどは取るに足らないセットアップであったり、サービスの取り壊しであったりで、Cで書き直すべきであり、別の実行ファイルにあるべきだったり、デーモン自身の中に移すべきだったり、あるいは、シンプルにinitシステムがやるべきだったりするのだ。

システムのブートアップからシェルスクリプトをすっかり取り除くというのは、近いうちにできるようなことではない。 これらをCで書き直すのには時間がかかるし、いくつかのケースではまったく意味がなかったりするだろうし、場合によってはシェルスクリプトがこれなしに済ませるには便利すぎるということもあるだろう。 だが、少なくとももっと目立たせないようにすることはできる。

ブートプロセスへのシェルスクリプトの蔓延具合を図る良きメトリックはシステムが完全にブートアップしたあとに、開始される最初のプロセスのPIDである。 ブートアップ後、ログインし、ターミナルを開き、echo $$をタイプしよう。 これを手元のLinuxシステムで試してみて、MacOSでやってみた結果と比べてみよう!(ここでヒント。こんな感じだ。我々が持っているテストシステムでやってみたところ、LinuxのPIDは1823なのに対しMacOSのPIDは514だ。)

プロセスを追跡し続ける

サービスを起動し、維持するシステムの中心的部分は、プロセスのベビーシッターを務めること、つまり、サービスを監視するということである。 サービスがシャットダウンすれば再起動させる。 サービスがクラッシュすれば、関連する情報を集め、管理者の手元に置き、その情報とABRTのようなクラッシュダンプシステムから利用可能なものとつなぎ合わせ、syslogやauditシステムのようなロギングシステムに入れておく。

このシステムはサービスを完全にシャットダウンさせる能力が必要である。 これは簡単に聞こえるが、考えているよりは難しいものである。 Unixの伝統ではプロセスはダブルフォークしており、その親の監督下を逃れることができていしまう。 古い親は、自身が確かに起動させたサービスと新しく生まれたプロセスとの関係を知ることはないのである。 例を挙げよう。 現在のところ、ダブルフォークされ、誤った挙動をしているCGIスクリプトApacheをシャットダウンしても停止されることはない。 さらに言えば、名前と目的を知らない限りはそのプロセスとApacheの関係を知ることさえできないのである。

さて、プロセスを追跡し続け、数え切れないほどフォークしているような場合でも、それらがベビーシッターから逃れることなく、1つのユニットとしてコントロールするにはどうしたらいいのだろうか?

様々な人々がこれに対する様々な解法を考えついた。 ここで詳細を触れることはしないが、少なくとも次のことは言わせてほしい。 ptraceないしはnetlinkコネクター(カーネルインターフェイスで、任意のプロセスがfork()exit()するたびにnetlinkメッセージを得られるようになるもの)は、いくらかの人々が調査し、生み出したものだが、これらをベースにしたアプローチは、美しくなく、そこまでスケーラブルではないとして批判されてきた。

では、どうすればよいか? あぁ、ちょっと前から、カーネルコントロールグループ("cgroups"としても知られている)を知っている*6。 基本的にcgroupではプロセスのグループの階層を作り出すことが可能になる。 階層が仮想ファイルシステムとして直接見えるため、簡単にアクセスすることができる。 グループの名前は基本的にそのファイルシステムディレクトリ名である。 特定のcgroupに属している、あるプロセスがfork()をすると、その子は同じグループのメンバーとなる。 その子が特権を与えられ、cgroupファイルシステムにアクセスできない限りは、そのグループから逃れることはできない。 もともと、cgroupがカーネルに導入されたのはコンテナーのためであった。 あるカーネルのサブシステムはあるグループのリソースに対し制限をかけることができるのだ。例えば、CPUやメモリーの使用量だ。 伝統的なリソース制限(setrlimit()で実装されたようなもの)は(ほとんど)プロセスごとである。 一方、cgroupはプロセスのグループ全体に対し制限をかけることができる。 また、cgroupはコンテナーというユースケース直接でなくても、制限をかけるのに便利である。 例えば、Apacheとその子すべてが使ってもよいメモリーやCPUの合計量に制限をかけることができる。 そして、誤った挙動をするCGIスクリプトは、簡単にフォークすることで、setrlimit()のリソースコントロールからもはや逃れることはできないのである。

コンテナーやリソース制限の強化に加え、cgroupはデーモンを追跡し続けるのに大変便利である。 cgroupのメンバーであることは確実に子に継承され、逃れることはできないのだ。 ある通知システムが利用できるのだが、これによってcgroupが空になったら監督者のプロセスは通知を受けることが可能になる。 あるプロセスのcgroupは/etc/$PIDを読むことで調べることができる。 したがって、cgroupはベビーシッターの目的でプロセスを追跡し続けるのにとても良い選択肢なのである*7

プロセスの実行環境をコントロールする

良きベビーシッターはデーモンの起動・終了・クラッシュを監督・コントロールするだけのものではない。 デーモンに優れた、最小限で、安全な実行環境をセットアップすることもある。

これはsetrlimit()によるリソース制限やユーザーIDやグループID、環境ブロックといった、プロセスの明らかなパラメータを設定することを意味するが、それだけでは終わらない。 Linuxカーネルによりユーザーと管理者にプロセスに対する多大なコントロール(いくつかは現時点ではめったに使われない)を享受する。 それぞれのプロセスに対し、CPUとIOスケジューラーのコントロール、ケーパビリティ―バウンディングセット*8、CPUアフィニティやもちろん、追加の制限をcgroup環境に設定することなどができる。

例えば、ioprio_set()IOPRIO_CLASS_IDLEを組み合わせるとlocateupdatedbによるシステムの対話への影響を最小限にする優れた方法である*9

加えて、ある高度なコントロールも非常に便利である。読み込み専用のbindマウントをベースにしたリードオンリーのファイルシステムのオーバーレイを設定するようなことである。 この方法だと、あるデーモンをすべて(あるいは一部)のファイルシステムをデーモンに対し、読み込み専用で見せることができ、それによりすべての書き込み要求に対しEROFSを返すことになる。 このような感じで、これはデーモンができることを制限するのに使うことができる。 これはやり方としては手軽なSELinuxポリシーシステムと似ている(ただ、SELinuxを確実に置き換えるものではない。そういう悪い考えは持たないでくれ、頼むから。)

最後に、ログを取ることはサービスを実行する上で重要な部分である。 理想的にはサービスが生み出す、僅かな出力のすべてをログに取るべきである。 そのため、initシステムは自分がスポーンさせたデーモンを起動直後からログを取る機能を提供し、stdout(訳注: 標準出力)やstderr(訳注: 標準エラー出力)をsyslogにつなぐ。 あるいは、いくつかのケースでは/dev/kmsgに繋ぐ。 ちなみに、/dev/kmsgは多くのケースで便利なsyslogの代替となるものだ(組み込みな人たちよ、よく聞くがいい!)。 特に、カーネルログバッファーが非常に巨大なすぐ使えるものとして設定されている場合は特にそうだ *10

*1:編集履歴 17.11.07-01: 'Controlling the Process Execution Environment' を訳出 17.11.06-01: 'Keeping Track of Processes'を訳出 17.11.05-03: 誤字を修正 原文へのリンクを追加 thanks to nekomatuさん 17.11.05-02: 誤字を修正 thanks to いくやさん 17.11.05-01: 'Keeping the First User PID Small'まで訳出

*2:原文のリンクは http://git.0pointer.net/?p=systemd.git どうやら、レナートの個人サイトにgitレポジトリがあったようだ。訳文ではgithubのレポジトリにリンクを張り直した

*3:原文の記述が複雑で、訳文としては適切かはちょっと怪しい。ただ、ここで言いたいことは、1. 他のサービスのソケットにメッセージを送るだけで、返信を必要としないサービスであれば、ソケットのバッファーに貯めておけば、そのサービスは起動を続けられる。2. 他のサービスとの同期(メッセージの送受信)が必要なサービスであれば、メッセージを受け取るだけ受け取っておいて、待たせておけばいい、ということである。

*4:画像があったはずだが、picasa終了に伴いリンク切れ

*5:原文は'crakish'で、「麻薬中毒者」を形容する口語のようである。日本語で相当するのは「ラリった」なのかもしれないが、訳文として書くのが憚られたため、このように訳した。

*6:原文のリンクは切れていたため適切なものに付け替えた。cgroupについて日本語で読めるものに Redhatのドキュメントがあるほか、幸いにして、 LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術:連載|gihyo.jp … 技術評論社 もある。

*7:原文では案内がないが、このへんで一旦コーヒーを入れに行くのが良い。

*8: https://linuxjm.osdn.jp/html/LDP_man-pages/man7/capabilities.7.html

*9:原文は'a great away'とあるが、'a great way'の間違いか。

*10:この一文は自信がない。また、どこにかかるのかも自信がない。当該部分の原文は'especially in times where the kernel log buffer is configured ridiculously large out-of-the-box.'である。ここでは、これが前にある'a very useful replacement for syslog'にかかるものと解釈して訳した。