カーネルを再コンパイルする時代は終わった:eBPFとCiliumで実現する「次世代」の観測性とネットワーク
こんにちは。夜中の2時に「なぜかパケットが特定のノードで消える」という謎の現象と戦い、結局カーネルのバグに辿り着いて絶望した経験があるSREの皆さん、お疲れ様です。
かつて、ネットワークの挙動を深く調査しようとすれば、モジュールを自作してカーネルを再コンパイルしたり、iptablesの長大なチェインを眺めて頭を抱えたりするのが常でした。しかし、今は違います。eBPF(Extended Berkeley Packet Filter)という強力な武器が、Linuxカーネルを「ブラックボックス」から「完全に制御可能なプラットフォーム」へと変貌させました。
今回は、現代のクラウドネイティブ環境の屋台骨であるeBPFと、それを活用したCiliumについて、実務的な視点で深掘りしていきます。
—
1. eBPFとは何か?:カーネルを汚さずに「魔法」をかける技術
一言で言えば、eBPFは「カーネルのソースコードをいじらずに、カーネル内の任意の場所で独自のロジックを実行できる仕組み」です。
通常、パケット処理やシステムコールのトレーシングを行うには、カーネルモジュールを読み込む必要がありました。しかし、もしそのモジュールがバグを含んでいれば、システム全体がパニックを起こしてサーバーは道連れです。eBPFは、ユーザーが書いたプログラムを「検証器(Verifier)」という厳格なゲートキーパーに通し、メモリ破壊や無限ループを未然に防いだ上で、JIT(Just-In-Time)コンパイルしてカーネル内で実行します。
これにより、パケットがネットワークスタックのどの階層で破棄されたのか、なぜWeb APIのレイテンシがスパイクしたのかを、オーバーヘッドを最小限に抑えてリアルタイムに観測できるようになったのです。
—
2. なぜCiliumがネットワークの「デファクト」なのか
Kubernetesの標準的なネットワーク(CNI)であるkube-proxyは、古き良きiptablesやIPVSに依存しています。しかし、サービス数が増大するにつれ、iptablesのルール数は爆発的に増加し、パケット処理の計算コストは線形的に増えていきます。
ここで登場するのがCiliumです。Ciliumはiptablesを完全にバイパスし、eBPFを用いてパケットをカーネルの最深部(XDPやTCフック)で直接処理します。
Ciliumによるパケット処理フロー
1. NIC到達: パケットがカーネルに到着。
2. XDP(eXpress Data Path): ドライバー層でパケットを即座に破棄、または転送(超高速)。
3. TC(Traffic Control): Podのインターフェース付近でルーティングやフィルタリングを実行。
4. ソケット送出: アプリケーションへ直接パケットを渡す。
このフローにより、従来のコンテキストスイッチを大幅に削減し、マイクロサービス間の通信性能を劇的に向上させます。
—
3. 実践:Ciliumの観測機能を使ってみる
理論だけでは現場は動きません。Ciliumの強力なコマンドラインツールであるhubbleを使って、実際に通信を可視化してみましょう。
Hubbleのセットアップと観測
まず、Pod間の通信をリアルタイムで見守るコマンドです。
# クラスター内の全トラフィックをライブモニタリング
hubble observe --pod default/my-api-app
# 特定のHTTPエラーが発生している通信だけを抽出する例
hubble observe --pod default/my-api-app --http-status 500
ここで出力されるログには、verdict: DROPPED(破棄)や verdict: FORWARDED(転送)といった明確なステータスが含まれます。どのポリシーが適用され、なぜパケットが通過できなかったのかが一目瞭然です。
—
4. Web API設計で意識すべき「カーネルの観測」
Web APIを開発する際、単に curl でレスポンスコードを確認するだけでは不十分です。インフラ側で何が起きているかを知るために、Pythonで簡単なトレーシングのコンセプトコードを書いてみましょう(実際には bcc や bpftrace を用います)。
# bpftrace風のロジック: 送信されたパケットのサイズとレイテンシを計測
# 実際にはカーネルの kprobe を利用してフックする
from bcc import BPF
# eBPFプログラムの定義(C言語風の構文)
bpf_source = """
#include <uapi/linux/ptrace.h>
int kprobe__tcp_sendmsg(struct pt_regs *ctx) {
// TCP送信メッセージを検知してログを出力するイメージ
bpf_trace_printk("TCP sendmsg called!\\n");
return 0;
}
"""
b = BPF(text=bpf_source)
print("Tracing... Press Ctrl+C to stop.")
b.trace_print()
このようなコードを仕込むことで、APIの遅延が「アプリケーションコードの問題」なのか「ネットワークスタックのTCP再送待ち」なのかを、勘ではなくデータで切り分けることが可能になります。
—
5. 現場の教訓:トラブルシューティングのTips
最後に、長年現場で戦ってきた私からのアドバイスです。
1. iptablesとの併用には注意: CiliumなどのeBPFベースのCNIを導入する際は、ノード上の古いiptablesルールが干渉していないか必ず確認してください。iptables -L -n -v でルールを確認する癖はまだ捨てないでください。
2. カーネルバージョン: eBPFはLinuxカーネルの機能に深く依存します。特に古いカーネル(4.x系など)では、使えるeBPFのヘルパー関数が制限されることがあります。uname -a で環境を確認し、可能な限り最新のLTSカーネルを採用してください。
3. 可観測性(Observability)を開発に組み込む: APIの開発段階から、ヘッダーに X-Request-ID などを付与し、Hubble等のツールと紐付けられるように設計してください。障害時に「誰が、いつ、どこで」失敗したかを特定するスピードが段違いになります。
まとめ
eBPFは単なるネットワーク高速化の手段ではありません。それは、私たちが「カーネル」という暗黒の領域を照らすための「光」です。Ciliumのようなツールを使いこなすことは、現代のクラウドエンジニアにとって、避けては通れない必須スキルと言えるでしょう。
さあ、次はあなたの番です。まずは開発環境のクラスタにCiliumをインストールして、hubbleの画面を開くところから始めてみてください。きっと、これまで見えなかった「パケットの真実」が見えてくるはずです。
コメント