Service の向き先が変わると、通信を転送するルールも更新する必要があります。 Service や Pod が増えるほど、ルールを調べる処理と、書き換える処理の負担が気になってきます。
この章では、その 2 つの処理を分けて見ます。 iptables、IPVS、nftables、eBPF で何が変わるのかを、同じ通信の例で比べていきます。
GOAL この章のゴール
- ルールの照合と更新、それぞれの負担を区別できる
- iptables、IPVS、nftables、eBPF の違いを説明できる
- 方式を選ぶときに、規模や必要な機能を確認できる
年表
kube-proxy は Kubernetes 1.0 (2015 年) の時点では userspace モード で、ノード上のプロセスが TCP 接続を受けて Pod に中継していました。 1.2 (2016 年) で iptables モードが既定になり、書き換えをカーネルの netfilter に任せる形に変わります。 2018 年の 1.11 で ipvs モードが GA になり、2019 年 8 月に Cilium 1.6 が「100% kube-proxy replacement」を掲げて kube-proxy を丸ごと置き換える機能を出しました。 nftables モードは 2023 年の 1.29 で alpha、2025 年の 1.33 で GA です。 userspace モードは 1.26 で削除され、ipvs モードは 1.35 で非推奨になりました。
iptables モードが遅くなる 2 つの理由
iptables モードはクラスタ規模に比例して遅くなります。 原因は 2 つあり、それぞれ別の場所で効きます。
1 つ目は 照合の線形スキャン です。 nftables モードを紹介した Kubernetes のブログによれば、iptables モードのルールセットの最上位には「Service の IP とポートごとに 1 つずつ照合ルール」が並び、カーネルがパケットを全 Service のルールと照合する時間は Service 数に比例 (O(n)) します。 ただし比較が走るのは接続の最初の 1 パケットだけです。 第 2 章で見たとおり、nat 型のチェーンを通るのは接続の最初のパケットだけで、2 パケット目以降は conntrack の表に乗り、ルールを通らずに書き換えられます。 そのため影響が出るのは新規接続のレイテンシで、確立済みの接続のスループットは落ちません。
2 つ目は 更新のコスト です。
同じブログは、iptables モードは「もともと更新のたびに全ルールを書き直していて、1.26 から変更の無いルールの大半を飛ばすよう改善したが、iptables-restore という API の制約で、更新のサイズはやはり Service 数に比例した」と説明しています。
公式ドキュメントも、Pod と Service が数万あるクラスタでは、Service や EndpointSlice が変わったときにカーネルへのルール更新に時間がかかる、と書いています。
その間は新しい Pod へ転送されない、消えた Pod へ転送され続けるといった遅れが出ます。
minSyncPeriod で更新をまとめる設定がありますが、まとめた分だけ反映が遅れます。
ipvs: ハッシュで照合を解いた世代
IPVSアイピーブイエスIP Virtual Server。Linux カーネルに古くからある L4 ロードバランサーで、宛先 IP:ポートから backend の一覧をハッシュテーブルで引く。用語集で見る モードは、照合の問題をハッシュテーブルで解きました。 公式ドキュメントは「netfilter の hook を使う点は iptables モードと似ているが、ハッシュテーブルをデータ構造として使う」と説明しています。 宛先の検索は Service の数によらず一定時間で済み、振り分けアルゴリズムをラウンドロビンや最小接続数から選べる利点もあります。
一方で ipvs モードは netfilter を完全には離れていません。 公式ドキュメントは「カーネルの IPVS と iptables の両方の API を使う」と書き、2018 年の GA 時のブログは、パケットのフィルタリング、SNAT、NodePort の処理に iptables (と ipset) を併用すると説明しています。 さらに公式ドキュメントは、IPVS の API が Kubernetes の Service API とうまく噛み合わず、Service の全ての細かい挙動を正しく実装できなかった、として ipvs モードを 1.35 で非推奨にし、1.40 で既定で無効、1.43 で削除する予定を示しています。
nftables: map と差分更新
nftables モードは、照合と更新の両方を netfilter の中で解きます。
第 5 章で見た service-ips の map は、宛先 IP とプロトコルとポートの組をキーにした verdict map で、ブログの言い方では「ルールは 1 本だけで、map の表引きはほぼ O(1) なので、パケットの処理時間はクラスタの規模によらずほぼ一定」です。
更新は map の要素とチェーンの単位で行えるので、「更新のサイズは前回の同期から変わった Service と endpoint の数に比例する」だけで済みます。
公式ドキュメントは、nftables モードを iptables と ipvs 両方の後継と位置づけ、ipvs からの移行先として推奨しています。
ここまでの 3 世代を、同じ構成で見比べます。 Service を増やしたときに iptables の行数がどう伸び、nftables の map がどう変わるかを確認してください。
- default/web10.96.12.34:80 · NodePort 3008010.244.0.23:808010.244.0.31:8080
- default/api10.96.40.7:44310.244.0.52:8443
eBPF (Cilium) (15 行)
# cilium-dbg service list (cilium-agent の Pod の中で)
ID Frontend Service Type Backend
1 10.96.12.34:80/TCP ClusterIP 1 => 10.244.0.23:8080/TCP (active)
2 => 10.244.0.31:8080/TCP (active)
2 0.0.0.0:30080/TCP NodePort 1 => 10.244.0.23:8080/TCP (active)
2 => 10.244.0.31:8080/TCP (active)
3 10.96.40.7:443/TCP ClusterIP 1 => 10.244.0.52:8443/TCP (active)
# cilium-dbg bpf lb list (eBPF map cilium_lb4_services_v2 / cilium_lb4_backends_v3 の中身)
SERVICE ADDRESS BACKEND ADDRESS (REVNAT_ID) (SLOT)
10.96.12.34:80/TCP 0.0.0.0:0/TCP (1) (0) [ClusterIP, non-routable]
10.244.0.23:8080/TCP (1) (1)
10.244.0.31:8080/TCP (1) (2)
0.0.0.0:30080/TCP 0.0.0.0:0/TCP (1) (0) [ClusterIP, NodePort, non-routable]
10.244.0.23:8080/TCP (1) (1)
10.244.0.31:8080/TCP (1) (2)
10.96.40.7:443/TCP 0.0.0.0:0/TCP (2) (0) [ClusterIP, non-routable]
10.244.0.52:8443/TCP (2) (1)
# スロット 0 がサービス自身 (backend 数などのメタ)、1.. がバックエンド。
# tc / socket の eBPF プログラムが接続ごとにスロットを 1 つ選び、宛先を書き換える。
# nft list ruleset を見ても、kube-proxy 相当のルールはどこにも無い。Service や Pod を増減すると、3 つの表現が同時に変わります。iptables は Service と endpoint の数だけルールとチェーンが伸び、nftables は map の要素と Service ごとのチェーンが増え、eBPF は map のスロットが増えます。
eBPF: netfilter の外で解く
Cilium や Calico の eBPF モードは、書き換えを netfilter の外で行います。
Cilium のドキュメントによれば、eBPF プログラムを取り付けられる場所は 3 つあります。
NIC ドライバの最も早い段階で動く XDP、ネットワークスタックが初期処理を終えた直後に NIC や veth ごとに動く tc ingress/egress、そして TCP のイベントや send のたびに動く socket 層です。
表は eBPF map (ハッシュ) で、照合は 1 回の表引き、更新は map のエントリ 1 つの書き換えです。
この構成で kube-proxy を外すのが、Cilium の kube-proxy replacement です。
netfilter の外に出たことで、次の機能も使えます。
- socket LB:
connect()などのシステムコールの時点で宛先を backend に差し替える。アプリは Service に接続したつもりでも、ソケットは最初から backend に接続している。 - DSR (Direct Server Return):NodePort や LoadBalancer で受けたノードが別ノードの Pod へ転送するとき、SNAT せず、backend が Service の IP とポートを送信元にしてクライアントへ直接返す。第 6 章の「余計なホップ」が戻り方向で消え、クライアントの送信元 IP も backend に残る。
- XDP:NIC ドライバの段階で NodePort / LoadBalancer の転送を処理する。ドライバが XDP に対応している必要がある。
- Maglev:一貫性ハッシュで backend を選び、ノードが増減しても既存の接続が同じ backend に当たりやすくする。外部からの (N-S) 通信に適用される。
kube-proxy は消えるのか
2026 年でも、kube-proxy は Kubernetes 本体に残っていて、多くのクラスタで動いています。 理由は 3 つあります。
- Pod ネットワークの実装と独立している:公式ドキュメントは「Kubernetes はサービスプロキシの既定の実装として kube-proxy を提供し、一部の Pod ネットワーク実装は代わりに自前のプロキシを使う」と書いている。Flannel のように Service の書き換えを持たない実装では kube-proxy が要る。
- カーネル要件が緩い:Cilium 1.20 はカーネル 5.10 以降と BTF などの eBPF 関連設定を前提にし、nftables モードも 5.13 以降が要る。iptables モードはそれより古いカーネルでも動く。
- Service の新機能が先に入る:GKE のドキュメントは「kube-proxy は Kubernetes コミュニティが保守しているので、Service の新機能は Dataplane V2 の Cilium より先に kube-proxy に実装されやすい」と注意書きを置いている。
ふりかえり
Qiptables モードの線形スキャンが影響するのはどれ?
KUBE-SERVICES の照合は接続ごとに最初の 1 パケットだけで、2 パケット目以降は conntrack の表で書き換えられます。影響は新規接続のレイテンシに出ます。
Qnftables モードが iptables モードに対して改善したことは?
nftables モードも netfilter の中で動きます。verdict map で Service を 1 回で引き、更新は map の要素とチェーンの単位で行えるので、更新のサイズが変更分に比例します。
QDSR (Direct Server Return) が解決することは?
SNAT の代わりに backend から直接クライアントへ返させるので、戻り方向で受けたノードを経由せず、クライアント IP も保たれます。eBPF 実装が netfilter の外で動くために可能になった機能です。
参考
- Kubernetes ドキュメント「Virtual IPs and Service Proxies」(iptables、ipvs、nftables モード、数万ルールでの更新の遅さ、既定の予告、ipvs の非推奨スケジュール) https://kubernetes.io/docs/reference/networking/virtual-ips/
- Kubernetes ブログ「NFTables mode for kube-proxy」(O(n) の照合、iptables-restore の制約、O(1) の map、カーネル 5.13) https://kubernetes.io/blog/2025/02/28/nftables-kube-proxy/
- Kubernetes ブログ「IPVS-Based In-Cluster Load Balancing Deep Dive」(2018 年、iptables モードの歴史と ipvs の GA) https://kubernetes.io/blog/2018/07/09/ipvs-based-in-cluster-load-balancing-deep-dive/
- Kubernetes CHANGELOG 1.26 (userspace モードの削除) と 1.33 (nftables モードの GA) https://github.com/kubernetes/kubernetes/tree/master/CHANGELOG
- Kubernetes ドキュメント「The Kubernetes network model」(kube-proxy は既定の実装) https://kubernetes.io/docs/concepts/services-networking/
- Cilium ブログ「Cilium 1.6」(2019 年 8 月、100% kube-proxy replacement) https://cilium.io/blog/2019/08/20/cilium-16/
- Cilium ドキュメント「Kubernetes Without kube-proxy」(socket LB、SNAT / DSR、Maglev、XDP) https://docs.cilium.io/en/stable/network/kubernetes/kubeproxy-free/
- Cilium ドキュメント「eBPF Datapath」(XDP / tc / socket の hook) https://docs.cilium.io/en/stable/network/ebpf/intro/
- Cilium ドキュメント「System Requirements」(カーネル 5.10 以降) https://docs.cilium.io/en/stable/operations/system_requirements/
- GKE ドキュメント「GKE Dataplane V2」(kube-proxy の代わりに Cilium、新機能は kube-proxy が先) https://cloud.google.com/kubernetes-engine/docs/concepts/dataplane-v2