【実務・中級編】 CalicoにおけるIP-in-IPトンネリングとSNAT設定の排他制御 – クラウド&コンテナネットワーク実践ガイド

Calicoの「闇」を暴く:IP-in-IPとSNATの排他制御がネットワークトラブルの元凶になる理由

現場で「なぜかPodからの外部通信がパケットロスする」「パケットが途中で消える」という不可解なトラブルに直面したことはないだろうか。KubernetesのネットワークをCalicoで組んでいるとき、その原因の多くはルーティングの最適化と、クラウド基盤側が求めるNATの「お作法」の不整合にある。

今回は、Calicoの真髄であるIP-in-IP(もしくはVXLAN)と、クラスター外通信時のSNAT(Masquerading)の密接かつ危険な関係について、現場の視点から深掘りする。

—

Calicoのトンネリング:パケットは「二重」に包まれている

Calicoの基本戦略は、Pod間通信をBGPでルーティングすることだ。しかし、AWSやGCPのようなクラウド環境では、ホスト(Node)が認識していないIPアドレス(PodのIP)をパケットに乗せてVPC内を流すと、クラウド側のルーターが「未知のIPだ」としてパケットをドロップしてしまう。

そこで登場するのが IP-in-IP や VXLAN といったトンネリング技術だ。

  • IP-in-IP: パケット全体を、NodeのIPアドレスを送信元・宛先とするIPヘッダーで包む。オーバーヘッドが少ないが、セキュリティグループでの制御が少し厄介になる。
  • VXLAN: L2オーバーレイを構築する。UDPで包むため、ロードバランサーや各種ネットワーク機器との親和性が高い。

ここで重要なのは、「Podのパケットは、トンネル内では内側のIPヘッダーとして隠蔽されている」という事実だ。

—

SNAT(Masquerading)という名の「覆面」

クラスター外、つまりインターネットやVPC内の他のリソースへ通信する場合、PodのIPアドレス(クラスタープライベートIP)をそのまま外に出すわけにはいかない。宛先サーバーは戻りパケットをどこに返せばいいのか分からないからだ。

ここでCalicoの natOutgoing 設定が登場する。

# CalicoのIPPool設定例
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
  name: default-ipv4-ippool
spec:
  cidr: 192.168.0.0/16
  # trueにすると、クラスター外への通信時にNodeのIPへSNATされる
  natOutgoing: true

natOutgoing: true にすると、Calicoはiptables(あるいは最近のトレンドであるeBPF)を操作し、PodからのパケットがNodeから出ていく際に、送信元IPをNode自身のIPに書き換える。これが「SNAT(Masquerading)」だ。

陥りやすい罠:トンネリングとSNATの排他

ここで現場が最も混乱するのは、「トンネリングを有効にしているのに、SNATが正しく適用されないケース」だ。

Calicoの IPIPMode が Always に設定されている場合、全ての通信がカプセル化される。もしAWSのVPC内部など、物理ネットワークがPodのIPを直接解釈できない環境でSNATをオフにすると、パケットはVPCのルーターに到達した瞬間にブラックホールへ消える。

逆に、natOutgoing: true を設定していても、Podのルーティングテーブルや cali インターフェースの挙動によって、SNATがバイパスされることがある。特に、Cloud NativeなCNI運用において、「ホストのルーティングテーブル」と「Calicoのiptablesルール」のどちらが先にパケットを捕まえるかという競合は、デバッグの鬼門だ。

—

現場で役立つデバッグ手順

PodからAPIサーバーへの通信がうまくいかないとき、私はまず以下のステップでパケットの生死を確認する。

1. tcpdump でパケットの「顔」を覗く

Nodeにログインし、パケットがどのヘッダーを持っているか確認する。

# Node上で、caliインターフェースを監視
# pod-ipは対象PodのIP
sudo tcpdump -ni any host <pod-ip> -vv

もしパケットが tunl0 インターフェース(IP-in-IP用)を通過しているのに、宛先にSNATされた形跡がない場合、そのパケットは物理ネットワークで捨てられている。

2. iptablesのカウンターを確認する

SNATが適用されているかを確認する最も確実な手段だ。

# NATテーブルを監視
sudo iptables -t nat -L cali-POSTROUTING -v -n

ここで MASQUERADE ルールのパケットカウンターが増えていないなら、Podの通信はNAT処理を「スキップ」していることになる。原因は、Podが属するIPPoolの natOutgoing が false であるか、通信先がCalicoによって「ローカル(クラスター内)」と誤判定されている可能性が高い。

—

開発者が知っておくべき「通信のライフサイクル」

Web APIを開発する際、以下のようにPythonで外部APIを叩くコードを書いたとする。

import requests

# 外部のAPIエンドポイント
url = "https://api.external-service.com/data"

try:
    # タイムアウトを設けるのは鉄則だが、
    # CalicoのSNATが死んでいると、この接続は即座にSYN-SENTで止まる
    response = requests.get(url, timeout=5)
    print(response.status_code)
except Exception as e:
    # ここで出るエラーが「Connection Timeout」なら、
    # 戻りパケットがNATの不整合で帰ってきていない可能性を疑う
    print(f"Network Error: {e}")

もしエラーが Connection Timeout であれば、「パケットは出ているが、戻ってきていない」状況だ。これはSNATによる送信元IPの書き換えは成功しているが、戻りのパケットがNodeのルーティングテーブルで正しくPodへ戻されていない(Reverse Path Filteringの干渉など)ケースが多い。

—

SREからのアドバイス

Calicoの設定は、クラウドのネットワークトポロジーと強く結びついている。

  • AWSやGCPを使うなら: 基本的にVPCのルーティングテーブルとPodのIPレンジを統合(VPCネイティブルーティング)し、IP-in-IPを使わない設定を目指すべきだ。
  • どうしてもIP-in-IP/VXLANが必要なら: SNATのルールが、NodeのIPを正しく選択しているか、iptables のルール順序を確認する癖をつけてほしい。

Kubernetesのネットワークは「魔法」ではない。パケットは常に、物理的なNICから仮想的なインターフェースへと、泥臭いルールに従って流れている。その流れを可視化できれば、どんな難解なネットワークトラブルも、ただの「パケットの迷子探し」に過ぎない。

現場からは以上だ。次は、eBPFを用いた kube-proxy 置き換えの際の落とし穴について話そうと思う。

コメント

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