アプローチ比較:従来のパケットキャプチャ vs eBPFベースのオブザーバビリティ
火曜日の深夜2時14分、PagerDutyのアラートが鳴り響きます。チェックアウトサービスで504 Gateway Timeoutが断続的に発生し、後続の決済リクエストが次々と失敗しています。踏み台サーバーにログインしてkubectl get podsを実行すると、すべてのコンテナはHealthy(正常)状態です。アプリケーションログには不可解なコネクションリセット以外、ほとんど手掛かりが残されていません。クラスター内部のネットワークがブラックボックスと化している状況です。
Kubernetesの仮想ネットワーク内でパケットが消失した場合、エンジニアは通常、次の3つの断片的な手段に頼らざるを得ませんでした。
- tcpdumpを用いた一時的なデバッグコンテナ:対象ポッドのネットワーク名前空間にデバッグコンテナをアタッチするか、ノード上で直接
nsenterを実行して生のPCAPファイルをキャプチャします。確実な手法ではあるものの、障害対応中に行うには極めて時間がかかります。毎秒50,000パケットをキャプチャするとノードのCPU使用率が20%〜30%跳ね上がるリスクがあり、ターミナルにはポッド名、対象サービス、Kubernetes名前空間の情報が欠落した16進数のダンプデータが溢れかえります。 - サービスメッシュのサイドカー(Envoy / Istio):アプリケーション層のプロキシはHTTPトラフィックの処理に優れていますが、レイヤー7より下層で起きている事象を把握できません。iptablesのルールや基盤となるCNIがSYNパケットをサイレントに破棄している場合、サイドカープロキシは原因不明のアップストリームタイムアウトを報告するだけです。さらに、プロキシの注入によってポッドごとに80MB〜150MBのメモリオーバーヘッドが生じ、RTT(往復レイテンシ)も1ms〜3ms悪化します。
- Cilium HubbleによるカーネルレベルのeBPF:ポッドへのプロキシ注入や、インシデント深刻化後の生バイト解析に頼る代わりに、CiliumはExtended Berkeley Packet Filter(eBPF)プログラムをLinuxカーネルのフックへ直接ロードします。Hubbleはこのエンジンの上で動作し、ソケット層およびパケット層でフローを検査します。ハンドシェイク、DNSルックアップ、HTTPトランザクション、ドロップされたパケットは、ユーザー空間に届く前に送信元ポッド、接続先サービス、ラベル、名前空間といったKubernetesメタデータへマッピングされます。
ネットワークオブザーバビリティ戦略のメリット・デメリット
どの検査手法を採用するかは、本番環境の障害時におけるランタイムオーバーヘッド、運用上の安全性、コンテキストの明瞭性のバランスを考慮して決定する必要があります。
1. 従来のツール(tcpdump、iptablesロギング、conntrack)
- メリット:ほぼすべてのLinuxディストリビューションに標準搭載されており、クラスター全体のアーキテクチャ変更が不要。
- デメリット:キャプチャ実行中に高負荷なノードを枯渇させる重大なリスクがある。ワークロードのメタデータが欠落する。ローリングアップデート等で頻繁に入れ替わる一時的なポッドIPを追跡できない。
2. レイヤー7サービスメッシュプロキシ
- メリット:充実したHTTP/gRPCトレーシング。きめ細かなゴールデンシグナル(レイテンシ、エラー率)の取得。透過的な相互TLS(mTLS)の実現。
- デメリット:リソース消費が大きい(ワークロードのレプリカごとに100MB以上のRAMを消費することも多い)。TCP以外のトラフィックや低レベルなカーネルドロップを把握できない。プロキシの自動注入やアップグレードのライフサイクルが壊れやすい。
3. Cilium + Hubble
- メリット:カーネルレベルでのキャプチャによるオーバーヘッドが極めて小さい。サイドカーなしでL3/L4/L7の可視性をネイティブに提供。生フローをKubernetesラベルへ動的にマッピング。適用中の
CiliumNetworkPolicyルールによって遮断されたパケットを即座にフィードバック。 - デメリット:モダンなLinuxカーネルが必要(Linux 5.4以降、推奨はLinux 5.10以降)。CiliumをプライマリCNIとして導入するか、既存プロバイダーの上にチェーン構成で稼働させる必要がある。
推奨構成:耐障害性に優れたeBPFオブザーバビリティスタック
インシデントの対応中に、高トラフィックで停止してしまうようなロギングエージェントは役に立ちません。Ciliumはカーネル内のパケット検査とユーザー空間のデータ集約を切り離すことで、このボトルネックを防ぎます。
- Cilium Agent(DaemonSet):vethペアや物理NICなどのノードネットワークインターフェースにeBPFバイトコードをロードします。フローのメタデータをカーネル空間内のCPUごとのリングバッファに直接送信します。
- Hubble Server(Cilium Agentに内蔵):各ノード上のメモリ内リングバッファ(4,096エントリ)に直近のフローを保持します。この局所的なストレージ設計により、大量のトラフィックスパイクが発生してもネットワーク帯域の枯渇を防ぎます。
- Hubble Relay:各ノードのローカルgRPCソケットに接続する軽量な集中型デプロイメントで、クラスター全体のフローを単一のクエリ可能なエンドポイントに集約します。
- Hubble CLI & UI:運用者のコントロールデッキです。CLIはターミナル上でストリーム指向の即時フィルタリングを提供し、UIはリアルタイムのサービス依存関係グラフを生成してパケットドロップを視覚的にハイライトします。
実装ガイド:ゼロから始めるリアルタイムフロー検査
サービスが原因不明のサイレント障害を起こした際、カーネル層でパケットフローを瞬時にトレースできるかどうかで、10分でトリアージを終えられるか、6時間の障害報告書作成に追われるかの違いが生じます。
ステップ1:HubbleとRelayを有効にしてCiliumをインストール
公式Helmチャートを使用してCiliumをデプロイまたはアップグレードします。その際、Hubble Relay、Web UI、コアメトリクスモニターを明示的に有効化します。
helm repo add cilium https://helm.cilium.io/
helm repo update
helm upgrade --install cilium cilium/cilium \
--namespace kube-system \
--set hubble.enabled=true \
--set hubble.relay.enabled=true \
--set hubble.ui.enabled=true \
--set hubble.metrics.enabled="{dns,drop,tcp,flow,port-distribution,icmp,http}"
クラスター全体でロールアウトのステータスを確認します:
kubectl rollout status daemonset/cilium -n kube-system
kubectl rollout status deployment/hubble-relay -n kube-system
kubectl rollout status deployment/hubble-ui -n kube-system
ステップ2:Hubble CLIのインストールと接続
Webダッシュボードは全体的なアーキテクチャの把握に有用ですが、障害対応の最前線ではスタンドアロンCLIが最も強力な武器となります。公式のクライアントバイナリを取得します:
export HUBBLE_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/hubble/master/stable.txt)
curl -L --fail --remote-name "https://github.com/cilium/hubble/releases/download/${HUBBLE_VERSION}/hubble-linux-amd64.tar.gz"
tar xzvf hubble-linux-amd64.tar.gz
sudo mv hubble /usr/local/bin/
rm hubble-linux-amd64.tar.gz
次に、Hubble Relayのポートフォワードをバックグラウンドで実行します:
cilium hubble port-forward &
ローカルCLIがクラスターと正常に通信できることを確認します:
hubble status
Hubble Relayが正常に稼働し、全ノードに100%接続されている旨が出力されます(例:Connected Nodes: 12/12)。
ステップ3:ドロップされたトラフィックをリアルタイムに追跡
深夜2時14分のインシデントに戻りましょう。checkoutポッドがpayment APIと通信できない状態です。ログをgrepするのはやめ、クラスター全体でドロップされているパケットをリアルタイムにストリーミング監視します:
hubble observe --verdict DROPPED --follow
ターミナルのスクロールが速すぎる場合は、production名前空間とpaymentデプロイメントに対象を絞り込みます:
hubble observe \
--namespace production \
--to-label app=payment \
--verdict DROPPED \
-o compact
数秒以内に、eBPFがドロップイベントとその正確なカーネル理由をログに出力します:
TIMESTAMP SOURCE DESTINATION TYPE VERDICT
Nov 07 02:18:41.102 production/checkout-6d48b99-xk92w:42180 production/payment-574b6df-j9xkp:8080 Policy denied DROPPED (Policy denied (Ingress))
原因が判明しました。適用中のIngressポリシーが、paymentコンテナの仮想インターフェース直前でパケットをブロックしていました。推測もパケットキャプチャも、無駄な再起動の繰り返しも一切不要です。
ステップ4:不整合が発生しているNetworkPolicyの確認と修正
paymentサービスに適用されているポリシーを確認します:
kubectl get ciliumnetworkpolicies -n production -o yaml
問題にお気づきでしょうか。ポリシーはtier: frontendラベルを持つポッドからのインバウンドトラフィックを許可していました。しかし、直近のデプロイパイプラインでネットワークルールを更新しないまま、checkoutのセレクターをapp: checkoutに変更してしまっていたのです。明示的なCiliumNetworkPolicyを作成して修正します:
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
name: allow-checkout-to-payment
namespace: production
spec:
endpointSelector:
matchLabels:
app: payment
ingress:
- fromEndpoints:
- matchLabels:
app: checkout
toPorts:
- ports:
- port: "8080"
protocol: TCP
修正したポリシーを適用します:
kubectl apply -f allow-checkout-to-payment.yaml
ステップ5:ポリシー適用の検証とレイヤー7フローの監視
ターミナルのログフィードは開いたままにしておきます。マニフェストを適用してから1秒も経たずにパケットドロップが停止し、パケットステータスがDROPPEDからFORWARDEDへと切り替わります:
hubble observe \
--namespace production \
--from-pod checkout \
--to-pod payment \
--protocol tcp \
--follow
アプリケーション層も確認したい場合は、--protocol httpフラグを付与してHTTPステータスコードとネットワークレイテンシを検査します:
hubble observe \
--namespace production \
--to-label app=payment \
--protocol http \
-o dict
HTTPのPOST /chargeリクエストが12ms以内に200 OKを返していることが確認できます。オンコール担当のチームメンバーが目を覚ます前に、カーネルテレメトリによる確証と客観的なデータに基づいて障害復旧が完了しました。

