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

なぜあなたのパケットは迷子になるのか?kube-proxyが裏側で仕掛ける「NATの魔術」を解き明かす

クラウドネイティブな環境でKubernetesを運用していると、避けては通れないのが「ネットワークのブラックボックス化」です。特に、Service という抽象概念を叩いたとき、パケットが一体どこへ消え、どうやってPodに辿り着くのか。その裏側で起きている iptables や IPVS によるルーティングの魔法を知らなければ、本番環境で発生する不可解な通信断を解決することはできません。

今日は、SREの現場で幾多のパケットキャプチャを重ねてきた経験から、kube-proxy が担う「DNAT(Destination NAT)」の真実を、泥臭い実務の視点で紐解いていきましょう。

—

1. Serviceの正体:それは「実体のない仮想IP」である

まず大前提として、Kubernetesの ClusterIP は、どこかのNICにアサインされたIPアドレスではありません。あれは単なる「概念」です。

kube-proxy は、API Serverから Service や Endpoints の変更を監視し、各ノードのカーネル空間にある iptables(あるいは IPVS)のテーブルを書き換えます。我々が curl で ClusterIP にパケットを投げた瞬間、カーネルがパケットの宛先を強引に書き換えているのです。

なぜDNATが必要なのか?

Service のIP(Virtual IP)を、生のPodのIPアドレスへ突き通すためには、パケットの「宛先情報」を書き換える必要があります。これが DNAT です。

  • 宛先: 10.96.0.10(Service IP)
  • → DNAT変換後: 10.244.1.5(実際のPod IP)

この変換処理こそが、kube-proxy がノードごとに行っている「ロードバランシングの正体」です。

—

2. iptablesモード:ルールという名の巨大な迷路

多くの環境でデフォルト設定されている iptables モード。これを理解するには、PREROUTING チェーンから辿るのが鉄則です。

以下のコマンドを、Kubernetesノード上で叩いてみてください。

# 現在のnatテーブルのルールをダンプする
# 読みづらいですが、ここが全ての起点です
sudo iptables -t nat -S

パケットが辿るシーケンス

1. PREROUTING: パケットがノードに入ると、まずここで KUBE-SERVICES チェーンに飛ばされます。
2. KUBE-SERVICES: 宛先IPが ClusterIP と一致するかを判定します。
3. KUBE-SVC-xxx: 該当するService専用のチェーンへジャンプします。
4. KUBE-SEP-xxx: ここで確率的にPodのIP(Endpoint)へ転送(DNAT)されます。

このとき、iptables はランダムな確率で SEP (Service Endpoint) を選択し、宛先IPを書き換えます。これが「Service経由のロードバランシング」の仕組みです。

—

3. IPVSモード:高負荷環境の切り札

もしあなたが、数千規模のServiceを抱える巨大クラスタを運用しているなら、iptables は遅すぎます。iptables はルールが増えるほど線形探索に近い挙動をするため、レスポンスが悪化します。

ここで登場するのが IPVS です。Linuxカーネルのロードバランサ技術である IPVS は、iptables とは異なり「ハッシュテーブル」で管理されるため、どれだけServiceが増えてもパフォーマンスが落ちません。

IPVSのデバッグコマンド

iptables の代わりに ipvsadm を使うと、現在のルーティング状況が一目瞭然です。

# 現在の仮想サーバーとバックエンドPodの対応を確認
sudo ipvsadm -Ln

# 出力例:
# TCP  10.96.0.10:80 rr
#   -> 10.244.1.5:8080            Masq    1      0          0
#   -> 10.244.2.3:8080            Masq    1      0          0

ここで Masq と表示されているのが重要です。これは「マスカレード(NAT)」を行うことを意味します。パケットがノードを離れる際、送信元IPをノード自身のIPに置き換えることで、Podが応答を返せるようにしているのです。

—

4. 実務的なデバッグTips:パケットはどこで消えた?

開発中に「Serviceに繋がらない」という事象に遭遇した際、私はまず以下の順序で確認します。

ステップ1: ServiceとEndpointの整合性確認

そもそも、Serviceの背後にPodが紐づいているか? これが最も多いミスです。

# Endpointが空になっていないか確認
kubectl get endpoints <service-name>

ステップ2: 接続テストの自動化(Pythonによる疎通確認)

簡単なPythonスクリプトで、接続先IPとポートを明示してテストします。

import socket

# Service IPとPort
target_ip = "10.96.0.10"
target_port = 80

def check_connection(ip, port):
    try:
        with socket.create_connection((ip, port), timeout=2) as sock:
            print(f"成功: {ip}:{port} に接続できました")
    except Exception as e:
        print(f"失敗: {ip}:{port} - {e}")

# これをPodの中から実行し、iptablesの挙動を確認する
check_connection(target_ip, target_port)

ステップ3: NATの追跡(conntrack)

NAT処理はカーネルの conntrack テーブルに依存します。もし接続がタイムアウトする場合、ここが溢れているか、エントリが壊れている可能性があります。

# conntrackのテーブル数を確認(上限に達していないか?)
sysctl net.netfilter.nf_conntrack_count

—

まとめ:ネットワークは「見える化」から始まる

kube-proxy が行っているのは、パケットに対する「宛先書き換え」という極めてシンプルかつ強力な操作です。しかし、それが巨大な iptables ルール群や IPVS テーブルの中で行われるため、ブラックボックスに見えてしまいます。

  • 小〜中規模なら iptables: 設定の可視化がしやすく、トラブルシュートが容易。
  • 大規模なら IPVS: 性能重視だが、ツール(ipvsadm)への習熟が必要。

皆さんがもしWeb APIの設計やインフラ運用で悩んだら、まずは kubectl get endpoints で「宛先は生きているか」を確認し、次にノードへSSHして ipvsadm や iptables で「パケットの宛先が正しく書き換わっているか」を追ってください。

ネットワークは魔法ではありません。すべてはカーネルのテーブルに書き込まれた、冷徹なビットの列なのです。現場からは以上です!

コメント

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