WebAssemblyを実行するユニカーネルの構築 - パート1
(flavio.castelli.me)- SUSE Hackweek 22でWebAssemblyモジュールを実行するユニカーネルのPOCを作成し、実装過程を複数パートに分けて整理
- 一般的なアプリケーションをユニカーネルへ直接ポーティングするには依存関係まで合わせる必要があるが、WebAssemblyプラットフォームではランタイムが提供する機能の境界がより明確
- SpiderlightningアプリケーションはKey/Valueのような機能だけを要求し、ホストがRedisやAzure Cosmos DBで実装しても、同じ
.wasmモジュールはその違いを知る必要がない - ベースはRustユニカーネル RustyHermitで、WasmtimeとWasmerがビルドできなかったため、純粋なRustランタイムである wasmiを選択
- Component ModelとWITに合わせるため wit-bindgenにwasmiサポートを追加したうえで、ホスト側のKey/Value機能をスキャフォールドして
keyvalue-demoを実行
Hackweekプロジェクトと目標
- SUSEのHackweek 22期間中に、WebAssemblyを実行するユニカーネルの構築プロジェクトを実施
- 実装過程全体を1本の記事に収めるには長いため複数の記事に分けており、この記事はその第1パート
- POCコードは別の場所で公開されているが、提供された本文には実際のリンクURLはない
ユニカーネルとWebAssemblyを一緒に使う理由
- アプリケーション開発者にとってユニカーネルへのポーティングは負担が大きい
- アプリケーションとすべての依存関係が対象ユニカーネルをサポートしている必要がある
- アプリケーションスタック全体の中でパッチが必要になる場合がある
- ユニカーネルのメンテナーにとっても、任意のアプリケーションを滑らかに実行できるようにするには多くの労力が必要
- ユーザーアプリケーションがどのシステムプリミティブを使うか予測しにくいため
- 一方、SpinやSpiderlightningのようなWebAssemblyプラットフォームを対象にすると、ランタイムが提供すべき機能セットが明確になる
- Spiderlightningのシナリオでは、アプリケーションはランタイムにKey/Valueストア機能を要求できる
- ホストがその機能をRedisで実装してもAzure Cosmos DBで実装しても、アプリケーションには透過的
- 同じ
.wasmモジュールを異なるホスト実装の上で実行できる
目標アーキテクチャ
- ユニカーネルアプリケーションがWebAssemblyモジュールを実行し、Spiderlightning APIセットをサポートすれば、同じSpiderlightningアプリケーションを通常の
slightランタイムとそのユニカーネルの両方で実行できる - アプリケーション開発者は追加作業をする必要がなく、Wasmモジュールも自分がどこで実行されているかを知る必要がない
- 複雑さはユニカーネル開発者に集中するが、実装すべき範囲は「すべてのアプリケーション実行のサポート」よりはるかに明確
RustyHermitベースの実装
- ベースとしてRustyHermitを選択
- Rustで書かれたユニカーネル
- Rust nightlyに含まれており、通常のRustアプリケーションを書くのに近い開発体験を提供する
- RustyHermitアプリケーションのビルドは比較的わかりやすい
- ドキュメントはやや散在しているが品質は高く、サンプルが大いに役立つ
- すべてのRust crateがRustyHermitでそのまま動作すると期待することはできず、この制約がPOC開発に影響した
WebAssemblyランタイムの選択
- 有力候補だったWasmtimeはRustyHermit上ではビルドできない
- 多くの依存関係が
libcや他の低レベルライブラリを想定している
- 多くの依存関係が
- wasmerにも同じ問題がある
- WebAssembly Micro Runtimeも検討したが、Rustで書かれたランタイムを使って「完全なRustyHermit体験」を維持することにした
- 最終的に、純粋なRust製WebAssemblyランタイムであるwasmiを選択
- RustyHermit上で問題なく動作する
- 設計がWasmtimeに着想を得ており、既存の知識を多く再利用できた
WebAssembly Component ModelとWIT
- SpiderlightningはWebAssembly Component Modelの提案を使用する
- WebAssemblyゲストに機能を提供する
- ホストがWebAssemblyゲストの提供する機能を利用できるようにする
- ホストとゲストの通信はWasm Interface Typeで定義された型を使用する
- デモではComponent Modelを次の流れで使用する
- ゲストがホストにHTTPサーバーの開始を要求し、登録するHTTPルートと内部ハンドラー関数名を渡す
http-server型を使用し、ゲストがホスト提供機能を使用する
- ホストはゲストが提供したルーティング情報で受信HTTPリクエストを処理する
- HTTPハンドラーはWebAssemblyゲストが公開した関数
- サーバーはゲスト提供機能を利用し、
http-handler型で通信する
- 一部のHTTPハンドラーはKey/Valueストアとやり取りする
- この場合もゲストがホスト提供機能を使用し、
keyvalue型として定義される
- この場合もゲストがホスト提供機能を使用し、
- ゲストがホストにHTTPサーバーの開始を要求し、登録するHTTPルートと内部ハンドラー関数名を渡す
wit-bindgenの拡張とデモ実行
- 複数のWIT型ごとに、ゲスト側SDKの性格を持つコードとホスト側実装コードが必要
- wit-bindgenは、
.witファイルからホスト/ゲストコードを生成するCLIツール - 今回のPOCでは、ユニカーネル内にホスト側インターフェースだけを実装すればよい
wit-bindgenが生成するコードは、WebAssemblyランタイムを使って低レベル作業を行う- 生成コードはプログラミング言語とホスト側WebAssemblyランタイムに依存する
wasmiはwit-bindgenでサポートされていなかったため、wit-bindgenを拡張してwasmiを扱えるようにした- コードはwasmiブランチのforkにある
- その後、Key/Value機能のホスト側コードをスキャフォールドし、簡単なホストtrait実装を追加した
- ホストコードはデバッグ情報を出力する程度だった
- この状態で、Spiderlightningプロジェクトのkeyvalue-demoを変更なしで実行できた
次のパートの予告
- ユニカーネルアプリケーションがSpiderlightningの
http-serverデモを実行する録画がある - 次のパートではRust async、Redis、いくつかの奇妙なエラーを扱う予定
1件のコメント
Hacker Newsのコメント
すぐに https://www.destroyallsoftware.com/talks/the-birth-and-death... が思い浮かばない?
OSハッカーではない人がユニカーネルを欲しい場合、最もまともなアプローチは何だろう?
思い浮かぶ選択肢は、アプリケーションをLinuxカーネルモジュールにして通常のカーネルに載せ、ユーザー空間は無視すること、Linuxを思い切って削って自分のコードを組み込むこと、GitHub上のユニカーネルプロジェクトから始めること、FreeBSDのような別のOSを削り込むこと、くらいだ。
ネットワークカードにつながったVM上でx64マシンが汎用計算資源のように動作し、ネットワーク経由でデータを送ってタスクを割り当てる、という絵が気に入っている。ユーザー空間デーモンより面倒なのでまだ大きな価値はなかったが、いつか時間ができたらOSレベルのハッキングをどこから始めればいいのか気になる。
Unikernel Linux(UKL)は、Linuxの構成可能性を活用しようとする試みから始まり、汎用OSからアプリケーション・ハードウェア特化のユニカーネルまでをカバーするカーネルを目指している。関連領域としてio_uringとeBPFも言及されており、io_uringはシステムコールのコストを分散し、eBPFは限定的ながらカーネル空間でコードを実行する別の方法だ。
コード: https://github.com/unikernelLinux/ukl
UKLはLinuxとglibcへの小さなパッチで、多くのプログラムを修正なしにユニカーネルとしてビルドできるようにする。プログラムはLinuxカーネルと最終的なvmlinuzにリンクされてカーネル空間で実行され、ベアメタルやVMでブート可能で、Linuxのほぼすべての機能とドライバを使える。
/initと名付け、カーネルに組み込んでブートすればよい。そうするとそのアプリがPID 1であり、実質的に唯一のプロセスになり、いくつかのカーネルスレッドを除けば望みどおりにできる。
複数の言語とアプリ、x86/ARM64、QEMU/Firecrackerをサポートし、LinuxでビルドしたELFをユニカーネルとして実行することもできる: https://unikraft.org/guides/bincompat
Discordは https://unikraft.org/discord にある。
OCamlを学びたいしユニカーネルも欲しいなら、取り得る道だ。
詳細はUnikraftのドキュメントで見られる: https://unikraft.org/docs/concepts/design-principles#approac...
良いプロジェクトだ。WASM は最初からサンドボックスと移植性を念頭に設計されていて、そこが気に入っている。
90年代にJavaScriptではなくWASMが登場していればよかったと思うし、WASMが世界を飲み込む気がする。いちばん望んでいるのは持続性だ。今ではもう実行できないプログラムが多く、古いゲームがその代表だ。単純な仕様ほど長く生き残る可能性が高いので、新機能の追加には少し不安があるが、バイナリの未来は面白そうに見える。
JSが当初クリックハンドラやフォーム検証のような基本的なスクリプティングに限られていたのには理由がある。別の何かへと肥大化したのは、JSの設計上の欠陥だけでなく、無理やり押し込まれた用途のせいでもある。ブラウザをこうしたアプリの配信メカニズムとして使うのは、Tim Berners-LeeやMarc Andreesenが想像していたものとは程遠い。
当時「ネットワークこそがコンピュータだ」陣営は、よりリッチなアプリのために薄いXクライアントを出していた: https://en.wikipedia.org/wiki/Network_Computer
WASMには複雑な感情がある。今は誇大宣伝と目新しさの幕が大きくかかっている。Webブラウザを、UIデザイナーや開発者がその時々に好む言語幻想のビューポートとしてだけ扱うと、アクセシビリティやスクリーンリーダーのような領域で悪いことがたくさん起きる。
ブラウザの外でWASMを汎用VMとして扱う流れも、30年前にすでに通った道だ。それはJVMがやろうとしていたことだが、今では「クール」ではないらしい。
中間地帯がどうあるべきなのか、なぜそれを望むべきなのかはよく分からない。ただ現実的には、WASMが文書コンテンツまで食い尽くすだろうし、そうなれば広告ブロッカーとリーダーモードは終わる可能性が高い。
本当に気に入った。リンクされている技術の中には見たことがないものもいくつかあったので、全部ブックマークした
次はハイパーバイザーに WireGuard 接続を設定してみたい。接続の確立は Tailscale のようなものを経由することもできる
そうすると、このマシンの WebAssembly があのマシンの WebAssembly と直接やり取りすることになる。プロセスが任意の場所へ TCP 接続を開く方式ではなく、渡された構成と権限に基づいて通信する構造だ
遅ればせながら、Zephyr をユニカーネルとして実行することを考えた人はいるだろうか? https://docs.zephyrproject.org/latest/boards/x86/acrn/doc/in...
専用 WASM ハードウェアが出てくるまで、どれくらいかかるだろうか?
ただし「表向きは wasm だが、実際には下に RISC-V がある」といったものなら、誰かが作れるかもしれない
ただし、そうしたハードウェアは常にニッチにとどまると思う。一般的な市販ハードウェアで WASM を動かす方が、たいていはより速いはずだからだ。WASM 自体が市販ハードウェア上で高速に動くよう設計されており、汎用プロセッサの規模の経済の方がはるかに優れている
International Conference on Functional Programming も当初は Functional Programming and Computer Architecture という会議だったが、その後 Haskell のような遅延評価の関数型言語を既存ハードウェア上で効率よくコンパイルする方法が見つかった
Lisp や Java マシンも似たようなものだ。そういうものを今ではあまり見かけなくなった理由の一つは、コンパイラ技術が追いついたからだ
ユニカーネルと WASMのユースケースは何だろう?
ユニカーネルの価値は、1) 性能: 不要なものを捨て、必要なものを「ring 0」に引き寄せて、数サイクルでもさらに絞り出すこと、2) 単純化: 不要な部分を捨てて複雑さを減らせる可能性、3) セキュリティ: 同じく不要なものを減らして攻撃対象領域を変えられる可能性、だと思う
ただし、このフォーラムの多くの人がやっているようなマイクロサービスや Web アプリの作成に向いた方式ではないと思う。用途としては、データベースやロードバランサーのようなインフラ構成要素を作る方向に近い
そのため、一部のエッジクラウド提供者は Docker イメージを実行する際にマイクロ VM へ変換している
ただしエッジでは、マイクロ VM 内の WASM はエッジのサンドボックス化された WASM と競合するのが難しいかもしれない。提供者の立場では、後者の方が有用な境界機能や統合を追加しやすい可能性が高い
ずいぶん前の Birth & Death of Javascript で「予告」されていたように、いつかカーネル空間で安全なガベージコレクション付きランタイムを動かすユニカーネルが登場し、そうなれば CPU から仮想メモリマッピングのサポートを取り除いて高速化できる、という構図だった
2014年には作者は JS と asm.js を想定していたが、今では WASM がその道に見える。楽しみだ、はは
https://www.destroyallsoftware.com/talks/the-birth-and-death...
しかしその後、単一プロセスのブラウザはセキュリティ上の悪夢だと分かり、最近のブラウザは適切なサンドボックス化のために、もはや単一プロセスではない
それでも、あの動画がどれほど正解に近かったのかは良い点で、どのように間違っていたのかを見るのも興味深い
正確には指摘しにくいが、どこか見覚えがある。ヒントは、それも「J」で始まるということだ
https://en.wikipedia.org/wiki/JavaStation
プロセスの仮想使用量は、スワッピングのためでなくても RSS を超えることがあり、OS とアロケータが連携して、一般的なケースではかなり賢く処理している
そのため、これを取り除けば自動的に性能上の利益が生まれるとは考えにくい。特に、かなり遅い WASM VM レイヤーを経由するならなおさらだ
一部のアプリケーション、たとえばデータベースでは、ユニカーネルとして動かす、あるいはカーネルにより近い位置で MMU に直接アクセスすることに大きな利点があるかもしれない: https://github.com/tuhhosg/exmap & https://github.com/viktorleis/vmcache & https://www.cs.cit.tum.de/fileadmin/w00cfj/dis/_my_direct_up...
しかし、POSIX 標準を前提にしたり、実行環境が現代的な一般的コンピュータのように見えることを前提にしたりする通常のアプリケーションについては疑問だ。結局、VMM レイヤーが担っていた多くの仕事をユーザーコードで書き直すことになりそうだ
JS エンジンは VMM に依存しており、WASM もさまざまな形でそうだ。組み込みでない、ほぼすべての非自明なプログラムは、微妙に VMM を前提としている。特にマイクロ VM 周辺の一部の VM 技術も VMM を使っており、ユニカーネルは VM として使う場合にこそ本当の意味がある