Kubernetesの「見えない壁」:CNIマスカレードが隠蔽するPod IPの真実
「なぜ、Podから外部のAPIを叩くと、送信元IPがノードのIPに化けているのか?」
インフラエンジニアとして現場に立っていると、一度は必ずこの疑問に突き当たります。KubernetesのPodは、クラスタ内で一意なIPアドレス(Pod CIDR)を持っています。しかし、そのパケットがAWSのVPCの外、あるいはNATゲートウェイを越えてインターネットへ飛び出すとき、PodのIPはそのままでは通用しません。
ここで暗躍するのが、CNI(Container Network Interface)プラグインによる「マスカレード(IP Masquerade)」です。今回は、この「パケットの変身」の裏側を、泥臭いトラブルシューティングの視点から紐解いていきます。
なぜマスカレードが必要なのか?
結論から言えば、「ルーティングの不整合を回避するため」です。
Pod CIDRは、多くの場合、クラスタ内でのみ有効な「プライベートな空間」です。これをそのままVPC外やインターネットへ流すと、戻りのパケットがどこへ向かえばいいのか(どのノードのどのPodへ届けばいいのか)、ルーターは理解できません。
そこで、CNIはパケットがノードの境界を出る瞬間に、送信元IPアドレスを「ノード自身のIPアドレス」に書き換えます。これが iptables や ebpf を用いたSNAT(Source NAT)の正体です。
通信のシーケンス:パケットの「変身」
Podから外部API(例: https://api.example.com)を叩く際の通信フローは、以下のようになります。
1. Pod内: curl がパケットを生成。送信元は 10.244.x.x (Pod IP)。
2. veth pair: パケットは仮想NICを通り、ホストOS(ノード)のネットワーク名前空間へ到着。
3. iptables (POSTROUTING): ここが運命の分かれ道。CNIの設定により、送信元IPをホストのNIC IP(例: 192.168.1.10)へ書き換えるルールが適用される。
4. VPCルーター/NAT Gateway: 送信元がノードIPになったパケットを受け取り、インターネットへ転送。
5. 返信: 応答はノードIPへ戻り、ホストは conntrack テーブルを使って、どのPodへの戻りかを判断し、再度Pod IPへ変換してPodへ渡す。
CNIの設定と実務的なパラメーター
多くのCNI(CalicoやFlannelなど)では、このマスカレードを有効にするかどうかのフラグがあります。
例えば、Calicoの IPPool 設定ファイルを見てみましょう。
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
name: default-ipv4-ippool
spec:
cidr: 10.244.0.0/16
# ここが重要!NATOutgoingを有効にすると、
# このプール内のPodから外部への通信がマスカレードされる
natOutgoing: true
# もしVPC内で直接Pod IPと通信したい場合はfalseにするが、
# その場合はVPCルートテーブルの更新が必要
nodeSelector: all()
なぜ natOutgoing: false にするのか?
VPC内の他のサーバー(RDSやオンプレミスへのVPN接続先など)とPodが直接通信したい場合、マスカレードは邪魔になります。送信元が「ノードのIP」になってしまうと、受取側はどのPodからのアクセスか判別できないからです。
現場で遭遇する「マスカレードの罠」
トラブルシューティングで最も多いのが、「外部APIからアクセス制限を受けている」というケースです。
例えば、相手先のAPIが「ホワイトリスト方式」でIP制限をかけている場合、PodごとのIPを登録しても無意味です。ノードがオートスケーリングで増減するたびに送信元IPが変わるため、API担当者にノードのサブネット範囲をすべて伝え、NAT Gatewayの「固定IP(EIP)」を経由させる構成を組むのが、クラウド運用の定石です。
デバッグコマンド:今、何が起きているか?
パケットが変換されているか確認したいときは、ノード上で iptables を覗くのが最も確実です。
# マスカレードのルールが適用されているか確認
sudo iptables -t nat -L POSTROUTING -n --line-numbers
# 特定のPod通信をトレースする場合(要tcpdump)
# ノード上でPodからのパケットをキャプチャし、IPの変化を確認する
sudo tcpdump -ni eth0 host 192.168.1.10 and port 443
Pythonで疎通確認用コード
単純な curl だけでなく、送信元IPがどう見えているかを確認するための簡単なスクリプトをPod内で走らせると、検証が捗ります。
import requests
# 外部のグローバルIP確認サービスを叩く
# マスカレードが効いていれば、PodのIPではなく「NAT GatewayやノードのIP」が表示されるはず
try:
response = requests.get("https://ifconfig.me/ip", timeout=5)
print(f"現在の送信元グローバルIP: {response.text}")
except Exception as e:
print(f"通信エラー: {e}")
まとめ:ネットワークの透明性は諸刃の剣
CNIのマスカレード機能は、Kubernetesを既存のネットワーク環境に「無理やり」馴染ませるための便利な糊です。しかし、この「隠蔽」があるからこそ、アプリケーション開発者はネットワークの複雑さを意識せずに開発ができます。
一方で、いざ大規模なシステムを構築する際は、「どのIPで外部と通信しているか」を把握しておくことが、セキュリティ要件を満たすための唯一の道です。
「Pod IPが見えない」と嘆く前に、まずはそのノードが背負っているNATのルールを確認してみてください。ネットワークエンジニアとしての視点を持てば、Kubernetesのトラブルシューティングは、パズルのように面白くなってくるはずです。
コメント