現場の課題:iptablesが直面するスケーラビリティの限界
昨年の秋、当社のプライマリKubernetesクラスターは40台のベアメタルワーカーノード上で500以上のマイクロサービスを稼働させる規模にまで拡大しました。全体のCPUおよびメモリ使用率は45%程度と安定しているように見えましたが、チェックアウトサービスや決済サービスで突発的なレイテンシスパイクが頻発するようになりました。定常的な15,000 req/secのバーストトラフィック発生時には、通常8msだったp99レスポンスタイムが240ms以上にまで跳ね上がっていたのです。
監視ダッシュボードでは、以下の3つの深刻な問題が浮き彫りになっていました。
- アプリケーションのスループットがそれほど高くない場合でも、ワーカーノードのCPUでカーネル空間の処理時間(
system %)が高止まりしていた。 - Horizontal Pod Autoscaler(HPA)によってレプリカが追加されるたびに、Serviceのルーティング更新が全ノードに行き渡るまでに20〜45秒もかかっていた。
- パケットドロップの調査には、インシデント発生中にエフェメラルなPodへ
tcpdumpをアタッチする必要があり、貴重なデバッグ時間が浪費されていた。
頻繁なPodの生成・破棄(churn)により、ネットワークサブシステムが限界に達していました。
根本原因:大規模環境でkube-proxyが破綻する理由
この問題を解決するため、標準的なKubernetesネットワークの内部挙動を詳細に調査しました。デフォルトのkube-proxyは、Linuxのiptables(またはIPVS)を設定することで、インバウンドのServiceトラフィックを各Podエンドポイントへルーティングします。
1. O(N) の線形・逐次パケット探索
iptablesでは、Linuxカーネルがファイアウォールルールを先頭から順番に走査します。クラスター内に5,000個のバックエンドエンドポイントが存在する場合、1つの受信パケットが適切な宛先に到達するまでに数千回ものルール評価が行われる可能性があります。この線形探索が、カーネルのネットワーク高速処理パスで膨大なCPUリソースを消費します。
2. テーブル全体の同期ロック
Podが起動、停止、またはReadyプローブに失敗するたびに、kube-proxyはiptablesの全ルールリストを再生成して書き直します。急激なオートスケーリングが発生すると、カーネルはアプリケーショントラフィックのルーティングではなく、メモリバッファのロックやコピー処理に貴重なCPUサイクルを費やすことになります。
3. Conntrackテーブルの枯渇とサイレントドロップ
従来のkube-proxyは、ServiceのNAT処理をLinuxのコネクショントラッキング(conntrack)に大きく依存しています。同時接続TCPセッション数が200,000を超えると、テーブルの割り当て上限に達してしまいます。その結果、カーネルはnf_conntrack: table full, dropping packetエラーを出力し、アクティブなクライアント接続をサイレントに破棄し始めます。
選択肢の比較:Calico vs IPVS vs Cilium eBPF
本番ノードに変更を加える前に、10,000個のServiceエンドポイントと20,000の同時RPS環境を想定し、3つのネットワークアーキテクチャでベンチマーク検証を実施しました。
| アーキテクチャ指標 | Calico(標準 iptables) | kube-proxy(IPVSモード) | Cilium CNI(eBPFネイティブ) |
|---|---|---|---|
| サービス検索の計算量 | O(N) 逐次探索 | O(1) IPVS ipsetハッシュ | O(1) BPF Map直接メモリルックアップ |
| パケットのフックポイント | Netfilterフックチェーン | Netfilter + IPVSフック | ソケット層およびTC / XDP直接アタッチ |
| ルール更新の遅延(5,000エンドポイント) | 約12〜30秒 | 約2〜5秒 | < 50ミリ秒(アトミックなMap入れ替え) |
| レイヤー7ネットワークテレメトリ | Envoyサイドカーの注入が必要 | 組み込み機能なし | Hubble eBPFによるネイティブ対応 |
| 大規模環境でのカーネルオーバーヘッド | 高いCPU消費 | 中程度 | 最小(ノードのシステムCPUが約35%削減) |
IPVSはハッシュセットを用いることで線形探索の問題を解決しますが、依然としてNetfilterのconntrackによるオーバーヘッドが残ります。一方、Ciliumは別のアプローチを取ります。extended Berkeley Packet Filter(eBPF)を活用し、Linuxのソケット層やTraffic Control(tc)フックでサンドボックス化されたプログラムを直接実行します。これにより、パケットはNetfilterスタックを完全にバイパスします。
実践手順:Strictモードでkube-proxyを完全置換するCiliumの導入
ステージング環境および本番クラスターにおいて、kube-proxyを完全に置き換える形でCiliumを導入しました。以下が実際の導入手順です。
ステップ1:カーネルバージョンの確認とBPFファイルシステムの初期マウント
Ciliumの動作にはモダンなeBPFヘルパーが必要です。Linuxカーネル5.4以上(5.15+ LTSまたは6.xを推奨)を使用してください。また、全クラスターノードでBPFファイルシステム(bpffs)がマウントされていることを確認します。
# 稼働中のカーネルバージョンを確認
uname -r
# 未マウントの場合はbpffsをマウント
mount | grep /sys/fs/bpf || sudo mount -t bpf bpffs /sys/fs/bpf
ステップ2:既存のkube-proxy DaemonSetを削除
kubeadmで新規構築するクラスターの場合は、初期化時に--skip-phases=addon/kube-proxyを指定します。稼働中の既存クラスターの場合は、DaemonSetを削除し、残存するレガシーなiptablesチェーンをクリアします。
# kube-proxyのDaemonSetとConfigMapを削除
kubectl -n kube-system delete daemonset kube-proxy
kubectl -n kube-system delete configmap kube-proxy
# ワーカーノード上の残存KUBE-* iptablesルールをフラッシュ(SSHまたはAnsible経由)
sudo iptables-save | grep -v KUBE | sudo iptables-restore
ステップ3:Helmを使用したCiliumのインストール
ネットワークPodが完全に初期化される前でもCiliumがクラスター間通信を確立できるよう、API Serverのコントロールプレーンエンドポイントを明示的に指定します。
# CiliumのHelmリポジトリを追加・更新
helm repo add cilium https://helm.cilium.io/
helm repo update
# 完全なソケット負荷分散とHubbleの可観測性を有効化してCiliumをデプロイ
helm install cilium cilium/cilium --version 1.15.4 \
--namespace kube-system \
--set kubeProxyReplacement=true \
--set k8sServiceHost="10.0.0.100" \
--set k8sServicePort="6443" \
--set bpf.masquerade=true \
--set ipam.mode="kubernetes" \
--set hubble.enabled=true \
--set hubble.relay.enabled=true \
--set hubble.ui.enabled=true \
--set autoDirectNodeRoutes=true
※ 10.0.0.100 は、ご利用のクラスターのAPI ServerロードバランサーVIPまたはプライマリコントロールプレーンのIPアドレスに置き換えてください。
ステップ4:eBPFホストルーティングとService Mapの確認
Ciliumデーモンのステータスを確認し、ソケットレベルの負荷分散がすべてのルーティング処理を引き継いでいることを検証します。
# Ciliumクラスター全体の健全性を確認
cilium status --wait
# kube-proxyの完全置換がStrictモードで稼働していることを確認
kubectl -n kube-system exec -ti ds/cilium -c cilium-agent -- cilium-dbg status | grep -A 10 "KubeProxyReplacement"
出力結果にKubeProxyReplacement: True (Strict)と表示されていれば成功です。これにより、NodePort、ClusterIP、HostPort、および外部トラフィックがeBPFマップを通じて直接管理されるようになります。
ステップ5:Hubbleを使用したトラフィックとパケットドロップのリアルタイム追跡
HubbleはカーネルのリングバッファからeBPFイベントを直接読み取るため、サイドカーやtcpdumpのオーバーヘッドなしに通信フローを詳細に検査できます。
# クラスター全体のパケットドロップをリアルタイムでストリーミング監視
cilium hubble observe --type drop
# checkoutネームスペースへ入るトラフィックを追跡
cilium hubble observe --namespace checkout --follow
本番環境への導入から6か月後の成果
kube-proxyからの移行により、高負荷時におけるクラスターのネットワーク安定性は劇的に向上しました。
- テールレイテンシの予測可能性向上: ピーク時のトラフィック急増時でも、決済マイクロサービスのp99レイテンシが240msから安定した6msへと大幅に低減しました。
- コンピュートリソースの回収: 以前はiptablesチェーンの評価やconntrackテーブルの管理で浪費されていたワーカーノードのCPUキャパシティが、約14%回収されました。
- 即時性のあるオートスケーリング: 100以上のレプリカを追加する急激なスケールアウト時でも、アトミックなBPFマップ挿入によりルーティングが即座に更新され、ネットワークジッターが発生しなくなりました。
数百規模のサービスが稼働している、あるいは数千以上の同時接続を処理するKubernetesインフラストラクチャを運用している場合、Cilium eBPFへの移行はパフォーマンスと運用の両面で即座に大きなメリットをもたらします。

