この記事で解決する課題
ゼロからeBPF(拡張Berkley Packet Filter)を用いた可観測性ツールを開発する完全なプロセスを解説します。
結論
eBPFツール開発の鍵は、要件の明確化、適切な技術スタックの選択、段階的な反復、十分なテストです。
背景
2024年5月、プロセスのファイルアクセス動作を監視するツールが必要となりました。
要件
- プロセスが開くファイルを記録する
- ファイルアクセス回数を集計する
- アクセスパターンを分析する
- 結果をリアルタイムで表示する
初期の考え方
要件分析
コア要件:
openシステムコールの追跡- プロセス名、ファイル名、アクセス時刻の記録
- 各ファイルのアクセス回数の集計
- 結果のリアルタイム表示
非コア要件:
- フィルタリング(プロセス名、ファイル名)のサポート
- アラート(異常アクセス)のサポート
- 履歴クエリのサポート
技術選定
検討した方案:
- strace
- auditd
- eBPF
比較:
| 方案 | 性能 | 正確性 | リアルタイム性 | 開発コスト |
|---|---|---|---|---|
| strace | 低 | 高 | 高 | 低 |
| auditd | 中 | 高 | 中 | 中 |
| eBPF | 高 | 高 | 高 | 高 |
eBPFを選択しました。性能とリアルタイム性が最も優れているためです。
アーキテクチャ設計
アーキテクチャ:
- eBPFプログラム:openシステムコールをフックし、データを収集する
- ユーザースペースプログラム:データを読み取り、結果を表示する
データフロー:
openシステムコール -> eBPFプログラム -> ringbuf -> ユーザースペースプログラム -> 表示
実際の技術的な道のり
ステップ1:eBPFプログラムの設計
データ構造の設計:
struct file_access_record {
__u32 process_id;
char process_name[TASK_COMM_LEN];
char accessed_file[256];
__u64 access_time_ns;
};
Mapの設計:
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 1 << 24);
} access_events SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 10240);
__type(key, char[256]);
__type(value, __u64);
} access_counter SEC(".maps");
eBPFプログラムの記述:
#include "vmlinux.h"
#include
#include
struct file_access_record {
__u32 process_id;
char process_name[TASK_COMM_LEN];
char accessed_file[256];
__u64 access_time_ns;
};
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 1 << 24);
} access_events SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 10240);
__type(key, char[256]);
__type(value, __u64);
} access_counter SEC(".maps");
SEC("kprobe/__x64_sys_openat")
int kprobe_openat(struct pt_regs *ctx)
{
struct file_access_record *record;
u64 filename_ptr;
record = bpf_ringbuf_reserve(&access_events, sizeof(*record), 0);
if (!record) {
return 0;
}
record->process_id = bpf_get_current_pid_tgid() >> 32;
bpf_get_current_comm(&record->process_name, sizeof(record->process_name));
filename_ptr = ctx->di; // openatの第一引数(ファイル名)
bpf_probe_read_user_str(&record->accessed_file, sizeof(record->accessed_file), (void *)filename_ptr);
record->access_time_ns = bpf_ktime_get_ns();
bpf_ringbuf_submit(record, 0);
return 0;
}