はじめに
本記事はLinux Advent Calenda 2018の最終日、25日目の記事です。
ここ数年、一つないし複数のプロセスから成るアプリをコンテナと呼ばれるサンドボックス上で動かすのが流行っています。このときアプリを動かす実行環境のことをコンテナランタイムと呼びます。このコンテナランタイムには例えば次のようなものがあります。
- runC: アプリをそれぞれ別々のnamespace上で実行。カーネルは全アプリで共有。世間的に「コンテナ」というと今はだいたいこれを指す
- Kata Containers: アプリを個々のアプリ専用VM上で実行
- gVisor: アプリをユーザ空間で独自実装されたLinuxカーネルのサブセット上で実行
本記事は最近筆者が気になっているlinuxdというコンテナランタイムについて簡単に紹介したいと思います。
何がどう"謎"なのか
タイトルに「謎の」と書いているのは、後述の通り世の中に情報がほとんど出回っていないからです。私の知る限り、linuxdが初めて表に出たのは2018年のLinuxCon Chinaであり、そのあとOpen Source Summit 2018 North Americaでも発表があったようです。スライドはこちら。で、いまのところ世に出ている情報は悲しいことにこれだけです。
スライドにも細かい実装の話は載っていないので、本記事は「たぶんこうではなかろうか」という推測をもとに書いていることをあらかじめお断りしておきます。「いやその解釈はおかしいだろう」「たぶんほんとはこうなっているはず」などの突っ込みお待ちしています。
概要
linuxdとはユーザ空間で動作するlinuxカーネル上でアプリを動かすコンテナランタイムです。将来的にはOCIやCRIに準拠したランタイムになる予定だそうです。
上記のさまざまなコンテナランタイムのうち、ユーザ空間で動作するカーネル上で実行するという意味で、linuxdはgVisorに似ています。これ以降は主にgVisorとの比較という形でlinuxdがどのようなものなのかについて述べます。
linuxdはgViorとは異なりカーネルは独自実装のものではなく、linuxそのものに少し手を加えたものです。どのような手を加えているかについては次節で述べます。
linuxdの作者はユーザ空間カーネルを独自実装ではなくlinuxベースにする利点として、次のようなものを挙げています。
- 成熟したlinuxのコードベースを利用できる
- 現在はupstreamのlinuxに独自パッチを当てているが、将来的にupstreamに取り込まれれば以後のカーネルアップデートに自動的に追従できる
- アプリを一切改変せずに動作させられる。gVisorはlinuxの全機能をサポートしているわけではないのでアプリを動作させるためには改変が必要なことがある
linuxカーネルの変更点
linuxdにおいて動かすユーザ空間カーネルはupstreamのlinuxカーネルそのものではなく、それに3つの変更を加えたものです。以下それぞれについて説明します。
アドレス空間操作処理
ユーザ空間カーネル上でアプリを動かすためには、ユーザ空間カーネルがアプリ内の個々のプロセスのためにアドレス空間を作る必要があります。かつ、その後アドレス空間に変更があったときにもユーザ空間カーネルが処理してやる必要があります。詳細は省略しますが、gVisorのようにupstreamカーネルの機能を使ってこれを実現しようとすると、これらの処理のためにユーザ空間カーネルからカーネルに対して大量のシステムコールを発行する必要があります。
これに対してlinuxdにおいては、上記のようなアドレス空間の操作専用システムコールをupstreamカーネルに追加しました。これによってアドレス空間の操作にかかるコストを削減しました
システムコールや例外の横取り処理
ユーザ空間カーネル上でプロセスを動かすためには、アプリ内の各プロセスの動作中にシステムコールの発行や例外が発生したことをユーザ空間カーネルが認識し、処理してやる必要があります。gVisorではこれをptraceシステムコールを使ったカーネル処理の横取りによって実現しています1。ところがこれには複雑な処理が必要なため、非常に低速です。
注意: 本節のここからの部分は冒頭にリンクを張った"Containerize Linux Kernel"というスライドの中の以下の部分から推測しました。
update code on the path for syscalls and exceptions for the new ASes
これに対してlinuxdにおいては、ホストカーネルのシステムコールの入り口部分に変更を加えていると考えられます。ホストカーネル上で直接動作しているプロセスがシステムコールを発行したときは従来通りの動作をします。その一方でlinuxdから立ち上げられたプロセスの場合は、linuxdのユーザ空間カーネルにシステムコールの処理を委託します。linuxdは委託されたシステムコールを実行した上でプロセスに制御を戻します。
この一連の処理はptraceを使う場合に比べて少ない手番で済むので高速、ということでしょう。
ユーザ空間カーネルとホストカーネル間のインターフェース
ユーザ空間カーネルはシステムコールを処理する必要ができたときに、処理が自分自身の中で完結できればそうするのですが、そうではないとき、たとえば物理ハードウェアにアクセスしなければいけないときなどにはホストカーネルに処理を委託します。
本節のここからの部分は元スライドの以下の部分を元に推測しました。
Reduced Attack Surface to Host Kernel - No Hardware ABIs - Limited linux ABI with host kernel, less than 30 sysalls or even less - Limited usage of host kernel functionalities
linuxdにおいてはホストカーネルのうち、ハードウェアを操作する部分はホストカーネルに処理を委託するシステムコール発行に置き換えていると考えられます。このとき使用するシステムコールは必要最小限にしておき、かつ、使っていないシステムコールはseccompによって実行不能にしておくことによって堅牢性を高めているはずです。
おわりに
ユーザ空間でlinuxカーネルを動かすというlinuxdのアプローチはなかなかアツいアイデアに見えます。ただし、Linuxには遥か昔からこれとかなり似たUser Mode Linux(UML)という機能がありました。UMLについてはスライドの中でも次のように言及がありました。
Its experimental stage has started for about 1 year, spawning PoC product based on UML showed the approach workable.
個人的にはUMLの性能向上および拡張ではなくlinuxdという新規のプロジェクトにしている理由がよくわかりませんでしたが、これまでに述べた通り謎が多いので実際のところはよくわかりません。近いうちに上記のような私の理解を発表者にして確認してみようと思っています。回答をもらえて、かつ、公開可能であれば本記事を更新する予定です。