【実務・中級編】 インターネットゲートウェイを通じたSNATとDNATの動作 – クラウドインフラと仮想化ネットワーク実践ガイド

こんにちは。現場のSREとして、日々クラウドのネットワークの底地を這い回っている私です。

Web APIの設計やインフラ運用をしていると、「このリクエスト、本当にちゃんと外のインターネットに出ていってるのか?」「返り値のIPアドレス、なんでプライベートIPじゃなくてあっちのグローバルIPに化けてるんだ?」といった疑問に直面したことはありませんか?

クラウド、特にAWSのVPC(Virtual Private Cloud)を使っていると、あたかも魔法のようにプライベートIPを持つインスタンスが外の世界と通信できてしまいます。しかし、魔法の裏側には、厳密なネットワークの物理・仮想レイヤーの泥臭い仕組みが存在します。

今回は、パブリックサブネットに配置されたElastic IP(EIP)を持つEC2インスタンスを主役に据え、インターネットゲートウェイ(IGW)を通過する際のSNAT(Source NAT)とDNAT(Destination NAT)のリアルなパケット処理プロセスを、現場の知見をたっぷり交えて紐解いていきましょう。教科書には載っていない、パケットの「心の声」が聞こえるようになるのが今回のゴールです。

—

1. そもそもAWSのVPCネットワークにおける「NAT」の正体とは?

私たちが普段何気なく使っているAWSのVPC。実は、インスタンス(ENI: Elastic Network Interface)に割り当てられているIPアドレスは、基本的にプライベートIPアドレス(RFC 1918に基づくもの等)です。

しかし、パブリックなWeb APIを叩いたり、外部のSaaSと連携したりするためには、宛先(または送信元)がインターネット上でルーティング可能なグローバルIPアドレスである必要があります。

ここで登場するのが、パブリックサブネットのルートテーブルに紐づくインターネットゲートウェイ(IGW)です。IGWは、VPCのプライベート空間とカオスなインターネットを繋ぐ境界線であり、ここでマジック(アドレス変換)が行われます。

SNAT(Source Network Address Translation)とは

インスタンスから外の世界(インターネット)へ向けてパケットを送り出す際、送信元のプライベートIPアドレスを、AWS側で保持しているグローバルIPアドレス(EIPなど)に書き換える処理です。

  • なぜ必要か? プライベートIPアドレスはインターネット上でルーティングされないため、そのままでは返り値が戻ってこないからです。

DNAT(Destination Network Address Translation)とは

インターネット上のクライアントから、あなたの持つEIP宛てにパケットが飛んできた際、宛先のグローバルIPアドレスを、対応するインスタンスのプライベートIPアドレスに書き換える処理です。

  • なぜ必要か? パケットがIGWに到達した時点ではグローバルIP宛てですが、VPC内のEC2インスタンスは自分のプライベートIP宛てのパケットしか処理できないためです。

—

2. 通信の全貌:パケットがたどる運命のシーケンス

それでは、パブリックサブネット内のEC2(プライベートIP: 10.0.1.100、EIP: 203.0.113.50)から、インターネット上の外部APIサーバー(IP: 198.51.100.200)へHTTPSリクエストを投げるシーンを想像してください。

通信の往復において、パケットのIPヘッダーとポート番号がどのように書き換わっているのか、シーケンスを見てみましょう。

[EC2 (10.0.1.100)] 
       │
       │ (1. アウトバウンド通信開始)
       ▼
[IGW (SNAT実行)] ──(送信元IPを 203.0.113.50 に書換)──> [インターネット] ──> [外部API (198.51.100.200)]
       │                                                                         │
       │                                         (2. レスポンス返却)              │
       │                               <──(宛先IPを 203.0.113.50 に書換)──────────┘
       │
[IGW (DNAT/コネクション追跡実行)]
       │ (宛先IPを 10.0.1.100 に書換)
       ▼
[EC2 (10.0.1.100)] に到着!

ステップ1: アウトバウンド(外向き)の通信とSNAT

1. EC2からの出立:
アプリケーションから https://198.51.100.200/api に対してリクエストが発生します。

  • 送信元(Src): 10.0.1.100:54321 (プライベートIP + エフェメラルポート)
  • 宛先(Dst): 198.51.100.200:443

2. IGWでのSNAT処理:
パケットがパブリックサブネットのルートテーブルに従いIGWに到達します。IGW(厳密にはAWSの分散型ソフトウェア定義ネットワーク(SDN)基盤)は、このインスタンスに紐づくEIP (203.0.113.50) を参照し、パケットの送信元IPアドレスを書き換えます。

  • 書換後(Src): 203.0.113.50:54321
  • 書換後(Dst): 198.51.100.200:443

ステップ2: インバウンド(内向き)のレスポンスとDNAT

1. 外部APIからの返答:
外部APIサーバーは、見知らぬプライベートIP (10.0.1.100) ではなく、しっかり正規のグローバルIP (203.0.113.50) に対してレスポンスを返します。

  • 送信元(Src): 198.51.100.200:443
  • 宛先(Dst): 203.0.113.50:54321

2. IGWでのDNATおよびコネクション追跡:
IGWに戻ってきたパケットを、AWSのネットワーク基盤がコネクションテーブル(ステートフルインスペクション)と照合します。「あ、これはさっき 10.0.1.100:54321 が外へ投げたやつの返り値だな」と即座に特定し、宛先IPアドレスを元のプライベートIPに書き戻します。

  • 書換後(Src): 198.51.100.200:443
  • 書換後(Dst): 10.0.1.100:54321

3. EC2への到着:
無事にEC2のOSカーネル(netfilter / iptables等)がパケットを受け取り、アプリケーション層へデータを渡します。

—

3. 実務で役立つ!コードとデバッグの現場Tips

理屈が分かったところで、これを実務のコードや運用の現場でどう活かすかを見ていきましょう。今回はPythonの requests ライブラリを用いて、実際に外部APIを叩くコードと、ネットワークの挙動を追うためのデバッグ手法をご紹介します。

実装例:Pythonによる外部APIリクエスト

以下のコードは、パブリックサブネット上のEC2から外部のIP確認APIを叩き、自分自身がインターネットからどう見えているかを検証するスクリプトです。

import sys
import requests

def check_outbound_ip():
    # 接続先のパブリックAPI(グローバルIPをそのまま返すサービス)
    target_url = "https://httpbin.org/ip"
    
    try:
        # タイムアウトを3秒に設定(クラウド間通信の鉄則)
        response = requests.get(target_url, timeout=3.0)
        
        # ステータスコードのチェック
        response.raise_for_status()
        
        data = response.json()
        print(f"[INFO] 外部から見えている送信元グローバルIP: {data.get('origin')}")
        
    except requests.exceptions.RequestException as e:
        print(f"[ERROR] ネットワーク通信に失敗しました: {e}", file=sys.stderr)
        sys.exit(1)

if __name__ == "__main__":
    check_outbound_ip()

このスクリプトを実行した際、出力される origin のIPアドレスが、そのEC2インスタンスにアタッチされているElastic IPの数値と完全に一致していれば、SNATが正常に機能している証拠です。もしNATゲートウェイやプロキシを経由している場合は、別の共有IPが表示されるため、設計通りのルーティングになっているかの簡易チェックにも使えます。

—

トラブルシューティング:パケットが届かない時の「泥臭い」デバッグ手順

インフラ運用で最も肝を冷やすのが、「なぜか外向きの通信がタイムアウトする」というインシデントです。綺麗なアーキテクチャ図を描いても、現実は容赦なくパケットをドロップします。そんな時に私が現場で必ず実行するステップを伝授します。

1. セキュリティグループとネットワークACL(NACL)の二重チェック

  • 落とし穴: 「セキュリティグループ(SG)でアウトバウンドを ALL にしたから大丈夫」と思っていませんか?
  • 確認ポイント: NACLはステートレスです。パブリックサブネットのNACLで、インバウンドのエフェメラルポート(TCP 1024-65535)の戻りパケットが拒否されていないか、あるいはアウトバウンドのポート443がブロックされていないかを明示的に確認してください。

2. ルートテーブルの次ホップ(Target)の確認

  • 落とし穴: パブリックサブネットと言いながら、ルートテーブルに 0.0.0.0/0 の宛先が igx-xxxxxxxx(IGW)ではなく、NATゲートウェイやローカルに向いている。
  • 確認ポイント: AWS CLIでサクッとルーティングを確認します。
# 特定のルートテーブルのルーティング情報を確認するAWS CLIコマンド
aws ec2 describe-route-tables \
    --route-table-ids rtb-0123456789abcdef0 \
    --query "RouteTables[].Routes[*].{Destination:DestinationCidrBlock, Target:GatewayId}" \
    --output table

3. EC2内部からのパケットキャプチャ(tcpdump)

もしOSレベルまでパケットが来ているのか、あるいは外へ出ようとして消えているのかを切り分けたい場合は、EC2にSSHでログインして tcpdump を回します。

# 外部APIのIP宛ての通信をキャプチャし、パケットが外に向かって流れているか確認する
sudo tcpdump -nnvvS -i eth0 host 198.51.100.200

ここでパケットの送信元IPがきちんとプライベートIP (10.0.1.100) になっているか、そしてTCPの3ウェイハンドシェイク(SYN -> SYN-ACK -> ACK)がどこで途切れているかを確認すれば、問題がAWS側のネットワークにあるのか、OSやアプリケーションにあるのかが秒速で判明します。

—

まとめ

今回は、AWSのパブリックサブネットにおけるインターネットゲートウェイを介したSNAT・DNATの裏側の動きを、パケットの視点から深掘りしました。

  • SNAT: プライベート空間の安全性を保ったまま、IGWが送信元IPをEIPへと魔法のように書き換えて外へ送り出す。
  • DNAT: 返ってきたパケットをステートフルに追跡し、元のEC2のプライベートIPへと正確に連れ戻す。

クラウドがどれだけ抽象化されて便利になっても、その下を流れているのは泥臭いIPパケットの群れです。この仕組みを解像度高く理解しているかどうかが、複雑なマイクロサービス間通信や突発的なネットワーク障害を切り抜けるときの強力な武器になります。

皆さんの日々のインフラ運用やAPI設計に、今回の知見が少しでも役立てば幸いです。それでは、また次回の現場でお会いしましょう!

コメント

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