【テクニカル・上級編】 NATゲートウェイを経由するTCPセッションの最大コネクション数とSNATポート枯渇問題 – クラウドインフラと仮想化ネットワーク実践ガイド

NATゲートウェイの「沈黙の断絶」:SNATポート枯渇が突きつけるインフラの限界と最適解

クラウドネイティブな環境において、NATゲートウェイ(以下NATGW)は、プライベートサブネットのインスタンスが外部と通信するための「不可欠な門番」です。しかし、この門番が突如として「通信拒否」を始めたとき、多くのエンジニアは原因特定に迷走します。

今日は、教科書には載っていない、NATGWにおけるSNATポート枯渇という「見えない壁」と、その深淵に潜むトランスポート層の挙動について、SREの視点から紐解いていきましょう。

—

1. 55,000ポートの制約:なぜパケットは消えるのか

AWSのNATGWは、単一のIPアドレスに対して、宛先(IP:ポート)の組み合わせごとに最大64,512のポートを割り当て可能ですが、実運用上の安全圏は55,000程度です。

ここで注意すべきは、SNAT(Source Network Address Translation)の仕組みです。パケットがNATGWを通過する際、送信元IPはNATGWのIPに置換されます。このとき、一意性を保つために「送信元IP:送信元ポート」と「宛先IP:宛先ポート」のタプルをNATGWが追跡します。

問題は、「同じ宛先サーバーに対して、短時間に大量の接続を生成する」ケースです。宛先サーバーが固定されている場合、NATGWが払い出せるポート数はあっという間に枯渇します。パケットは捨てられ、クライアント側ではConnection Timed Outが頻発します。これはアプリケーションのバグではなく、トランスポート層における「物理的なキャパシティ不足」なのです。

—

2. 現場で戦うための「TCP/TLS」最適化戦略

ポート枯渇を防ぐには、接続を「使い捨てる」のではなく「再利用する」アーキテクチャへの転換が不可欠です。

2.1 Keep-Aliveと接続プーリングの徹底

HTTP/1.1のデフォルト挙動を疑ってください。Connection: keep-aliveが有効であっても、サーバー側で短いタイムアウトが設定されていれば、TCPのFINやRSTが飛び交い、TIME_WAIT状態のソケットが量産されます。

Pythonのrequestsを使用する場合、HTTPAdapterを用いた接続プーリングは必須です。

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

# 接続を再利用するためのプール設定
session = requests.Session()
adapter = HTTPAdapter(
    pool_connections=50,  # プールする接続数
    pool_maxsize=50,      # 同時接続の最大数
    max_retries=Retry(total=3, backoff_factor=0.1)
)
session.mount("https://", adapter)

2.2 TLSハンドシェイクの削減(TLS False StartとSession Resumption)

ハンドシェイクを繰り返すことは、CPU負荷のみならず、接続確立までのRTT(Round Trip Time)を増大させ、結果としてポートの滞留時間を延ばします。TLS 1.3への移行は、RTTを1往復分削減するだけでなく、セキュリティ強化の観点からも最優先事項です。

—

3. カーネルレベルのチューニング:tcp_tw_reuseの是非

かつてLinuxカーネルのnet.ipv4.tcp_tw_reuseを有効にすることが「魔法の解決策」とされた時代がありましたが、現代のクラウド環境では慎重な検討が必要です。

TIME_WAIT状態のソケットを再利用することでポート枯渇は緩和されますが、パケットのシーケンス番号の衝突リスクがゼロではありません。これを行う前に、まずは「宛先ホストを分散させる」か「NATGWを複数配置してIPを増やす(VPCのプレフィックス委任)」といった、アーキテクチャレベルの解決を優先してください。

もし、どうしてもカーネルパラメータを触るなら、tcp_max_tw_bucketsの調整や、TCPバッファの最適化(tcp_rmem, tcp_wmem)を検討すべきです。

# TCPバッファサイズを最適化し、スループットを向上させる
sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
sysctl -w net.ipv4.tcp_wmem="4096 65536 16777216"

—

4. 究極の回避策:NATゲートウェイからの脱却

もし貴方のサービスが、単一の宛先に対して数万リクエスト/秒を捌く必要があるなら、NATGWという共有リソースを使う構成自体が限界です。

1. VPCエンドポイント(Gateway型/Interface型)の利用: S3やDynamoDBへのアクセスであれば、NATGWを通さないことでポート消費を完全にゼロにできます。
2. 分散NAT構成: NATGWをサブネットごとに配置し、ルートテーブルを細分化することで、ポートの払い出し上限を物理的に拡張します。
3. Global Acceleratorの検討: ユーザーに最も近いエッジでTCP接続を終端し、AWSのバックボーンを通すことで、インターネット側の不安定な挙動と接続管理を一括して最適化できます。

結びに代えて:SREとしての矜持

「なぜ落ちたのか」を追及する際、メトリクスを見るだけでは不十分です。NATGWのErrorPortAllocationメトリクスが跳ね上がったとき、それは「インフラが悲鳴を上げている」のではなく、「貴方のアプリケーションがトランスポート層の制約を無視している」というシグナルです。

ネットワークは魔法ではありません。パケットは、論理的なルールと物理的な制約に従って淡々と流れるだけです。その挙動を深く理解し、プロトコルの作法に沿った実装を行うことこそが、真にスケーラブルなシステムを構築する唯一の道だと私は確信しています。

次回のデプロイ前には、ぜひnetstatやssコマンドで、貴方のアプリケーションがどれだけのソケットを「握りしめているか」を確認してみてください。そこには、まだ見ぬ最適化のヒントが隠されているはずです。

コメント

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