3 ポイント 投稿者 GN⁺ 2023-08-26 | 1件のコメント | WhatsAppで共有
  • AWS Lambda向けの Linux 中心の microVM である Firecracker VMM 上で FreeBSD 14 カーネルを起動する取り組みは、最小限の仮想化環境が OS 初期化経路に潜む前提やボトルネックをあぶり出した事例だった
  • 最初の起動は、PVH boot mode の ELF Note 形式の違い、Xen 専用ハイパーコール、Firecracker のメモリ配置の違いによって阻まれたが、FreeBSD の PVH コード修正で解決した
  • Firecracker が ACPI を提供しないため、CPU・割り込み情報を取得する経路が変わり、Linux の MPTable 処理バグに合わせるための MPTABLE_LINUX_BUG_COMPAT オプションが追加された
  • シリアルコンソールと Virtio デバイス接続の過程で UART 動作、カーネルコマンドライン解析、非整列ディスク I/O 制約が明らかになり、FreeBSD は hw.broken_txfifo、FIFO クリア回避、busdma ベースの bounce 処理を適用した
  • 未コミットのパッチを含めると、1 CPU・128MB RAM の VM で FreeBSD カーネルは 20ms 未満で起動し、残る課題は PVH サポートのマージ、Xen コードの分離、より小さなカーネル構成、Firecracker の FreeBSD 移植検討である

Firecracker に FreeBSD を載せようとした理由

  • Firecracker は AWS Lambda のようなサーバーレス実行環境向けに、Linux KVM 上で低オーバーヘッドの microVM を作成・管理する VMM である
  • FreeBSD への移植作業は 2022 年 6 月に開始された
  • 取り組みの動機は FreeBSD と Firecracker の両方の限界をあわせて確認することにあった
    • FreeBSD の起動速度改善作業を続ける中で、最小ハイパーバイザー上でどこまで速くできるかを確かめたかった
    • FreeBSD を新しいプラットフォームへ移植すると、FreeBSD 側とそのプラットフォーム側の両方のバグが露呈する
    • AWS Lambda は現在 Linux のみをサポートしており、Lambda を採用するかどうかとは別に、FreeBSD が Firecracker をサポートすることは必要な前提条件である
    • Firecracker 自体が興味深いプラットフォームなので、実際に動作するかを確認したかった

カーネル実行までの最初の障害

  • Firecracker はもともと Linux カーネル実行向けに設計されたが、2020 年に linuxboot に加えて PVH boot mode をサポートするパッチがあった
  • FreeBSD は Xen で PVH 起動をサポートしていたため、同じ経路を Firecracker で試した
  • 最初の問題は、Firecracker が FreeBSD カーネルをメモリに載せた後で kernel entry point を見つけられなかったことだった
    • PVH ブートプロトコルはこの値を ELF Note に格納する
    • ELF Note には PT_NOTESHT_NOTE があり、FreeBSD は Firecracker が探していた形式を提供していなかった
    • FreeBSD カーネルのリンカスクリプトを少し修正すると、Firecracker は FreeBSD カーネルの実行を開始できるようになった
  • しかしカーネル実行は約 1 マイクロ秒後に再び停止した

初期デバッグと Xen 依存の除去

  • カーネルデバッガやシリアルコンソールが初期化される前にカーネルが落ちると、FreeBSD のデバッグ機能はほとんど役に立たなかった
  • Firecracker プロセスから得られた情報は、FreeBSD ゲストが triple-fault を起こしたという事実だけだった
  • hlt 命令をカーネル開始コードの途中に挿入して、クラッシュ位置を絞り込む「kernel bisection」を行った
    • hlt に到達すると Firecracker は動き続けるが、ホスト CPU 使用率は 0% になる
    • Firecracker が終了した場合、その地点より前でクラッシュしたと判断できた
  • 最初の原因は Xen hypercall だった
    • FreeBSD の PVH エントリポイントは実際には Xen 上で起動するためのコードであり、Xen 内で実行されることを前提にしていた
    • Firecracker が利用する KVM は Xen ハイパーコールを提供しないため、呼び出すと VM がクラッシュした
    • 当初は Xen ハイパーコールをコメントアウトし、その後 CPUID で Xen シグネチャを確認した場合のみ呼び出すよう修正した
  • 物理メモリマップの取得は、本来 Xen ハイパーコールが担っていた必須機能だった
    • PVH version 1 以降では、メモリマップポインタは start_info ページを通じて渡される
    • FreeBSD は Xen ハイパーコールの代わりに PVH version 1 のメモリマップを使うよう変更された
  • Firecracker と Xen のメモリ配置の違いも問題を引き起こした
    • Xen はまずカーネルをロードし、その末尾に start_info ページを配置する
    • Firecracker は start_info ページを固定の低位アドレスに置き、その後でカーネルをロードする
    • FreeBSD の PVH コードは start_info の直後の領域を scratch space と仮定しており、Firecracker では初期カーネルスタックを上書きしてしまった
    • ハイパーバイザーが初期化したすべてのメモリ領域の後ろに scratch space を割り当てるよう修正して解決した

ACPI 不在と MPTable 互換性

  • x86 上の FreeBSD は通常、ACPI を通じてディスク、ネットワークアダプタ、CPU、割り込みコントローラの情報を取得する
  • Firecracker は意図的に最小実装を採用しており、ACPI を提供しない
  • その代わり FreeBSD は、旧来の Intel MultiProcessor Specification の MPTable 構造を利用できる
    • GENERIC カーネル構成には標準では含まれていない
    • Firecracker 向けの軽量カーネル構成では device mptable を追加して利用できた
  • Firecracker が提供する MPTable は標準どおりではなく、Linux が受け入れる方式に合わせられていた
    • Linux には MPTable の探索と解析方法にバグがあった
    • Firecracker は Linux の起動を目標に設計されているため、Linux が対応する非標準配置を提供する
    • 標準に沿って独立実装された FreeBSD は、不正な場所に置かれた MPTable を見つけられず、見つけても無効な MPTable を解析できなかった
  • FreeBSD には options MPTABLE_LINUX_BUG_COMPAT カーネルオプションが追加された
    • Linux の MPTable 処理との bug-for-bug 互換性が必要なときに使う
    • このオプションにより Firecracker 上の FreeBSD 起動はさらに先へ進んだ

シリアルコンソールと Virtio デバイス対応

  • Firecracker が提供する数少ないエミュレーションデバイスのひとつが シリアルポート である
    • 一般的な構成では、Firecracker プロセスの標準入出力が VM のシリアルポート入出力になる
  • FreeBSD カーネルはルートディスクをカーネルイメージに含めた状態で起動し、カーネルコンソール出力までは読めるようになった
  • ユーザー空間の起動に移ると、コンソール出力が 16 文字で止まった
    • これは過去の QEMU の UART バグと同じ症状だった
    • UART の transmit FIFO が空になっても割り込みが来ず、FreeBSD は 16 バイト以降を書き込めなかった
    • FreeBSD カーネルの既存の回避策 hw.broken_txfifo="1" をカーネル環境変数としてコンパイルして解決した
  • コンソール入力も動作しなかった
    • Firecracker は、エミュレーション UART の receive FIFO が満杯だと判断してコンソールを読まなかった
    • FreeBSD は UART 初期化時に FIFO サイズを測るため、receive FIFO をゴミ値で満たしてから FIFO Control Register でクリアする
    • Firecracker は FIFO Control Register を実装していないため、FIFO は満杯のまま残った
    • FreeBSD は FIFO クリア後にも LSR_RXRDY が残っている場合、文字を 1 つずつ読み捨てて FIFO を空にするよう修正された
  • ディスクとネットワークを使うには Virtio block/network デバイスが必要だった
    • Firecracker はこれらを mmio デバイスとして公開する
    • FreeBSD の Firecracker 用カーネル構成には device virtio_mmio が追加された
  • FreeBSD はもともと mmio デバイスを FDT で発見することを想定していたが、Firecracker はカーネルコマンドラインで virtio_mmio.device=4K@0x1001e000:5 のような指定子を渡す
    • FreeBSD はこの指定子を解析して virtio_mmio デバイスノードを作成するコードを追加した
    • デバイスノードができると、既存のデバイス probe 処理が Virtio デバイス種別を判定し、適切なドライバを接続する
  • 複数の Virtio デバイスがある場合、カーネルコマンドライン解析に問題があった
    • Firecracker は Linux 方式どおりに key=value の組を複数渡す
    • FreeBSD はカーネルコマンドラインを環境変数として解析するため、同名の virtio_mmio.device= が 2 つあると 1 つしか残らなかった
    • 初期カーネル環境解析コードは、重複変数に番号サフィックスを付けて virtio_mmio.device=virtio_mmio.device_1= のように保持するよう修正された
  • 異常終了の次の起動で fsck が実行されると、カーネルパニックが発生した
    • fsck は FreeBSD でページ整列されていないディスク I/O を発生させる珍しいケースのひとつである
    • Firecracker の Virtio 実装は単一のデータバッファしか受け付けず、ページ境界をまたぐバッファを複数セグメントに分割する一般的な Virtio 方式をサポートしない
    • FreeBSD の Virtio block ドライバは busdma を使うよう修正され、非整列リクエストは一時バッファを経由する bounce 処理で Firecracker の制約に合わせた

Firecracker があぶり出した起動最適化

  • FreeBSD が Firecracker 上で動き始めると、起動時間とメモリ使用量を減らせる箇所が明確になった
  • 128MB RAM の VM では、システムメモリのほぼ半分が wired 状態になり、プロセスが頻繁に終了した
    • 調査の結果、busdma が bounce page 用に 32MB を予約していた
    • Firecracker の制約では、各ディスク I/O に必要なのは最大でも 4KB の bounce page 1 枚だけだった
    • 少数の I/O セグメントしかサポートしないデバイス向けに bounce page 予約を制限するパッチにより、メモリ使用量は 512KB に減少した
  • カーネル乱数生成器の最適化で起動時間が短縮された
    • VM ではハードウェアデバイス由来のエントロピーが効果的でないことがある
    • x86 の RDRAND をバックアップのエントロピー源として使っていたが、1 回あたりのエントロピー量が少なく、100ms ごとに 1 回しか要求していなかった
    • Fortuna 乱数生成器を完全に seed できる量を要求するよう変更し、2.3 秒短縮した
  • Host ID の処理も高速化された
    • 通常はブートローダが BIOS または UEFI 情報に基づいて smbios.system.uuid を設定する
    • Firecracker にはブートローダがないため、ID は提供されない
    • ハードウェアが不正な ID を提供した場合は警告後に 2 秒待機し、ID がまったくない場合は静かにすばやく進むよう修正された
  • IPv6 DAD の待機条件も絞り込まれた
    • FreeBSD はネットワークインターフェースで IPv6 が有効だと Duplicate Address Detection を待っていた
    • loopback インターフェースでは常に IPv6 が有効になっていた
    • loopback 以外のインターフェースで IPv6 がある場合のみ DAD を待つよう変更し、2 秒短縮した
  • 再起動と終了処理の固定待機時間も取り除かれた
    • 再起動時に Rebooting... メッセージの後、printf 完了と読み取り時間のために 1 秒待っていた動作は kern.reboot_wait_time sysctl に変更され、デフォルト値は 0 になった
    • 終了または再起動時に BSP が他 CPU の停止シグナル受信後、追加で 1 秒待っていた動作も削除された
  • TSLOG を使って起動 flame chart を解析した
    • Firecracker の最小環境はノイズが少なく、残るボトルネックを見つけやすかった
    • VM の実行が非常に速いため、新カーネルのビルド、実行、flame chart 生成までがしばしば 30 秒未満で可能だった
  • TSLOG 解析により、複数のミリ秒単位ボトルネックが削減された
    • lapic_init の 100000 回の較正ループを 1000 回に減らして 10ms短縮した
    • ns8250_drain が文字ごとに DELAY を呼んでいた部分を、LSR_RXRDY 確認後に必要な場合のみ遅延するよう変更し、27ms短縮した
    • Firecracker が TSC と local APIC のクロック周波数を通知する CPUID leaf を実装することで 20ms短縮した
    • kern.nswbuf を常に 256 にしていた方式を 32 * mp_ncpus に変更し、1 CPU VM で 5ms短縮した
    • mi_startup の bubblesort を quicksort に置き換えると 2ms短縮でき、2023 年 8 月 22 日時点ではまだコミットされていない
    • vm_mem が全物理メモリの vm_page 構造体を即時初期化していた方式を lazy 初期化に変えると 2ms短縮でき、同日時点ではまだコミットされていない
    • Firecracker の guest memory mmapMAP_POPULATE を追加すると、最初のページアクセス時に Linux がページ構造を作るコストを減らせて 2ms短縮でき、同日時点ではまだコミットされていない

現在の状況と残る作業

  • FreeBSD は Firecracker 上で起動し、非常に高速に動作する
  • FreeBSD と Firecracker の未コミットパッチを含めれば、1 CPU・128MB RAM の VM で FreeBSD カーネルは 20ms 未満で起動できる
  • 残る作業は PVH サポートの整理とカーネル構成の縮小に集中している
    • 上で触れたパッチをコミットする必要がある
    • PVH boot mode サポートを Firecracker mainline にマージする必要がある
    • PVH booting コードは Xen サポートと混在しているため、これを分離する必要がある
    • FreeBSD arm64 カーネルは現状 PCI または ACPI サポートなしではビルドできず、この誤った依存関係を除去すれば FreeBSD/Firecracker カーネルをさらに小さくできる
    • Intel GPU 向けメモリ予約の必要性確認に 25µs を使っているため、より小さなカーネル構成なら起動時間をさらに数マイクロ秒削減できる
  • より長期的には、Firecracker 自体を FreeBSD 上で動かすよう移植する可能性もある
    • Firecracker は Linux KVM の利用を前提に書かれている
    • FreeBSD の bhyve ハイパーバイザーのカーネル部分を使うようにできない根本的理由はないと考えられている
  • 実験するには、FreeBSD 14.0 カーネルを amd64 の FIRECRACKER カーネル構成でビルドし、Firecracker プロジェクトの feature/pvh ブランチ を使える
    • もしそのブランチがすでに存在しないなら、コードが mainline Firecracker ツリーにマージされたことを意味する

1件のコメント

 
GN⁺ 2023-08-26
Hacker News のコメント
  • Firecracker VM が単なる Linux コンテナ技術ではなく、完全な仮想マシンだということをよく分かっていなかった
    最初は非効率に聞こえるかもしれないが、fly.io のような実際のユースケースを見ると、マイクロ VM が非常に小さく、それでいて十分に強力なので驚く

    • もっと知りたいなら、私たちがなぜこの方向を選んだのかを扱った NSDI'20 論文(https://www.usenix.org/conference/nsdi20/presentation/agache) と Firecracker のソース/ドキュメント(https://github.com/firecracker-microvm/firecracker)を見るとよい
      KVM と最小限のハードウェアサポートのおかげで(PCI、ACPI なしなど)、Firecracker のソースはかなり単純で、専門家でなくても比較的読める
    • AWS のようなエンタープライズ級クラウドベンダーが、ECS や Lambda で異なる顧客のコンテナを単一 VM 内に同居させることを許すはずがない
      Firecracker が存在する理由はまさにそこにある
    • Firecracker は「完全な」マシンというわけではない。Lambda と、偶然にも fly.io のユースケースに不要なものをかなり削ぎ落としているためだ
      記事で挙げられている ACPI が一例。それでもカーネルではなくハードウェアを仮想化しているのは確かで、初期化が非常に速いので、たいていのユーザーは通常の containerd を firecracker-containerd に置き換えても違いに気づかないと思う
    • KVM はすごい
      Firecracker 以外にも crosvm、cloud-hypervisor、Kata の Dragonball など、いくつものマイクロ VM が今 KVM 上で開発されている
    • Firecracker と QEMU が提供する仮想ハードウェアのサブセットに合わせて、Linux ユーザー空間または *NIX ユーザー空間を模倣するマイクロカーネルを作るのが標準になっていないのは意外だ
      プログラミング言語に新しいターゲットを実装するのがそこまで難しいという印象はないので、WASI/WASM のような疑似 OS ターゲットを作り、対応言語に PR を送れば、オーバーヘッドの大半を減らせそうだ。最も難しい部分は Linux ユーザー空間を十分正確に模倣することだろうが、表面積が非常に広いので、むしろ疑似 OS ターゲットを作る経路がいちばん良さそうに見える
  • Colin のパッチが FreeBSD と Firecracker に入れば、カーネル全体の起動時間が 20ms 未満になる
    本当に信じがたい時代に生きている

    • これが Firecracker 上の Linux と比べてどうなのか気になる
      少し検索すれば数字は出てくるが、数年前の資料で、起動時間の測定方法が同じなのか、「起動時間」の定義が同じなのかも不明なので、比較できるのかよく分からない
  • 数日前に上がっていた Colin の最近の BSDCan 発表
    https://youtu.be/MT3cdeuRTzs?si=l6baNriUjcvy0ZOE

    • 参考までに、これはほぼ同じ内容
      BSDCan の発表後、FreeBSD Journal から「良い発表だったので記事にしてもらえないか」と言われ、FreeBSD Journal の記事が出た後には、;login: から再掲載してよいか尋ねられた
  • qemu には firecracker に影響を受けた microvm がある
    https://qemu.readthedocs.io/en/latest/system/i386/microvm.ht...

    • QEMU ではこうした回避策がどれほど多く必要なのか気になる
      もちろん一部は FreeBSD のバグ修正なので、必要にならざるを得ない
  • 1秒待機のかなり多くが、実は必ずしも必要ではないと分かるのが面白い
    間違ったマシン UUID のせいでシステムが停止したとき、実際に意味のある対応をしたシステム管理者がどれほどいたのだろうと思う

    • おそらく、その 1 秒待機に遭遇したシステム管理者のかなり大きな割合は何かしたのだと思う
      一方で「ユーザーに再起動するとメッセージを表示し、コンソールを読めるように 1 秒待ってから再起動する」という方は少し違う
  • 偉そうに聞こえたいわけではないのだが、Firecracker インスタンスのようなものはどんなユースケースに合うのか気になる
    FreeBSD をコロケーションサーバーから個人 PC まで全部で使っていて、開発者というよりは古参の Unix 管理者に近い。ベアメタルを好むが、OS に貢献する未来技術は歓迎する。ただ、Lambda や Firecracker のようなバズワードを聞いても、実際にどこで使われるのかよく分からない。Docker とコンテナは理解しており、k8s は何とか理解している程度だが、普通に VM を立ち上げて必要なときに使えばいいのに、なぜ VM を立ち上げてすぐ消す必要があるのか分からない。純粋にクラウド体験やコスト削減のためなのだろうか?

    • アプリケーションインスタンスがリクエスト/レスポンスのライフサイクルの一部として作成される
      こうすると、コンピュートプレーンのどのノードでも、どのアプリケーショントラフィックでも処理できる。1 つのアプリケーションは、トラフィックパターンの変化に応じてプレーンの余剰コンピュートリソースを動的に消費する形で大きくなれ、トラフィックを処理していないときはリソースを使わない。コンピュートプレーンの容量を増やすとは、より多くのノードをオンラインにすることだ。大規模デプロイを大量に管理すること以外には、特にユースケースは思い浮かばず、「規模」のない環境ではベンダーの境界の下に隠れている技術だろう
    • 主なユースケースはたまに使われる API
      API が頻繁には使われないが、呼び出されたときには素早く応答する必要があるサービスを運用しているなら、Lambda や似た方式がよく合う。実際、携帯電話アプリ向け API のかなり多くがこの範疇に入り、そうした API 呼び出しに応答するために、99% の時間アイドル状態のマシンを置き続けたくはない
    • 「普通に VM を立ち上げて必要なときに使えばいい。常に起動していて常に準備できている」というのは、常に課金もされるという意味だ
    • ほぼすべての会社はトラフィックが 24 時間ずっと一定ではないため、スケーリングの恩恵を受けられる
      たいていは節約額より手間の方が大きいのでやらないだけで、潜在力はある。Lambda や Firecracker のようなものが、それをはるかに簡単にしてくれる
  • AWSもARM版macOSもネストされた仮想化をサポートしていないのが惜しい
    サポートしていれば、Firecrackerベースの技術を開発してデプロイするのがはるかに簡単だったはず

    • 知る限り、.metalインスタンスでは仮想化が可能
      実際にはどのインスタンスタイプでも仮想化自体は可能だが、ハードウェアアクセラレーションを使えるのは.metalインスタンスだけだと理解している
    • ちなみにAWSのa1.metalインスタンスはかなり小さいので、仮想化技術を扱うにはコスト面で合理的
  • Firecrackerは驚くべきものだが、ドキュメント化が必要な例外ケースが多い
    Colin Percivalがこれを共有してくれたことに大いに感謝している。特に「低く実った果実を全部摘み取った後」という一文が気に入っているが、Colinにとってそれはカスタムのbus_dmaパッチ群を意味する。今では誰もが「CPU 1個とRAM 128MBでFreeBSDカーネルが20ms未満で起動する」ことを無料で享受できる。k8sクラスタや大量のDockerを使うDevOpsに慣れているなら、本当に驚くべきことだ

  • Firecrackerを少し触ってみたが、起動時間は約束どおり出る一方で、利用体験はかなり険しい
    例えば起動に成功して歓喜した後、ネットワーク設定をするにはまた長いチュートリアルに従う必要があると知って気力が削がれた

    • ここには自動化ツールを作って大きな価値を加える余地が明らかにある
      単一バイナリをダウンロードして実行すれば、WebインターフェースとAPIの両方が立ち上がり、素早く設定でき、必要なものを自動でダウンロードしてくれるなら本当に良さそう
  • 「CPU 1個とRAM 128MBの仮想マシンで、FreeBSDカーネルは20ms未満で起動できる」
    なんてことだ、VMなしで実ハードウェア上で同じことをどう達成すればいいんだろう ;)

    • 実ハードウェアでもカーネル起動は十分速く、通常は1秒未満
      遅いのはそれ以外のすべて。例えば自分のマシンでは Startup finished in 14.552s (firmware) + 2.885s (loader) + 741ms (kernel) + 23.116s (initrd) + 11.191s (userspace) = 52.488s となっている