【テクニカル・上級編】 Kubernetes Service(NodePort/LoadBalancer)におけるkube-proxyのiptables/IPVSとNATの連携 – クラウド&コンテナネットワーク実践ガイド

Kubernetesネットワークの深淵:kube-proxyが操るDNATとパケットの「目に見えない旅」

クラウドネイティブな環境で、我々SREが最も頭を悩ませ、同時に最も魅了されるのが「パケットの行方」です。kubectl expose でサービスを作成し、LoadBalancer や NodePort を経由してトラフィックを流す。その背後で、kube-proxy が iptables や IPVS を使ってどのような魔法をかけているのか。

今日は、教科書的な説明を飛び越えて、Linuxカーネル内部でパケットが書き換えられる瞬間と、その先にあるパフォーマンスの深淵を紐解いていきましょう。

—

1. DNATの正体:パケットはどこで「運命」を変えるのか

Kubernetesの Service(ClusterIP)は、実体を持たない仮想的なIPです。トラフィックが ClusterIP に向けて発射された瞬間、パケットは Netfilter のフックポイント、具体的には PREROUTING または OUTPUT チェーンで捕捉されます。

ここで kube-proxy が仕込んだ DNAT(Destination NAT)ルールが発動します。

  • iptablesモードの場合: KUBE-SERVICES チェーンから始まる巨大なルールセットが、宛先IPをPodの実際のIPアドレスへと書き換えます。このとき、コネクション追跡(conntrack)テーブルにエントリが作成され、戻りのパケットに対する SNAT の準備も整えられます。
  • IPVSモードの場合: Netfilter の重い処理をスキップし、カーネル空間のハッシュテーブルでロードバランシングを行います。数千規模のサービスを抱えるクラスタでは、iptables の線形探索コストを回避できる IPVS は必須の選択肢です。

なぜ conntrack がボトルネックになるのか?

高負荷な環境において、conntrack テーブルの溢れ(nf_conntrack_count の上限到達)は、パケットドロップという最も静かで致命的な障害を引き起こします。以下のチューニングは、大規模クラスタを運用する者の嗜みです。

# conntrackのハッシュテーブルサイズを拡大
sysctl -w net.netfilter.nf_conntrack_buckets=262144
# 最大追跡数を増やす(メモリに余裕があることが前提)
sysctl -w net.netfilter.nf_conntrack_max=1048576

—

2. ネットワークパフォーマンスの極限:TCPバッファとRTTの制御

kube-proxy の挙動を理解した上で、さらに一歩進んだパフォーマンスチューニングを行います。Pod間の通信、あるいはNodePort経由の通信において、スループットを最大化するにはカーネルのTCPスタックを「現代的」な設定に倒す必要があります。

特に、広帯域・高遅延なクラウド間通信を行う場合、BBR(Bottleneck Bandwidth and Round-trip propagation time)の導入は不可欠です。

# TCP輻輳制御アルゴリズムをBBRに変更
sysctl -w net.core.default_qdisc=fq
sysctl -w net.ipv4.tcp_congestion_control=bbr

# TCPウィンドウサイズの動的調整を最適化
# メモリを消費するが、高スループットを維持する
sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
sysctl -w net.ipv4.tcp_wmem="4096 65536 16777216"

—

3. セキュリティとトランスポート最適化:TLSハンドシェイクの「重さ」

ネットワーク層でパケットを最適化しても、アプリケーション層で TLS ハンドシェイクが RTT を食いつぶしていれば意味がありません。LoadBalancer からバックエンドのPodへ通信する際、可能であれば Proxy Protocol を利用し、クライアントIPを保持しつつ、サイドカープロキシ(Istio/Linkerd)で mTLS を終端させるのが現代の解です。

ここで注意すべきは、HTTP/2 や gRPC を使用する場合のヘッダー圧縮(HPACK)です。何度も接続を張り直すのではなく、Keep-Alive を活用し、コネクションを再利用することで、TCP のスロースタートを回避し、TLSのネゴシエーションコストを極小化してください。

—

4. 現場の知恵:トラブルシューティングの勘所

もしネットワークの挙動が怪しいと感じたら、まずは conntrack の状態を確認してください。以下のコマンドは、何らかの理由でパケットが「迷子」になっている状況を特定するのに役立ちます。

# conntrackテーブルの現在のエントリ数を確認
sysctl net.netfilter.nf_conntrack_count

# 特定のサービスIPに関連するconntrackの状態をダンプ
conntrack -L -d <ClusterIP>

また、kube-proxy が作成したルール自体が壊れていないか、iptables-save を眺めるのも良いですが、数万行のルールを人間が読むのは苦行です。特定のPodへの疎通確認には nsenter を使い、Podのネットワークネームスペースに飛び込んで直接 tcpdump を行うのが、SREとしての「泥臭い」正しい作法です。

# コンテナのPIDを特定し、そのネットワークネームスペースでパケットをキャプチャ
PID=$(docker inspect -f '{{.State.Pid}}' <container_id>)
nsenter -t $PID -n tcpdump -i eth0 port 80 -w capture.pcap

—

まとめ:インフラは生き物である

kube-proxy による DNAT は、単なるパケットの宛先書き換えではありません。それは、コンテナという儚い存在を、静的なIPという概念と結びつけ、スケーラブルなサービスへと昇華させるための高度な抽象化レイヤーです。

このレイヤーの挙動を深く理解し、カーネルの限界値と向き合うことは、単なるインフラ構築を超えた「エンジニアリングの芸術」と言えるでしょう。皆さんのクラスタが、今日も安定したパケットの奔流に満たされていることを願っています。

コメント

タイトルとURLをコピーしました