【実務・中級編】 パケットキャプチャとVPCフローログを用いたNATゲートウェイ通信のデバッグ手法 – クラウド&コンテナネットワーク実践ガイド

パケットとログの海を泳ぎ切れ!VPCフローログとパケットキャプチャで暴くNATゲートウェイ通信の真実

やあ、エンジニア諸君。日々のインフラ運用やAPI設計、お疲れ様だ。
「ステージング環境からは外部APIへスムーズに繋がるのに、なぜか本番環境のECSやKubernetes Podから叩くと、タイムアウトの暗黒面に飲み込まれる……」
夜中にpagerに叩き起こされ、冷や汗を流しながらKibanaやCloudWatch Logsの画面を睨みつけた経験はないだろうか?

クラウドネイティブなアーキテクトやSREであれば、パブリックサブネットとプライベートサブネット、そしてNATゲートウェイの関係は「知っていて当然の基本」かもしれない。しかし、いざ「なぜ通信できないのか」を根本から突き止めようとしたとき、ENI(Elastic Network Interface)のレイヤーで何が起きているのかを正確に説明し、ログとパケットから真実を導き出せる者は、意外と少ない。

今回は、数々の修羅場をくぐり抜けてきたシニアネットワークエンジニアの私から、VPCフローログの解析とパケットキャプチャを駆使して、NATゲートウェイ通信の闇を完全に暴く実践的なデバッグ手法を伝授しよう。

—

1. 現場で起きる悲劇:なぜNATゲートウェイ通信はハマるのか?

まずは、私たちが普段対峙しているネットワークの現実を整理しておこう。
モダンなクラウドアーキテクト(AWSのVPCを想定して話を進めよう)では、セキュリティの鉄則として、アプリケーションが稼働するコンテナやEC2インスタンスはプライベートサブネットに配置する。これにより、インターネットからの不審なインバウンドトラフィックを完全に遮断するのだ。

しかし、アプリケーションが外部のSaaSや決済API、外部マイクロサービスと通信するためには、アウトバウンドの通信経路が必要になる。ここで登場するのがNATゲートウェイ(NAT Gateway)だ。

[プライベートサブネットのPod/EC2] 
  (送信元IP: 10.0.1.100)
       │
       ▼ デフォルトルート (0.0.0.0/0)
[NATゲートウェイのENI] 
  (ここでSNAT: プライベートIP ➔ Elastic IPへ変換)
       │
       ▼ インターネット
[外部APIサーバー]
  (送信元IPとして Elastic IP を観測)

この時、パケットの裏側では何が起きているのか?
プライベートサブネット内のリソース(例えば 10.0.1.100)から発せられたパケットは、ルーティングテーブルに従ってNATゲートウェイのENIへと送られる。ここでSNAT(Source Network Address Translation)が行われ、送信元IPアドレスがNATゲートウェイに割り当てられたElastic IP(EIP)に書き換わってインターネットへと飛び出す。

通信が成功しているうちは平和だが、いざ「タイムアウト」や「Connection Refused」が発生した途端、エンジニアは迷宮に迷い込む。

  • 「セキュリティグループ(SG)の egress 設定が間違っているのか?」
  • 「ネットワークACL(NACL)でエフェメラルポートがブロックされているのか?」
  • 「そもそもアプリが正しい宛先にリクエストを送っているのか?」

これらを感覚や推測でデバッグするのは、目隠しで地雷原を歩くようなものだ。確実な証拠を掴むためには、ENIレベルのVPCフローログとパケットキャプチャの合わせ技が不可欠となる。

—

2. VPCフローログを極める:ENIでの観測とREJECTの真実

AWSのVPCフローログは、VPC内のENIを行き来するIPトラフィックに関する情報をキャプチャしてくれる強力な機能だ。しかし、デフォルトのフォーマットや、どこをターゲットにしてログを見るべきかを知らないと、単なる「文字の羅列」に終わってしまう。

フローログの基本フォーマットと重要フィールド

トラブルシューティング時に必ず確認すべきカスタムログフォーマットの例を挙げる。最低限、以下のフィールドは含めておこう。

${version} ${account-id} ${interface-id} ${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${packets} ${bytes} ${start} ${end} ${action} ${log-status}

この中で、デバッグの鍵を握る最重要プレイヤーは以下の3つだ。

1. interface-id (eni-xxxxxxxx):
どのENIを通過したログか。プライベートインスタンスのENIか、NATゲートウェイのENIかを取り違えないようにすることが第一歩。
2. action (ACCEPT / REJECT):
セキュリティグループやNACLによってパケットが許可されたか、拒否されたかを示す。ここが REJECT になっている場合、インフラ側の設定ミスが確定する。
3. log-status (OK / NODATA / SKIPDATA):
ログが正常に記録されているか。NODATA の場合、そもそもトラフィックがそのENIに到達していない(ルーティングミスや経路の断絶)ことを示唆する。

REJECT 判定を引き起こす2大要因

フローログで REJECT が記録されている場合、原因はほぼ以下のどちらかに絞られる。

  • 原因A:セキュリティグループ(SG)の方向違い、またはポート閉塞

アウトバウンド(egress)のSGで宛先ポートが塞がれている、あるいはインバウンド(ingress)側で戻りのトラフィックがブロックされている(通常、AWSのSGはステートフルなので戻りは自動許可されるが、NACLはステートレスなので注意が必要)。

  • 原因B:NACL(ネットワークACL)のステートレスの罠

NACLはステートレスであるため、リクエストを送信する際のエフェメラルポート(通常はポート 1024-65535)だけでなく、外部から返ってくるレスポンスを受け取るためのインバウンドNACLルールも明示的に許可しておく必要がある。ここを忘れて「NATゲートウェイだから大丈夫」と油断してハマる現場を、私は何度も目撃してきた。

—

3. SNAT前後のIPアドレス変化を追跡する(シーケンスと実務Tips)

NATゲートウェイを挟む通信では、パケットの「顔(IPアドレス)」が途中で変わる。ここを頭の中で正しくシミュレートできないと、ログ追跡で迷子になる。

通信のライフサイクルとIP変化のシミュレーション

1. プライベートインスタンスからの送出

  • 送信元(Src):10.0.1.100 (Private IP)
  • 宛先(Dst):203.0.113.50 (外部APIのIP)
  • 判定:プライベートサブネットのENIでフローログを見ると、srcaddr=10.0.1.100, dstaddr=203.0.113.50 として ACCEPT が記録される。

2. NATゲートウェイでのSNAT

  • NATゲートウェイのENIを通過する際、パケットの送信元IPが書き換わる。
  • 送信元(Src):198.51.100.10 (NAT GatewayのEIP)
  • 宛先(Dst):203.0.113.50 (外部APIのIP)
  • 判定:NATゲートウェイのENIのフローログでは、srcaddr=198.51.100.10 として外部へ向かうログが記録される。

デバッグ時の落とし穴:「宛先サーバー側で見えているIP」の勘違い

外部APIの運営者から「あなたたちのサーバー(10.0.1.100)から変なリクエストが来てブロックしたよ」と言われたことはないだろうか?
ネットワークに詳しくない他部署や外部ベンダーは、プライベートIPをそのまま口にすることがある。しかし、外部APIサーバーに到達している時点で、相手に見えているのはNATゲートウェイのEIP (198.51.100.10)だけだ。
ログを調査する際は、自分たちのVPC内(プライベートIP)と、外部へ出る瞬間(EIP)の視点を切り替えて検索する必要がある。

—

4. 実践コードとパケットキャプチャ(TCPDump)の現場レシピ

百聞は一見にしかず。実際にアプリケーションから外部APIへリクエストを飛ばし、その挙動をコードとネットワークの双方向から検証してみよう。

4-1. アプリケーションコード例 (Python / requests)

まずは、タイムアウトやコネクションエラーの挙動を再現・テストするためのシンプルなPythonスクリプトだ。プロキシや接続タイムアウトの設定を明示的に記述している。

import sys
import requests
from requests.exceptions import RequestException

def test_external_api_connection(api_url):
    """
    指定された外部APIへHTTPリクエストを送信し、
    NATゲートウェイ経由の通信が正常に行われているかを検証する。
    """
    # 接続タイムアウト(3秒)、読み込みタイムアウト(5秒)を設定
    timeout_setting = (3.0, 5.0)
    
    try:
        print(f"[*] 接続テスト開始: 宛先 -> {api_url}")
        response = requests.get(api_url, timeout=timeout_setting)
        
        # ステータスコードの確認
        print(f"[+] 成功: HTTPステータスコード {response.status_code}")
        print(f"[+] レスポンスヘッダーの一部: {response.headers.get('Content-Type')}")
        
    except requests.exceptions.Timeout as e:
        print(f"[-] タイムアウトエラー: 通信経路またはNATゲートウェイでパケットが迷子です -> {e}", file=sys.stderr)
    except requests.exceptions.ConnectionError as e:
        print(f"[-] 接続エラー: SG、NACL、またはDNS解決に失敗している可能性があります -> {e}", file=sys.stderr)
    except RequestException as e:
        print(f"[-] 予期せぬリクエストエラー: {e}", file=sys.stderr)

if __name__ == "__main__":
    # テスト用のダミー外部APIエンドポイント
    target_url = "https://httpbin.org/ip"
    test_external_api_connection(target_url)

4-2. 踏み台・コンテナ内での curl による疎通確認

コンテナ内やEC2インスタンスにSSH/SSMでアクセスできるなら、curl の詳細フラグ(-v や --interface)を駆使して、どのインターフェースから出ていこうとしているのかを直接確認する。

# 冗長出力(-v)とタイムアウト設定(--connect-timeout)を付与して実行
curl -v --connect-timeout 5 https://httpbin.org/ip

もし特定のローカルIP(複数NICがある場合など)をバインドしたい場合は、--interface オプションで送信元インターフェースを明示してテストすることも有効だ。

4-3. パケットキャプチャ実践:tcpdump によるリアルタイム監視

VPCフローログは便利だが、1分〜数分単位の集約遅延がある。リアルタイムにパケットの往来を確認したい、あるいはTCPのハンドシェイク(SYN / SYN-ACK / ACK)のどこで止まっているのかを正確に知りたい場合は、インスタンス上で tcpdump を回す。

# 外部APIのIPアドレス(例: 203.0.113.50)との通信をキャプチャする
# -nn: ホスト名やポート名を名前解決せず数値のまま表示
# -vv: 詳細情報を表示
# -i any: すべてのインターフェースを対象にする(または特定のeth0を指定)
sudo tcpdump -nnvv -i eth0 host 203.0.113.50 and port 443

【パケットから読み解く障害のサイン】

  • SYN が出ているが SYN-ACK が返ってこない場合:

→ NATゲートウェイ以降のルーティングミス、あるいは宛先サーバー側のファイアウォール(セキュリティグループ)でブロックされている。

  • SYN に対し RST (Reset) が返ってくる場合:

→ 宛先ポートが空いていない(Connection Refused)、または途中のルーター・NACLで厳しく弾かれている。

—

5. シニアSREからの教訓

パブリック/プライベートサブネットとNATゲートウェイは、クラウドネットワークの基本でありながら、一度トラブルが起きると複数のレイヤー(アプリケーション、OS、VPC、SG、NACL、ルートテーブル)が絡み合う複雑な迷宮と化す。

障害対応の鉄則は、感情を排し、「パケットはどこを通り、どこで消えたのか」を事実(ログとキャプチャ)ベースで追うことだ。
「動かない」と嘆く前に、ENIのVPCフローログで action=REJECT が出ていないかを確認し、tcpdump でTCPのハンドシェイクの息吹を聞く。このアプローチを身につければ、どんなに複雑なクラウドネットワークのトラブルであっても、必ず最短経路で真実にたどり着くことができるはずだ。

さあ、ログとパケットという名のコンパスを手に、次のデバッグへ向かうとしよう。健闘を祈る!

コメント

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