【図解】eBPFとは?bpftraceの使い方とLinuxトラブルシューティングを実例で解説

はじめに

Linuxの性能問題や障害を調査していると、topやiostatで「CPU使用率が高い」「I/Oが遅い」ことは分かっても、Linux Kernel内部でなぜその現象が起きているのかまでは分からないことがあります。

top、vmstat、iostat、sar、pidstatなどのコマンドはLinuxの状態を把握するうえで非常に有効です。そこで問題のResourceやProcessを絞り込み、さらに原因を深掘りしたいときに役立つのがeBPF(extended Berkeley Packet Filter)です。

eBPFを利用すると、System Call、Kernel Function、Scheduler、Filesystem、Block I/O、TCPなど、Linux Kernel内部のさまざまなEventへProgramをAttachして観測できます。

本記事では、eBPFの基本的な仕組みから、Verifier、BPF Map、Tracepoint、kprobeなどの主要な要素、bpftraceを利用した実際の観測方法、Linuxトラブルシューティングでの活用方法まで解説します。

eBPFとは

eBPFは、Linux Kernel内で特定のEventを契機としてProgramを実行できる仕組みです。

もともとのBPF(Berkeley Packet Filter)はNetwork PacketをFilterするために利用されていましたが、現在のeBPFではNetworkだけでなく、System Call、Scheduler、Filesystem、Block I/O、Securityなど幅広いKernel Eventを対象にできます。

eBPFの大きな特徴は、Kernel Moduleを新たに作成したりKernel自体を変更したりしなくても、Kernel内部の動作を詳細に観測できることです。

eBPFの全体像

eBPFを理解するには、Programを作成してからKernel内で実行されるまでの流れを整理すると分かりやすくなります。

重要なのは、作成したProgramがそのまま無条件にKernel内で実行されるわけではないという点です。

KernelへLoadする際にはVerifierによる検証が行われ、Kernelを危険な状態にする可能性があるProgramはRejectされます。

eBPFはどこにHookできるのか

eBPF Programは、さまざまなKernel EventやFunctionなどにAttachできます。

Hook主な用途
TracepointKernelが提供するTrace Eventの観測
kprobe / kretprobeKernel Functionの入口・戻りを観測
uprobe / uretprobeUser SpaceのFunctionを観測
XDPNetwork Driverに近いLayerでPacketを処理
TCNetwork Trafficの制御・観測
SocketSocket / Network通信の観測・制御
cgroupcgroup単位でNetworkなどを制御・観測

性能調査で特に利用しやすいのが、Tracepoint、kprobe、uprobeです。

Tracepointとは

Tracepointは、Kernel内にあらかじめ用意されているTrace用のEventです。

System Call、Scheduler、Block I/Oなど、さまざまなEventにTracepointが用意されています。

TracepointはKernelが明示的に提供しているTrace Interfaceであり、Kernel内部Functionそのものへ直接Probeするkprobeよりも、一般的にはKernel Versionによる内部実装変更の影響を受けにくいという特徴があります。

kprobe / kretprobeとは

kprobeはKernel Functionなどに動的にProbeを設定する仕組みです。

kprobeではFunctionの入口、kretprobeではFunctionから戻るタイミングなどを観測できます。

Kernel内部を詳細に観測できる一方、内部Function名や引数などはKernel Versionによって変更される可能性があるため、利用するKernelで確認することが重要です。

uprobe / uretprobeとは

eBPFで観測できるのはKernelだけではありません。uprobeを利用すると、User Spaceで動作するApplicationやLibraryのFunctionにもProbeを設定できます。

Kernel側だけではなくApplication側まで含めて観測できることも、eBPFの大きな特徴です。

eBPF Programが実行される仕組み

eBPF Programは、KernelへLoadされる際にVerifierで検証され、環境によってはJIT Compileを経て実行されます。また、観測したDataの保持やUser Spaceとの共有にはBPF Mapなどが利用されます。

Verifierとは

「Kernel内でProgramを実行して、本当に大丈夫なのか?」という疑問が出てくるかもしれません。

そこで重要になるのがeBPF Verifierです。

VerifierはProgramを解析し、Kernelが要求する安全性の条件を満たしているかを検証します。たとえば、無効なMemory Accessにつながる処理や、Verifierが安全性を証明できない処理などはRejectされます。

この仕組みによって、任意のKernel Codeをそのまま実行する場合とは異なり、eBPF Programには実行前の安全性検証が行われます。

JIT Compileとは

Verifierによる検証を通過したeBPF Programは、環境によってJIT(Just-In-Time)CompilerによってNative Machine Codeへ変換して実行できます。

これにより、Kernel Eventが頻繁に発生する環境でも効率的にProgramを実行できるようになっています。

BPF Mapとは

Kernel内でeBPF Programが取得したDataをUser Spaceから利用するために重要なのがBPF Mapです。

BPF MapはeBPF ProgramとUser Spaceの間でDataを共有するために利用できるData Structureです。

種類用途
HashKey / Value形式でDataを保持
ArrayIndexを利用してDataを保持
Per-CPU Hash / ArrayCPUごとに独立したDataを保持
Ring BufferEvent DataをUser Spaceへ効率的に送信

eBPFで何ができるのか

eBPFはNetworkだけではなく、LinuxのさまざまなSubsystemのObservabilityや制御に利用されています。

分野観測例
CPUScheduling、Run Queue、Context Switch
MemoryMemory Allocation、Page Fault
I/OFilesystem、Block I/O、I/O Latency
NetworkTCP、Socket、Packet
ApplicationUser Space Function、Latency
SecurityProcess、System Call、File Accessなど

つまりeBPFは、「Network専用の仕組み」ではなく、Linux内部の動作を幅広く観測・制御するための基盤技術へと発展しています。

eBPFを利用する代表的なTool

eBPFそのものを理解しようとすると、bpftrace、BCC、libbpf、bpftoolなど多くの名前が登場するため混乱しやすいところです。

Linuxの性能調査やeBPFの学習を始める場合は、比較的簡潔にTracingできるbpftraceから触ってみると仕組みを理解しやすいでしょう。

bpftraceを使ってみる

ここからはbpftraceを利用して、実際にLinux内部のEventを観測してみます。

bpftraceのVersionを確認する

# bpftrace --version

利用できるVersionはDistributionやRepositoryによって異なります。

利用できるProbeを確認する

# bpftrace -l 'tracepoint:syscalls:*'

bpftrace -lを利用すると、利用可能なProbeを確認できます。

bpftrace -l 表示例:

System Callだけではなく、SchedulerやBlock I/Oなど対象を変更して確認できます。

openat()を観測する

たとえば、Processがopenat() System Callを呼び出したことを観測してみます。

# bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%-16s %s\n", comm, str(args.filename)); }'

commから、Eventを発生させたProcessのCommand名を確認できます。

bpftrace 出力例:

従来の性能監視ToolとeBPFの違い

eBPFはtop、vmstat、iostatなどを置き換えるものではありません。

まず従来のToolでシステム全体の状態を確認し、原因を絞り込んだあとにeBPFで詳細を観測する、という使い分けが有効です。

Tool主な役割
top / vmstatSystem全体の状態を把握
pidstatProcess / Thread単位で確認
iostatBlock DeviceのI/O状態を確認
ss / nstatSocket / TCP統計を確認
perfCPU Performance CounterやKernel / User SpaceのProfilingなど
eBPF特定のKernel / User Space Eventを動的に観測

eBPFをLinuxトラブルシューティングで使う

eBPFが特に力を発揮するのは、通常のMonitoring Toolだけでは原因を十分に説明できないケースです。

CPU高負荷を調査する

たとえばCPU使用率だけではなく、ProcessがどのSystem Callを大量に呼び出しているのか、Schedulingでどのような待ちが発生しているのか、といった観点まで調査できます。

試しに、CPU上で頻繁に実行されているProcessを確認してみましょう。

# bpftrace -e 'profile:hz:99 { @[comm] = count(); }'

このコマンドは、CPUを1秒間に99回サンプリングし、その時点でCPU上で実行されていたProcessをcommごとに集計します。出力される値が大きいProcessほど、CPU上で実行されている頻度が高いことを示します。

bpftrace 出力例:

※ 集計値はCPU使用率そのものではなく、Samplingで観測された回数です。

I/O遅延を調査する

iostatではDevice単位のLatencyやQueueの状態を確認できますが、eBPFを利用するとI/O Request単位など、より詳細な情報まで追跡できます。

試しに、Block I/O Requestの発行を監視し、TaskごとのI/O発行回数を確認してみましょう。

# bpftrace -e 'tracepoint:block:block_rq_issue { @[comm] = count(); }'

このコマンドは、block_rq_issue Tracepointを利用して、Block I/O Requestが発行されるたびにTask名(comm)を取得し、TaskごとのI/O発行回数を集計します。値が大きいTaskほど、多くのBlock I/O Request発行時に観測されています。

※ I/O処理の経路によっては、Applicationではなくkworkerやjbd2などのKernel Threadがcommに表示される場合があります。また、ここで集計しているのはRequest数であり、I/O Data量そのものではありません。

bpftrace 出力例:

Network問題を調査する

NetworkではtcpdumpによるPacket Captureが非常に強力ですが、eBPFを利用するとKernel内部のTCPやSocketのEventを観測できるため、Packetだけでは分かりにくいKernel内部の状態を調査できます。

試しに、TCP Retransmission(再送)の発生状況を確認してみましょう。

# bpftrace -e 'tracepoint:tcp:tcp_retransmit_skb { @retrans = count(); }'

tcp_retransmit_skb Tracepointを利用して、Linux KernelでTCP再送Eventが発生した回数をリアルタイムに集計します。@retransはBPF Mapで、tcp_retransmit_skb Eventが発生するたびにcount()でカウントアップされます。

たとえば@retrans: 3と表示された場合、観測期間中にtcp_retransmit_skb Eventを3回検出したことを示します。

bpftrace 出力例:

eBPFを利用するときの注意点

eBPFは非常に強力ですが、「eBPFなら本番環境でも無条件に安全」というわけではありません。

  • Kernel Version:利用できるeBPF機能やProbeがKernel Versionによって異なる。
  • 権限:Program Typeや環境、Kernel設定によって必要なCapabilityや権限が異なる。
  • Overhead:非常に高頻度のEventを大量に取得するとCPU負荷やData量が増える可能性がある。
  • kprobe:Kernel内部Functionへ依存するため、Kernel Versionによる変更の影響を受ける可能性がある。
  • Data量:取得対象を絞らず大量のEventをUser Spaceへ送る設計は避ける。

本番環境では、まず取得対象を限定し、CPU負荷や取得Data量を確認しながら利用することが重要です。

eBPFトラブルシューティングの考え方

障害が発生したからといって、最初からeBPFで大量のEventを取得する必要はありません。

まず既存のLinux Commandで問題のResourceやProcessを絞り込み、その後に「何を確認したいのか」を決めてeBPFを利用することが重要です。

eBPFは「とりあえず実行するTool」ではなく、既存Toolで絞り込んだ仮説をKernel内部のEventから検証するための強力な手段と考えると、Linuxトラブルシューティングで活用しやすくなります。

まとめ

eBPFを利用すると、Linux Kernel内部のさまざまなEventにProgramをAttachし、従来のMonitoring Toolだけでは見えにくかった情報を観測できます。

  1. Hook:Tracepoint、kprobe、uprobe、XDPなどを利用してさまざまなEventへAttachできる。
  2. Verifier:eBPF ProgramをKernelへLoadする前に安全性を検証する。
  3. JIT:eBPF InstructionsをNative Machine CodeへCompileして効率的に実行できる。
  4. BPF Map:eBPF ProgramとUser Spaceの間などでDataを共有できる。
  5. bpftrace:比較的簡潔な記述でeBPFを利用したTracingを実行できる。
  6. Troubleshooting:CPU、I/O、Networkなどの問題をKernel Eventまで深掘りできる。

topやiostatで「CPUが高い」「I/Oが遅い」と把握するだけでなく、Linux Kernel内部で実際に何が起きているのかまで追跡できることがeBPFの大きな強みです。

まずはbpftraceでSystem CallやSchedulerなど身近なEventを観測してみると、eBPFだけでなくLinux Kernel内部の仕組みそのものへの理解も深められます。

コメント