eBPF可観測ツールの開発:ゼロから始める

この記事で解決する課題

ゼロからeBPF(拡張Berkley Packet Filter)を用いた可観測性ツールを開発する完全なプロセスを解説します。

結論

eBPFツール開発の鍵は、要件の明確化、適切な技術スタックの選択、段階的な反復、十分なテストです。

背景

2024年5月、プロセスのファイルアクセス動作を監視するツールが必要となりました。

要件

  1. プロセスが開くファイルを記録する
  2. ファイルアクセス回数を集計する
  3. アクセスパターンを分析する
  4. 結果をリアルタイムで表示する

初期の考え方

要件分析

コア要件:

  • openシステムコールの追跡
  • プロセス名、ファイル名、アクセス時刻の記録
  • 各ファイルのアクセス回数の集計
  • 結果のリアルタイム表示

非コア要件:

  • フィルタリング(プロセス名、ファイル名)のサポート
  • アラート(異常アクセス)のサポート
  • 履歴クエリのサポート

技術選定

検討した方案:

  1. strace
  2. auditd
  3. 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;
}

タグ: eBPF Linuxカーネル システム監視 可観測性 ファイルアクセス監視

5月30日 12:26 投稿