【ITニュース解説】eBPF on Linux — kprobe vs fentry: Hooking Internals & What Production Observability Misses
2026年09月08日に「Dev.to」が公開したITニュース「eBPF on Linux — kprobe vs fentry: Hooking Internals & What Production Observability Misses」について初心者にもわかりやすく解説しています。
ITニュース概要
eBPFでカーネル関数にフックする際、kprobeとfentryという二つの方法がある。kprobeは幅広い環境で使えるが、fentryはモダンなカーネルで低コストかつ型安全に動作する。頻繁に実行される関数では、選択によってシステム性能に差が出るため、適切な使い分けが重要だ。
ITニュース解説
システムエンジニアとしてシステムを安定稼働させるには、その内部、特にOSの核であるLinuxカーネルの動きを詳しく知ることが非常に重要だ。eBPFという技術は、カーネルに手を加えることなくその動作を監視・制御する強力なツールとして注目されている。eBPFプログラムは、カーネル内の特定の関数が呼び出される瞬間に割り込み、その情報を取得したり、動作を変更したりできる。これを「カーネルフック」と呼ぶ。このカーネルフックには、主に「kprobe」と「fentry」という二つの異なる方法があり、これらは単なる選択肢ではなく、それぞれに大きな違いと、特に本番環境での利用において重要な影響がある。
kprobeは、非常に柔軟性の高いフック方式だ。これは、監視したいカーネル関数の開始位置にある機械語命令を一時的に書き換え、特別な「ブレークポイント」(x86系CPUではint3命令)を挿入することで機能する。このブレークポイントが実行されると、CPUは通常の処理を中断し、カーネル内のkprobe処理機構へと制御を移す。ここでユーザーが作成したeBPFプログラムが実行され、その後、元の命令が実行されて処理が再開される。kprobeの大きな特徴は、カーネルのビルド設定にほとんど依存しない点にある。カーネル関数に関する詳細な情報がなくても、そのアドレスさえわかればフックできるため、広範な環境で利用可能だ。しかし、この柔軟性には代償がある。eBPFプログラムは、関数が呼び出された時点のCPUのレジスタ情報をまとめたstruct pt_regs *という生のデータを受け取る。プログラムの引数にアクセスするには、PT_REGS_PARM1などのマクロを使って、レジスタの特定の位置から手動で値を読み出す必要がある。もし監視対象のカーネル関数の引数の順序や型がカーネルのバージョンアップで変更された場合、プログラムはコンパイルエラーにならず、誤ったレジスタの値を読み取ってしまう可能性があり、問題が表面化しにくいという危険性がある。
一方、fentryは、より現代的で洗練されたフック方式だ。これは、カーネルが提供するftraceというトレース機能のために、コンパイラがあらかじめ各関数開始地点に用意している特殊な呼び出しスロット(__fentry__サイト)を利用する。fentryは、このスロットにBPF専用の「トランポリン」と呼ばれる小さなコードを挿入し、そこからユーザーのeBPFプログラムを呼び出す。fentryの最大の利点は、引数を型安全に扱えることだ。BTF(BPF Type Format)というカーネルの型情報にアクセスし、BPF_PROGというマクロを使うことで、カーネル関数のプロトタイプ(引数の型と名前の定義)をそのままeBPFプログラムの引数として記述できる。もし記述したプロトタイプが実際のカーネル関数と一致しなければ、eBPFプログラムのロード時にエラーとして検出されるため、kprobeのようにサイレントなデータ破損を防ぐことができる。また、関数が戻る際に実行されるfexitという機能もあり、関数の引数と戻り値を一つのプログラムでまとめて取得できる点も便利だ。しかし、fentryを利用するには、カーネルバージョンが5.5以上であること、カーネルがBTF付きでビルドされていること、そして監視対象の関数がftraceに対応していることが条件となる。
この二つの方式の最も重要な違いの一つは、プログラムが呼び出されるたびに発生する「オーバーヘッド」、つまりCPUコストだ。kprobeが例外(ブレークポイントによるトラップ)を発生させることで動作するのに対し、fentryは通常の関数呼び出しに近いトランポリン方式で動作する。例外処理は、CPUが現在の状態を保存し、特別なルーチンを実行し、元の場所に戻るという一連の非常にコストのかかるプロセスだ。そのため、kprobeは本質的にCPUリソースを多く消費する。対してfentryは、レジスタの保存と復元、そして関数呼び出しという、比較的低コストな処理で済む。現代のカーネルでは、kprobeも最適化され、ftraceサイトを利用するなどしてオーバーヘッドが削減される場合もあるが、それでもfentryの方が根本的に設計上、より低い、かつ予測可能なコストで実行できる。
システムエンジニアとして本番環境でeBPFを活用する際、多くのチュートリアルが示す「アタッチしてデータを読む」という単純なプロセスだけでは不十分だ。重要なのは、以下の三点である。一つ目は、プローブのインストールメカニズムで、これが1回あたりのCPUコストを決定する。二つ目は、1回あたりの実際のコストで、プログラムがどれだけのCPU時間を消費するかを把握する必要がある。三つ目は、BTFとカーネルバージョンの制約で、これらがそもそもプログラムが対象のシステムで動くかどうかを左右する。これらの要因が、特に「呼び出し頻度」という点で大きく影響する。もし監視したい関数が、プロセス終了やモジュールロードのようにたまにしか呼ばれない「コールドパス」にあるなら、kprobeのオーバーヘッドは無視できるほど小さいかもしれない。しかし、ネットワークのパケット処理やファイルI/Oのように、1秒間に何十万回、何百万回も呼び出される「ホットパス」にある場合、kprobeのわずかなオーバーヘッドでも、積み重なるとシステム全体のパフォーマンスに深刻な影響を及ぼす。例えば、AIインフラのような、非常に高速な処理が求められる環境では、eBPFによる計測自体がレイテンシの原因となる可能性があるため、フック方式の選択は極めて重要だ。
どのフック方式を選ぶかは、監視対象の関数や利用するカーネルの状況によって決まる。もし、広範囲なカーネルバージョンに対応する必要があったり、BTFが利用できない古いカーネルで動作させたり、あるいは呼び出し頻度が低いイベントを監視するなら、kprobeが適している。kprobeは汎用性が高く、特別なカーネル要件なしに多くの場所へアタッチできる。一方で、最新のBTF対応カーネル(バージョン5.5以上)が利用でき、監視対象がシステムの性能に直結するような「ホットパス」にある場合は、fentryが断然有利だ。fentryは型安全な引数アクセスを提供し、プログラムの信頼性を高めるとともに、低オーバーヘッドで動作するため、システムのパフォーマンスへの影響を最小限に抑えられる。重要なのは「このeBPFフックが、この呼び出し頻度において、システムのCPU予算内で収まるほど十分安価か」という視点だ。フック方式の選択はプロジェクト全体での決定ではなく、個々のフックが監視する関数やその環境に合わせて行うべきである。
fentryは、モダンなカーネルで型安全かつ低オーバーヘッドを実現する、本番環境での監視において強力な選択肢だ。しかし、全ての環境で使えるわけではないため、古いカーネルやBTF非対応の環境、あるいは特定のユースケースではkprobeが依然として有効な手段となる。システムエンジニアとしてeBPFを使いこなすには、単にプログラムを動かすだけでなく、それぞれのフックメカニズムの内部動作、パフォーマンス特性、そして利用条件を深く理解し、状況に応じて最適な選択をすることが求められる。この知識こそが、本番環境で安定したオブザーバビリティを実現するための鍵となる。