【実務・中級編】 フローログ(VPC Flow Logs / NSG Flow Logs)を活用したNAT変換前後パケットのデバッグ手法 – クラウド&コンテナネットワーク実践ガイド

NATゲートウェイの向こう側を透視する:VPCフローログで通信断の「犯人」を特定する実務的アプローチ

「開発環境からは繋がるのに、本番のプライベートサブネットからだとAPIがタイムアウトする」。
SREとして深夜の呼び出しを受けたとき、真っ先に疑うべきは「NATゲートウェイ(NAT GW)」と「セキュリティグループ(SG)」の境界線です。

パケットがプライベートサブネットを飛び出し、NAT GWでグローバルIPに変換され、インターネットの荒波へ漕ぎ出す……。この一連の動きの中で何が起きているのか。今回は、VPCフローログを武器に、NAT変換前後のパケットを追いかけて「通信断の真因」を炙り出す、現場のデバッグ術を解説します。

—

1. なぜNAT GWでパケットが迷子になるのか

プライベートサブネット内のインスタンスが外部APIを叩く際、パケットは次のように変身します。

1. 送信元(Private IP):10.0.1.5 から api.service.com へ送信
2. NAT変換:NAT GWを通過する際、送信元IPがNAT GWの Elastic IP に書き換えられる
3. 宛先(Public IP):インターネット上のAPIサーバーに到達

ここで「通信断」が起きる原因の多くは、以下の3点に集約されます。

  • ポート枯渇:同一IPへの同時接続数が多く、Ephemeral Port(エフェメラルポート)が足りない。
  • ACL/SGの不整合:NAT GWの戻りパケットを許可するルールが抜けている。
  • パスMTU問題:大きなパケットがNAT GWでフラグメント処理できずに破棄されている。

これらを推測ではなく「証拠」として掴むために、フローログが不可欠なのです。

—

2. フローログで「追跡」するための設定と準備

フローログを有効にする際、単に「全て許可」にするのはやめましょう。コストとノイズの無駄です。特にデバッグ時は、対象のENI(Elastic Network Interface)に絞るのが定石です。

AWS CLIでのフローログ設定例

特定のENIに対して、詳細なログを取得する設定です。

# ターゲットとなるインスタンスのENI IDを確認し、フローログを作成する
aws ec2 create-flow-logs \
    --resource-type NetworkInterface \
    --resource-ids eni-0123456789abcdef0 \
    --traffic-type ALL \
    --log-group-name /vpc/flow-logs/debug-api \
    --max-aggregation-interval 60 # 集約間隔を最短にしてリアルタイム性を確保

—

3. 実践:NAT変換前後の「痕跡」を追跡する

フローログをCloudWatch Logs Insightsで解析すると、NAT変換の前後関係が鮮明になります。以下のクエリは、特定の宛先IPへの通信を追いかける際に非常に有用です。

CloudWatch Logs Insightsの解析クエリ例

# NAT変換前後のIP/ポートの挙動を可視化するクエリ
fields @timestamp, srcAddr, dstAddr, srcPort, dstPort, protocol, action
| filter dstAddr = "203.0.113.10" # 接続先のAPIサーバーのグローバルIP
| sort @timestamp desc

ここで注目すべきは srcPort です。
もし action が REJECT になっているなら、それはセキュリティグループかネットワークACLの仕業です。逆に ACCEPT なのに応答がない場合は、NAT GWの先でのドロップか、サーバー側のレスポンスが戻ってきていないことを示唆します。

—

4. 検証コード:通信の「振る舞い」を再現する

デバッグのために、意図的に特定のポートを使って通信を試行するスクリプトを書いておくと便利です。Pythonの requests ライブラリを使えば、接続のタイムアウトを細かく制御できます。

import requests

# タイムアウトを短く設定し、失敗時の挙動を即座に確認する
def test_api_connection(url):
    try:
        # タイムアウトを2秒に設定
        response = requests.get(url, timeout=2)
        print(f"Status: {response.status_code}")
    except requests.exceptions.ConnectTimeout:
        print("エラー: 接続がタイムアウトしました。NATゲートウェイ、または宛先SGを確認してください。")
    except requests.exceptions.RequestException as e:
        print(f"予期せぬエラー: {e}")

# 実行
test_api_connection("https://api.external-service.com/v1/health")

—

5. SREが教える「現場のトラブルシューティングTips」

フローログを読み解く際、以下の事実に気づけるかどうかがシニアの分かれ道です。

1. エフェメラルポートの限界を見抜く:
srcPort が 32768 から 61000 の範囲で埋め尽くされている場合、接続数のオーバーフローが疑われます。この場合は、NAT GWを増設するか、接続先に対して Keep-Alive を適切に設定してコネクションを使い回す設計変更が必要です。

2. 非対称ルーティングの罠:
フローログで ACCEPT が続いているのに通信が成立しない場合、往路(パケット送信)はNAT GWを通るのに、復路(レスポンス)が別のルートを通ろうとしてファイアウォールに遮断されているケースがあります。ルーティングテーブルとルートの対称性を常に意識してください。

3. ログの「集約」に注意:
AWSのフローログはデフォルトで数分間集約されます。秒単位のスパイクを追う場合は、必ず max-aggregation-interval を最小(60秒)に設定すること。これを忘れると、重要な「瞬間的な拒否」を見逃します。

—

最後に:ネットワークは「嘘をつかない」

ネットワークトラブルにおいて、ログは唯一の真実を語ります。
「設定は合っているはずだ」という思い込みを捨て、パケットの送信元IPがNAT GWのEIPに変換されているか、変換後のポート番号が意図した範囲内か、そして戻りのパケットがどのインターフェースを通過しようとしているのか。

この一連のフローをフローログ上でなぞれるようになれば、あなたはもう単なる運用者ではなく、クラウドインフラを自在に操るエンジニアです。さあ、次はあなたの担当している環境のログを開いてみてください。そこに、まだ見ぬボトルネックが隠れているはずです。

コメント

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