NATゲートウェイの「壁」を突破せよ:複数IPアドレスによるスケーラビリティの最適化
「またか……」深夜のインシデントアラート。見慣れた Connection timed out のログがDatadogのダッシュボードを真っ赤に染めている。
Web APIの負荷が急増した際、多くのエンジニアが真っ先に疑うのはCPUやメモリ、あるいはDBのクエリだ。しかし、ネットワークの最前線で戦っていると、そのボトルネックが意外な場所――NATゲートウェイの「ポート枯渇」にあることに気づかされる。
今回は、AWSのマネージドNATゲートウェイを例に、なぜ単一のIPアドレスでは限界が来るのか、そしてそれを「複数IP」でどう華麗に回避するのか、現場の知見を交えて深掘りしていく。
—
1. なぜ「NATゲートウェイ」がボトルネックになるのか
TCP通信において、送信元IPアドレスと送信元ポート番号、宛先IPと宛先ポートの組み合わせは「5タプル」と呼ばれ、これによって接続が一意に識別される。
NATゲートウェイ(AWSの場合)は、プライベートサブネットからの通信をパブリックIPに変換してインターネットへ送り出す。このとき、ゲートウェイが保持できる送信元ポート数は、1つのIPアドレスあたり最大64,512個という物理的な制約がある。
ここが落とし穴だ。
APIサーバーが外部の決済APIやSaaSへ大量のリクエストを投げる際、もし通信先が同一であれば、5タプルのうち「宛先IP/ポート」と「送信元IP」が固定されるため、使い分けられるのは「送信元ポート」だけになる。
ポート枯渇のメカニズム
1. TIME_WAIT問題: HTTP接続が終了しても、OSはすぐにポートを解放せず TIME_WAIT 状態を維持する。
2. ポートの蒸発: 大量の接続を短時間で繰り返すと、解放待ちのポートが積み上がり、NATゲートウェイが「もう空きポートがない!」と悲鳴を上げる。
3. パケットドロップ: 結果、新規のコネクションはすべて Connection timeout となり、エンドユーザーには503エラーが返される。
—
2. 「複数IPアドレス」という解決策
この状況を打破する最も実務的かつ強力な手法が、NATゲートウェイに複数のパブリックIPアドレスを割り当てることだ。
AWSのNATゲートウェイは、複数のElastic IP(EIP)を付与することで、それらをラウンドロビン的に使い分け、理論上のポート数を「EIPの数 × 64,512」まで拡張できる。
設定の勘所
Terraform等で構成管理しているなら、以下のようにリソースを定義する。
# 複数のEIPを確保
resource "aws_eip" "nat_eip" {
count = 2 # 2つのIPでポート数を倍増させる
vpc = true
}
# NATゲートウェイに複数のEIPを割り当て
resource "aws_nat_gateway" "main" {
allocation_id = aws_eip.nat_eip[0].id
subnet_id = aws_subnet.public.id
# 2つ目以降のIPを追加する場合の記述
secondary_allocation_ids = [aws_eip.nat_eip[1].id]
}
これで、NATゲートウェイは通信を分散させ、ポートの枯渇を劇的に遅らせることができる。
—
3. 実践:クライアント側からの確認とデバッグ
インフラ側で対策を施した後、本当にIPが分散されているかを確認する術を知っておく必要がある。単純な curl を使った実験コードがこれだ。
import requests
import time
# 外部のグローバルIP確認用エンドポイント
url = "https://ifconfig.me"
def check_nat_ip():
try:
# セッションを張らずに毎回新しいコネクションを作成
response = requests.get(url, timeout=5)
print(f"Current Source IP: {response.text}")
except Exception as e:
print(f"Error: {e}")
# 連続でリクエストを投げてIPが切り替わるか確認
for _ in range(5):
check_nat_ip()
time.sleep(1)
このコードを実行し、出力されるIPアドレスが交互に変わるようであれば、NATゲートウェイの負荷分散が正しく機能している証拠だ。
—
4. SREからのアドバイス:根本治療を忘れるな
複数IPによるスケールアウトは非常に有効だが、あくまで「対症療法」であることを忘れてはならない。以下のチューニングを併せて行うことが、本当の意味での「強いインフラ」を作る。
- HTTP Keep-Aliveの活用:
TCPコネクションを使い回すことで、ポートの消費を最小限に抑える。Node.jsやPythonの requests (Session) を使う際は、必ずコネクションプールを有効化すること。
- 通信タイムアウトの短縮:
不要に長いコネクションを保持せず、サーバー側で適切に切断する。
- VPCエンドポイントの利用:
もし通信先がS3やDynamoDBであれば、NATゲートウェイを通す必要はない。VPCエンドポイントを使えば、NATゲートウェイのポートを1つも消費せずに通信できる。
まとめ
NATゲートウェイのポート枯渇は、成長するサービスにとって必ず通る道だ。慌てず、まずはパケットの出口を増やし、次に通信の質(Keep-Alive)を見直す。この順序を守れば、急激なトラフィック増大も恐れることはない。
ネットワークの挙動を可視化し、論理的な裏付けを持ってインフラを組む。それが、僕らエンジニアが泥臭いトラブルを「過去の経験」へと変える唯一の方法だ。
コメント