【実務・中級編】 大規模トラフィック時におけるNATゲートウェイのポート枯渇(Port Exhaustion)問題 – クラウド&コンテナネットワーク実践ガイド

NATゲートウェイの「ポート枯渇」という悪夢:大規模トラフィックを捌くための防衛術

「なぜか一部のAPIリクエストだけが確率的にタイムアウトする」「負荷試験では問題なかったのに、スパイク時にだけ502/504が頻発する」……。

クラウドエンジニアであれば、一度は経験するこの「不可解なパケットロス」。その犯人の多くは、インフラの縁の下の力持ちである「NATゲートウェイ」のポート枯渇にあります。今日は、華やかなWeb API設計の裏側で起きている、シビアなTCPポートの生存競争について、現場の知見を交えて解説しましょう。

—

1. なぜ「65,535」という壁にぶつかるのか

まず、基本に立ち返りましょう。TCP通信は 送信元IP:送信元ポート と 送信先IP:送信先ポート の4つの組み合わせ(4タプル)で一意に識別されます。

プライベートサブネット内のインスタンスやPodがインターネットへ出る際、NATゲートウェイはプライベートIPを自身のグローバルIPに変換(SNAT)します。このとき、NATゲートウェイは「どの通信がどこからのものか」を管理するために、送信元ポート番号を書き換えます。

ここで立ちはだかるのが、ポート番号の理論上の上限である 65,535 です。

誤解されがちな「同時接続数」の正体

「NATゲートウェイ1つにつき65,535までしか繋げないのか?」と聞かれることがありますが、厳密には違います。NATゲートウェイは 送信先IP と 送信先ポート が異なれば、同じ送信元ポートを再利用できます。しかし、「特定の送信先サーバー(例: 外部API)」に対して大量の同時リクエストを送る場合、実質的にその上限がボトルネックとなります。

特に、Keep-Alive が無効なHTTP通信を大量に行うと、接続のたびに新しいポートが消費され、TIME_WAIT 状態のソケットがポートを占有し続けます。これが「ポート枯渇」の正体です。

—

2. 現場での検知手法:メトリクスとログから追い詰める

この問題の厄介なところは、NATゲートウェイ自体がエラーログを吐くわけではなく、ただ黙々とパケットをドロップし続ける点です。

AWS CloudWatch Metricsで異常を察知する

AWSを利用している場合、以下のメトリクスを監視するのが鉄則です。

  • ErrorPortAllocation: これがゼロより大きくなったら、即座にアラートを鳴らしてください。ポートの割り当てに失敗した回数を示します。
  • IdleTimeoutCount: アイドルタイムアウトによるコネクション切断数。これも間接的なヒントになります。

パケットを捕まえるデバッグコマンド

コンテナ内で接続が詰まっている際、netstat や ss コマンドでソケットの状態を確認しましょう。

# TIME_WAIT状態のソケットが異常に溜まっていないか確認
ss -tan | grep TIME-WAIT | wc -l

# 特定の外部エンドポイントへの接続数を確認
ss -tan | grep <外部APIのIPアドレス>:443 | wc -l

—

3. 実践:コードと設計でポートを解放する

インフラ側で NATゲートウェイを増やす(サブネットを分割してNATを分散させる)のも一つの手ですが、まずは「使い捨てのポートを減らす」コードの最適化が先決です。

Python (requests) での接続再利用

デフォルトの設定では、リクエストのたびに新しい接続が作られる可能性があります。Session オブジェクトを使い、TCPコネクションを維持しましょう。

import requests

# Sessionオブジェクトを使うことでTCPコネクションをプーリング(再利用)する
session = requests.Session()

# HTTP Adapterでプールのサイズを調整するのも有効
adapter = requests.adapters.HTTPAdapter(pool_connections=50, pool_maxsize=50)
session.mount("https://", adapter)

# これにより、同じ送信先に対してポートを使い回せる
response = session.get("https://api.external-service.com/data")

Node.js (axios / http.agent) の設定

Node.jsでは keepAlive: true を明示しないと、高負荷時にポートが枯渇します。

const http = require('http');
const https = require('https');

// Keep-Aliveを有効にしたエージェントを作成
const keepAliveAgent = new https.Agent({ 
  keepAlive: true, 
  maxSockets: 100 // 同時接続数を制限し、ポート消費を抑制する
});

axios.get('https://api.external-service.com', { httpAgent: keepAliveAgent });

—

4. アーキテクトからのアドバイス

ポート枯渇を根本的に解決する設計上のヒントを3つ残しておきます。

1. VPCエンドポイントを活用せよ: AWSのS3やDynamoDBへの通信であれば、NATゲートウェイを通さない「ゲートウェイ型VPCエンドポイント」を使いましょう。これでポート消費を劇的に減らせます。
2. NATゲートウェイの分散: どうしても回避できない大規模通信がある場合、サブネットをAZごとに分け、AZごとにNATゲートウェイを配置する「AZ分散」を行い、利用するIPアドレスを増やしてください。
3. プロキシの導入: 外部APIへの通信が多い場合、間に Envoy などのプロキシを挟み、接続をプーリングさせる構成が、大規模トラフィック下での鉄板構成です。

ポート枯渇は「見えない障害」です。しかし、ネットワークの基礎を理解していれば、必ず観測可能であり、制御可能です。皆さんのインフラが、スパイクに負けない強靭なものになることを願っています。

それでは、また次回の深掘りでお会いしましょう。

コメント

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