#きょうのsystemd: (翻訳)Socket Activation (1)

今日のUbuntu Weekly Recipeはsystemdネタだった!

gihyo.jp

それで、というわけでもなく、ちょうどたまたま書き溜めてただけだけど、今日はsocketユニットについて。

またですが、レナートのブログ記事の翻訳。

原文: 0pointer.net

ソケットアクティベーション

systemdについてオリジナルなブログストーリーで、私はなぜ、ソケットアクティベーションがサービスをスポーンするための素晴らしい技術であるかについて説明しようとした。 ここでちょっと、その背景を繰り返しになるが説明しようと思う。

ソケットアクティベーションの基本的な考え方は新しいものではない。 inetdスーパーサーバーは、ずっとほとんどのLinuxUnixシステムで標準的なコンポーネントであった。 その挙動はこうだ。 起動時にすべてのローカルのインターネットサービスをスポーンする代わりに、スーパーサーバーがそれらのサービスの代わりにリスンする。 そして、接続が来たらいつでも対応するサービスのインスタンスがスポーンされるというものである。 これによりリソースの少ない比較的貧弱なサーバーでも多種多様なサービスを同時に提供することが可能になった。 しかし、この方法はすぐに、いくぶんか遅いという評判を得るようになってしまった。 接続が入ってくるたびに、デーモンがスポーンされるため、サービスのフォークと初期化--それぞれの接続につき1回だ。それらすべてについて1回ではない--にたくさんの時間が必要となる。

接続のたびに1つのインスタンスをスポーンするのはinetdの主たる使われ方であった。一方で、inetdは現に別のモードも理解した。 こちらの挙動はこうだ。 最初の接続が入ってきたら、poll()(ないしはselect())を通じて、このことを伝え、以降のすべての接続のために1つだけインスタンスをスポーンさせる(これはwait/nowaitオプションでコントロールできる)。 この方法だと最初の接続はセットアップに時間がかかるが、続く接続ではスタンドアローンのサービスと同じくらい早い。 このモードでは、inetdは真の意味でオンデマンドモードとして動作する。つまり、サービスは必要とされるときになって初めて利用可能になるのである。

inetdのフォーカスは明らかにAF_INET(つまりインターネット)ソケットにあった。 ときが進んで、Linux/Unixがサーバーという枠から離れ、デスクトップやモバイル、組み込みといった環境に徐々に関係するようになった結果、inetdは時間のトラブルに巻き込まれるようになる。 その評判は遅いというものであったし、インターネットサービスのみがLinuxの焦点でなくなったことで、inetd(ないしは、xinetdのようなその新しい実装の1つ)を動かしているLinuxマシンは一般的ではなく例外的となった。

AppleのエンジニアがMacOSのブート時間の最適化に取り組んでいたところ、彼らはソケットアクティベーションを使う新しい方法を発見した。つまり、彼らはAF_INETソケットからAF_UNIXソケットに焦点をシフトさせたのである。 そして、彼らは、オンデマンドのソケットアクティベーションは物語の一部に過ぎなかったことに気づいた。つまり、何があっても起動時にスタートさせなければならないモノを含め、すべてのローカルサービスにそれを使えば、ソケットアクティベーションがもっともっと強力になると気づいたのである。 彼らはこのアイディアをlaunchdで実装した。launchdはモダンなMacOS Xシステムの礎石となるものであり、おそらく、MacOSがすぐに起動してくる一番の理由である。

しかし、次へ行く前にここで、非オンデマンド、非インターネットサービスへのソケットアクティベーションを使うことの利益が何であるかを詳細に見ておこう。 Syslog、D-Bus、Avahi、Bluetoothデーモンの4つのサービスを考える。 伝統的なLinuxシステムではD-BusはSyslogにログを書き出すため、Syslogより後に起動しなければならない。 同様に、AvahiははSyslogとD-Busを必要とするためこの2つのあとに起動しなければならない。 最後に、BluetoothはAvahiと似ており、SyslogとD-Busを必要とするが、Avahiとは接続されていない。 伝統的なSysVベースのシステムでは同時に1つのサービスだけしか、起動中のプロセスにできないため、スタートアップのシリアル化は次のようになる。Syslog→D-Bus→Avahi→Bluetoothである(もちろん、AvahiとBluetoothは逆の順序で起動させることもできる。しかし、ここでは1つだけを取り上げたいので、単純にアルファベット順にする)。 これを図式化すると、これがシステムのスタートアップで始まるスタートアップの順番を示しているプロットになる(トップに来ている)。

f:id:popo1897:20171116223724p:plain

特定のディストリビューションではこの厳格なシリアルのスタートアップを改善しようとしている。AvahiとBluetoothとは互いに独立しているため、これらは同時に開始することができる。 パラレル化が進み、全体のスタートアップ時間は少し小さくなっている(これはプロットの真ん中で図示されている)*1

ソケットアクティベーションは4つすべてのサービスを、いかなる順序化もなしに、完全に同時に開始させることができる。 リスンするソケットの作成をデーモン自身の外に移すことにより、これらを同時に実行することができ、これらは互いのソケットにすぐに接続することができるのである。 つまり、単一のステップで/dev/logソケットと/run/dbus/system_bus_socketソケットを作成し、次のステップで4つすべてのサービスを同時にスポーンさせるのである。 D-Busがsyslogにログを書き出したいと思ったときは、D-Bus/dev/logにメッセージを書くだけである。 ソケットバッファーがいっぱいにならない限りは、D-Busはその他、初期化のためにしたいと思うことをすぐに続けることができるのである。 syslogサービスがD-Busに追いつけば、syslogはキューに入っているメッセージの処理を始める。 もし、ソケットバッファーがいっぱいになった場合には、クライエントのロギングはソケットが再び書き込み可能になるまで一時的にブロックされ、ログメッセージが書き込み可能になるまでそれが続く。 これは、サービスのスケジューリングが全体的にカーネルにより行われることを意味する。ユーザースペースの観点からすると、すべてのサービスは同時に実行され、あるサービスがそれを必要とする他のサービスに追いつかない場合は、前者は後者の要求を一時的にブロックすることになるが、リクエストが発行されるとすぐに続く。 したがって、以前は厳格にシリアル化が必要であると考えられていたサービスを同時に起動することができようになったことで、ソケットアクティベーションによりスタートアップのパラレル化は劇的に進むことになる。 多くのLinuxサービスはコミュニケーションチャネルとしてソケットを利用している。ソケットアクティベーションでこれらのチャネルのクライアントとサーバーの起動を同時におこなうことができるのである。

しかし、話はパラレル化だけにとどまらない。ソケットアクティベーションは他にも多くの利点がある。

  • 我々はもはや依存関係を明示的に設定する必要がなくなる。ソケットはすべてのサービスの前に初期化されるため、簡単に利用可能になる。また、ユーザースペースでのサービスのスタートアップを順序化する必要ももうなくなる。したがって、ソケットアクティベーションはサービスの設定と開発を劇的に単純化する。
  • サービスが死んだとしても、ソケットはそのままで残り、メッセージをロストすることがなくなる。クラッシュしたサービスが再起動したら、サービスが残してしまったものを引き継ぐことができる。
  • サービスがアップグレードされると、ソケットを残したままサービスを再起動させることができ、これで、サービスが継続的に反応できるようになる。アップグレード中にコネクションが1つも失われることはない。
  • クライアントに見えないような方法で実行中のサービスを入れ替えることだって可能になる。例えば、systemdを動かしているすべてのシステムが小さいsyslogデーモンを起動時に動かしている。これはすべてのログメッセージをカーネルメッセージバッファーに繋がっている/dev/logに書き出す。この方法で信頼の置けるユーザースペースロギングを起動時の最初の瞬間から提供することができる。そして、実際のrsyslogデーモンが起動すると、ミニ版のデーモンを停止させ、それを本物のデーモンに置き換えられるようになる。オリジナルのロギングソケットを維持し、これを2つのデーモンで共有することで、メッセージを1つも失わなくて済む。rsyslogが起動後にカーネルログバッファーをディスクに書き出すため、カーネルからの、ブートの初期段階からの、実行時からの、すべてのメッセージを完全にディスク上に置くことができる。

この考えについての他の説明はsystemdのオリジナルのブログストーリーを参照してほしい。

2へ続く

というか、2いるかな?

*1:UbuntuについてはRethinking PID 1Upstartをこき下ろしているが、SUSEの件は軽くスルーされている。SUSE(自分が触ったことのあるSLES 11)の場合、startpar(manページ)というのを利用し、サービス起動のパラレル化を図っている。これは、/etc/init.d以下のLSBヘッダーを読み取り、依存関係(=起動順)に指定あるものだけをピックアップし、理解する。これを

  • /etc/init.d/.depend.boot
  • /etc/init.d/.depend.start
  • /etc/init.d/.depend.stop

というパスでMakefileの書式で保持する。.depend.*に書かかれたサービスはその内容にしたがって起動・停止され、何も書かれていないサービスはパラレルに起動・停止される。

管見の限り(ググった限り)では、日本語で読めるのは次のサイト: