eBPF入門——カーネルを書き換えずにLinuxの仕組みを変える技術はどう動くのか

はじめに——カーネルを変えるにはなぜ難しいのか?

「Linuxの仕組みそのものを変えたい」と思ったら、どうすればいいでしょうか。たとえば、特定のシステムコールを監視したい、カーネルのネットワーク処理に独自の処理を組み込みたい——そんなとき、カーネル内部に新しい処理を追加する代表的な方法は事実上2つでした(ebpf.io)。

1つ目はカーネルソースの変更です。カーネルはシステム全体を制御する特権的な存在であるがゆえに安定性・セキュリティ要件が厳しく、変更はコミュニティの合意を経てメインラインにマージされ、各ディストリビューションへ普及するまで長い時間がかかります。

2つ目はカーネルモジュールの自作です。こちらは即座に反映できますが、セキュリティ境界のない状態でカーネル空間にコードをロードするため、バグひとつでカーネルごとクラッシュする危険を伴い、カーネル更新のたびに破損リスクと隣り合わせになります(ebpf.io)。

「待つか、危険を冒すか」。カーネル内部に新しい処理を加える方法としては、この2択に_flock_してきました。そして、第3の選択肢として登場したのが eBPF(extended Berkeley Packet Filter) です。カーネルのソースを変えず、モジュールもロードせず、サンドボックス化されたプログラムを実行時にカーネルへ挿入する——これがeBPFの核心です(ebpf.io)。なお、カーネル挙動の調整手段としては sysctl や既存トレース機構、LSM、ネットフィルタなども存在しますが、いずれも用意された枠内の操作にとどまる点が異なります。

従来手法とeBPFの違いは、次の分岐図のとおりです。

flowchart TD
    A[Linuxカーネルの挙動を変えたい] --> B{どう対応する?}
    B --> C[カーネルソースを変更]
    B --> D[カーネルモジュールを自作]
    B --> E[eBPF]
    C --> C1[コミュニティ合意が必要]
    C1 --> C2[普及まで長い時間]
    D --> D1[カーネル更新のたびに破損リスク]
    D1 --> D2[セキュリティ境界なし・カーネル破壊の危険]
    E --> E1[verifierによる事前検証]
    E1 --> E2[サンドボックスで実行]
    E2 --> E3[多くの場合、再起動なしでロード・アタッチ可能]
    style E stroke:#e6a817,stroke-width:2px
    style E3 stroke:#2e9e4f,stroke-width:2px

本記事では、Linuxインフラやホームサーバーを運用する方に向けて、eBPFの仕組み(verifier・JIT・マップ)、可観測性・セキュリティ・ネットワーキングの3大ユースケース、主要ツール、そして今日から始められる実践ステップまでを解説します。

eBPFとは何か?——「カーネル界のJavaScript」という比喩で理解する

eBPF(extended Berkeley Packet Filter)は、Linuxカーネルのソースコードを変更したりカーネルモジュールをロードしたりせずに、カーネルの能力を実行時に拡張できる技術です。起源は1990年代の「Berkeley Packet Filter」——その名の通りパケットをフィルタするための仕組みでした。しかし機能が拡張されるにつれ、パケットフィルタの枠を超えた存在になったため、現在ではeBPF自体が独立した用語として扱われ、旧来のものはcBPF(classic BPF)と呼んで区別されます。

eBPFの本質を理解するのに、ebpf.ioが使う「静的HTMLにJavaScriptが登場したときの革命」という比喩が役立ちます。かつてページの動きを変えるにはブラウザの更新を待つしかなかったHTMLに、JavaScriptは安全にプログラムを組み込む道を開きました。eBPFとカーネルの関係も同じです。共通点は3つあります。まず安全性——サンドボックス内で実行され、データアクセスが抽象化されています。次に継続的デリバリー——カーネルのバージョン更新を待たずにロジックを追加できます。そしてパフォーマンス——JITコンパイルにより最小限のオーバーヘッドで動きます。

カーネルモジュールとの違いもここです。モジュールにはセキュリティ境界がなく、書き方を誤ればカーネルごと破壊しかねません。対してeBPFは、ロード時にverifier(検証器)が安全性を事前検証したうえで、サンドボックス化された環境で実行されます。もっとも、verifierは一定の安全性条件を検査するものであり、カーネルやJIT、ドライバ等の実装上の脆弱性まで含めた無条件の安全性を保証するものではない点は念のためおさえておきましょう。

なおeBPFには公式ロゴ「eBee」と活発なコミュニティがあり、Ciliumやbpftraceといったプロジェクトがエコシステムを支えています。

eBPFはどう動くのか?——Verifier・JIT・マップの3大仕組み

eBPFの魅力的な性質——安全性と速さの両立——は、3つの中心的な仕組みによって支えられています。それぞれ順番に見ていきましょう。

イベント駆動で動くプログラム

eBPFプログラムはイベント駆動で動きます。システムコールの発行、関数の入口・出口、カーネルトレースポイント、ネットワークイベントなど、カーネルやアプリケーションが特定の「フックポイント」を通過した瞬間に実行されます。事前定義のフックがない場所でも、kprobe(カーネルプローブ)やuprobe(ユーザー空間プローブ)を使えば、利用可能な関数やシンボルといった、対応する箇所にアタッチできます(利用可否はカーネル設定・シンボル情報・権限などに依存します)。

開発の流れはシンプルです。擬似Cコードをclang/LLVMでeBPFバイトコードにコンパイルし、bpfシステムコールでカーネルにロードします。ここから先がeBPFの本番です。

Verifier——ロード時に安全性を静的検証

プログラムのロード時には、まずカーネル側の権限チェック(原則rootまたはCAP_BPF等、プログラム種別によって必要な権限は異なります)があり、続いてVerifier(検証器)によるプログラム検証が行われます。verifierは全実行パスを解析し、次のような条件を確認します(カーネル公式ドキュメント)。

  • プログラムが必ず完了して終了すること(無限ループの禁止。終了条件が保証される有界ループは許可)
  • 未初期化変数の使用や範囲外メモリアクセスの禁止
  • プログラムサイズと計算複雑度が上限内であること

つまり、カーネルを壊しうるプログラムは実行前に拒否されるのです。これがカーネルモジュールとの決定的な違いです。

JITコンパイル——ネイティブ同等の速さ

検証を通過すると、JITコンパイラが汎用バイトコードをCPU固有の機械語に翻訳します。これによりインタプリタ実行よりも低いオーバーヘッドで動作し得ますが、実際の性能はプログラムの処理内容、アタッチポイント、マップ操作やヘルパー呼び出しの頻度、イベント量などに依存します。「安全だけど遅い」というトレードオフになりにくいのは、このJITの存在ゆえです。

eBPFマップ——カーネルとユーザー空間をつなぐ

eBPFプログラムはデータの共有や状態の保存をマップを介して行います。マップにはeBPFプログラムからも、ユーザー空間のアプリからも(システムコール経由で)アクセスできます。代表的な型はハッシュテーブル、配列、LRU、Ring Buffer(イベントの高効率な送信)、スタックトレース、LPM(Longest Prefix Match)など。トレーシング結果の集計や、ポリシーの状態保持に使われます。

なお、eBPFプログラムは任意のカーネル関数を直接呼べません。代わりに、カーネルが提供する検証済みのヘルパー関数(乱数生成、時刻取得、パケット操作など)を、プログラム種別ごとの制約内で利用します(利用可能なヘルパーはカーネルバージョンによっても異なります。公式ドキュメント)。末尾呼び出し(tail call)で他のeBPFプログラムに処理を移譲することもできます。

多層セキュリティ

さらに、実行時のプログラムメモリを読み取り専用にするハードニング、Spectre対策(投機実行の検証・マスキング)、JITスプレー攻撃への定数ブラインディングなど、複数レイヤーの防御が重ねられています。

一連の流れを図にまとめると次のとおりです。

flowchart TD
    A["擬似Cコード"] -->|"clang/LLVMでコンパイル"| B["eBPFバイトコード"]
    B -->|"bpfシステムコールでロード"| C["Verifierによる静的検証"]
    C -->|"検証OK"| D["JITコンパイル(CPU固有機械語へ)"]
    C -->|"検証NG"| E["ロード拒否"]
    D --> F["フックポイントで実行<br/>(syscall / kprobe / XDP など)"]
    F -->|"データ共有・状態保存"| G["eBPFマップ"]
    G <-->|"システムコール経由で読み書き"| H["ユーザー空間アプリ"]

書いて、コンパイルして、検証されて、機械語に翻訳され、イベントで動く。この流れさえ押さえれば、eBPFのツール群も「どこにどうアタッチし、結果をどう受け取るか」の組み合わせとして理解できます。

3大ユースケース——可観測性・セキュリティ・ネットワーキングで何ができる?

eBPFは「カーネルのあらゆるイベントをフックできる」という性質上、応用範囲が非常に広いですが、ebpf.ioが公式に挙げる主要用途は可観測性・セキュリティ・ネットワーキングの3分野に集約されます。それぞれ見ていきましょう。

可観測性——カーネルレベルでの「透明な計装」

可観測性はeBPFの中でも最も親しみやすい用途です。トレースポイントやkprobe/uprobeにアタッチすることで、システムコールの呼び出し、関数の入口/出口、ネットワークイベントなどをカーネル内部から直接観測できます。アプリケーションのコードを一切変更せずに、パフォーマンス分析やトレース、詳細なセキュリティ可観測データの抽出が可能です。JITコンパイルによりネイティブ同等の速度で動作するため、計装によるオーバーヘッドが小さい点も大きな利点です。

セキュリティ——ランタイム防御とLSMフック

セキュリティ分野では、LSM(Linux Security Module)フックなどを利用した予防的なアプリケーション・コンテナランタイム防御が代表的です。「システムコールを監視して不審な挙動を検知する」といった監視・検知に対し、検知した挙動をブロックする強制は、適切なフック(BPF LSM等)・ポリシー・権限と、対応するカーネルバージョンが別途必要になります。カーネル空間で低オーバーヘッドに扱える点、ホスト型EDRのようにエージェントを個別導入するのと異なり、カーネル自身が持つ特権的な視点をそのまま活かせる点が強みです(Cilium概要)。

ネットワーキング——XDPとTCによる高速パケット処理

ネットワーキングでは、XDP(eXpress Data Path)とTCが2大アタッチポイントです。特にXDPはNICドライバーレベルでパケットを処理できるため、DDoS軽減やパケットドロップを極めて高い効率で行えます。CiliumのようなeBPF基盤のソフトウェアは、この仕組みを活用してKubernetes環境で分散ロードバランシングを実現しています(実装・モード依存の詳細はBPF & XDP Reference Guide参照。例として、東西通信でのsocketレベルの負荷分散や、南北通信でのXDP活用が挙げられます)。

3大ユースケース比較表

ユースケース 主なアタッチポイント 代表ツール 向いている場面
可観測性 トレースポイント、kprobe/uprobe、システムコール bpftrace、BCC、Pixie パフォーマンス分析、トレーシング、低オーバーヘッドな計装
セキュリティ LSMフック、システムコール、cgroup Cilium(Network Policy)、ランタイム防御系ツール コンテナ・アプリのランタイム防御、脅威検知とブロック
ネットワーキング XDP、TC、socketレベル Cilium / Hubble DDoS軽減、ロードバランシング、Kubernetesのサービス間通信制御

このように同じeBPFでも、アタッチポイントの選択次第で「観測する」「守る」「制御する」という異なる役割を担えます。次のセクションでは、これらの用途を支える主要ツールの使い分けを詳しく見ていきます。

主要ツール紹介——bpftrace・BCC・Cilium・Pixieはどう使い分ける?

eBPFを「触ってみたい」と思ったとき、いきなりバイトコードを書く必要はありません。用途ごとに成熟したツールがいくつも存在します。ここでは代表的な4つを、初心者への入りやすさとともに紹介します。

bpftrace は、Linux向けの高レベルなトレーシング言語です。awkやDTraceに着想を得た簡潔な構文で、複雑なeBPFの内部を意識せずに可観測性プログラムを書けます。LLVMバックエンドでスクリプトをeBPFバイトコードにコンパイルし、kprobe・uprobe・tracepointを利用。たった1行の「ワンライナー」から始められるのが最大の魅力で、公式のOne-Liner Tutorialも用意されています。動作要件(対応カーネル・LLVM・BPF機能)はbpftraceのバージョンによって異なるため、公式サイトの要件を確認してください。

BCC(BPF Compiler Collection) は、Pythonプログラムの中にeBPFプログラムを埋め込めるフレームワークです。主にアプリケーションやシステムのプロファイリング/トレーシングに使われ、ユーザー空間側のPythonでデータを収集・表示できます。bpftraceで表現しきれない複雑な処理を組みたいときの次のステップに適しています。

Cilium / Hubble は、Kubernetes向けのeBPFベースのネットワーキング・セキュリティ・可観測性ソリューションの代表格です。従来のiptables方式がIPアドレスとポートでフィルタするのに対し、Ciliumはサービス・Pod・コンテナのIDベースでL3〜L7(HTTPメソッドやgRPCまで)のポリシーを実現します。アプリコードやコンテナ設定を変えずにセキュリティポリシーを適用・更新でき、eBPFによる分散ロードバランシングでkube-proxyを置き換えることも可能です。さらにHubbleを組み合わせれば、サービス依存マップやDNS解決失敗・TCPタイムアウトの特定など、トラブルシュートに必要な可視化が手に入ります。

Pixie は、コード変更なし(No Instrumentation)でKubernetesの可観測性を実現するオープンソースプラットフォームです。動的なeBPFプローブにより、数秒でメトリクス・イベント・トレース・ログにアクセスできます。PXLスクリプトによる「debug as code」も特徴で、既定構成ではテレメトリをクラスタ内で完結できる設計とされています(構成やクラウド連携によってデータフローは変わり得ます。How Pixie Works)。

このほか、C/C++の libbpf、Goの eBPF Go Library、RustでeBPFプログラム自体も書ける aya や libbpf-rs など、本格開発向けのライブラリも揃っています。

各ツールの使い分けは、次の比較表を参考にしてください。

ツール 言語 レイヤー 主用途 初心者への入りやすさ
bpftrace 独自の高レベル言語 トレーシング ワンライナーでのトレーシング・パフォーマンス分析 ◎ ワンライナーから始められる
BCC Python+C トレーシング プロファイリング・複雑なトレーシングツール開発 ○ Pythonが書ければ挑戦しやすい
Cilium / Hubble 設定ベース(YAML等) ネットワーキング/セキュリティ K8sのCNI・ネットワークポリシー・可観測性 △ K8sの基礎知識が必要
Pixie PXLスクリプト 可観測性 ノーコード計装のK8s可視化・デバッグ ○ 導入すればUIで手軽に確認できる

まずはbpftraceでワンライナーを試し、可観測性に興味があればPixie、Kubernetesのネットワーク運用に関わるならCilium——というように、目的に応じて段階的にステップアップするのが現実的なルートです。

実践ステップ——bpftraceで今日から始める

eBPFの理屈を学んだら、次は実際に動かしてみるのが理解への近道です。ここでは、高レベルトレーシング言語「bpftrace」を使って、今日から始められる5つのステップを紹介します。

Step 1: 環境を確認する

eBPFに対応したカーネルが必須です。必要なカーネルバージョンは使いたい機能によって異なります。カーネルバージョンは次のコマンドで確認できます。

uname -r

機能によって要求されるカーネルバージョンが異なるため、新しい機能ほど新しいカーネルが必要になります。

Step 2: bpftraceをインストールする

多くのディストリビューションが公式パッケージを提供しています。

# Ubuntu / Debian
sudo apt install bpftrace

# Fedora
sudo dnf install bpftrace

Step 3: ワンライナーを実行する

root権限(またはCAP_BPFケイパビリティ)で実行します。まずはシステムコールを数えるワンライナーから。

# 直近のシステムコール呼び出しを集計
sudo bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'

プロセス実行の監視も一行で書けます。

# 実行されるコマンドを監視
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve { join(args->argv); }'

Step 4: 公式One-Liner Tutorialで学ぶ

bpftrace公式サイトの「One-Liner Tutorial」(bpftrace.org/tutorial-one-liners)に、読み書きするファイルの集計やI/Oレイテンシの測定など、実用的なパターンが揃っています。順に写経していくことで、probe(kprobe/uprobe/tracepoint)とアクションの組み合わせという言語の型が自然に身につきます。

Step 5: ステップアップの目安

「集計・表示をもっと自在に制御したい」と感じたら、BCC(PythonでeBPFを書くフレームワーク)やlibbpf(C/C++)へ進みましょう。さらにKubernetes環境ならCiliumやPixieが、eBPFのネットワーキング・可観測性を体験する入口になります。

注意点

eBPFプログラムのロードは原則としてroot権限かCAP_BPFケイパビリティを持つ特権プロセスに限られます。これはverifierによる検証と並ぶ多層セキュリティの第一層です。また、非特権プロセスがeBPFを利用できる「unprivileged eBPF」は、セキュリティ上の理由から多くのディストリビューションでデフォルト無効になっている点も覚えておきましょう。

ケーススタディ——ホームサーバー/エッジ運用でのeBPF活用

ここまで仕組みやツールを見てきたら、次に気になるのは「自宅のサーバーやエッジ機器で実際にどう役立つのか」ということでしょう。ここでは代表的な活用パターンを紹介します。

まず有力なのが XDP(eXpress Data Path)によるDDoS軽減です。XDPはNICドライバーレベルでパケットを処理するeBPFのアタッチポイントで、悪意あるトラフィックを高い効率でドロップできます。従来のファイアウォールがパケットをカーネルのネットワークスタックまで運んでから処理するのに対し、XDPはもっと早い段階で扱うため、攻撃トラフィックが大量に届いてもマシンへの負荷を抑えられます。技術詳細はCiliumのBPF & XDP Reference Guide(docs.cilium.io)が一次資料として参考になります。

次に、オーバーヘッドの小ささがエッジ運用と相性が良い点です。eBPFプログラムはJITコンパイルにより低いオーバーヘッドで動作し得ますが(ebpf.io)、実際の性能は処理内容とアタッチポイントに依存します。低スペック機器での採用を検討する場合は、実際のワークロードで確認するのが安全です。

Kubernetesを運用しているなら Cilium の検討価値は高いでしょう。CiliumはeBPFによる分散ロードバランシングを提供し、構成によっては kube-proxyを置き換えることも可能です(東西トラフィックでのsocketレベル負荷分散、南北でのXDP活用など。詳細・要件はCilium公式ドキュメント参照)。

さらに、Cilium上に構築された Hubble はトラブルシュートの強い味方です。サービス依存マップの可視化に加え、DNS解決の失敗やTCP接続タイムアウトといった「つながらない理由」、ポリシーでブロックされた通信まで特定できます(Cilium公式docs)。

導入時の注意点も押さえておきましょう。eBPFプログラムのロードには原則rootまたはCAP_BPF等の権限が必要で(プログラム種別により必要権限は異なります)、権限管理を怠らないこと。また、利用する機能によって要求されるカーネルバージョンが異なるため、自分の環境が対応しているか事前確認が大切です。

よくある質問(FAQ)

Q1: eBPFとカーネルモジュールは何が違うの?

最大の違いは安全性です。カーネルモジュールはセキュリティ境界なしでカーネル空間で動作するため、バグがあるとカーネルごと破壊する危険があります。一方eBPFは、ロード時にverifierが無限ループや範囲外メモリアクセスなどを事前検証し、サンドボックス化された環境で実行されるため、任意コードを直接カーネル空間で実行する方式よりも実行可能範囲が強く制約されます。ヘルパー関数も、カーネルが提供する検証済みのものをプログラム種別ごとの制約内で利用するため継続的な運用にも有利です(利用可能なヘルパーはカーネルバージョン依存)。

Q2: eBPFを使うにはroot権限が必須?

原則としてroot権限、またはCAP_BPFケイパビリティを持つ特権プロセスが必要です。これはカーネルの安全性を守る多層セキュリティの第一層にあたります。ただし、unprivileged eBPF(非特権eBPF)が明示的に有効な場合は、限定された機能範囲で非特権ユーザーによるロードも可能です。なお多くのディストリビューションではセキュリティ上の理由から、unprivileged eBPFはデフォルトで無効化されている点に注意しましょう。

Q3: オーバーヘッドはどのくらい?

心配するほどではありませんが、一律ではありません。eBPFプログラムは検証通過後にJITコンパイラによってCPU固有の機械語へ翻訳され、インタプリタ実行より低いオーバーヘッドで動作し得ます。ただし実際のオーバーヘッドはプログラムの処理内容・アタッチポイント・イベント量に依存するため、低スペック機器での採用は実際のワークロードでの確認が安全です。XDPによるパケット処理のように高スループットが求められる場面で採用事例があるのは、低オーバーヘッド性を支える一つの根拠といえます。

Q4: どのLinuxカーネルバージョンから使える?

eBPFの基本機能はLinux 3.18以降で導入され、以降のカーネルで継続的に拡張されています。ただし、使える機能はカーネルバージョンによって大きく異なります。有界ループ(bounded loops)のサポート、Ring Buffer、BPF LSMなど比較的新しい機能は、より新しいカーネルでのみ使えます。ツールごとにも動作要件が異なるため(例: bpftraceはバージョンによって必要なカーネル・LLVMが変わる)、各公式ドキュメントで要件を確認してください。本格的に学ぶなら、ディストリビューションの標準カーネルが新しいLTS系である環境が快適です。

Q5: eBPFはLinux以外(Windows/macOS)でも使える?

eBPFはもともとLinuxカーネル技術として生まれたもので、主役の舞台はLinuxです。ただし、eBPFのランタイム仕様をOS非依存に標準化しようという取り組み(eBPF standardization)が進められており、Linuxカーネル公式ドキュメントにも標準化の専門ページが設けられています。Windows向けにeBPFを実装するプロジェクトなども存在しますが、実務で安心して使えるのは依然としてLinuxが中心と考えるのが妥当です。

Q6: まずどのツールから始めればいい?

目的別のおすすめがあります。「システムの挙動を手軽に覗きたい」ならbpftraceが最適で、ワンライナーから始められます。「Pythonで解析ツールを組みたい」ならBCC、「Kubernetesのネットワーシングやセキュリティ」ならCilium/Hubble、「コード変更なしのKubernetes可観測性」ならPixieが向いています。迷ったら、まずbpftraceの公式One-Liner Tutorialをroot権限のあるLinuxマシンで試すのが最短ルートです。慣れたらBCCやlibbpfへステップアップしましょう。

まとめ——eBPFが開く『プログラマブル・カーネル』の時代

ここまで見てきたように、eBPFの中核はシンプルな3大仕組みです。Verifierがロード時に安全性を静的検証し、JITコンパイラがバイトコードをCPU固有の機械語へ翻訳して低オーバーヘッドの実行を可能にし、eBPFマップがカーネルとユーザー空間のデータ共有を担う。この土台の上で、可観測性・セキュリティ・ネットワーキングの3大ユースケースが、カーネルを書き換えることなく、多くの場合は再起動なしでロード・アタッチできます。

もっと本質的な変化は、開発モデルそのものが変わったことです。かつてはカーネル内部に新しい処理を加えるのに、ソース変更とコミュニティ合意による長い待ち時間か、カーネルモジュールという壊れやすい危険な賭けの2択しかありませんでした。eBPFは「カーネルのバージョン更新を待たずに、安全にロジックを追加し続ける」第3の道を開いた。まさに静的HTMLにJavaScriptがもたらしたのと同じ、プログラマビリティの革命です。

今日から始める3つのアクション

  1. bpftraceのOne-Liner Tutorialを試す — root権限のあるLinux環境で、ワンライナーからシステムコールトレースを体験しましょう(bpftrace.org/tutorial-one-liners)。
  2. Hubbleで可観測性を体感する — Kubernetes環境があればCilium+Hubbleを導入し、サービス依存マップやDNS解決失敗の可視化を確認してみてください。
  3. Pixieでノーコード計装を経験する — コード変更なしでメトリクス・トレース・ログにアクセスできるPXLスクリプトを触り、eBPFの実力を実感しましょう(px.dev)。

カーネルはもう「変えられない黒箱」ではありません。eBPFという安全な窓口を通じて、あなたのLinuxを自分の手で再プログラミングする時代が始まっています。

参考文献・出典

関連記事