#きょうの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として振る舞うソフトウェアは由緒あるsysvinitパッケージであった。一方で、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

Upstartの場合

最初に、強調しておきたいのは、私はUpstart*11のコードが本当に好きで、十分にコメントがついていて、中身を追いやすい。他のプロジェクトもここから学ぶべきものがある(自分も含めて。)

それはそうだが、私はUpstartの全体的なアプローチには同意できない。 だが、最初に少し、このプロジェクトについて説明しておこう。

Upstartはsysvinitとはコードを共有していないが、機能面では上位互換であり、よく知られたSysV initスクリプトとある程度は互換性がある。 Upstartの主な特徴はイベントベースのアプローチである。 プロセスの起動と停止はシステムに起こる「イベント」に紐付いており、「イベント」は多種多様なものがなりえる。例えば、ネットワークインターフェイスが有効になったり、他のソフトウェアが起動したりだ。

Upstartはこれらのイベントによりサービスをシリアル化している。 syslog-startイベントがトリガーされると、これはD-Busを開始することの指示として使われる。というのも、D-BusがSyslogを使えるようになったからだ。 そして、dbus-startedがトリガーされると、NetworkManagerが起動する。これはNetworkManagerが今やD-Busを使えるようになったからだ、という具合だ。

この方法で、存在し、管理者や開発者が理解する実際の論理的な依存関係のツリーがイベントやアクションのルールに翻訳、エンコードされるという人がいる。 すべての「AがBを必要とする」というルールは管理者や開発者によって「Bが起動したらAが起動する」プラス「Bが停止したらAも停止する」ものとして、理解されるのである。 ある点ではこれは確かに簡素化ではある。Upstart自身のコードにとっては特にそうだ。 しかし、この簡素化は実際には有害であると私は主張したい。 何と言っても最初に、論理的な依存関係の体系はどこへも行っておらず、Upstartファイルを書く人間がいまや依存関係を手動でこれらのイベントやアクションルール(実際には、それぞれの依存関係のための2つのルール)に翻訳しなければならないのである。 なので、コンピューターに依存関係をベースに何をすべきかを示させる代わりに、ユーザーが手動で依存関係を簡単なイベントやアクションルールに翻訳しなければならないのである。 また、依存関係の情報は決してエンコードされないため、実行時に利用することはできず、これは事実上、なぜ、これが起きたのか、例えば、なぜ、Bが起動したら、Aが起動したのか、を調べようとする管理者には何もわからないということを意味する。

さらに、イベントロジックはすべての依存関係を、つま先から頭の先までぐるっと一回りすることになる。仕事の量を最小化する(これはこのブログストーリーの冒頭で指摘したとおり、良きinitシステムがフォーカスすべきものである)代わりに、実際には作業中にすべき仕事の量を最大化することになる。 他の言い方をすれば、明確なゴールを持ち、そのゴールに到達するために本当に必要なことだけをする代わりに、Upstartは一歩進んでしまうと、その後、それに続くすべてのステップを歩んでしまうのである*12

より簡単に説明してみよう。 ユーザーがD-Busを起動させただけという事実は、決してNetworkManagerも起動すべきという指示にはならない(しかし、これがUpstartのやっていることである)。 逆に考えてみよう。ユーザーがNetworkManagerを必要としたときに、これが明確な指示となって、D-Busも起動すべきだ(これがほとんどのユーザーが本当に期待するものではないかい?)。

良きinitシステムは必要なものだけを起動すべきで、オンデマンドであるべきだ。遅延かパラレル化と先行だ。 しかし、initシステムは必要以上のものを起動させるべきではない。とくに、そのサービスを使えるようなもので、インストールされているすべてを起動させてはいけない。

最後に、私はイベントロジックの実際の利便性を見出すことができなかった。 Upstartで見せられるほとんどのイベントが実際には本来は即座のものではなく、持続性があるこように私には思えた。 サービスが起動し、実行中になり、停止する。 サービスがプラグインされ、利用可能になり、またプラグアウトされる。 マウントポイントがマウントされるプロセスにあり、完全にマウントされ、アンマウントの途上にある。 電源プラグが差し込まれ、AC電源でシステムが稼働し、電源プラグが抜かれる。 initシステムやプロセスの監督者が取り扱うべきイベントのほんの少数だけが、即座のものであり、それらのほとんどは、起動、実行状態、停止のタプルである。 Upstartに戻ると、こういった情報はUpstartでは利用できないのだ。なぜなら、Upstartは単一のイベントにフォーカスしており、持続性のある依存関係は無視してしまうからである。

さて、気づいてしまったのだが、上で私が指摘したような問題のいくつかは、最近のUpstartの変化でいくらか和らげられている。 とくに、Upstartルールファイルの状況ベースのシンタックス、例えば、start on local-filesystems and net-device-up IFACE=loといったものだ。 だが、私からすれば、これはほとんど、コアのデザインに弱点を抱えるシステムを直そうとする試みのように見えてしまう。

いくつかの選択に疑問があるかもしれない(上記参照)としても、そして、実際に多くの機会を逸している(これも上記参照)としても、その他は、Upstartにデーモンの子守としてOKを出せる。

sysvinitを除けば、他にinitシステムとして挙げられるのはUpstartとlaunchdだ。 initシステムの多くはUpstartやsysvinit以上のものを打ち出せていない。 この他、最も興味深い競争相手はSolaris SMFである。これはサービス間の適切な依存関係をサポートする。 しかし、多くの点でSolaris SMFはとても複雑であり、言わせてもらえば、XMLを過度に使っていて、知られていることへの新しい術語がちょっとアカデミックである。 これはcontractシステムといったSolarisに固有な機能に密接に結びついてもいる。

すべてをうまくやる: systemd

おっと、もう1回、ちょっと小休止を挟むのにいい時間になったようだ。というのも、ここまでで、私が考える良きPID 1が何をすべきで、現時点で最も使われているinitシステムが何をしているかを説明できたことだし、そろそろメインディシュに移るころだからだ。 さて、席を立って、もう一度、コーヒーカップをいっぱいにしよう。 それだけの価値はあるはずだ。

読者はこう思っているかもしれない。 ここまでで理想的なinitシステムの必要条件と機能として提案してきたようなものが、実際には、systemdと呼ばれる(まだ実験段階の)initシステムにはもうあるのだろと。これこそが、私がここでアナウンスしたいものだ。 再度になるが、コードはここにある*13。 そして、ここではその機能について簡単な紹介とその背後にある理由付けだ。

systemdはシステム全体の起動と監督をおこなう(だから、この名前なんだ)。 systemdはここまでで指摘してきたような機能プラスアルファを実装している。 systemdはユニットという概念を軸につくられている。 ユニットは名前とタイプを持つ。 ユニットの設定は通常はシステムより直接読み込まれるため、ユニット名は実際にはファイル名である。 例えばこうだ。 avahi.serviceというユニットは同名の設定ファイルから読み込まれ、もちろんAvahiデーモンをカプセル化したユニットになる。 ユニットにはいくつかの種類がある。

  1. service: ユニットの中でも明確な種類のユニットである。 デーモンは起動、停止、再起動、リロードすることができる。 SysVとの互換性のため、我々は自分たちのサービスの設定ファイルだけでなく、古典的なSysv initのスクリプトも読み込むことができる。 とりわけ、存在すれば、LSBヘッダーをパースする。 したがって、/etc/init.dは別の設定ファイルの置き場所以上のものである。

  2. socket: このユニットはファイルシステム上やインターネット上のソケットをカプセル化する。 現時点ではAF_INETAF_INET6AF_UNIXソケットをサポートし、種類としてはストリーム、データグラム、シーケンシャルパケットをサポートしている。 また、古典的なFIFOも転送としてサポートしている。 それぞれのsocketユニットは対応するserviceユニットを持っており、ソケットやFIFOに最初の接続が来ると、対応するserviceユニットが起動される。 例えば、nscd.socketは、接続があるとnscd.serviceを起動させる。

  3. device: このユニットはLinuxのデバイスツリーにあるデバイスカプセル化する。 デバイスがudevのruleを通じて、このユニットにマークされると、systemdではdeviceユニットとして見ることができるようになる。 udevに寄って設定されたプロパティーはデバイスユニットの依存関係を設定するための設定情報として利用することができる。

  4. mount: このユニットはファイルシステム階層のマウントポイントをカプセル化する。 systemdはすべてのマウントポイントを監視し、マウントポイントが行ったり来たりする様子を把握する。 また、マウントポイントをマウント・アンマウントするのにも使える。 /etc/fstabはここではこれあのマウントポイントに関する追加の設定情報として使われる。 これはSysVのinitスクリプトserviceユニットの追加の設定情報として使われるのと似ている。

5.automount: この種類のユニットはファイルシステム階層のautomountポイントをカプセル化する。 それぞれのautomountユニットは対応するmountユニットを持ち、mountユニットはautomountディレクトリにアクセスがあるとすぐに起動(=マウント)される。

  1. target: このユニットタイプはユニットの論理的なグループとして使われる。 それ自身では一切何もしない代わりに、targetユニットは単純に他のユニットを参照し、参照されたユニットを一緒にコントロールすることができる。 例を上げるとこうだ。 multi-user.targetは基本的に、古典的なSysVシステムではランレベル5の役割を果たすユニットである。 bluetooth.targetBluetoothドングルが利用可能になるとすぐに要求されるターゲットで、Bluetooth関係のサービスを制御する。 これによって制御されるサービスはこういうときでなければ起動されないようなサービスで、bluetoothdobexdといったものだ。

  2. snapshot: targetユニットと同様に、snapshotユニットも自分自身では何もせず、他のユニットを参照することだけを目的とするユニットである。 スナップショットはinitシステムのすべてのサービスやユニットの状態を保存し、ロールバックするのに使える。 主として2つのユースケースが考えられている。 1つは、「エマージェンシーシェル」のような特殊な状態に一時的にユーザーが入ることを許可する場合だ。 現在のサービスを停止し、元の状態に戻るための簡単な方法を用意し、一時的に引き下げたすべてのサービスを再び引き上げるのである。 もう1つはシステムのサスペンドである。 未だに多くのサービスが正しくシステムのサスペンドを取り扱えないが、サスペンドする前にこれらをシャットダウンし、後から復帰させてしまうのはたいていベターな考えだ。

これらすべてのユニットには互いに依存関係がある(依存関係はポジティブなのもネガティブなのもある。例えば、「必要とする(Requires)」と「対立する(Conflicts)」だ)。 デバイス(訳注: ユニット)にはサービスに対する依存関係をもたせることができる。これは、デバイスが利用可能になれば、すぐにあるサービスが起動することを意味する。 マウント(訳注: ユニット)には、自身がそこからマウントされているデバイスに対する明示的な依存関係を得ることになる。 マウント(訳注: ユニット)はまた、それらのプレフィックスであるマウント(訳注: ユニット)に対し明示的な依存関係を持つことになる(つまり、/home/lennartというマウントは追加で/homeのマウントに明示的に依存関係を得るのだ)。

その他の機能を短いリストに記す

  1. スポーンされたプロセスに対して、それぞれ管理することができる。 環境、リソース、制限、ワーキングディレクトリ・ルートディレクトリ、umask、OOMキラー調整、niceレベル、IOのクラスと優先度、CPUのポリシと優先度、CPUアフィニティ、timer slack*14、ユーザーID、グループID、補助グループID、読み込み可能・書き込み可能・アクセス不可のそれぞれのディレクトリ、共有・プライベート・スレーブそれぞれのマウントフラグ、ケーパビリティ・バウンディングセット*15、セキュアビット、CPUスケジューラーのフォークの再設定、プライベートな/tmp名前空間、様々なサブシステムへのcgroup。 また、簡単にサービスの標準入力、標準出力、標準エラー出力をsyslogや/dev/kmsgや任意のTTYに接続することができる・ 入力にTTYを接続すると、systemdは必要に応じてそれを待ったり、強制したりし、プロセスが排他的なアクセスを得られるようにする。

  2. 実行されるプロセスはすべて自分自身のcgroupを獲得し(現時点のデフォルトではdebugサブシステムにおいて。というのも、このサブシステムは他では使用されておらず、プロセスの最も基本的なグループ化以上でもなければ以下でもないから*16である)、systemdを設定し、外部ですでに設定されたcgroupにサービスを置くことができる。例えば、libcgroupユーティリティー経由で(外部でcgroupを設定する)といった具合である。

  3. ネイティブな設定ファイルの構文はよく知られている.desktopファイルのそれに厳格に従っている。 これは簡単な構文で、すでに多くのソフトウェアフレームワークでこれのパーサーが存在する。 また、これのおかげで、サービスの説明やその他類似のもので、国際化のための既存のツールを利用することができるようになる。 管理者や開発者は新しい構文を学ぶ必要はないのである。

  4. すでに述べたとおり、我々はSysV initスクリプトに対するご完成を提供する。 我々はLSBおよびRed Hat chkconfigヘッダーが利用可能であれば、それを駆使する。 もし、これらが利用できなければ、/etc/rc.dの起動の優先順といったその他の利用可能な情報で最善を尽くすようにする。 これらのinitスクリプトは単純に設定の異なるソースとして考えられており、そのため、systemdサービスへの簡単なアップグレードパスを提供することができる。 必要に応じて、古典的なPIDファイルをサービスがデーモンのメインプロセスのIDを特定するのに読み込むことも可能である。 我々はLSB initスクリプトヘッダーからの依存関係の情報を利用し、これらをネイティブなsytemdの依存関係に翻訳することに注意してほしい。 さらに、Upstartはこれらの情報を取得し、利用できないことに注意してほしい。 したがって、素のUpstartシステムと大半のLSB SysV initスクリプトとの組み合わせでは、パラレル化はできない一方、systemdを動かしている似たようなシステムではそれができるのである。 事実、Upstartにとって、SysVスクリプトはまとまって、1つのジョブとなり、実行されるため、これらを別個に扱うことができない。 再び、systemdと比べると、systemdではSysV initスクリプトはただ別の設定元であり、これらはすべて、その他のネイティブなsystemdのサービスと同じように別個に扱われ、制御される。

  5. 同様に、我々は既存の/etc/fstab設定ファイルも読み込むことができ、これを、ただの別の設定元として考慮する。 commnet=というfstabオプションを使うことで、/etc/fstabエントリーをsystemd制御下のautomountのポイントとしてマークすることさえできる。

  6. 複数の設定素で同じユニットが設定されている場合(例えば、/etc/systemd/system/avahi.service/etc/init.d/avahiとが存在する場合)、ネイティブな設定が常に上位となり、レガシーなフォーマットは無視される。 これにより、簡単なアップグレードパスを用意でき、パッケージはSysV initスクリプトとsystemdのサービスファイルとの双方をしばらくの間、持つことができる。

  7. 我々は簡単なテンプレートおよびインスタンスのメカニズムをサポートする。 例えば、6つのgettyのための6つの設定ファイルを用意する代わりに、我々は1つのgetty@.serviceファイルを用意するだけで良い。 これにより、getty@tty2.serviceといった具合に、インスタンス化することができる。 インターフェイス部分さえも依存関係の表現により継承することができる。 例えば、eth0という文字列はワイルドカードで扱いながら、dhcpd@eth0.serviceというサービスはavahi-autopid@eth0.serviceを制御するようにエンコードすることが簡単にできる。

  8. ソケットアクティベーションにより我々は伝統的なinetdモードとの完全な互換性をサポートする。これは、launchdのソケットアクティベーションを真似しようとするもので、新しいサービスには推奨されている非常に簡単なモードに加えてサポートされるものである。 inetdモードはソケットを起動済みのデーモンに渡すことだけができるが、ネイティブモードでは任意の数のファイル記述子を渡すことができる。 また、我々は接続ごとに1つのインスタンスをサポートする。これは、すべての接続に対し1つのインスタンスというモードに加えてサポートされる。 前者では、接続パラメーターに従って、デーモンがcgroupを名付け、これのために、既述のテンプレートロジックを利用する。 例えば、sshd.socketsshd@192.168.0.1-4711-192.168.0.2-22.servicesshd@.service/192.168.0.1-4711-192.168.0.2-22というcgroupでスポーンさせるかもしれない(つまり、IPアドレスとポート番号がインスタンス名に使われるのである。AF_UNIXソケットに対してはPIDと接続してきたクライアントのユーザーIDを利用する)。 これにより、管理者はデーモンの様々なインスタンスを識別子、その実行を別個に制御するための素晴らしい方法を手に入れる。 ネイティブなソケットを渡すモードはアプリケーションで非常に簡単に実装できる。 $LISTEN_FDSが設定されていると、これには渡されたソケットの番号が含まれており、デーモンは.serviceファイルに一覧でソートされているとおりに、ソケットを見つけてくる。 このリストはファイル記述子3から始まる(うまく書かれたデーモンは自身が受け取るソケットが1つ以上の場合に、fstat()getsockname()も、ソケットを識別するのに使うことができる)。 加えて、$LISTEN_PIDをファイル記述子を受け取るデーモンのPIDに設定することもできる。というのも、環境変数は通常、サブプロセスにより継承されるものであり、(訳注: プロセスの親子関係の)鎖を下るほどプロセスを混乱させるものとなるためである。 このソケットを渡すロジックをデーモンで実装するのが非常に簡単であったとしても、ソケット渡しをどうすべきかを示しているBSDライセンスのリファレンス実装をサポートする。 我々はすでに既存のデーモンのいくつかを新しいスキームへポートしている。

  9. 我々は/dev/initctlとの互換性をある程度は提供する。 この互換性は実際にはFIFOによりアクティブ化されるサービスとして実装される。 これはレガシーな要求をD-Busの要求へと単純に変換するものである。 事実上、これは、Upstartsysvinitの古いshutdownpoweroff、あるいは類似のコマンドをsystemdでも動かし続けられるということを意味する。

  10. utmpwtmpとの互換性も提供する。 utmpwtmpのお粗末さを考えると、妥当な程度以上のものになるかもしれない。

  11. systemdはユニット間のいくつかの依存関係をサポートする AfterBeforeを使って、ユニットがどのようにアクティブになるかを決めることができる。 これは完全にRequiresWantsと直行している。この2つは必須ないしは任意のポジティブな要件の依存関係を表現するものである。 そして、Conflictsはネガティブな要件の依存関係を表現する。 最後には、これらはあまり使われないが、さらに3つの依存関係がある。

  12. systemdは最小限のトランザクションシステムを持っている。 意味するところはこうだ。 あるユニットが起動ないしはシャットダウンを要求された場合、一時的なトランザクションにそれとそのすべての依存関係が追加される。 そして、そのトランザクションが一貫性のあるものか(つまり、すべてのユニットのAfterBeforeを順番付けることが循環しないかどうか)を検証する。 そうでなければ、systemdはこれを修復しようとし、ループを除くかもしれない不必要なジョブをトランザクションから除く。 また、systemdはトランザクションの中の不必要なジョブで実行中のサービスを止めるようなジョブを抑制しようとする。 不必要なジョブとは、オリジナルのリクエストには直接含まれていないが、Wantsタイプの依存関係により制御されているようなジョブのことである。 最後に、トランザクションのジョブがすでにキューに入れられているジョブと矛盾しないかどうか、そのときに任意にトランザクションを中止できるかをチェックする。 すべてがうまくいき、トランザクションには一貫性があって、影響が最小限に抑えられれば、そのトランザクションはすでに進行中のジョブとマージされ、実行キューに追加される。 事実上、これが意味するところは、我々は、要求された操作が実行する前に、これが妥当なものであるかを検証し、可能であれば修復し、本当に実行できない場合にのみ、失敗させるようにすることである。

  13. スポーンし、監督するすべてのプロセスのPIDと終了ステータスに加え、開始・停止時間も記録する。 このデータは、abrtdやauditd、syslogのデータを持つデーモンとの橋渡しに、使うことができる。 こう考えてほしい。クラッシュしたデーモンをハイライトして見せてくれるUIであり、syslogやabrtsic.)、auditdそれぞれのUIへ簡単にナビゲートしてくれるUIへだ。 syslogやabrt[sic.]やauditdは特定の進行の際に、このデーモンからデータを生成したり、このデーモンのためにデータを生成するだろう。

  14. initプロセス自身を任意のタイミングで再実行することをサポートする。 デーモンの状態は再実行前にシリアル化され、事後にシリアル化を解除される。 この方法で、我々はinitrdデーモンから最終のデーモンまでをハンドオーバーし、initシステムのアップグレードを容易にする簡単な方法を提供する。 開かれたソケットとautofsマウントは適切にシリアル化される。 これは、クライアントがinitシステム自身の再実行に気づきすらしない方法で、ソケットやマウントをいつでも接続可能な状態に保ち続けるためである。 また、システムサービスの大部分はともかくcgroupの仮想ファイルシステムエンコードされていることで、シリアル化のデータにアクセスすることなく再開することさえ可能である。 再実行のコードパスはinitシステムの設定をリロードするコードパスと実際にはほとんど同じであり、このコードパスでは、再実行(これはおそらく、(訳注: リロードに比べれば)トリガーされることは少ないだろう)がリロード(こちらのほうがおそらくより一般的)として同様のテストを受けられることを保証するものである。

  15. ブートプロセスからシェルスクリプトを取り除く作業をはじめたことで、基本的なシステムのセットアップの部分をCで再コーディングし、これを直接systemdへと移している。 この中には、APIファイルシステムのマウント(つまり、/proc/sys/devといった仮想ファイルシステム)やホスト名の設定がある。

  16. サーバーの状態はD-Busを通じて、検証可能*17・制御可能である。これはまだ完璧ではないが、かなりの広がりを持つ。

  17. ソケットベースやバス名ベースのアクティベーションを我々は強調したく、それゆえ、ソケットとサービスの間の依存関係をサポートしている一方、伝統的なサービス間の依存関係もサポートしている。 我々はこういったサービスが自身の準備が整ったことを知らせる方法をいくつもサポートしている。 設定したサービス名が現れるまでバスを監視することに加え、フォークし、起動プロセスを終了させること(つまり、伝統的なdaemonize()の振る舞い)で(訳注: サービスの準備が整ったことを知らせることができる)。

  18. systemdによりプロセスがスポーンされるたびに確認を求めるインタラクティブモードがある。 これは、カーネルコマンドラインsystemd.confirm_spawn=1を渡すことで有効にすることができる。

  19. systemd.default=というカーネルコマンドラインパラメーターにより起動時にどのユニットをsystemdが開始するかを指定することができる。 通常、ここでは、multi-user.targetのようなものを指定するが、ターゲットの代わりに単一のサービスを指定することも可能である。 例えば、提供されたままの状態で、我々はemergency.serviceというサービスを同梱している。 これは、使い勝手という点ではinit=/bin/bashと似たものであるが、実際にはinitシステムを動かしているという利点があり、それゆえ、緊急のシェルから完全なシステムを起動させるというオプションも提供している。

  20. サービスを起動、停止、検証することを可能にする最小限のUIがある。 完成にはほどとおいが、デバッグツールとしては便利なものである。 これはValaで書かれており(イェイ!)、systemadmという名前でいく。

systemdはLinux固有の機能を多く使っており、POSIXに閉じこもっていない点は注意すべきでだろう。 他のオペレーティングシステムへの移植性のためにデザインされているシステムでは提供できないような機能の多くを解き放っている。

状態

上で挙げたすべての機能はすでに実装されている。 今からでも、すでにsystemdはUpstartやsysvinitを気軽に入れ替えるものとして使うことができる(少なくとも、多すぎるほどのネイティブなUpstartのサービスがまだない限りは。幸いなことに、多くのディストリビューションで、ネイティブなUpstartサービスは多すぎるというほどには多くない)。

しかし、テストは最小限であり、バージョン番号は現時点では象徴的な0である。 現在の状態で実行すれば、壊れることは予期してほしい。 とは言うものの、かなり安定しているし、我々の中にはすでに通常の開発環境をsystemdで動かしているものもいる(VMだけと比べて)。 特に、もし、これを我々開発者が使っていないディストリビューションで使えば、どうなるかはわからない。

どこへ向かうのか?

上で説明したような機能は確かにすでに包括的である。 だが、我々は取り掛かったばかりである。 私はビッグマウスは好まないのだが、これは、我々が推し進める方向の短い概要に過ぎない。

我々は少なくともユニットの種類をあと2つは追加したいと思っている。 swapスワップバイスを、すでにマウントを制御しているのと同じ方法で制御するのに使えるものだ。 つまり、デバイスツリーのデバイスの自動的な依存関係により、それらがアクティブになるのと同じような感じだ。 timerではcronと似たような機能を提供したい。 つまり、時間イベントをベースにサービスを開始させる。 ここで対象となっているのは単調な時計(monotonic clock)と経過時間・カレンダーイベントとの双方である(つまり、前者が「最後に実行されてから5時間後に開始」で、後者が「毎週月曜午前5時に開始」といったものだ)。

しかし、より重要なのことに、我々の計画では、systemdで実験するのは、起動時間の最適化だけではなく、systemdを理想のセッションマネージャーとすること、つまり、gnome-sessionkdeinit、その他類似のデーモンを置き換える(ないしは可能であれば修正するだけ)ことも目的の1つである。 セッションマネージャーとinitシステムの抱える問題は非常に似通っている。 迅速な起動が必須であり、プロセスのベビーシッターを務めることだ。 したがって、それぞれの使用目的のために同じコードを使うということが自ずと考えとして出てくる。 Appleはこれをわかっていて、launchdでこれをやった。 我々もそうすべきだ。 ソケットとバスのアクティベーションとパラレル化は、セッションサービスとシステムサービスとが平等に恩恵を受けることができるものである。

これらの機能のうち3つすべてがすでに部分的に現在のコードベースで利用できること、しかし、完璧ではないことはおそらく書いておくべきだろう。 例えば、すでに、一般ユーザーとしてsystemdは十分に動き、systemdはそのように動いていることを検出でき、最初から、このモードをサポートし、核心である*18。(これは例外的にデバッグにも便利なのである!これはシステムをsystemdでブートするようにしなくてもうまく動くのである。)

しかし、この仕事を終わらせる前に、カーネルや他の場所で修正すべきことがいくつかあるのである。 我々はスワップのステータス変更の通知をカーネルから受け取るようにする必要がある。これは、すでに我々がマウントの変更を見ることができるようになっているのと同じようなものだ。 CLOCK_REALTIMECLOCK_MONOTONICに対してジャンプしたときに通知を受け取りたい。 通常のプロセスがinit-likeなパワーの一部を得られるようにしたい。 ユーザーのソケットを置ける、十分に定義された場所が必要だ。 これらの問題点で本当にsystemdにとって必要なものはないが、きっと物事は良くなるだろう。

動いているこれが見たいかい?

現時点では、tarballによるリリースはないが、我々のレポジトリからコードをチェックアウトするのが簡単だろう。 さらに、使い始めようと思えば、ここにあるユニット設定ファイルのtarball*19を使えば、これ以外に変更を加えていないFedora 13のシステムでsystemdを動かすことができる。 今のところ、提供できるようなRPMはない。

より簡単な方法はこのFedora 13のqemuイメージ*20を使う方法である。これはsystemdを動かすように準備ができている。 grubのメニューでシステムをUpstartでブートさせるか、systemdでブートさせるかを選ぶことができる。 このシステムは最小限の修正しかされていないことに注意してほしい。 サーバーの情報は既存のSysV initスクリプトからしか読み出していない。 したがって、ここまでに書いたような、完全なソケットないしはバスによるパラレル化の恩恵は受けていない。 しかし、LSBヘッダーからパラレル化のヒントを解釈し、Upstartのシステムより速く起動させることができる。Upstartは現時点のFedoraではいかなるパラレル化もしていない。 このイメージでは、カーネルログバッファー(これはdmesgでアクセスできる)に加えて、デバッグ情報はシリアルコンソールに書き出されるように設定されている。 仮想シリアル端末で設定されたqemuを実行したくなるかもしれない。 すべてのパスワードはsystemdに設定されている。

qemuイメージをダウンロードしてブートさせるよりもさらに簡単なのは小奇麗なスクリーンショットを見ることだ。 initシステムは通常、ユーザーインターフェイスの下に十分に隠されているため、systemadmpsのショットを数枚見せる。 f:id:popo1897:20171114212714p:plain

これはsystemadmが読み込まれたすべてのユニットを表示しているところで、gettyインスタンスの1つの詳細な情報を下に表示している。

f:id:popo1897:20171114212822p:plain

これはps xaf -eo pid,user,args,cgroupの出力の抜粋で、プロセスがそれらのサービスのcgroupにどれだけきちんとソートされているかを示している。 (4つ目の列はcgroupでdebug: という接頭辞が表示されているのは、先に述べたとおrい、我々がsystemd用にデバッグのcgroupコントローラーを使っているからである。これは一時的なものに過ぎない。)

この2枚のスクリーンショットはどちらも、Fedora 13のLive CDインストールを最小限に修正したものであることに注意してほしい。 ここでは、サービスは既存のinitスクリプトからのみロードされている。 したがって、既存のサービスのためにソケットやバスのアクティベーションは使われていない。

残念だが、現時点では、起動時間に関するブートチャートやハードデータはない。 デフォルトのFedoraインストールのすべてのサービスを完全にパラレル化したらすぐに公開するつもりだ。 なので、我々は読者がsystemdのアプローチをベンチマークすることを歓迎するし、我々自身のベンチマークデータを提供することもする。

さて、おそらく、みんな、これが気になってしつこく聞くだろうから、2つの数字を伝えておこう。 だが、これらは完全に非科学的なものだ。 というのも、VM(シングルCPU)で測ったものや、私の時計のストップウォッチで測ったものだからだ。 Feodra 13はUpstartではブートに27秒かかったが、systemdは24秒だった(これは、grubからgdmまでの時間で、同じシステム、同じ設定、2回の起動のうち短い方の値で、このうち1つはもう1つのすぐ後に続いた((Upstartとsystemdとで2回ずつブートさせたが、それぞれの2回はそんなに差がなかったと言いたいのだろう)))。

しかし、これは、initスクリプトからパースされたLSB依存関係情報をパラレル化に利用しただけで到達できたスピードアップ効果以外のことは示していないことに注意してほしい。 ソケットないしはバスによるアクティベーションはこれには利用されていない。 したがって、これらの数字は上で述べたようなアイディアを評価するには不適切なのだ。 また、systemdはデバッグ用の詳細出力をシリアルコンソールに出すように設定されている。 だから、再度となるが、このベンチマークデータはほとんど価値がない。

デーモンを書く

systemdで使われる理想的なデーモンは伝統的に行われてきたことと、数個の違いしかない。 あとで、もっと長いガイドを出して、systemdで使われるデーモンをどう書くべきかを説明、提案するつもりではある。 基本的にはデーモン開発者にとって物事は簡単になる。

  • デーモンの作成者はプロセスをフォークやダブルフォークをするのではなく、systemdが起動させる最初のプロセスからイベントのループを始めること。 また、setsid()は呼び出さないこと。

  • デーモン自身でユーザー特権をドロップさせず、これをsystemdに任せ、systemdのサービス設定ファイルの中で設定すること。 (例外はある。例えば、いくつかのデーモンにとって、特権の昇格を必要とする初期化フェーズのあとで、デーモンコードの中で特権をドロップさせるべき正しい理由があるかもしれない。)

  • PIDファイルは書き出さないこと。

  • バス名をつかむこと

  • ロギングはsystemdに任せることができる。標準エラー出力にログを出す必要があるものは何でもログに出すのは歓迎だ。

  • systemdにソケットをつくらせ、監視させること。これでソケットアクティベーションが動作する。 それゆえに、上で述べたように$LISTEN_FDS$LISTEN_PIDが解釈される。

  • デーモンをシャットダウンするように要求するにはSIGTERMを使うこと

この上のリストはAppleがlaunchdと互換性のあるデーモンにすすめているものと非常によく似ている*21。 すでにlaunchdアクティベーションをサポートしているデーモンを拡張して、systemdアクティベーションもサポートするようにするのは簡単なはずだ。

systemdはこのスタイルで書かれていないデーモンもすでに完全にサポートする。というのも、互換性の理由からだ(launchdはこれを部分的にしかサポート指定いない)。 すでに述べたとおり、これは、使用可能で、systemdによるソケットアクティベーションに対応させるための修正を加えていない、inetd対応の既存のデーモンにも広げることさえできる。

なので、そうだ、systemdが我々の実験の中で自らを示し、ディストリビューションで採用されれれば、これらのサービスがソケットやバスベースのアクティベーションをデフォルトに起動するようにポートすることも腑に落ちるだろう。 すでに、概念実証のためのパッチを書いており*22、チューンアウトされたポーティングは非常に簡単だ。 また、すでにlaunchdでおこなわれた業績をある程度、利用することもできる。 さらに、ソケットによるアクティベーションのサポートを追加することで、サービスが非systemdのシステムと非互換になるというわけでもないのである。

FAQs

バックに控えているのは何?

えー、現時点でのコードベースはほとんど私、Lennart Poettering (Red Hat)の成果だ。 だが、その詳細のすべてのデザインに関してはKay Sievers (Novell)*23と私の密接な強力の結果である。 他に関わっている人物としては、Harald Hoyer (Red Hat)やDhaval Giani (前IBM)*24、そして、その他、IntelSUSENokiaといった様々な会社からの数人だ。

これはRed Hatのプロジェクトなのか?

いいえ。これは私の個人的なサブのプロジェクトだ。これも強調させてくれ: ここに書かれている意見は私自身の意見だ。これらは私の雇用者やRonald McDonaldやその他の人の見解ではない

これはFedoraになるのか?

もし実験でこのアプローチがうまく動くとわかり、Fedoraのコミュニティーでの議論がこれに賛意を示すなら、イエスになるだろう。 実際、これをFedoraに入れようとしている。

OpenSUSEには来るのか?

KayはFedoraがやるのと同じような方法でそうしようとしている。

これはDebian/Gentoo/Mandriva/MeeGo/Ubuntu/(ここにお好みのディストロを入れる)に来るのか?

彼ら次第だ。関心を示してくれるのは実際、歓迎するし、統合の手伝いだってするつもりだ。

どうしてこれをUpstartに追加しなかったのか?なぜ新しいものを発明したのか?

Upstartについて述べた部分で指摘したのは、Upstartの核のデザインには欠点がある、というのが我々の意見だということだ。 もし既存のソリューションが核に欠点を抱えているなら、完全にスクラッチで始めることがそれを示唆することになる。 しかし、我々はUpstartのコードベースその他で多大なインスピレーションを受けたことは注意しなければならない。

そんなにAppleのlaunchdが好きなら、なぜそれを使わなかった?

launchdは偉大なイノベーションだが、それがLinuxに合うとも、Linuxのような、数多くの目的と用途とに対しかなりのスケーラビリティーと柔軟性とを持つシステムに適合的とは思えないからだ。

これはNIHのプロジェクトなのか?*25

えっと、ここまでの文章で、我々がなぜ新しい物に至り、Upstartやlaunchdの上に作ろうとしなかったかを説明できていればいいなと思う。 systemdに落ち着いたのは技術的な理由で政治的理由ではない。

NIHと呼ばれるライブラリ(glibの再実装の一種)を含んでいるのはsystemdではなく、Upstartであることは忘れないでほしいな!*26

これは(お好みの非Linux OSをここに入れる)で動くのか?

多分動かない。 すでに書いたとおり、systemdは多くのLinux固有のAPI(epoll、signalfd、libudev、cgroup、その他もろもろ)を使っており、これを他のオペレーティングシステムにポートするのはそこまで妥当とは思えない。 また、我々、関わった人間は他のプラットフォームにもしかしたらありうるかもしれないポートをマージすることに関心はないし、制限のもとでこれを導入することに従事するつもりもない。 そう入っても、本当にこれをポートしたいと考える人がいれば、gitがブランチやリベースでかなり助けてくれるだろう。

実際、ポータビリティーの限界は他のOSに移す以前の問題である。 我々はかなり最近のLinuxカーネルglibc、libcgroupおよびlibudevを要求している。 あまり最近ではないLinuxシステムのサポートはない。すまない。

他のオペレーティングシステムに似たようなものを実装したい人たちがいれば、協力の好ましい状態はおそらく、そうしたいシステムとどのインターフェイスが共有できて、systemdとsystemdに対応するものとの双方をサポートするデーモンを書く人の生活をより楽にさせる事で我々が手伝いをする、というものになるだろう。 おそらく、焦点はインターフェイスの共有であって、コードの共有ではないはずだ。

私は(次のうち1つを選んで埋める: Gentooのブートシステムやinitng、Solaris SMF、runit、uxlaunch、…)が素晴らしいinitシステムで、パラレルな起動もおこなうと聞いたんだが、なぜ、それを使わないのか?

さて、これを始める前に、実際、我々は様々なシステムを非常に細かく調べてきたが、どれ一つとしてsystemdとして心に描いたものではなかった(もちろんlaunchdは例外だ)。 わからないようだったら、もう一度、上で書いたことを読み直してほしい。

貢献

パッチやヘルプに大変関心がある。 すべてのフリーソフトウェアプロジェクトは幅広い可能性ある外部の貢献からのみ恩恵を受けられることは広く共有されている感覚である。 これは特にinitシステムのようなOSのコア部分では特に真実である。 我々は皆さんの貢献を評価しており、したがって、著作権の割り当てを要求したりはしない(Canonical/Upstartとは大違いだ!) そして、gitも使っている。みんな大好きなVCSだ。イェイ!

我々は特に、systemdをFedoraOpenSUSEに加え、他のディストリビューションで動かすための助けを欲している(へい、DebianGentooMandriva、MeeGoを使っている人で何かやることを探している人はいないか?((Ubuntuが入ってないのが面白い。)))。 しかし、それを抜きにしても、各レベルで貢献者を集めることに必死である。C開発者の人、パッケージャー、ドキュメントを書くのに関心がある人、ロゴを作ってくれる人、歓迎する。

コミュニティ

現時点では、我々にはソースコードレポジトリIRCチャンネル(#systemd on Freenode)しかない。 メーリングリストも、ウェブサイトも、バグトラッキングシステムもない。 freedesktop.orgに間もなく何かをセットアップするかもしれない。 何か質問や我々と連絡を取りたい、その他あれば、招待するからIRCに参加してくれ!

*1:編集履歴 17.11.16-01: 誤字を修正 thanks to Kazuhiro NISHIYAMAさんいくやさん 17.11.14-01: 最後まで翻訳 17.11.13-01: 'Putting it All Together: systemd'の残りを訳出 / 'Status'と'Where is This Going?'を訳出 17.11.12-02: 手元の環境がおかしく、UpstartUbuntu 17.10でインストール可能と書いていたのを修正 thanks to MurabitoLさん 17.11.12-01: 'A short list of other features' in 'Putting it All Together: systemd'の8までを訳出 17.11.09-01: 'Putting it All Together: systemd'の前半を訳出 17.11.07-02: 'On Upstart' を訳出 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'にかかるものと解釈して訳した。

*11:Ubuntuを後援するCanonicalの社員であったScott James Remnantらが開発した、従来のSysV initの代替実装。2006年にリリースされた。Upstartの特徴(とその限界)についてはレナートが本文で語っているため、省略するが、「Upstartは[...]特に動的なシステムに向けて設計されて」おり、「イベントを発行することで非同期性を取り扱う」「革新的な」ものとして当時、登場した。Ubuntuが6.10で採用したのみならず、REHL6でもinitとして採用されていた。だが、アップストリームにあたるDebianが標準のinitをsystemdに移行したことで、Ubuntuは15.10でinitをsystemdに移行した。なお、Ubuntu 17.10でUpstartはレポジトリから削除された(参考)。UpstartのマニュアルはUpstart Cookbookを参照。このマニュアルを日本語化するプロジェクトの残骸はこちらを参照。

*12:ドミノ倒しをイメージするとよいか。

*13:再度になるが、リンクは張り直している。

*14:https://linuxjm.osdn.jp/html/LDP_man-pages/man7/time.7.html

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

*16:毒にも薬にもならないので、実験段階では好都合位の意で解釈した

*17:原文では'introspectable': introspect「内省する」 + able「〜可能な」ということから、文脈を意識して、このように訳した。

*18:自信がない。原文は'already, you can run systemd just fine as a normal user, and it will detect that is run that way and support for this mode has been available since the very beginning, and is in the very core.'

*19:このリンクはまだ生きているようだ

*20:一応リンクは張ったが、torrentファイルなのでダウンロードは無理だった

*21:原文では、リンクが貼られているがリンク切れ。相当するページはここか。

*22:リンクがあったが切れている

*23:今はRed Hatのようだ。 kaysievers (Kay Sievers) · GitHub

*24:今はOracleのようだ。 Dhaval Giani / Linux Plumbers Conference: Developing the Kernel, Libraries and Utilities

*25:UpstartCanonical社主導なのに対し、LennartがRed Hatの人間であることを当てこすって

*26:Upstart側の人間から何か言われたのかなと想像させるくらいには当てこすりである