#きょうのsystemd : 大きな神話

大きな神話

(原文: http://0pointer.de/blog/projects/the-biggest-myths.html *1はきれいに分割されており、これらは systemd の外でも非常に使い勝手の良いものである。

69の別個のバイナリーを必要とする1つのパッケージというのはモノリシックとはほとんど呼べないかもしれない。 しかしながら、それまでのソリューションと異なるのは、1つの tarball でより多くのコンポーネントを提供しており、上流では単一のリリースサイクルで、1つのレポジトリでこれらをメンテナンスしている点にある。

2. 神話: systemd はスピードが命である

systemd が速い(900msまででユーザースペースが完璧に起動し切る。他にある?)のはそうだが、これは主に物事を正しくおこなった副作用に過ぎない。 事実、我々はじっと腰を落ち着けて、 systemd の最後のほんのわずかなパフォーマンスを最適化したことはない。 代わりにやっていたのは、コードをより読みやすくするために、頻繁に、わずかに遅いコードパスを抜け目なくつまみ出すことだ。 これは、速いということが我々と無関係だったということを意味するのではないが、スピードのために systemd を軽量化することは、かなり間違ったことだ。 というのも、このことが我々の目標のリストの上らへんに来ることはなかったのだ。

3. 神話: systemd の高速なブートはサーバーには関係ない

これはまったく真実ではない。 多くの管理者はメンテナンスウィンドウでのダウンタイムの時間を減らすことに熱心だ。 HAのセットアップでは、フェイルしたマシンが本当に素早く戻ってくるなら、それは素敵なことだ。 クラウドのセットアップでは、VMやコンテナが多ければ、ブートが遅いゆえの価格はインスタンスの数だけ倍加される。 CPUおよびIOの時間が数百のVMやコンテナの遅いブートに費やされるなら、システムの集約度は劇的に落ちるし、費用もエネルギーもかかってしまう。 遅いブートは費用面で高く付くかもしれない。 そのため、コンテナの高速なブートはソケットアクティベートなコンテナのような実装を可能にする。 これは、クラウドシステムの集約度を劇的に控除させるものである。

もちろん、多くのサーバーのセットアップではブートアップはあまり関係ないかもしれない。 だけど、 systemd はすべての領域をカバーするように考えられている。 そして、そう、ブートアップ時にほとんどの時間を費やしているのはサーバーファームウェアであることは私も気づいているし、OSはいつだって、それに比べたら速いのも知っている。 だけど、 systemd はすべての領域をカバーするように考えられているし、すべてのサーバーに良くないファームウェアが載っているわけではないし、サーバーの一種である、VMやコンテナもそうじゃない((また、我々はこれをより良くするかもしれないと思って、ちょっとしたことをやろうとしている。ファームウェアのブートタイムパフォーマンスが、 systemd のブートの結果でより明らかになって、ファームウェアのライターたちが恥ずかしがって、彼らの作ったものをきれいにしてくれるんじゃないかと期待している。)))。

4. 神話: systemdはシェルスクリプトと互換性がない

これも真っ赤な嘘だ。我々シェルスクリプトをブートプロセスでは使わない。というのも、特定の目的にとって、シェルスクリプトはベストなツールとは言えないと思っているからだ。 だけど、このことと systemd がシェルスクリプトと互換性がないというのは違う。 シェルスクリプトを簡単に systemd のサービスとして動かすことはできるし、いかなる言語で書かれたスクリプトであっても、 systemd のサービスとして動かすことができる。 systemd は用意された実行可能ファイルの中身がなんであるかなんて、一切気にしない。 さらに言えば、我々は自分たちの目的のために、シェルスクリプトをヘビーに使っている。systemd のインストールやビルド、テストにだ。 さらに、自分で作ったスクリプトをブートプロセスの初期に挟み込んだり、それを通常のサービスに使ったり、シャットダウンの最後に使ったりと、事実上、制限はない。

5. 神話: systemd は難しい

これも全くのナンセンスだ。 systemd のプラットフォームというものは、伝統的なLinuxよりも実際、非常にシンプルなものだ。 というのも、 systemd はシステムのオブジェクトおよび依存関係を systemd のユニットとして統一するものであるからだ。 設定ファイルの言語は非常にシンプルだし、冗長な設定ファイルは我々が取り除いた。 我々が提供するのはシステムの多くの設定に使える、統一的なツールである。 このシステムは伝統的なLinuxよりも非常に礫岩性の低いものだ。 また、我々は非常に包括的なドキュメント(すべてホームページからリンクされている)を systemd の非常に詳細な部分についても提供している。 これは、管理者やユーザーが触れるインターフェイスのみではなく、開発者のAPIについてもカバーしている。

systemd はもちろん、ある学習曲線を伴うものだ。何でもそうだ。 だけど、殆どの人間にとって、シェルスクリプトベースのブートよりも、 systemd を理解することのほうが実際には簡単であると我々は思っている。 こう言ったら驚くんだろうか? さて、 systemd が追い出したように、シェルスクリプトは学ぶには良い言語とは言えない。分布はわかりにくいし、複雑だ。 systemd のユニットファイルは実質的によりわかりやすいし、プログラミング言語が出てくるのではなく、本来より、シンプルで declarative である。 すべて言ったが、シェルの経験があるなら、そう systemd を受け入れるにはちょっと勉強するだけでいいだろう。

学習を簡単にするため、我々は以前のソリューションに対して、最大限の互換性を与えるよう努力してきた。 だけど、このことだけじゃなく、多くのディストリビューションで、いくつかの伝統的なツールが -- 要求に応じて実行されている一方で -- ひょっとしたらよりよい方法で、より新しいツールをかわりに使う方法を伝えてきさえすることに気づくだろう。 ((訳者注: なんか翻訳が厳しい。原文は "But not only that, on many distributions you'll find that some of the traditional tools will now even tell you -- while executing what you are asking for -- how you could do it with the newer tools instead, in a possibly nicer way." イメージとしては、service your_service stopsystemctl stop your_service にリダイレクトされる感じだと思った。))

ともかく、持ち帰れるのはたぶん、 systemd は可能な限りシンプルであろうこと、そして、我々は systemd を学習しやすいようにするため努力しているということだ。 だけど、もし sysvinit を知っているなら、 systemd を受け入れるのに、ちょっと学習が必要だろうが、本当に率直に言うと、 sysvinit をマスターしているなら、 systemd は簡単なはずだ。

6. 神話: systemd はモジュラーではない

これもまったく違う。 コンパイル時には数多くの configure のスイッチがあり、何をビルドして、何をビルドしたくないかを選べる。 そして我々がドキュメントに記していることだが、我々の configure スイッチを超えて、より詳細に、必要なものを選ぶことができる。

このモジュラリティはLinuxカーネルのものとは全然違うということはない。Linuxカーネルの場合は、コンパイル時に多くの機能を個別に選ぶことができる。 もし、カーネルがあなたにとって十分にモジュラ―であるならば、systemdもそれに近いはずである。

7. 神話: systemd はデスクトップのためだけにある

これは事実ではない。 systemd で我々はLinux自体がカバーしているのと同じだけの範囲をカバーしようとしている。 デスクトップユーザーを気にする一方、同じだけ、サーバー用途や組み込み用途も同じように気にしている。 systemd がサーバー上のサービスを管理するのにベストなオプションじゃないなら Red Hat が RHEL7 の中心のピースに systemd を据えることはないと読者は賭けても良いだろう。

数多くの会社の人々が systemd に取り組んでいる。 自動車制作会社は systemd をビルドして車へ、 Red Hat は systemd をサーバーオペレーティングシステムに使っており、 GNOME はデスクトップを改善するために systemd のインターフェイスの多くを使っている。 読者は systemd をおもちゃや、天体望遠鏡や風力タービンで見ることができるだろう。

私がもっとも最近取り組んでいる昨日の殆どは、主にサーバーに関係してくるであろうものである。 コンテナサポートリソースマネジメント、そして、セキュリティ機能だ。 我々はすでにデスクトップについてはかなりカバーしており、 systemd の開発を組み込み向けにおこなう会社は数多くあり、いくつかは systemd にコンサルティングサービスをオファーしているところすらある。

8. 神話: systemdはNIHシンドロームの結果としてつくられた

これは真実ではない。 我々は systemd に取り組み始める前は、我々は CanonicalUpstart が幅広く受容されるよう後押ししていた(そして、Fedora/RHELUpstart をしばらくの間、使ってもいた)。 しかし、我々は Upstart のデザインは内在的に、そのコアに欠点を抱えているという結論に至った(少なくとも我々の目には、である。もっとも根本的には Upstart は依存関係の管理を管理者や開発者に残したのだ。コードでこの難しい問題を解決する代わりに)。 そして、そのコアに誤ったものがあるのなら、それを直すのではなく、置き換えてしまうのがベターだ。 これが唯一の理由というわけではなく、それ以外のものも現れだした感じだ。ライセンスやコントリビューションの合意といったそのへんの systemd にまつわるものだ。 NIHは1つの理由というわけではないんだ((原文注: そして、 "libnih" というライブラリーが含まれているプロジェクトは何か考えてほしい。 Upstart or systemd のどっちだ?(ヒント: systemd ではない!)))。

9. 神話: systemd は freedesktop.org のプロジェクトである

ああ、たしかに systemd は fdo でホストされているが、 freedesktop.org はコードとドキュメントのレポジトリ以外のほとんどなにものでもない。 ほとんど誰でも、コーダーはそこにレポジトリをリクエストして、作ったものを置くことができる(作ったものが、フリーなシステムのインフラに関係がある限り)。 関わっている派閥もないし、「標準化」のスキーマもない、審査もなければ、何もない。 fdo は優れた、フリーで、頼りになる、レポジトリを持てる場所である。 それに関していえば、 fdo は SourceForgegithub 、 kernel.org と、ちょうど、商用向けじゃなく、常軌を逸した要件もなく、そのため、自分の作ったものを置くのに良い場所という点で同じだ。

なので、そう、我々は自分たちの成果物を fdo でホストしているが、この、人々の集まりがあり、その人々が話し合っては、将来のフリーシステムがどうあるべきかについて合意しているという、神話の暗黙の推定はまったくの嘘である。

10. 神話: systemd は UNIX ではない

確かにこれにはいくつかの真実が含まれている。 systemd のソースはオリジナルの UNIX を起源とするコードは1行も含まれていない。 しかし、我々は UNIX からインスピレーションを受けているし、その結果、 systemd にはたくさんの UNIX がある。 例えば、 UNIX の考え方である「すべてはファイルである」というのは、 systemd ではすべてのサービスは実行時にはカーネルファイルシステムcgroupfs に晒されるという形で反映されている。 そして、 UNIX のオリジナルの機能の1つである、ビルトインターミナルサポートをベースとする multi-seat サポートがある。 とはいえ、テキストターミナルは、今日、コンピューターとインターフェイスでどうつながるかの最先端技術水準とは言い難いだろう。 systemd では我々はネイティブな multi-seat サポートを連れ戻したが、今回は、グラフィックやマウス、オーディオ、ウェブカム、その他をカバーする今日のハードウェアをフルにサポートし、それらがすべて、完全に自動的、ホットプラグ対応で、設定が不要な状態にしている。 事実、 systemd のデザインはそれぞれが個別の目的を持っているが、組み合わせて使われたときに、単に部分を集めた以上のものになるという、一連の統合的なツールであり、これは、ほとんど UNIX の哲学のコアの部分である。 そして、我々のプロジェクトのハンドルのされ方(例えば、単一の git レポジトリでコアの OS のほとんどをメンテすること)は Linuxのやってきたこととと違って BSD (Linux と違って BSD は真の UNIX だ)のもののやり方(BSD ではコアの OSの は単一の CSV/SVN レポジトリで維持されている))のモデルにかなり近い。

究極的には、 UNIX はみんなにとって違ったものだ。 我々 systemd のメンテナーにとっては、それは、インスピレーションを得た何かである。 その他の人にとっては、宗教であり、他の世界宗教と同じように様々な読み方や理解がある。 UNIX を特定のコードの遺産に基づくものと定義する人もいるし、単なる考え方のセットだと見る人もいて、コマンドやAPIのセットであるとする人もいる。さらには、ふるまいの定義であると考える人もいる。 もちろん、これらすべての人をハッピーにすることなんてできやしない。

究極的には、何かが UNIX であるかないかという問題はほとんど問題にならない。 技術的に優れているということは、 UNIX に専用ということはほとんどない。 我々にとって、 UNIX は主要な有力者(もちろん、最大の勢力)だが、他の有力者も我々は持っている。 なので、いくつかの領域では systemd は非常に UNIX であるが、その他ではそうでもないのだ。

11. 神話: systemd は複雑である

これはちょっと正しいかもしれない。 現代のコンピューターは複雑なビーストであり、その上で動く OS も複雑に違いない。 だが、 systemd は同じコンポーネントのそれ以前の実装より複雑というわけではない。 むしろ、 systemd はよりシンプルで、(ここまでみてきたように)より冗長じゃない。 さらに言えば、 systemd をベースにシンプルなOSを作るには従来の Linux よりもかなり少ないパッケージで済むようになる。 パッケージがより少なければ少ないほど、システムをビルドするのがより簡単になるし、相互依存性やすべてのコンポーネントがバラバラの挙動をするということの多くを取り除くことができるようになる。

12. 神話: systemd は肥大化している

まぁ、肥大化というのにはきっと様々な定義がある。 だけど、たいていの定義でいえば、systemd はおそらく肥大化の反対に位置するだろう。 systemd のコンポーネントは共通のコードベースを共有しているため、共通のコードパスのコードをずっとより多く共有する傾向がある。

例えば: 従来の Linux のセットアップでは、 sysvinitに、 stop-start-daemon、 inetd、 cron、 dbus、といったすべてが様々な設定オプションを、あるうまくいけばクリーンな環境でプロセスを実行するためのスキームを実装している。 systemd では、このすべての、つまり、設定のパースのコードパスを、実際の実行とともに共有されている。 これは、より少ないコードで、ミスの発生する箇所をより少なくし、より少ないメモリとキャッシュプレッシャーを意味し、それは、非常に良いことである。 そして、副次的な効果として、それについて、非常に多くの機能性を利用者は得ることができる……。

前述の通り、systemd はかなりもジューラーでもある。 ビルドするときに、どのコンポーネントが必要で、どれが不要かを選ぶことができる。 人々は、したがって、自らが必要とするだけの「肥大化」のレベルを明確に選ぶことができる。

systemd をビルドする際、たった3つの依存関係が満たされていれば良い。 glibc と libcap、そして dubs だ。 これだけだ。 もっと多くの依存関係を使うこともできるが、これはまったくのオプションだ。

さて、これで、どういうふうに見たって、 systemd は本当に肥大化したとは言えないだろう。

(ぼちぼち訳します)

*1:誤字を指摘いただいた @henrich さんありがとうございます。)))

我々が最初ディストリビューションに systemd を入れることを提案してからというもの、多くのフォーラムやメーリングリスト、カンファレンスでこのことがしばしば論じられてきた。 これらのディスカッションでは、よく systemd に関するある神話を聞くことができた。 この神話は何度も何度も繰り返されているが、繰り返されているほどには真実を捉えていない。 では、時間をとって、そのいくつかを暴いてみよう。

1. 神話: systemd はモノリシックである

すべての設定オプションを有効にして systemd をビルドすると、69個のバラバラのバイナリーがビルドされる。 これらのバイナリーはすべて異なるタスクを提供するものであり、様々な理由できちんとわかれている。 例えば、我々はセキュリティを念頭に systemd を設計しているため、ほとんどのデーモンは最小限の特権(例えば、kernel capabilities)で実行され、これらは、セキュリティ上の表面とインパクトを最小化するため、非常にはっきりとしたタスクのみを担う。 また、 systemd はそれ以前のいかなるソリューションよりもブートをパラレル化している。 そのため、systemd が多くのバイナリーおよびプロセスにうまく分割されているというのは本質的なことなのである。 事実、これらの多くのバイナリー((原文注: 例えば、 systemd-detect-virt, systemd-tmpfiles, systemd-udevd がある。

#きょうのsystemd : ポータブルサービス

超久しぶり。 systemdが話題(?)ということで再開の機運が高まり、レナートのブログ見ていたら、また変なのが出ていたので、翻訳。

原文はこれ。

Walkthrough for Portable Services

systemd v239でポータブルサービス

systemd v239 には数多くの新機能がある。 そのうちの1つに、ポータブルサービスのファーストクラスのサポートがある。 このブログストーリーでは、ポータブルサービスとは何か、それがみんなのアプリケーションにとってなぜ興味深いものなのか、というところに光を当てたいと思う。

"ポータブルサービス"とは?

"ポータブルサービス" というコンセプトは、コンテナ管理に加え、古典的な chroot() 環境からインスピレーションを得たもので、より一般的なシステムサービスの管理にこれらの特徴のいくつかを持ち込むものだ。

"コンテナ" が何なのかという定義は非常にホットな話題だが、みんな、一般的に"コンテナ"のコンセプトには主要な2つの特徴があるということには同意するだろうということは言える。

  1. リソースバンドリング: コンテナは一般に自分自身のファイルシステムツリーを持っていて、共有ライブラリやその他のリソースといった、メインのサービスを実行するのに必要なものを束ねている。
  2. アイソレーションサンドボックス: コンテナはホストから比較的隔離された名前空間の環境で動いている。自身のファイルシステム名前空間で動いているのに加え、コンテナは自身のデータベースやプロセスツリーその他も持っている。コンテナからホストへのアクセスは様々なセキュリティ技術により制限されているものである。

この2つのコンセプトのうち、1つ目は伝統的な UNIX chroot() 環境についても言えることだ。

リソースバンドリングならびにアイソレーションサンドボックスの両方共、systemdが様々な度合いで、長い時間をかけて、実装してきたものである。 特に、RootDirectory=RootImage= は早い段階からあるし、systemdが提供する、様々な サンドボックス機能もある。 ポータブルサービスというコンセプトはこの上に成り立つものであり、これらの機能をまとめて、よりアクセスしやすくかつ使いやすくする、新しく、統合された方法にするものなのだ。

わかった、じゃあ、"ポータブルサービス"とは正確には何なんだ?

コンテナイメージとかなり似たような感じで、ディスク上のポータブルサービスはただのディレクトリツリーだ。 その中には、サービスの実行ファイルとその依存するものすべてが入っており、それらは通常のLinuxディレクトリ階層と似たような階層の中にある。 ポータブルサービスはrawディスクイメージとすることもできる。 そこには、ツリーを含む1つのファイルシステム(ループバックブロックデバイスを通じてマウント可能なもの)が入っていたり、複数のファイルシステム(この場合は、これらのファイルシステムDiscoverable Partitions Specificationに従い、GPTパーティションテーブル内に位置している必要がある)が入っていたりする。 ディスク上のポータブルサービスが単純なディレクトリであるか、rawディスクイメージであるかに関係なく、このコンセプトをポータブルサービスイメージと呼ぶことにしよう。

そのようなイメージはOSを何らかのディレクトリにインストールするために使われる古典的なツール、例えば、dnf --installroot=debootstrap で作ることができる。 これらのツリー上で必要となるものはほとんどないが、次の2つは必要になる。

  1. ツリーは関連したサービスの[systemd ユニットファイル]をサービスの中に含んでいる。
  2. ツリーは /usr/lib/os-release (ないしは /etc/os-release) のOSリリース情報を含んでいる。

もちろん、お気づきの通り、今日の主要なディストリビューションのいずれからも生成されるOSツリーは一般にこの2つの必要条件を特に変更しなくても含んでいる。 というのも、それらの多くは /usr/lib/os-release を受け入れているし、主要なサービスはsystemdユニットファイルとともに搭載されている。

このようなポータブルサービスイメージはホストに対し「アタッチ」や「デタッチ」することができる。

  1. イメージのホストへの「アタッチ」は新しい portablectl attach コマンドにより実行される。 このコマンドはイメージを分析する。つまり、os-release情報を読み込みその中にあるユニットファイルを探すのだ。 そうしたら、このコマンドは関連するユニットファイルイメージからコピーし、/etc/systemd/systemにコピーする。 その後、このコマンドはコピーしたサービスのユニットファイルを2つの方法で強化する。RootDirectory= ないしは RootImage= 行を追加するdrop-inが追加され、開始時にユニットファイルがホスト上でも利用できるようになる一方、ユニットファイルはイメージ内の参照されているバイナリーを実行するようになるのだ。 また、このコマンドでは、2つ目のdrop-inにシンボリックリンクを張る。これは「プロファイル」と呼ばれるもので、追加のセキュリティ設定をアタッチされたサービスに強制するようなmのので、つまり、ちょうどサンドボックスにあたるものを確保するものだ。

  2. ホストからのイメージの「デタッチ」は portable detach で実現される。 これは上のステップを逆さにしたものだ。 つまり、コピーされたユニットファイルは削除され、そのために生成された2つのdrop-inファイルも削除される。

ポータブルサービスがアタッチされると、関連するユニットファイルはホスト上で他のユニットファイルと同じように利用できるようになる。 つまり、それらのユニットファイルはsystemctl list-unit-filesで表示されるようになるし、有効にも無効にもでき、起動も停止もできる。 systemctl editで拡張もできる。 中も見れる。 他のサービスと同じようにリソース管理を適用できるし、他のサービスのようにログを処理すること、その他もできる。 それは、なぜなら、ポータブルサービスはネイティブなsystemdサービスである からである。ただし、望むように「ねじれている」という点を除いては。 つまり、ポータブルサービスはデフォルトでセキュリティが確保されており、ルートディレクトリないしはイメージの中に自身のリソースを持っている。

これはすでにポータブルサービスが何たるもののかのエッセンスである。

興味深いポイントは次のとおりだ:

  1. ポータブルサービスイメージの中にserviceユニットファイルを入れることが主眼ではあるが、timerユニットやsocketユニット、targetユニット、pathユニットもポータブルサービスに入れることができる。 これにより、非常に自然な形で、時間・ソケット・パスベースのアクティベーションが可能に鳴る。 より複雑なアプリケーションの場合は、複数のserviceユニットを同じイメージに入れることもできてしまう。

  2. このコンセプトは新しいメタデータを導入しない。 ユニットファイルは既存のコンセプトだし、os-releaseファイルもそうだ。--rawディスクイメージがお好きなら--GPTパーティションテーブルもすでに確立されたものだ。 これの意味するところは、イメージを作るための既存のツールがポータブルサービスを作るのに再利用でき、かなりの程度、完全に新しいタイプのアーティファクトが生み出されることないということだ。

  3. ポータブルサービスのコンセプトが新しいメタデータを導入せず、ただ既存のsystemdのセキュリティないしはリソースバンドリング機能の上に成立するため、これは個別のツールのセットで実装されており、比較的systemdのその他の部分と切り離されている点も挙げられる。 特に、主たるユーザーフェイシングなコマンドは portablectl であり、実際の操作は systemd-portabled.service で実装されている。 望むなら、ポータブルサービスはsystemdの真のアドオンであり、systemdその他が提供する基本的な操作よりも、使って便利な特定のワークフローとすることができる。 また、 systemd-portabled はバスAPIを提供し、それと接続したいいかなるプログラムからアクセス可能であり、portablectlはsystemdと一緒にたまたまついてくる1つのツールに過ぎないということにも注意してほしい。

  4. ポータブルサービスは最近追加したばかりの機能なので、まだまだ変化を加える余地を残したいと考えた。 そのため、 portablectl コマンドを /usr/lib/systemd に当面はインストールすることに決めた。 したがって、$PATH のデフォルトには現れてこない。 これの意味するところは、当面の間、フルパスで実行する必要があるということだ: /usr/lib/systemd/portablectl 早いうちに /usr/bin に移したいとは思っており、そのためにも、systemdの完全にサポートされたインターフェイスとしたいと考えている。

  5. ポータブルサービスイメージに含まれるユニットファイルのうち、どれが「関連する」とみなされ、 portablectl attach 操作で実際にコピーされるのだろうと思うかもしれない。 現時点では、これはイメージの名前に由来するようになっている。 仮に、 /var/lib/portables/foobar_4711/ というディレクトリ(ないしは代わりにrawイメージ /var/lib/portables/foobar_4711.raw) にイメージが保管されているとしよう。 その場合、コピーされるユニットファイルは次のパターンにマッチするものだ: foobar*.service, foobar*.socket, foobar*.target, foobar*.path, foobar*.timer

  6. ポータブルサービスの概念ではイメージをどのようにデプロイメントマシンに載せるかの方法は定義しておらず、それは完全に管理者の考えることだ。 scp で置くもよし、wgetするもよし。 RPMとしてパッケージするのもいいし、行けると思うなら dnf でデプロイするのもよしだ。

  7. ポータブルサービスは好きなディレクトリに置くことができる。しかし、 /var/lib/portables に置くと、 portablectl はそれらを簡単に見つけ、アタッチその他ができるイメージのリストを見せることができる。

  8. ポータブルサービスのアタッチは永続化できる。これにより、以後のブートでもアタッチしたままにすることができる(これはデフォルトだ)し、あるいは、--runtimeportablectl に渡すことで、次回のブートまでアタッチすることもできる。

  9. ポータブルサービスは完全にただの通常のOSイメージのため、自然で簡単に異なる3つの方法で利用できる1つのイメージを作ることができる。

    1. ポータブルサービスとしてどのホストにもアタッチできる
    2. OSコンテナとしてブートすることができる。例えば、[systemd-nspawn]https://www.freedesktop.org/software/systemd/man/systemd-nspawn.html) のようなコンテナマネージャーで。
    3. ホストシステムとしてブートすることも可能である。例えば、ベアメタルや仮想マシンマネージャーで。 もちろん、後者2つを実現するためには、イメージにはサービスのバイナリ、os-releaseファイルならびにユニットファイル以上のものを含んでいる必要がある。 systemd-nspawn のようなOSコンテナマネージャーでブート可能にするためには、イメージには例えばsystemdといった何らかの形のinitシステムが入っている必要がある。 ベアメタル上や仮想マシンとしてブート可能にするためには、例えば、systemd-bootといった、何らかのブートローダーも必要である。

プロファイル

前のセクションで「プロファイル」というコンセプトについては簡単に触れた。 プロファイルはポータブルサービスのコンセプトの主要な機能であるから、ある程度はフォーカスを当てる必要がある。 「プロファイル」は究極的にはただの pre-definedのdrop-inファイルで、ホストにアタッチされたユニットファイルのためのものである。 プロファイルはほとんど、サンドボックスとセキュリティの設定を含んでいると考えてよいが、実際にはその他の設定も含ませることができる。 ポータビルサービスがアタッチされると適切なプロファイルが選択される。 明示的にどのプロファイルも選択されない時、default と呼ばれるデフォルトのプロファイルが使われる。 systemdは4つの異なるプロファイルをデフォルトで提供する:

  1. default プロファイルは中間レベルのセキュリティを提供する。 これには、互換性のドロップ、システムコールフィルターの強制、制限された多くのカーネルインターフェイス、および、様々なファイルシステムのリードオンリーのマウントの設定が含まれる。

  2. strict プロファイルは default プロファイルと似ているが、全般的により制限的なサンドボックス設定を使用する。 例えば、ネットワーキングはオフで、 AF_NETLINK ソケットへのアクセスは禁止されている。

  3. trusted プロファイルはすべての中でもっとも制限がゆるい。 事実、ほとんどまったく制限をしない。 このプロファイルで起動しているサービスは基本的にホストシステムへのフルアクセスができる。

  4. nonetwork プロファイルはほとんど default と同じだが、加えてネットワークアクセスもオフになっている。

プロファイルはポータブルサービスイメージがアタッチされた時点で選択され、同じイメージに複数のサービスファイルが入っている場合、アタッチされるすべてのサービスファイルにプロファイルが適用される点に注意してほしい。 ゆえに、矯正されるサンドボックスの制限はイメージをアタッチする管理者により選択されるものであり、イメージの提供者が選択するものではない。

追加のプロファイルは管理者が簡単に定義することができる。 必要であれば、我々がsystemdと一緒に追加のプロファイルを早かれ遅かれ提供するかもしれない。

ユースケースは何?コンテナを持っていたら、なぜ困るの?

ポータブルサービスは主に、コードがホストシステムに対する「拡張機能」のようであるかのように感じられるユースケースをカバーすることを意図している。 接続がない、隔離された世界に住むということというよりかはそちらだ。 プロファイルというコンセプトはアプリケーションに執って必要とされる、統合と隔離をちょうどいいようにするためにチューニングできるようにと考えられたものだ。

コンテナの世界では、"super-privileged containers" というコンセプトが盛大に褒めちぎられている。つまり、完全な特権をもって起動するコンテナである。 ポータブルサービスが意図しているユースケースは正確にはこれである。 つまり、ホストOSの拡張機能であり、デフォルトでは隔離されているが、オプションで必要なだけのホストへのアクセスを得ることができ、本質的にはホストの完全な機能の恩恵を受けることができる。 したがって、このコンセプトは、OS自身とは一緒には提供されないが、様々な程度で、OSとの統合が必要とされるすべての種類の低レベルのシステムソフトウェアのユースケース向けである。 サーバーとアプライアンスに加え、これはIoTや組み込み系デバイスにとっても特に面白いことになるはずだ。

ポータブルサービスは、システムサービスがその他の方法でマネージされる方法への比較的小さな拡張機能に過ぎないので、ほぼすべてのユースケースで通常のサービスのように取り扱うことができる。 つまり、すべてのツールという観点では、systemdユニットデータを見ることができ、ロギング、リソース管理、ランタイムライフサイクルその他については同じ方法で管理することができ、通常のサービスの延長線上にある。

ポータブルサービスは非常に汎用的なコンセプトだ。 オリジナルのユースケースとしてはOSの拡張機能だが、もちろん、どう使うかはユーザ次第だ。

ウォークスルー

じゃあ、これがどう動くかを見ることに仕様。 ポータブルサービスイメージをホストにアタッチして、有効にして、起動する前に、スクラッチで作るところから始める。

ポータブルサービスイメージをビルドする

前述の通り、ポータブルサービスをビルドするためにOSツリーやrawイメージを作るのに、好きなツールを使うことができる。 例えば、 debootstrapdnf --installroot= などだ。 このウォークスルーでは、 mkosi を使うことにする。 これは、結局は dnfdebootstrap のファンシーなラッパーだが、ソースツリーから繰り返しイメージをビルドするときにはかなりのことを簡単にできる代物だ。

私はこのウォークスルーをローカルで再現するために必要なものすべてを GitHub レポジトリ にプッシュしておいた。 チェックアウトしよう。

$ git clone https://github.com/systemd/portable-walkthrough.git

レポジトリの中を見ておこう。

  1. まずはwalkthroughd.c は我々の小さなサービスのメインのソースファイルだ。 事を簡単にするために、Cで書いたが、言語は好きなものを使えばいい。 実装されたデーモンは多くのことをしない。 ただ起動して、シャットダウンのポイントである SIGTERMを待つ。 デーモンは結局は用途がないのだが、これがすべてどうフィットするかを希望をもって描く。 Cコードはlibc以外の依存関係を持たない。

  2. walkthrough.service はsystemdユニットファイルで、小さなデーモンをスタートさせるものだ。 シンプルなサービスのため、ユニットファイルは些細なものだ。

  3. Makefile はデーモンバイナリーをビルドするための、短い make のビルドスクリプトだ。 これも些細なもので、ただCファイルを取り上げ、そこからバイナリーをビルドするだけだ。 Makefileはデーモンのインストールもできる。 Makefileはバイナリーを /usr/local/lib/walkthroughd/walkthroughd に配置(なぜ、 /usr/local/bin じゃないかって?それは、このバイナリーはユーザーフェイシングのバイナリーではなく、システムサービスのバイナリーだからだ)し、ユニットファイルを/usr/local/lib/systemd/walkthroughd.service に配置する。 デーモンをホストでテストしたければ、単純に make を実行して、./walkthroughd をするだけで、ぜんぶちゃんと動いているかを確かめることができる。

  4. mkosi.defaultmkosi にどのようにイメージをビルドするかを伝える。 ここでは、我々は Fedora ベースのイメージを選ぶことにする(が、Debianを使うかもしれないし、その他かのサポートされたディストロを使うかもしれない)。 実行時に特定のパッケージは必要としない(なんといっても、libcにだけ依存している)が、ビルドフェ―ズには gcc と make が必要なので、これらばかりは BuildPackages= でリストしているパッケージだ。

  5. mkosi.buildシェルスクリプトで mkosi のビルドロジック中に実行される。 これがやることは、 makemake install を我々の小さなデーモンをビルドしてインストールするために実行し、その後、ディストリビューション提供の /etc/os-release ファイルを我々のサービスについてちょっと記述する追加のフィールドを着けて拡張する。

では、これを使ってポータブルサービスイメージをビルドしよう。 そのために、 mkosi ツールを使おう。 最初のイメージをビルドするにはパラメーター梨で実行するだけで十分だ。 mkosi は自動的に mkosi.defaultmkosi.build を見つけてこれらが伝える通りに処理をおこなう。 (このようなプロジェクトを長期に渡って実行する場合、 mkosi -if がおそらく使う上でベターなコマンドだろう。というのも、これは、インクリメンタルなビルドモードで、ビルドを実質的にスピードアップさせるものだからだ。) mkosi は必要なRPMをダウンロードして、全部一緒にする。 mkosi は我々の小さなデーモンをイメージの中でビルドして、すべて終わると、成果物のイメージを出力する。 walkthroughd_1.raw だ。

我々はGPT rawディスクイメージを mkosi.default で選択肢ているため、このファイルは実際にGPTパーティションテーブルを含む、rawディスクイメージだ。 fdisk -l walkthrough_1.raw を使えば、パーティションテーブルを列挙することができる。 また、必要なら systemd-nspawn -i walkthroughd_1.raw を使えば、イメージを素早く探検することができる。

ポータブルサービスイメージを使う

ポータブルサービスイメージは手にした。では、アタッチして、中には言っているサービスを有効にして起動してみよう。

まずはアタッチする。

# /usr/lib/systemd/portablectl attach ./walkthroughd_1.raw
(Matching unit files with prefix 'walkthroughd'.)
Created directory /etc/systemd/system/walkthroughd.service.d.
Written /etc/systemd/system/walkthroughd.service.d/20-portable.conf.
Created symlink /etc/systemd/system/walkthroughd.service.d/10-profile.conf → /usr/lib/systemd/portable/profile/default/service.conf.
Copied /etc/systemd/system/walkthroughd.service.
Created symlink /etc/portables/walkthroughd_1.raw → /home/lennart/projects/portable-walkthrough/walkthroughd_1.raw.

このコマンドは何をやったかを正確に表示してくれるだろう。 期待したとおり、このコマンドは、メインのサービスファイルを取り出してコピーし、2つのdrop-inを追加している。

では、約束したとおり、通常のユニットと同じように、ユニットがホストで利用可能になっているかを確かめよう。

# systemctl status walkthroughd.service
● walkthroughd.service - A simple example service
   Loaded: loaded (/etc/systemd/system/walkthroughd.service; disabled; vendor preset: disabled)
  Drop-In: /etc/systemd/system/walkthroughd.service.d
           └─10-profile.conf, 20-portable.conf
   Active: inactive (dead)

いいね、動いている。 ユニットファイルが利用可能で、systemdが正しく2つのdrop-inを見つけていることがわかる。 しかし、ユニットは有効なっていなければ、起動もしていない。 そう、ポータブルサービスイメージをアタッチするだけでは黙示的に有効にならなければ起動もしないのだ。 アタッチが意味するのは、イメージに入っているユニットファイルがホストで利用可能になったということだけなのだ。 それらを有効にする(つまり、例えば起動時など、必要なタイミングで自動的に起動する)ようにするのも、起動する(この場合では今すぐ起動する)のも、管理者次第だ。

では、サービスの有効化と起動をワンステップでやってしまおう。

# systemctl enable --now walkthroughd.service
Created symlink /etc/systemd/system/multi-user.target.wants/walkthroughd.service → /etc/systemd/system/walkthroughd.service.

動いていることを確認しよう。

# systemctl status walkthroughd.service
● walkthroughd.service - A simple example service
   Loaded: loaded (/etc/systemd/system/walkthroughd.service; enabled; vendor preset: disabled)
  Drop-In: /etc/systemd/system/walkthroughd.service.d
           └─10-profile.conf, 20-portable.conf
   Active: active (running) since Wed 2018-06-27 17:55:30 CEST; 4s ago
 Main PID: 45003 (walkthroughd)
    Tasks: 1 (limit: 4915)
   Memory: 4.3M
   CGroup: /system.slice/walkthroughd.service
           └─45003 /usr/local/lib/walkthroughd/walkthroughd

Jun 27 17:55:30 sigma walkthroughd[45003]: Initializing.

パーフェクト! サービスが有効化され、起動している。デーモンは PID 45003で動いている。

全部ちゃんと確かめたので、今度はサービスを停止し、無効化、デタッチをやってみよう。

# systemctl disable --now walkthroughd.service
Removed /etc/systemd/system/multi-user.target.wants/walkthroughd.service.
# /usr/lib/systemd/portablectl detach ./walkthroughd_1.raw
Removed /etc/systemd/system/walkthroughd.service.
Removed /etc/systemd/system/walkthroughd.service.d/10-profile.conf.
Removed /etc/systemd/system/walkthroughd.service.d/20-portable.conf.
Removed /etc/systemd/system/walkthroughd.service.d.
Removed /etc/portables/walkthroughd_1.raw.

最後に、ほんとに消えたかを見てみよう。

# systemctl status walkthroughd
Unit walkthroughd.service could not be found.

パーフェクト!うまく動いた!

上の通りやって、ポータブルサービスのスタートが切れたことを願うよ。 もっと疑問があれば、メーリングリスト にコンタクトしてほしい。

もっと知りたい人のために

詳細について説明している、より低レベルなドキュメントは systemdに同梱されている

関連するmanual ページもある: portablectl(1)systemd-portabled(8) だ。

mkosi についての詳細はそのホームページ をご覧いただきたい。

ジェーン・オースティン(小山太一訳)『自負と偏見』新潮文庫、2014年(原著:1813年)

自負と偏見 (新潮文庫)

自負と偏見 (新潮文庫)

本書のカバーの裏表紙には次の一文がある。

「幸福な結婚に必要なのは、恋心か打算か。」

もちろん、愛情もあり、金銭や人間関係で苦労のない結婚が一番だが、残念ながら、世の中、そういうものばかりでもない。 現代だってそうなのだから、本書の舞台であり、オースティンの生きた19世紀初頭なら、なおさらそうだろう。

本書では「恋心」と「打算」とのグラデーションでいろいろなパターンの結婚が描かれる。

次から次へと目まぐるしく何かが起きる。読み終えたとき、これが本当に数カ月の出来事なのかと思うくらいだ。 押さえるべきところは押さえ、流すべきところは流し、の緩急の付け方が巧妙で、最初を乗り切れば*1スイスイ読み進められる一冊だった。

読み進めながら、ひたすら「ダーシーかわいい」「エリザベスかわいい」と言いまくっていた。

岩波版でチャレンジしたこともあったが、訳があまり自分には合ってないな(現代的じゃないな)と思って、断念していた。 イアン・マキューアンの『贖罪』の訳が良かったので、きっと小山太一の訳なら大丈夫だと思ったら、大当たりであった。 あくまで印象論だが、原文を深く読み込んで、咀嚼し、原作と読者との双方に寄り添った丁寧な仕事をしていると思った。

ちなみに、大学の師匠いわく、当時は年収1000ポンドが、いい家でメイドを雇えるような生活の基準だそうだ。

*1:登場人物が多い上に、ミスターとミセスとミスで区別されているのは、初っ端から、なかなかつらい。

平野聡『大清帝国と中華の混迷(興亡の世界史)』講談社学術文庫、2018年(原著2007年)

高校世界史では「明清帝国」とひとくくりにされる明朝と清朝。 このくくり方には、両帝国の間に連続や継承があったというニュアンスがある。

実際、清は明の残した統治システムの多くを継承したと高校世界史的には説明されている*1

だが、このことは清が「中華文明に魅せられた」ことを意味するのであろうか?

以下、自分が面白いと感じた部分をピックアップしてみる。

現代への視点

本書の序章は「「東アジア」を疑う」と題されている。この章は読者を強く引きつける内容だ。 話は暦から始まる。1つ例にこれを取り上げてみよう。

中国五千年」というが、これは「漢人の社会と文化を最初に創始した伝説上の帝王である「黄帝」が今日の陝西省で即位して理想の統治を初めて以来の年数とされており、一般に「中国史」の枠組みにおいては「黄帝紀元」と呼ばれている。

しかし、この四七〇三という数字、さらには「中国五千年」という呼び方は、中国ですら、もともと自明ではなかった。漢人の社会、そして「天下」を支配する皇帝を戴く前近代「東アジア」の帝国が、伝統と近代の間で激しく揺れ動いたが、とくに近代日本に対する憧憬と反発がないまぜになった葛藤を深めて行く過程で自意識を突出させた表現なのである。 そもそも、この「黄帝紀元」という、一人の伝説上の人物を基準にすべての歴史を考えようという時間軸の立て方そのものが、漢人、そしていわゆる中国文明に固有の発想ではなかった*2

暦は権威の象徴であった。琉球や朝鮮は明や清の使う皇帝の名前を冠した年号を使っていた。 「時間軸と歴史の表現のされ方は、究極のところはひとつでしかないのが『東アジア』における帝国の姿だったのであり、それをどう選択するのか、強制するのか、拒否するのかという問題が、あまりにも痛切な歴史意識を作り出すことにもなった。あるいは、時間を専有することによってひとつの王朝は『帝国』たり得たといってもよい*3」のであった*4

この序章は短くはあるが、これ以外にも「中国史」や「東アジア」といった現代で一般に受け入れられているような言葉の理解を揺さぶってくる。 ここですでに明らかなように、本書全体を通じて、筆者の視線は現代へと向けられている。参考文献一覧にも書かれているとおり、本書は近現代をいったりきたりしながら記述されている。

中華思想」?

第1章では万里の長城が取り上げられる。 ここでの筆者の問題提起は簡単だ。つまり、「万里の長城中国文明の偉大さの象徴の一つとされるが、中国文明が偉大ならなぜ「壁」を必要としたのか?」ということである。

筆者は「中華思想」は使わず「華夷思想」というタームを使う。 華夷思想は、人間を「華夷」のそれぞれに分類し、前者が後者に優ると考えることである。 他にも同じような考え方は古来よりあるが、華夷思想が特徴的なのは、前者が後者を「教化」するところまで踏み込んでいることだと、筆者は主張する。 したがって、華夷の峻別は文化的なものであり、華を受け入れさえすれば、夷は華となることができたのである。当初、文化的優越の証は文字(漢字)であった。 「かつては『夷』と呼ばれていた人々を次々に巻き込む形で、今日の漢人にいたる大まかなまとまりが形作られた。それとともに、本来「夷」の側だったはずの諸都市国家や諸君主も、後世の歴史書の中では『華』を構成する一部としてとらえられた。*5

ここに儒学が入ってくる。 儒学が漢代に官学として採用されたことで、地位を確立すると同時に「本来は儀礼と道徳の学であったはずの儒学が、政治権力とのかかわりを媒介として、華夷思想の社会的影響力を強める方向にはたらくという副作用をともなった*6」のであった。 「要するに、「華」「夷」の基準とは、特定の儀礼作法(引用注: 漢字・儒学)を実践できるかという、ただそれだけの議論にすぎない。それにもかかわらず、それが早熟な漢人文化の下で拡大再生産された結果、絶大な思想的影響力を『東アジア』レベルにおよぼすようになったところに大きな特徴がある。*7

さらに、この「華夷」の区別を加速させたのが宋代の朱熹とその流れを汲む、朱子学であった。 これは当時一世を風靡していた仏教に対抗する思想であり、宗教的な信念をその考えに盛り込んだ。 厭世的な仏教に人々が自らの心の救済を求める用に慣れば、儒学は不要になってしまう。朱熹はそこに危機感を抱いた。 そこで、朱熹以下、儒学者たちは「天人合一」という「精神修養を通じて店の法則と人間の存在が一体化できるという視点を強く打ち出した。*8

だが、厳格な(厳格過ぎる)朱子学の理念を実践するには莫大なエネルギーを必要とし、人々の心の救済は依然として仏教や道教がになうこととなった。 これに対し、朱子学者たちは仏教や道教に対し「邪・淫」と主観的なレッテル貼りをするしかなかった。 この朱子学の独善性は、一般庶民の信仰と衝突した以外にも、朱子学と国家権力の関係、朱子学の考える「華」と「夷」との関係において深刻な矛盾を引き起こした。 つまり、「仏教や道教を尊重した皇帝の統治で、あるいは、「夷」が天下を支配した結果、朱子学の目指すところの、国家と社会の安定が達成されたら、朱子学儒学のの立場はどうなるのか?」という矛盾である。 筆者は次のように総括する。「華夷思想儒学思想、とくに朱子学の歴史とは、自らの主観的な判断基準と国家の現実とのあいだのずれをめぐって混迷を続けた歴史である。*9」 この部分の記述は本書全体を通じて、かなり効いてくるものである。

なかなかに興味深い記述が続くのであるが、本書全体を通じてそのような記述ばかりなので、先を急ぐこととする*10。 元に代わった明は、モンゴル人に支配された経験から、「理想的な『中華』を実現し、天命にのっとった平和な支配を回復しようという目標にひときわ強烈なこだわりを持った。*11」 明は躍起になって各国と朝貢関係を結び、財をばらまいては、「中華の偉大さ」を見せつけようとした。だが、これは一種のコンプレックスであり、明の財政的疲弊を招くこととなる。

また、北方との関係においては、「中華」を体現することはできず、モンゴルに対し、常に劣勢であった。その反動として、明は壁を築き、拒否の姿勢を示すのであった*12

清の登場

明が自滅に近いかたちで滅亡後、清が建国された。清は都を瀋陽から北京に移し*13、冒頭で述べたとおり、明からの連続性を強調する政策を打ち出した。

だが、明と清とはかなり志向が異なる。鄭和の遠征や倭寇といった用語が象徴するように、明は海を志向した帝国であった。 一方、清は内陸アジアを重視した。 清は第2章のタイトルの通り、「内陸アジアの帝国」として発展した。 「清が漢人地域を支配し始めたことは、地政学的にみれば漢人社会が「東アジア」の「核」から内陸アジアの『周辺』へと移行したことを意味*14」していた。

清は確かに、漢人社会に対しては明のシステムを継承した。だが、その漢人社会は帝国の「周辺」に過ぎなかったのである。 清は「中心」である内陸に対しては、チベット仏教の保護者であり、その立場をもってチベット仏教を信仰するモンゴル人のハーンとなった。また、ムスリムに対しては保護者として振る舞った。 つまり、複合的な統治機構を持つ帝国であった*15

清の登場は朱子学の矛盾をもろに露呈させる形となった。 その反応として登場したのが顧炎武と考証学であった*16

また、朱子学者・曾静と雍正帝との問答(『大義覚迷録』)で、雍正帝は「民族や文化の違いによって人間性や能力に本質的な違いがあるという見方を排除し」「『中外一体』という表現を積極的に用いるようになった。*17」 この「中外一体」が思想的に準備したところに、「中外一体」に欧米式の主権国家体制のシステムが到来したとき、現代にまで継承される、近現代中国の領域はほぼ確定することとなった。

主権国家への脱皮と苦しみ

第6章以降はざっとこのように説明して良いだろう。 欧米諸国の来航とそれらとの戦争・敗戦により、清は内とも外とも区別のつかない緩やかな従来の統治システムから、内外をはっきり区別し、内に対しては統治を確立し、外に対しては対等な者同士がやり取りをおこなう主権国家体制への適合を迫られた。

その中で、清自身が苦しんだのはもちろんだが、清が「内陸アジアの帝国」として重視していた部分であったがゆえに、内となったチベットや新疆、モンゴル、満州の今日まで続くような悲劇があった。 一方で、外となった琉球や朝鮮は日清の係争の対象となり、翻弄された。

所感

自分自身はあまり高校世界史の前近代の中国史を面白いと感じたことがなかった。 その理由はそれぞれの政治的な出来事同士や思想の流れにあまりつながりを感じることができず、ただ順番に覚えるだけのもののように感じたからであった。

その点、本書はその覚えたピース同士がうまくつなげて叙述しており、知的興奮を覚える一冊であった。 特に、政治と思想とを紐付けは膝を打つものばかりであった。

もちろん、著者が政治学者であるがゆえに、叙述が大雑把に感じない点がないわけではない。 この時代を専門とする歴史学者がすんなりと受容できるかと言われたら、微妙ではありそうだ。 専門外なのでわからないのだが、独自理論や独自解釈の罠もありそうだなぁという気がする。 それに、筆者の論文一覧著作一覧、特に最近のものを見ると、ちょっと大丈夫か、みたいな気持ちにはなる。

ただ、清朝だけでも300年くらいあるものをその前後含めつつ、しかも、地域的な広がりとダイナミズムのあるものをコンパクトに、かなりの程度の説得力と分かりやすさをもって、読者の持つであろう現代的な問題関心を常に刺激しながら、まとめ上げているのは、素直に「すごい」と思う。 他の本を読んだら、色々思うところも出てくるのかな、とは思うが、少なくとも現時点では、おすすめの一冊である。

*1:例えば、世界史の窓 - 満漢併用制

*2:本書、17頁。

*3:本書、20頁。

*4:余談だが、大学時代に西南アジア史の教授が教えてくれたのだが、現代ギリシャの美術館ではオスマン帝国時代の物品について「ポストビザンティン時代」と表記されているらしい。

*5:本書、75頁。

*6:本書、80頁。

*7:本書、81頁。

*8:本書、82頁。

*9:本書、85頁。

*10:本書、90-91頁。

*11:本書、94頁。

*12:この北方遊牧民に対する明のアプローチとその変化についてはアーサー・ウォルドロン「十四世紀から十七世紀にかけての中国の戦略」(『戦略の形成〈上〉―支配者、国家、戦争』所収)が詳しい。

*13:両都市の比較も面白い。本書、114-118頁および、123-127頁。

*14:本書、135頁。

*15:本書、153-162頁

*16:本書、216-221頁。

*17:本書、177頁。

#きょうのsystemd : ルートを変える (systemd for Administrators, Part 6)

これは

0pointer.de

の翻訳

ルートを変える

管理者であれ開発者であれ、遅かれ早かれ、chroot()環境に出会うことになる。 chroot()システムコールは単純にプロセスおよびその子がルートディレクト/ と考えるものをシフトさせ、そのプロセスが見ることができるファイルの階層をそのサブツリーに制限することができる。 主としてchroot()環境には2つの用途がある:

  1. セキュリティ目的: この用途では、特定の独立したデーモンがプライベートなサブディレクトリにchroot()され、デーモンが悪用された場合でも、攻撃者は完全なOSの階層ではなく、サブディレクトリしか見ることができないようにすることができる。
  2. OSイメージのデバッグ、テスト、ビルド、インストール、リカバリーをセットアップないしはコントロールするため: このためにゲストのOSの階層がホストOSのサブディレクトリにマウントないしはブートストラップされており、シェル(ないしはその他のアプリケーション)をその中で実行し、このサブディレクトリをその/にする。 例えば、異なるディストリビューションや異なるアーキテクチャ(例: ホスト側はx86_64でゲスト側がi386)であるかもしれない。 ホストOSの完全な階層はchroot()された環境から見ることはできない。

古典的なSystemVベースのOSの場合、chroot()環境を使うのは比較的簡単であった。 例えば、特定のデーモンをテストないしはその他の理由でchroot()ベースのゲストOSツリー内で実行するには、/proc/sysあるいはその他いくつかのAPIファイルシステムをツリーの内部にマウントし、それからchroot(1)を使ってchrootに入り、最終的にSysVスクリプトファイルを/sbin/service経由でchrootの中空実行すれば良い。

systemdベースのOSではそこまで簡単ではなくなってくる。 systemdの大きなアドバンテージの1つはすべてのデーモンはユーザーがサービスを開始させようとしたコンテクストとは一切関係がなく完全にクリーンで独立したコンテクストで実行されることが保証されていることにある。 SysVベースのシステムでは実行コンテクストの大きな部分(リソース制限や環境変数その他のようなもの)はinitスクリプトを実行したユーザーシェルから継承されたものである。 一方、systemdの場合、ユーザーはinitデーモンに通知をするだけであり、initデーモンはそれを受けて、デーモンを健全で良く定義された、きれいな実行コンテクストでフォークし、ユーザーコンテクストのパラメーターを一切継承させない。 これは驚くべき機能である一方、この機能により、サービスをchroot()環境で実行するための伝統的な手法は確かに破壊されてしまった。 というのも、実際のデーモンは常にPID1からスポーンされ、そこからchroot()の設定を継承するため、デーモンをスタートするように要求したクライアントがchroot()されたかどうかは関係なくなるのだ。 この最たるものとして、systemdは実際に自身のローカルコミュニケーションのソケットを/run/systemdに置いているため、chroot()環境にあるプロセスはinitシステムとトークすることすらできないのである(しかし、これはいいことかもしれないし、大胆な人間だったら、もちろんこれを、bindマウントを使うことで迂回することはできる)。

これで、当然のように、systemd環境でchroot()を適切に使うにはどうしたらいいのか?という問題が生じてくる。 そして、この記事がそれを説明するために思いついたものであり、この問題に対して徹底的かつ包括的に答えられたら嬉しいと思う。

最初のユースケースをまず考えよう。 セキュリティ目的でデーモンをchroot()監獄に閉じ込めるユースケースだ。 まずはセキュリティツールとしてのchroot()はかなり疑わしい。というのも、chroot()は一方通行ではないからだ。 manページですら指摘しているとおりchroot()環境から脱出するのは比較的簡単なのだ。 その他いくつかのテクニックを組み合わせた場合にのみ、chroot()はいくらかセキュアにすることができる。 そのため、これは通常はchroot()自体をタンパー防止な方法でサポートするアプリケーションで特定のサポートをする必要があるのだ。 そのてっぺんにあるのは、chroot()されたサービスの深い理解がないと、chroot()環境を設定することが通常できないということである。例えば、chroot()内でサービスが実際に要求するコミュニケーションチャンネルをすべて利用可能にするために、ホストのツリーからどのディレクトリをbindでマウントすべきか知っていることだ。 これらをまとめると、セキュリティ目的でサービスをchroot()することはデーモン自身のCコードの中でやってしまうことがほとんど常にベストな方法だ。 開発者はchroot()を適切にセキュアにするベストな方法、最小限のファイルのセット、chroot()内部でデーモンが必要とするファイルシステムディレクトリが何であるかを知っている(あるいは少なくとも知っているべきだ)。 今日では、多くのデーモンがこれをおこなうことができるが、不幸なことに、通常のFedoraのインストールでデフォルトで動いているデーモンのうちこれをやっているのはたったの2つである。 AvahiとRealtimeKitである。 どちらも同じ、本当にスマートなヤツによって明らかに書かれている。Chapeauだ! (これは ls -l /proc/*/root をシステムで実行したら簡単に確かめられる)

さて、systemdはもちろん特定のデーモンをchroot()させて、通常のツールでその他と同じように管理する方法を提供している。 これはsystemdサービスファイルのRootDirectory=オプションでサポートされている。 次のは例だ。

[Unit]
Description=A chroot()ed Service

[Service]
RootDirectory=/srv/chroot/foobar
ExecStartPre=/usr/local/bin/setup-foobar-chroot.sh
ExecStart=/usr/bin/foobard
RootDirectoryStartOnly=yes

この例では、RootDirectory=ExecStart=で指定されたデーモンのバイナリーを実行する前にどこでchroot()をすべきかを設定している。 ExecStart=で指定されるパスはchroot()内部のバイナリーを指定している必要があり、ホストツリーのバイナリーへのパスでないこと(つまり、この例の場合、実行されるバイナリーはホストOSからは/srv/chroot/foobar/usr/bin/foobardとして見えること)に注意してほしい。 デーモンが起動される前にsetup-foobar-chroot.shというシェルスクリプトが実行される。このスクリプトの目的は必要とされるchroot環境を設定することだ。例えば、/procや類似のファイルシステムchroot環境の中にマウントするなど、サービスが必要なものに応じて設定する。 RootDirectoryStartOnly=スイッチを使うことで、ExecStart=で指定されたデーモンだけをchrootさせ、ExecStartPre=スクリプトディレクトリをbindマウントするために完全なOS階層へのアクセスする必要があるために、chrootに入れないといったことを設定できる(これらのスイッチに関する詳細はmanページを見てほしい)。 このようなユニットファイルを/etc/systemd/system/foobar.serviceに置けば、systemctl start foobar.serviceとタイプすることで、chroot()されたサービスを起動することができる。 systemctl status foobar.serviceで中を見ることもできる。 他のサービスと同様にサービスへアクセス可能で、chroot()されたという事実は、--SysVの場合と異なり--その監視や制御ツールとの関わり方は変わらない。 比較的新しいLinuxカーネルファイルシステム名前空間をサポートしている。これらはchroot()と似ているが、さらにもっと強力で、chroot()と同じようなセキュリティ問題からの影響を受けない。 systemdはファイルシステム空間でできることのサブセットをユニットファイル自身にもそのまま適用する。 たいていの場合、サブディレクトリで完全なchroot()環境をセットアップすることの、便利でシンプルな代替案となる。 ReadOnlyDirectories=InaccessibleDirectories=と、2つのスイッチによって、サービスに対するファイルシステム名前空間の監獄を設定することができる。 最初は、これはホストOSのファイルシステム名前空間と全く同じである。 これらのディレクティブにディレクトリを列挙することで、ホストOSのディレクトリやマウントポイントをデーモンに対し読み取り専用ないしは完全にアクセス不能なものとしてさえ、マークすることができる。 例:

[Unit]
Description=A Service With No Access to /home

[Service]
ExecStart=/usr/bin/foobard
InaccessibleDirectories=/home

このサービスはホストOSのファイルシステムツリー全体へのアクセスを持つが、唯一例外がある。/homeはサービスからは見えず、潜在的な攻撃者からユーザーのデータは守られる(これらのオプションの詳細はこちらを見てほしい)。

ファイルシステム名前空間は実際に多くの方法でchroot()のより良い代替となる。 最終的にAvahiとRealtimeKitはchroot()で置き換える形で名前空間を利用するようにアップデートされるべきだろう。

セキュリティのユースケースはもう十分だろう。 さて、もう一つのユースケースを見てみよう。デバッグやテスト、ビルド、インストールあるいはリカバリーのためにOSイメージを設定し、管理する場合だ。

chroot()環境は比較的簡単なことである。つまり、ファイルシステム階層を仮想化しているに過ぎない。 サブディレクトリへchroot()しても、プロセスはすべてのシステムコールへの完全なアクセスは持ち、すべてのプロセスをkillすることができ、それの動いているホストとすべてを共有している。 したがって、chroot()の中でOS(ないしはOSの一部)を実行することは危険なことである。というのも、ホストとゲストの隔離はファイルシステムに限定されており、それ以外の全てはchroot()の中から自由にアクセスすることができてしまう。 例えば、もしchroot()内部のディストリビューションをアップグレードし、パッケージスクリプトがSIGTERMをPID1に送って、initシステムの再実行をトリガーした場合、これは実際、これがホストOSで起きてしまうのだ! その最たるものが、SysVは共有メモリー、抽象名前空間のソケット、その他のIPCプリミティブがホストとゲストの間で共有されている。 OSのテスト、デバッグ、ビルド、インストール、回復のために完全にセキュアな隔離はおそらく必要ないものの、chroot()環境内部からホストOSの偶発的な変更を避けるための基本的な隔離はあるに越したことはない。パッケージスクリプトが実行するホストOSに影響を与えるかもしれないコードを知ることは決してない。

この用途でchroot()セットアップを扱うために、systemdは2つの機能を提供している。

まず最初にsystemctlはそれがchrootで実行されていることを検知する。 もしchrootで実行されていることを検出した場合、systemctl enablesystemctl disableとを除いて、systemctlの操作はほとんどNOP(何もしない)になる。 もしパッケージインストールスクリプトがこれら2つのコマンドを実行した場合、ゲストOSの中でサービスは有効になる。 しかし、systemctl restartのようなコマンドをパッケージアップグレードプロセスに含んでいるパッケージインストールスクリプトがあるとして、これはchroot()環境では一切の効果を持たない。

さらに重要なことに、systemdはout-of-the-boxな状態(訳注: インストール直後の何も設定しない状態)でchroot(1)の強化版として機能するsystemd-nspawnが提供されている。これはファイルシステムおよびPID名前空間を使うことで、シンプルで軽量なコンテナをファイルシステムツリーでブートさせることができる。 これはchroot(1)とほとんど同じように使うことができる。違いはホストOSからの隔離がより完全であり、かなりセキュアで使いやすくもあることである。 事実、systemd-nspawn完全なsystemdないしはSysVinitのOSを1つのコマンドでコンテナ内にブートさせることが可能である。 PIDを仮想化しているため、コンテナ内のinitシステムはPID1として活動することが可能であり、そのため、その仕事を通常通りおこなうことができる。 chroot(1)と比較して、このツールは/proc/sysを黙示的にマウントする。

次は3つのコマンドでFedoraのマシンでnspawnコンテナ内にDebian OSをブートする例である。

# yum install debootstrap
# debootstrap --arch=amd64 unstable debian-tree/
# systemd-nspawn -D debian-tree/

これはOSディレクトリツリーをブートストラップし、単純にその中のシェルを起動させている。 もしコンテナ内に完全なシステムをブートしたい場合、このようなコマンドを使う。

# systemd-nspawn -D debian-tree/ /sbin/init

素早く起動したあと、コンテナにブートした完全なOSの内部のシェルプロンプトに移る。 コンテナはその外にあるプロセスは一切見ることができない。 ネットワーク設定は共有するが、変更することはできない(例外はブート時の2つのEPERMだが、これは致命的なものとはならない)。 /sys/proc/sysのようなディレクトリはコンテナ内でも利用可能だが、コンテナがカーネルやハードウェア設定を修正できないようにするため、読み取り専用でマウントされている。 しかし、注意してほしいのはこれはホストOSを偶発的なホストOSのパラメーターの変更から守るということである。 コンテナ内のプロセスは手動でファイルシステムを読み書き可能で再マウントし、加えたいだけの変更を加えることが可能である。

さて、systemd-nspawnのどこが偉大なのかもう一度まとめよう

  1. 簡単に使える。手動で/proc/syschroot()環境にマウントする必要がないこと。このツールはこれをやってくれ、コンテナが終了するときにはカーネルが自動でこれをきれいにしてくれる。
  2. 隔離がより完全で、コンテナ内部空の偶発的な変更からホストOSを守ってくれること。
  3. コンテナの中に完全なOSをブートさせることができること。たった1つの寂しいシェルではない。
  4. 実際に小さく、systemdがインストールされているならどこにでもインストールされていること。複雑なインストールやセットアップは不要だ。

systemd自体もこのようなコンテナでうまく動作するための修正が加えられている。 例えば、シャットダウンするときにコンテナで動いていることを検知すれば、systemdは最後の段階で、reboot()の代わりにexit()だけをコールする。

systemd-nspawnは完全なコンテナソリューションでないことに注意してほしい。 それが必要ならLXCがより良い選択だ。 LXCは同じ基礎となるカーネルテクノロジーを利用しているが、ネットワーク仮想化を含むより多くを提供している。 もし望むなら、systemd-nspawnはコンテナソリューション(分野で)のGNOME 3である。滑らかでほとんど努力せずに簡単に使える -- しかし、設定オプションはほとんどない。 一方で、LXCはよりKDEのようである。コードの行数以上の設定オプションがある。 systemd-nspawnを私が書いたのは、テスト、デバッグ、ビルド、インストール、リカバリーをカバーするためである。 これがsystemd-nspawnを利用するべきものであるし、これが得意なものだし、これがchroot(1) のより良い代替となるところである。

さて、そろそろ終わりにしよう。すでに長くなってしまっている。 以下が、このブログストーリーから家に持って帰ってもらいたいものだ。

  1. セキュアなchroot()はプログラムのCのソースコードでネイティブにやるのが一番。
  2. ReadOnlyDirectories=InaccessibleDirectories=は完全なchroot()環境への適切な代替手段である。
  3. RootDirectory=は特定のサービスをchroot()させたいなら、友となる。
  4. systemd-nspawnはすごいことができる。
  5. chroot()は不自由だが、ファイルシステム名前空間は全面的にエリートである。

Fedora15でこのすべてが利用できる。

さて、今日はここまで。次回また会おう。

きょうのsystemd: 3つのレベルの「オフ」

概要

これは

0pointer.de

の翻訳。

3つのレベルの「オフ」

systemdではサービス(ないしは、その他のユニット)を3つのレベルでオフにできる。 これについて見てみよう。

1.サービスを停止することができる。これは単純にサービスのインスタンスを停止させることであり、それ以外はほとんど何もない。 アクティベーションのいくつかの方法(例えば、手動によるアクティベーション、ソケットアクティベーション、バスアクティベーション、システム起動時のアクティベーション、あるいは、ハードウェアの抜き差しによるアクティベーション)によって、停止させた後に、サービスが再び要求されれば、そのサービスは起動する。 サービスを止めるということは非常に単純で、一時的なもので、表層的な操作なのである。 これはNTPサーバーを停止させる方法の例である:

$ systemctl stop ntpd.service

これはざっくり言うと、SysVで動いている多くのシステムで利用可能な、次の伝統的なコマンドと同等だ:

$ service ntpd stop

実際、Fedora15では、後者のコマンドを実行すれば、透過的に前者へと変換されるようになっている。

2.サービスを無効にすることができる。 これはサービスがそのアクティベーションのトリガーによりフックされないようにする。 これは、サービスによっては、起動時にアクティベーションされたり、ソケットやバス、ハードウェアの抜き差し(その他、そのサービスに適用されているトリガー)でサービスがもはやアクティベートされないことを意味する。 しかし、必要であれば、手動で起動させることはまだできる。 もしすでに起動済みのインスタンスがある場合、サービスを無効にしてもそれを止めるような効果はない。 これはサービスを無効にする方法の例である:

$ systemctl disable ntpd.service

伝統的なFedoraのシステムでは、これは、ざっくり言うと次のコマンドに等しい:

$ chkconfig ntpd off

ここでも、Fedora15では後者のコマンドは、必要に応じて、透過的に前者へと変換される。

しばしば、サービスの停止と無効化を一緒におこないたい場合がある。というのも、現在のインスタンスを取り除いて、(手動でトリガーする場合をのぞいて)確実にそれが再び起動しないようにするためである。

$ systemctl disable ntpd.service
$ systemctl stop ntpd.service

これらのコマンドは例えば、Fedora上のsystemdサービスのパッケージを取り除く(訳注: deinstall)ときに使われる。

サービスの無効化は永続的な変更である。 元の状態に戻すまで、再起動を繰り返しても、その状態は保たれる。

3.サービスをマスクすることができる。 これはサービスの無効化と似ているが、極端である。 これは、サービスが自動的にもはや起動しなくなるようにするだけでなく、手動でさえもサービスを起動できなくさせるようにする。 これはsystemdのちょっとした隠し機能である。というのも、一般に使うものでもないし、ユーザーを混乱させてしまうかもしれないためだ。 しかし、やり方はこうだ:

$ ln -s /dev/null /etc/systemd/system/ntpd.service
$ systemctl daemon-reload

サービスファイルを/dev/nullへのシンボリックリンクにすることで、systemdに件のサービスを決して起動させず、完全に実行をブロックするように伝えることができる。 /etc/systemd/systemにあるユニットファイルは/lib/systemd/systemにある同じ名前のファイルを上書きする。 前者のディレクトリは管理者の領域であり、後者はパッケージマネージャーの領域である。 /etc/systemd/system/ntpd.serviceシンボリックリンクを差し込むことで、systemdがアップストリームが同梱しているサービスファイル/lib/systemd/system/ntpd.serviceを決して使わないようにすることになる。

systemdは/dev/nullシンボリックリンクの張られたユニットを認識することができ、これをmaskedと表示する。 手動で(例えばsystemctl start経由で)サービスを起動させようとしても、エラーで失敗するだろう。

SysVのシステムでは(公式には)似たようなトリックは存在しない。 しかし、いくつかの非公式のハックは存在していて、例えば、initスクリプトを修正して、頭の方にexit 0を置いたり、実行ビットを取り除いたりする方法がある。 しかし、これらの解決方法は様々な欠点がある。例えば、これらの方法はパッケージマネージャーからの妨害を受けてしまう。

サービスのマスクは永続的な変化で、サービスの無効化とかなり似ている。

さて、3つのレベルでサービスをオフにする方法を学んだが、ただ1点、問題が残されている。つまり、どうやってオンに戻すのか? まぁ、これはかなり対称的なものだ。 systemctl startを使ってsystemctl stopを元に戻す。 systemctl enableを使って、systemctl disableを元に戻す。 rmを使ってlnを元に戻す。

今日はここまで。読んでくれてありがとう!

#きょうのsystemd : サービスを停止させる (systemd for Administrators, Part 4)

概要

これは

0pointer.de

の翻訳。

サービスを停止させる

システムデーモンを停止させるのは簡単、本当?

確かに、デーモンが単一のプロセスで構成されている限り、これは確かにある程度は正しいだろう。 killall rsyslogdとタイプすれば、syslogデーモンはどこかへ行く。 しかし、このようにプロセスを止めるのはちょっと汚いやりかただ。 というのも、このように呼び出されることになったプロセスをこれはすべて停止させ、そこには偶然そのような名前を付けてしまった不幸なユーザーのプロセスも含まれるかもしれないからだ。 ほんの少し正しいバージョンは.pidファイルを読み込む方法で、例えば

kill `cat /var/run/syslogd.pid`

というものだ。 この時点ですでにかなり進むことができたが、それでもまだ、これが本当に我々のやりたいことなのだろうか?

多くの場合で、実際にはそうではない。 Apacheやcrondあるいはatdといったサービスを考えよう。これらはたいていの操作の一部として、子プロセスをスポーンさせる。 任意の、ユーザー設定可能な子サービスであり、例えば、cronやatのジョブ、CGIスクリプト、完全なアプリケーションサーバーといった場合もある。 apache、crond、atdのメインプロセスを停止させると、子プロセスも一緒に落ちるかもしれなければ、そうでないこともあり、残り続けるか停止するかはそれらのプロセス次第なのである。 基本的に、Apacheをターミネートさせることは、そのCGIスクリプトを残留させ、initの子として再割り当てをし、追跡を困難なものにするのである。

systemdはこれをレスキューする。 systemctl killであるサービスのすべてのプロセスに対し、簡単にシグナルを送ることができる。 例:

# systemctl kill crond.service

これはSIGTERMがcrondサービスのメインプロセスのみならず、すべてのプロセスに届けられることを保証する。 もちろん、望むなら違うシグナルを送ることも可能である。 例えば、あなたのお行儀が悪いなら、SIGKILLをすぐに送ってしまおうと思うだろう。

# systemctl kill -s SIGKILL crond.service

これで望み通りに、サービスが何回フォークしようと、ダブルフォークやフォークボミングで監視下から逃れようと、サービスはその全体が虐殺される。

ときどき、サービスのメインプロセスに特定のシグナルを送ることだけが必要となることがある。例えば、SIGHUP経由でリロードをトリガーしたいといった理由によるものだ。 PIDファイル経由でおこなう代わりに、もっと簡単な方法がある。

# systemctl kill -s HUP --kill-who=main crond.service

そして再び、systemdでサービスを停止させることについて何がそんなに新しくてファンシーなのか? Linuxで初めて、我々がそれを実際、適切におこなうことができている。 以前のソリューションは常にデーモン頼みであり、デーモンが自分自身がターミネートするときに自分がスポーンしたすべてのものを停止させるよう実際に協力してくれるかに頼っていた。 しかし、普通、SIGTERMやSIGKILLを使いたい場合に、そのようにすることになる。それはデーモンが実際に適切に協力してくれないからなのだ。

これがsystemctl stopとどのように関係してくるのか? killはグループにあるすべてのプロセスに直行し、シグナルを送る。 しかし、stopはサービスをシャットダウンする公式の設定を通過する。例えば、サービスファイルのExecStop=で設定された停止コマンドを実行する。 通常はstopで十分なはずである。 killはより荒っぽいバージョンであり、実行するサービスの公式のシャットダウンコマンドを使いたくなかったり、サービスを他の方法できれいにしたりハングさせたりする場合に使われる。

(ところで、人に寄ってはSIGプレフィックスを-sスイッチに付けたり付けなかったりするが、どちらでも動く。)

Linuxでサービスを適切に停止させる方法なしでここまでやってきたのにはちょっと驚く。 systemdはこれを適切におこなうことを、初めて可能にするものなのである。