はじめに
アプリケーションのObservabilityを実現するには、これまでOpenTelemetry SDKをコードに組み込む「手動計装」が一般的でした。しかし、言語ごとにSDKを導入し、コードを修正し、依存関係を管理する作業は無視できないコストです。
eBPF(extended Berkeley Packet Filter)は、Linuxカーネル内でサンドボックス化されたプログラムを実行する技術です。もともとはネットワークパケットのフィルタリング用でしたが、現在ではセキュリティ、ネットワーキング、そしてObservabilityの領域で急速に活用が広がっています。
この記事では、eBPFを活用したGrafana Beylaを使い、アプリケーションコードを一切変更せずに分散トレーシングを実現する方法を、kind(Kubernetes in Docker)上のサンプルアプリで実際に手を動かしながら解説します。
Grafana Beylaとは
Grafana Beylaは、eBPFをベースとしたアプリケーション自動計装ツールです。2025年5月のGrafanaCON 2025で、Grafana LabsはBeylaのコアをOpenTelemetryプロジェクトに寄贈したことを発表しました。寄贈先での新プロジェクト名は**OpenTelemetry eBPF Instrumentation(OBI)**で、2025年11月に最初のalphaリリースが公開されています。現在のGrafana Beylaは、このupstreamプロジェクトのGrafana Labsディストリビューションという位置づけです。
特徴:
- ゼロコード計装: アプリケーションにSDKを追加する必要がない
- 言語非依存: Go, Java, Python, Node.js, Rust, C/C++など、コンパイル言語・インタプリタ言語を問わず対応
- カーネルレベルの計装: eBPFによりシステムコールやネットワークI/Oをカーネル空間で直接フック
- OpenTelemetry互換: OTLPでトレース・メトリクスをエクスポート
- 豊富なプロトコル対応: HTTP/HTTPS, HTTP/2, gRPC, SQL, Redis, MongoDB, Kafka, GraphQL, Elasticsearch/OpenSearchなど
BeylaはKubernetes上ではDaemonSetとして各ノードにデプロイされ、指定したnamespaceやポートのトラフィックを自動的に検出・計装します。
構成概要
今回構築する環境の全体像です。
-
Nginx: リバースプロキシとして
/api/へのリクエストをGo APIに転送 -
Go API:
net/http+database/sqlのみで構成したシンプルなTODO CRUD API - PostgreSQL: TODOデータの永続化
-
Beyla: eBPFで
appnamespaceのポート8080, 5432のトラフィックを自動計装 - OTel Collector: Beylaからのテレメトリデータを受信しバッチ処理
- Tempo: 分散トレーシングのバックエンド
- Grafana: トレースの可視化UI
ポイントは、Go APIにはOpenTelemetry SDKを一切組み込んでいないことです。net/httpとdatabase/sqlだけの素朴なコードを、Beylaが外部からカーネルレベルで計装します。
環境構築
前提条件
以下がインストールされていることを確認してください。
- Docker
- kind
- kubectl
サンプルリポジトリの取得
git clone https://github.com/rxmrsd/ebpf-beyla-kind.git
cd ebpf-beyla-kind
サンプルアプリの概要
Go APIのコードを見てみましょう(app/api/main.goの抜粋)。
func main() {
// PostgreSQL への接続(リトライ付き)
dsn := fmt.Sprintf(
"host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
getEnv("DB_HOST", "postgres"),
getEnv("DB_PORT", "5432"),
getEnv("DB_USER", "postgres"),
getEnv("DB_PASSWORD", "postgres"),
getEnv("DB_NAME", "postgres"),
)
// ... 接続処理 ...
mux := http.NewServeMux()
mux.HandleFunc("/health", handleHealth)
mux.HandleFunc("/api/todos", handleTodos)
mux.HandleFunc("/api/todos/", handleTodoByID)
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}
見ての通り、OpenTelemetry関連のインポートやコードは一切ありません。純粋なnet/httpサーバーです。
Dockerイメージのビルド
docker build -t sample-api:local ./app/api
docker build -t sample-nginx:local ./app/nginx
kindクラスタの作成
kindの設定ファイル(k8s/kind-config.yaml):
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30080
hostPort: 8080 # Nginx → localhost:8080
protocol: TCP
- containerPort: 30030
hostPort: 3000 # Grafana → localhost:3000
protocol: TCP
extraPortMappingsにより、kindノードのNodePortをホストマシンに公開しています。
kind create cluster --name ebpf-demo --config k8s/kind-config.yaml
イメージのロード
kindクラスタはローカルのDockerイメージレジストリに直接アクセスできないため、明示的にロードします。
kind load docker-image sample-api:local sample-nginx:local --name ebpf-demo
アプリケーションのデプロイ
# Namespace 作成
kubectl apply -f k8s/namespace.yaml
# PostgreSQL
kubectl apply -f k8s/postgres/
# Go API(PostgreSQL 起動を initContainer で待機)
kubectl apply -f k8s/api/
# Nginx(リバースプロキシ)
kubectl apply -f k8s/nginx/
Podがすべて Runningになるまで待ちます。
kubectl get pods -n app -w
NAME READY STATUS RESTARTS AGE
postgres-xxxxxxxxxx-xxxxx 1/1 Running 0 30s
api-xxxxxxxxxx-xxxxx 1/1 Running 0 25s
nginx-xxxxxxxxxx-xxxxx 1/1 Running 0 20s
Observabilityスタックのデプロイ
# Observability 用 Namespace
kubectl apply -f k8s/observability/namespace.yaml
# Beyla(eBPF 計装エージェント)
kubectl apply -f k8s/observability/beyla/
# OpenTelemetry Collector
kubectl apply -f k8s/observability/otel-collector/
# Tempo(トレースバックエンド)
kubectl apply -f k8s/observability/tempo/
# Grafana(可視化)
kubectl apply -f k8s/observability/grafana/
kubectl get pods -n observability -w
NAME READY STATUS RESTARTS AGE
beyla-xxxxx 1/1 Running 0 30s
otel-collector-xxxxxxxxxx-xxxxx 1/1 Running 0 25s
tempo-xxxxxxxxxx-xxxxx 1/1 Running 0 20s
grafana-xxxxxxxxxx-xxxxx 1/1 Running 0 15s
動作確認
APIへリクエスト送信
# ヘルスチェック
curl http://localhost:8080/health
# {"status":"ok"}
# TODO 作成
curl -X POST http://localhost:8080/api/todos \
-H "Content-Type: application/json" \
-d '{"title": "eBPFを学ぶ"}'
# {"id":1,"title":"eBPFを学ぶ","completed":false,"created_at":"2026-02-20T..."}
# TODO 一覧取得
curl http://localhost:8080/api/todos
# [{"id":1,"title":"eBPFを学ぶ","completed":false,"created_at":"..."}]
# 複数回リクエストを送ってトレースデータを蓄積
for i in $(seq 1 10); do
curl -s http://localhost:8080/api/todos > /dev/null
done
Grafanaでトレースを確認
ブラウザで http://localhost:3000 にアクセスします(認証なしでAdminとして自動ログインします)。
- 左メニューのExploreを開く
- データソースでTempoを選択
-
SearchタブでService Nameを選択し、
apiやnginxのサービスを確認 - 任意のトレースをクリックして詳細を確認
トレースには以下のような情報が自動的に含まれています:
-
HTTPメソッドとパス:
GET /api/todos -
ステータスコード:
200 - レイテンシ: 各スパンの所要時間
- サービス間の依存関係: Nginx → Go API → PostgreSQLのフロー
これらすべてが、アプリケーションコードに一行も計装コードを追加せずに取得できています。
Beylaの仕組み深掘り
eBPFによるカーネルレベルのフック
BeylaはeBPFプログラムをカーネルに注入し、以下のポイントにフックします:
-
ソケット関連のシステムコール:
accept,connect,read,write,closeなど - HTTP/gRPCパーサー: カーネル空間でプロトコルを解析し、リクエスト/レスポンスのメタデータを抽出
-
言語固有のフック: Goの
net/httpやJavaのjava.netなど、ランタイムの既知関数にuprobesでアタッチ
eBPFプログラムはカーネル内の検証器(verifier)を通過する必要があり、安全性が保証されています。無限ループやカーネルクラッシュを引き起こすコードはロードされません。
Discovery設定の解説
BeylaのConfigMapを詳しく見てみましょう。
discovery:
services:
- k8s_namespace: app
open_ports: 8080,5432
この設定でBeylaは以下の動作をします:
- Kubernetes APIを通じて
appnamespaceのPodを監視 - ポート8080(Go API)と5432(PostgreSQL)をリッスンしているプロセスを検出
- 検出したプロセスに対してeBPFプログラムをアタッチ
新しいPodがスケールアウトで追加された場合も、自動的に検出・計装されます。
privileged + hostPIDが必要な理由
BeylaのDaemonSetでは以下の設定が必須です:
spec:
hostPID: true
containers:
- name: beyla
securityContext:
privileged: true
-
hostPID: true: ホスト(ノード)のPID namespaceを共有し、他のPodのプロセスを認識するために必要 -
privileged: true: eBPFプログラムのカーネルへのロードにはCAP_BPF、CAP_PERFMONなどの権限が必要。privilegedはこれらを包含する
セキュリティについての補足: 本番環境では
privileged: trueの代わりに、必要最小限のcapabilities(CAP_BPF,CAP_PERFMON,CAP_NET_ADMIN,CAP_SYS_PTRACE)を個別に指定することが推奨されます。
従来のSDK計装との比較
| 観点 | Beyla(eBPF) | OpenTelemetry SDK |
|---|---|---|
| コード変更 | 不要 | 必要(SDKの初期化、スパン作成など) |
| 言語サポート | 言語非依存(カーネルレベル) | 言語ごとにSDKが必要 |
| 導入コスト | DaemonSetをデプロイするだけ | 各サービスにSDKを統合 |
| カスタムスパン | 不可(自動検出のみ) | 自由に追加可能 |
| ビジネスロジックの計装 | 不可 | 可能(カスタム属性、イベント) |
| トレースの粒度 | HTTP/gRPC/DBレベル | 関数・メソッドレベルまで可能 |
| パフォーマンス影響 | 極めて小さい(カーネル空間で動作) | SDKの初期化・スパン処理のオーバーヘッド |
| レガシーアプリ対応 | 得意(コード変更不可な環境) | 困難(コード変更が必須) |
| セキュリティ要件 | privileged権限が必要 | 特別な権限不要 |
使い分けの指針
-
Beylaが向いているケース:
- レガシーアプリや修正不可なサードパーティサービスの計装
- まずは最小限のコストでObservabilityを導入したい場合
- マイクロサービス群を一括で計装したい場合
-
SDKが向いているケース:
- ビジネスロジック固有のスパンやメトリクスが必要な場合
- カスタム属性やイベントで詳細なコンテキストを付与したい場合
- セキュリティポリシーでprivilegedコンテナが許可されない環境
-
ハイブリッドアプローチ: 実際のプロダクション環境では、Beylaでベースラインの計装を行い、重要なサービスにはSDKで詳細な計装を追加する組み合わせが効果的です。
Beylaの最新動向: OpenTelemetryへの寄贈
2025年5月、Grafana LabsはBeylaのコアコードをOpenTelemetryプロジェクトに寄贈しました。これにより、eBPFベースのゼロコード計装はベンダー中立なOpenTelemetryエコシステムの一部として発展していくことになります。
経緯
| 時期 | 出来事 |
|---|---|
| 2024年10月 | Grafana LabsがOpenTelemetryコミュニティに寄贈を提案 |
| 2025年5月 | GrafanaCON 2025で正式発表、初回コードドロップ完了 |
| 2025年11月 | OpenTelemetry eBPF Instrumentation(OBI)の最初のalphaリリース |
OpenTelemetry eBPF Instrumentation(OBI)とは
寄贈先でのプロジェクト名は**OpenTelemetry eBPF Instrumentation(OBI)**です。Grafana Labs, Splunk, Coralogix, Odigosなど複数の組織がSIG(Special Interest Group)に参加し、共同で開発を進めています。
OBIはライブラリレベルではなくプロトコルレベルで計装を行うため、プログラミング言語やフレームワークに依存せず、単一のツールですべてのアプリケーションを一貫して計装できるのが特徴です。
Grafana Beylaとの関係
Grafana Beylaは今後も存続しますが、その位置づけは変わります。
- Beyla 2.5以降、upstreamのOBIコードを直接vendoringする構成に移行
- 将来的にはGrafana固有の機能のみを含む薄いラッパーとなる予定
- メンテナーはupstreamでの開発を優先し、コードの重複を避ける方針
つまり、eBPFベースの自動計装のコア技術はOpenTelemetryコミュニティ全体の共有資産となり、Beylaはそのエンタープライズ向けディストリビューションという関係になります。
クリーンアップ
kind delete cluster --name ebpf-demo
まとめ
この記事では、Grafana Beylaを使ってkindクラスタ上のアプリケーションをゼロコードで計装する方法を実践しました。
- eBPFを使うことで、アプリケーションコードに一切手を加えずに分散トレーシングを実現できる
- BeylaはDaemonSetとして各ノードにデプロイするだけで、指定したnamespaceのサービスを自動検出・計装する
- 取得されるトレースにはHTTPメソッド、パス、ステータスコード、レイテンシなどが含まれ、サービス間のフローも可視化される
- ただし、ビジネスロジック固有の計装が必要な場合は従来のSDKとの併用が現実的
eBPFベースの自動計装はObservabilityの「最初の一歩」として極めて有効です。BeylaのOpenTelemetryへの寄贈により、この技術はベンダー中立なエコシステムの中で今後さらに発展していくことが期待されます。まずはBeylaでシステム全体の可視性を確保し、必要に応じてSDKで深掘りする — そんなアプローチを検討してみてはいかがでしょうか。


