eBPFとBCC ToolsでLinuxを「魔術師」のようにデバッグ:コードに触れずにカーネルを可視化する

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

午前2時、”Slow Request”という名の悪夢

スマホが激しく振動する。監視システムからの通知には一言こうあった:「レイテンシ急増、広範囲で504 Gateway Timeout発生」。目をこすりながら起き上がり、サーバーにSSHでログインする。CPU使用率は低く、RAMも十分に余裕がある。Nginxやアプリケーションのログは至って正常。一見すべてが順調そうに見えるが、実際にはシステムは這いつくばるように遅延している

新米システム管理者の頃の私なら、サービスを再起動したりRAMを増やしたりして、無駄に午後の時間を潰していただろう。だが、その夜は違った。アプリケーションログが無視してしまうような、オペレーティングシステムの皮を剥いだ先にある「カーネル」の中を覗き見る必要があった。そこで真価を発揮したのが、eBPFとBCC Toolsのセットだった。

この記事では、アプリケーションのコードを1行も修正することなく、システムの極めて微細な動作をeBPFで「透視」する方法を解説する。

クイックスタート:3分でインストールして即実行

eBPFという名前に惑わされないでほしい。BCC(BPF Compiler Collection)ツールキットを使えば、操作は非常にシンプルだ。UbuntuやDebianなら、以下のコマンドを実行するだけでいい:

# BCCツールと現在のカーネルヘッダーをインストール
sudo apt update
sudo apt install bpfcc-tools linux-headers-$(uname -r)

インストール後、ツール群は /usr/sbin-bpfcc という接尾辞付きで配置される。まずは execsnoop コマンドで、実行中のプロセスをリアルタイムで観察してみよう:

sudo execsnoop-bpfcc

新しく生成されたすべてのプロセスが即座に表示される。これは、実行タイミングの狂ったcronジョブや、裏でシステムに負荷をかけている怪しいスクリプトを特定するのに最も速い方法だ。

eBPFとは一体何なのか?

本質的に、eBPFはLinuxカーネル内で動作する(V8のような)JavaScriptエンジンのようなものだ。カーネル内でイベントが発生した瞬間に、超軽量の監視プログラムを実行できる。厳格な安全性チェック(検証器:verifier)のおかげで、システムをクラッシュさせる恐れのあるスクリプトは実行前にブロックされる。

かつて、深いレベルでデバッグするにはカーネルモジュールを導入する(カーネルパニックのリスクが高い)か、ptrace を使用する(アプリが10倍近く低速化する)しかなかった。eBPFはその両方を解決する。安全でありながら、オーバーヘッドがほとんどない高いパフォーマンスを維持できるのだ。

BCCの強力な機能

純粋なC言語でeBPFコードを書くのは苦行に近い。そこでBCCの出番だ。BCCを使えば、PythonやLuaでロジックを簡潔に記述できる。重い処理はBCCが自動的にeBPFバイトコードへコンパイルしてくれる。一般的なシステムトラブルの多くは、すでにBCCツールキット内に専用ツールが用意されているので、名前を呼んで実行するだけでいい。

実践:遅延の犯人を追い詰める

午前2時の話に戻ろう。execsnoop で異常が見つからなかったため、私はディスクI/Oを疑った。特定のプロセスが帯域を独占し、他のリクエストを待機させている可能性がある。

ステップ1:biolatencyでディスクのレイテンシを確認する

sudo biolatency-bpfcc 1 10

このコマンドは、I/Oレイテンシをヒストグラム形式で統計化する。グラフがマイクロ秒(usecs)の範囲に収まっていてれば正常だ。しかし、ミリ秒(msecs)や秒単位に跳ね上がっている場合は、ディスクに問題があるか、過負荷状態にあることが確実だ。

ステップ2:過剰に開かれているファイルを特定する

I/Oが遅いと感じたら、opensnoop を使ってアプリケーションがどのファイルにアクセスしているかを確認しよう:

sudo opensnoop-bpfcc

以前、無限ループに陥ったJavaアプリが、毎秒2,000回も設定ファイルを開閉し続けているのを発見したことがある。アプリのログには何も出ていなかったが、opensnoop は即座にその犯人を突き止めた。

ステップ3:tcptracerでネットワークをデバッグする

マイクロサービス間やデータベースの接続エラーが疑われる場合は、tcptracer ですべてのTCP接続を追跡できる:

sudo tcptracer-bpfcc

このツールは、接続が確立されるたびに送信元IP、送信先IP、ポート番号を明確にリストアップする。ファイアウォールが誤って接続をブロックしていないかを素早く判断するのに非常に役立つ。

応用:独自の監視スクリプトを作成する

既存のツールだけでは不十分なこともある。例えば、誰かが /var/www ディレクトリ内で rm -rf を実行したときに警告を受け取りたいとしよう。BCCとPythonを使えば、数行のコードで実現可能だ。

BCCスクリプトの基本構造は以下のようになる:

from bcc import BPF

# カーネル内で直接実行されるC言語コード
program = """
int kprobe__do_sys_open(struct pt_regs *ctx, int dfd, const char __user *filename) {
    // ここにチェックロジックやログ記録を記述
    return 0;
}
"""

# カーネルにロードして実行
b = BPF(text=program)
print("監視中... Ctrl+Cで停止します。")
b.trace_print()

カーネル空間(kprobes)やユーザー空間(uprobes)のほぼすべての関数に介入できる。カスタマイズの可能性は無限大だ。

実戦でeBPFを活用するためのTips

  • カーネルバージョン: eBPFには最低でもカーネル4.1が必要だ。しかし、安定して全機能を利用するには、カーネル4.15以上(Ubuntu 18.04以降)を推奨する。
  • オーバーヘッド: 非常に高速だが、1秒間に数百万回呼ばれる関数(ネットワークパケットの処理など)にプローブを設置すると、サーバーに負荷がかかる可能性がある。監視ポイントの選定は慎重に行おう。
  • 権限: ほとんどのツールは、システムカーネルに直接介入するため、root権限または CAP_SYS_ADMIN ケーパビリティを必要とする。
  • ドキュメントの活用: /usr/share/bcc/tools/doc/ ディレクトリには、各ツールのサンプルテキストファイルが用意されている。困難なデバッグに挑む前に、一度目を通しておくと良いだろう。

eBPFをマスターすることは、謎のトラブルを解決するだけでなく、Linuxがどのように動作しているかを深く理解することにも繋がる。topiostat といった従来のコマンドで行き詰まったときは、迷わずeBPFを頼ってみてほしい。静かな当直の夜が続き、隠れたバグに怯えることがなくなることを願っている!

Share: