eBPF Summit 2024

eBPF ドキュメント

eBPFはLinuxカーネルに起源を持つ革新的な技術で、オペレーティングシステムにおけるカーネルのような特権を要する環境の中で、サンドボックス化されたプログラムを実行できます。eBPFはカーネルのソースコードの改変や、カーネルモジュールのロードを必要とせずに、安全かつ効率的なカーネルの機能拡張に利用されています。

歴史的に、オペレーティングシステムはカーネルの特権によるシステム全体を観測・制御できる能力のおかげで、オブザーバビリティやセキュリティ、ネットワーキング機能を実装するのに理想的な場所でした。同時に、オペレーティングシステムのカーネルは、その基幹的な役割や安定性、セキュリティに対する高い要求のせいで進化が困難です。そのため、これまでオペレーティングシステムレベルの技術革新のペースは、オペレーティングシステムの外で実装される機能と比較して低いとされてきました。

概要

eBPF はこの常識を根本的に覆します。オペレーティングシステムの内部でサンドボックス化されたプログラムが実行できるため、アプリケーション開発者は eBPF プログラムを実行して、実行中のオペレーティングシステムに機能を追加できます。そして、オペレーティングシステムは JIT(Just-In-Time)コンパイラと検証器によって安全性とネイティブにコンパイルされたかのような実行効率を保証します。このことが次世代ネットワーキングやオブザーバビリティ、セキュリティなどの様々な分野でeBPFを活用したプロジェクトの台頭を促してきました。

今日、eBPFは様々な用途で利用されています。例として、現代的なデータセンタやクラウドネイティブな環境における高効率なネットワーキングや負荷分散、低負荷できめ細かいセキュリティ観測データの取得、アプリケーション開発者に対してトレースの補助、パフォーマンス改善のための情報の提供、攻撃を未然に防ぐアプリケーションやコンテナランタイムのセキュリティの強化などがあります。eBPF の可能性は無限大で、eBPF によってもたらされるイノベーションはまだ始まったばかりです。

eBPF.io は eBPF を学習したり、eBPF を協力して発展させていくすべての人々のための場です。eBPF は開かれたコミュニティで、すべての人々がコミュニティに参加・協力できます。あなたが eBPF に入門したいとき、さらに詳しい資料を見つけたいとき、あるいは主要な eBPF プロジェクトに貢献する最初の一歩を踏み出したいとき、eBPF.io はその道のりをサポートしてくれます。

BPF は元々は Berkeley Packet Filter の略称です。しかし、今では eBPF(extended BPF)はパケットフィルターより多くのことができるようになり、頭文字以上の意味を持つようになりました。現在 eBPF は略称ではなくひとつの単語として認識されています。Linux のソースコード上では BPF という用語が残っており、ツール・ドキュメント内では一般的に BPFと eBPF は同義の単語として用いられています。元来の BPF は eBPF と区別するために cBPF(classic BPF)と呼ばれることもあります。

この蜂は eBPF の公式ロゴで、Vadim Shchekoldin によって生み出されました。最初の eBPF Summit で投票があり、この蜂 は eBee という名前になりました。(ロゴの利用については Linux Foundation ブランドガイドラインを参照してください。)

この章では簡単に eBPF を紹介します。より詳細に eBPF について知りたい場合は eBPF & XDP Reference Guide を参照してください。eBPF プログラムを開発しようとしている、あるいは eBPF を利用した手法の活用に興味がある開発者にとって、基本的な概念やアーキテクチャを理解するのによい資料となっています。

eBPF はイベント駆動のプログラムで、カーネルやアプリケーションが特定のフックポイントを通ったときに実行されます。システムコールや関数の入口と出口、カーネルトレースポイントやネットワークイベントなど、様々なフックポイントが予め定義されています。

システムコールへのフック

ユースケースに対して適切な定義済みのフックが存在しないとき、kernel probe(kprobe)や user probe(uprobe)を作成して、カーネルやユーザープログラムの任意の場所にeBPFプログラムをアタッチできます。

フックの概要

多くの場合においてユーザは eBPF を直接利用せず、代わりに Ciliumbccbpftrace といったプロジェクトを通して間接的に利用します。これらのプロジェクトでは eBPF を抽象化した機能を提供し、ユーザは直接 eBPF プログラムを書く代わりに、eBPF で実装された機能を目的ごとに利用できます。

Clang

高レベルな抽象化が存在しない場合、ユーザは eBPF プログラムを直接書く必要があります。eBPF プログラムは通常バイトコードの形で Linux カーネルにロードされます。もちろんバイトコードを直接書けますが、一般的な開発手法としては、LLVM のようなコンパイラを利用して擬似的なC言語のコードを eBPF バイトコードにコンパイルします。

対象のフック先を特定したら、eBPF プログラムを bpf システムコールによって Linux カーネルにロードします。これは通常 eBPF ライブラリによって実行されます。次の節では利用可能な開発ツールを紹介します。

Go

プログラムが Linux カーネル内にロードされると、対象のフックにアタッチされるまで次の2つのステップがあります。

検証ステップでは eBPF プログラムが安全に実行できるかを確かめます。このステップでは以下のようにプログラムがいくつかの条件を満たしているかを検証します。

ローダ
  • eBPF プログラムをロードするプロセスは必要な権限(特権)を持っています。非特権 eBPF が有効でない限り、特権プロセスのみが eBPF プログラムをロードできます。
  • eBPF プログラムはクラッシュしたりシステムに悪影響を及ぼしたりしません。
  • プログラムは常に実行が完了します(つまり、プログラムは無限ループに陥って処理が実行し続けられることはありません)。

実行時(JIT)コンパイルはプログラムの実行速度を最適化するために、汎用的なバイトコードをマシン固有の命令セットに変換します。これにより eBPF プログラムはネイティブにコンパイルされたカーネルコードやカーネルモジュールと同等の実行速度になります。

eBPF プログラムの重要な要素は、収集した情報を共有し、状態を保存する機能です。この目的のため、eBPF プログラムは eBPF マップの概念を活用して幅広いデータ構造を使ってデータを格納・取得します。eBPF マップは eBPF プログラムからだけでなく、システムコールを介してユーザ空間のアプリケーションからも参照できます。

マップの構造

以下のリストはデータ構造の多様性を知るために、サポートされてるマップタイプの一部を示したものです。各マップについて、CPU 間で共有されるものと CPU 毎に作成されるものの両方が利用できます。

  • ハッシュテーブル、配列
  • LRU (Least Recently Used)
  • リングバッファ
  • スタックトレース
  • LPM (Longest Prefix match)
  • ...

eBPF プログラムは任意のカーネル関数を呼び出せません。これを許可すると、eBPF プログラムが特定のカーネルバージョンに依存し、プログラムの互換性が複雑になります。その代わり、eBPF プログラムはカーネルが提供する、一般的で安定したAPIであるヘルパー関数を使って関数を呼び出せます。

ヘルパー

利用可能なヘルパー関数は常に進化しています。利用可能なヘルパー関数の例:

  • 乱数生成
  • 現在日時の取得
  • eBPF マップへのアクセス
  • プロセス/cgroup コンテキストの取得
  • ネットワークパケットと転送ロジックの操作

eBPF プログラムは末尾呼び出しと関数呼び出しで構成できます。関数呼び出しは eBPF プログラム内で定義し、実行できます。末尾呼び出しは別の eBPF プログラムを呼び出して実行でき、通常のプロセスの execve() システムコールが操作するように、実行コンテキストを置換します。

末尾呼び出し

大いなる力には、大いなる責任が伴う。

eBPF は信じられないほど強力な技術であり、今や多くの重要なソフトウェアインフラストラクチャの中核で使われています。eBPF の開発当時、eBPF を Linux カーネルに組み込むにあたって安全性は最も重要とされました。eBPF の安全性は次のいくつかの要素によって保証されます。

特権の要求

非特権 eBPF が有効でない限り、eBPF プログラムを Linux カーネルにロードする全てのプロセスは特権モード(root)で実行されている、あるいは CAP_BPF 権限が必要です。これにより信頼されていないプログラムは eBPF プログラムをロードできません。

もし非特権 eBPF が有効であれば、非特権プロセスは機能を制限し、カーネルへのアクセスを制限した状態で eBPF プログラムをロードできます。

検証器

プロセスが eBPF プログラムのロードを許可されても、全てのプログラムは eBPF 検証器を通過しなければなりません。 eBPF 検証器はプログラム自身の安全性を保証します。例として:

  • プログラムは必ず実行が完了するか検証されます。例えば、eBPF プログラムが実行途中で停止しないかや、無限ループに陥らないかです。eBPF プログラムはループを含んでいる場合がありますが、検証器はループの終了条件が必ず真になることが保証されている場合のみ、そのようなプログラムを許可します。
  • プログラムは初期化されていない変数を使ったり、境界外のメモリにアクセスしてはいけません。
  • プログラムはシステムが要求するサイズ以内でなければなりません。つまり、任意の大きさのプログラムはロードできません。
  • プログラムの複雑さは有限でなければいけません。検証器は全ての実行されうる命令パスを評価し、設定された複雑さの上限値の範囲内で解析を完了できなければいけません。

検証器はそのプログラムが安全に実行できるかを検証するツールで、プログラムが何をするかを調査するツールではありません。

堅牢性

検証に成功した場合、eBPF プログラムはプログラムが特権または非特権プロセスからロードされたかに応じて堅牢化プロセスを通して実行されます。このプロセスの内容は次の通りです。

  • プログラムの実行時保護:カーネル空間のメモリに保存された eBPF プログラムは保護され、読み取り専用として扱われます。カーネルのバグであれ、悪意のある操作であれ、eBPF プログラムが何らかの理由で変更されそうになると、カーネルはその破壊された、あるいは変更されたプログラムを実行する代わりにクラッシュします。
  • スペクター脆弱性に対する緩和策:投機的実行が可能な CPU の使用においては、分岐の誤った予測や、サイドチャネルを通した動作状況の観測といった副作用をもたらします。例えば、eBPF プログラムは、投機的命令の実行下でのメモリアクセスを制御された領域にリダイレクトするために、メモリアクセスをマスクしたり、検証機が投機的実行でのみアクセス可能なプログラムパスも追跡したり、JIT コンパイラは末尾呼び出しを直接呼び出しに変換できない場合に Retpoline を発行したりします。
  • 定数の隠蔽:コード中の全ての定数は JIT スプレー攻撃を防ぐため隠蔽されます。これにより、攻撃者がコードを実行するために eBPF プログラムのメモリ区域にジャンプできるような別のカーネルバグが存在する場合に、攻撃者による実行可能なコードの定数への埋め込みを防ぎます。

ランタイムコンテキストの抽象化

eBPF プログラムは任意のカーネル空間のメモリに直接アクセスできません。プログラムのコンテキスト外にあるデータやデータ構造のアクセスには、 eBPF ヘルパー関数を介してアクセスしなければなりません。これにより一貫したデータアクセスが保証され、このようなアクセスは eBPF プログラムの特権の対象となります。例えば、実行中の eBPF プログラムはその変更が安全だと保証されれば、特定のデータ構造のデータを変更できます。eBPF プログラムはカーネル内のデータ構造を闇雲には変更できません。

まずはたとえ話から始めましょう。GeoCities を知っていますか?20 年前、ほとんど全ての Web ページは静的なマークアップ言語(HTML)で書かれていました。Web ページは基本的に、ある特定のアプリケーション(Web ブラウザ)で表示できるドキュメントでした。今日の Web ページを見ると、Web ページは成熟したアプリケーションとなり、Web 関連技術はコンパイルを必要とする言語で記述された多くのアプリケーションを置き換えてしまいました。何がこの進化を可能にしたのでしょうか?

Geocities

端的に言えば、JavaScript の登場によるプログラムの表現力の進化です。JavaScript はブラウザをほとんど独立したオペレーティングシステムへと進化させました。

どうしてこのような進化が起こったのでしょうか?プログラマはもはや特定のブラウザのバージョンに制限されなくなりました 。新しい HTML タグが必要だと標準化団体を説得する代わりに、必要な部品をプログラムできるようになり、ブラウザ上で動作するアプリケーションの開発ペースがブラウザ本体の開発ペースと切り離せるようになりました。もちろんこれは少し簡略化しすぎており、HTML は時と共に進化し成功に貢献しましたが、HTML そのものの進化だけでは不十分だったでしょう。

この例を eBPF に当てはめる前に、JavaScript を語る上で不可欠な概念をいくつか見てみましょう。

  • 安全性:信頼されていないコードがユーザのブラウザの中で実行されます。これは JavaScript プログラムをサンドボックス化して、ブラウザのデータへのアクセスを抽象化して解決しています。
  • 継続的デリバリー:プログラムロジックの進化は、定期的に更新される新しいブラウザのバージョンへの依存なしに可能でなければなりません。これは任意のロジックを構築するのに十分に低レベルな部品を提供して解決しています。
  • パフォーマンス:プログラムの表現力は最小限のオーバーヘッドで提供されなければなりません。これはJust-in-Time(JIT)コンパイラの導入により解決しています。上記すべての概念は eBPF でも同様に見つけられます。

eBPF へと話を戻しましょう。eBPF が Linux カーネルをプログラムできることへの影響を理解するために、Linux カーネルのアーキテクチャや、アプリケーションとハードウェアがどう相互に連携しているかの大まかな理解が助けになります。

カーネルのアーキテクチャ

Linuxカーネルの主な役割はハードウェアや仮想ハードウェアを抽象化して、アプリケーションが計算資源を利用・共有できる一貫した API(システムコール)の提供です。これを実現するために、幅広いサブシステムや領域に責任を分散して維持しています。それぞれのサブシステムは大抵の場合、ユーザーの需要に応じて設定可能です。もし、望む挙動が設定できない場合、カーネルの変更が必要となり、従来では2つの方法があります。

ネイティブサポート

  1. カーネルのソースコードを変更して、Linuxカーネルコミュニティにその変更の必要性を納得させる。
  2. 新しいカーネルのバージョンが一般的に利用可能となるまで何年か待つ。

カーネルモジュール

  1. カーネルモジュールを書く。
  2. 新しいカーネルがリリースされるたびに壊れる可能性があるため、定期的に修正が必要。
  3. セキュリティ対応の不足により、利用しているLinuxカーネルが脆弱になるリスクがある。

eBPF はカーネルのコード変更やカーネルモジュールの導入なしに、Linuxカーネルの挙動をプログラムできる新しい手段です。これは様々な面で、JavaScript や他のスクリプト言語が、修正が難しいシステムに進化をもたらしたことと似ています。

eBPF プログラムの開発や管理を助ける様々な開発ツールチェーンが存在します。それらすべてのツールがユーザの様々な需要に応えています。

bcc

BCC はユーザが eBPF プログラムを内部に埋め込んだ Python プログラムを書けるようにするフレームワークです。このフレームワークは主にアプリケーションやシステムのプロファイリング・トレーシングへの活用を想定しています。eBPF プログラムでは統計情報の取得やイベントの生成をし、一方でユーザー空間ではデータを収集して人間が読める形で表示します。Python プログラムを実行すると eBPF のバイトコードが生成され、カーネルにロードされます。

bcc

bpftrace

bpftrace は Linux eBPF で利用可能なトレーシング用の高レベルな言語で、カーネルバージョン4.xから利用できます。bpftrace はスクリプトを eBPF のバイトコードにコンパイルするために、LLVM をバックエンドに利用しています。さらに、bpftrace は BCC を利用してLinux eBPF サブシステムやカーネルの動的なトレーシング(kprobe)やユーザ空間の動的なトレーシング(uprobe)、tracepoint などの既存の Linux のトレーシング機能と連携します。bpftrace で使われるスクリプト言語は awk や C 言語、DTrace や System Tap といった以前からあるトレーシングツールの影響を受けています。

bpftrace

eBPF Go ライブラリ

eBPF Go ライブラリは eBPF バイトコードの取得や、eBPF プログラムのロード・管理する機能を疎結合にする包括的な eBPF ライブラリです。eBPF プログラムは一般的に高レベル言語で記述され、clang/LLVM を利用して eBPF バイトコードにコンパイルされます。

Go

libbpf C/C++ ライブラリ

libbpf ライブラリは clang/LLVM から生成された eBPF オブジェクトファイルをカーネルにロードしたり、アプリケーションが利用可能なライブラリ API を提供して BPF システムコールを抽象化する C/C++ ベースの包括的な eBPF ライブラリです。

Libbpf

さらにeBPFについて学びたい方は、以下の追加資料を読んでください。

一般

探求

Cilium

Hubble