悪夢の「ポート枯渇」を乗りこなせ:NATゲートウェイと戦うSREの現場知見
クラウドインフラを運用していると、ある日突然、晴天の霹靂のように「503 Service Unavailable」や「Connection Timeout」が特定のマイクロサービスで頻発し始めることがあります。ログを掘り下げると、外部APIとの通信で EADDRNOTAVAIL や Cannot assign requested address といったエラーが。
そう、これがインフラエンジニアを長年悩ませる「ポート枯渇(Port Exhaustion)」の正体です。今回は、AWSのNATゲートウェイやGCPのCloud NATを使いこなす上で避けては通れない、この「64,000の壁」について、現場の知見を交えて深掘りします。
—
1. なぜ「64,000」で詰まるのか?:通信の裏側にある物理的制約
私たちがパブリックサブネットからインターネット上のAPIへリクエストを投げる際、NATゲートウェイは「送信元IPアドレス」を自身のIPへと書き換えます。このとき、TCP/UDPの通信を識別するために使われるのが「送信元ポート番号」です。
TCP/IPの仕様上、ポート番号は16ビット(0〜65,535)で表現されます。OSやクラウドのNATゲートウェイは、1つのパブリックIPアドレスに対して、エフェメラルポート(一時的なポート)を割り当てて通信を管理しますが、現実的に利用可能なポート数は約64,000個に制限されます。
通信シーケンスのリアル
1. リクエスト: 10.0.1.5:40001 (プライベートIP) -> NAT GW -> 外部API:443
2. 変換: NAT GW(パブリックIP):55000 -> 外部API:443
3. 接続状態: この通信が終了(FINやRST)してTIME_WAIT状態を抜けるまで、そのポート(55000)は「使用中」としてロックされます。
外部へのリクエストが爆発的に増え、かつ接続のクローズが追いつかない、あるいはコネクションプーリングが適切でない場合、この「使用中」のポートが64,000個すべて埋まってしまいます。これがポート枯渇のメカニズムです。
—
2. 実践的デバッグ:なぜそのコネクションは閉じないのか?
ポート枯渇が疑われる場合、まずはアプリケーション側で接続が正しく再利用されているかを確認しましょう。例えば、Pythonのrequestsモジュールを使っている場合、デフォルトでは毎回新しい接続を作成しようとします。
import requests
# 悪い例:毎回接続を生成し、ポートを消費し続ける
for i in range(1000):
# 毎回TCPハンドシェイクが発生し、ポートを消費してTIME_WAITへ移行
response = requests.get("https://api.example.com")
これを解決するには、requests.Session() を使い、コネクションプールを活用するのが鉄則です。
import requests
# 良い例:Sessionオブジェクトでコネクションを再利用
session = requests.Session()
# アダプターで接続数を管理
adapter = requests.adapters.HTTPAdapter(pool_connections=10, pool_maxsize=100)
session.mount("https://", adapter)
for i in range(1000):
# 既存のコネクションを使い回すため、ポート消費を最小限に抑えられる
response = session.get("https://api.example.com")
—
3. アラート設計:CloudWatch/Azure Monitorでの「予兆」検知
ポート枯渇が発生してから気づくのはSREとして失格です。メトリクスで「予兆」を検知するアラートを仕込みましょう。
AWS CloudWatchでの監視ポイント
AWS NATゲートウェイの場合、ErrorPortAllocation というメトリクスが非常に重要です。
- メトリクス:
ErrorPortAllocation - 統計:
Sum - しきい値:
1以上(1回でも発生すれば即時通知)
しかし、これだけでは遅いことがあります。ポートが枯渇し始める直前には、NATゲートウェイの「アクティブな接続数」が急増します。
- メトリクス:
ActiveConnectionCount - 閾値の考え方: NATゲートウェイの最大同時接続数は約64,000です。安全を見て「最大容量の70%〜80%」を超えたら警告(Warning)が出るように、CloudWatch Alarmを設定しておくのがプロの現場です。
監視設計のサンプル(Terraformイメージ)
resource "aws_cloudwatch_metric_alarm" "nat_port_warning" {
alarm_name = "nat-gateway-port-high-usage"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "2"
metric_name = "ActiveConnectionCount"
namespace = "AWS/NATGateway"
period = "60"
statistic = "Average"
threshold = "45000" # 64,000の約70%を警告ラインとする
dimensions = {
NatGatewayId = "nat-0123456789abcdef"
}
alarm_description = "NATゲートウェイの接続数が限界に近づいています。コネクションプールの設定を見直してください。"
}
—
4. もし限界を超えたら?:根本的なスケーリング戦略
ポート枯渇が頻発する場合、根本的な解決策は以下の3つに集約されます。
1. アプリケーションの修正: 前述の通り、Keep-Aliveを有効にし、コネクションプールを適切に設定する。
2. NATゲートウェイの増設: AWSの場合、NATゲートウェイを複数設置し、各サブネットのルーティングテーブルを適切に分割することで、パブリックIPを増やし、IPあたりのポート制限を分散させる。
3. VPCエンドポイントの活用: S3やDynamoDBなど、AWSサービスへの通信であれば、NATゲートウェイを通さない「VPCエンドポイント(Gateway型/Interface型)」を利用する。これが最もコスト効率が良く、ネットワーク負荷を劇的に減らす特効薬です。
最後に:ネットワークを「意識しない」ために
ポート枯渇は、アプリケーションのコードとインフラの境界線で発生する「見えにくい障害」です。しかし、一度理解してしまえば、コネクションプールの設定やエンドポイントの利用といった「定石」で確実に防ぐことができます。
「なぜか繋がらない」と悩んだ時、まずは netstat -an | grep TIME_WAIT | wc -l を叩き、OSレベルで何が起きているかを覗いてみてください。パケットの動きが見えるようになれば、あなたはもう一人前のクラウドアーキテクトです。
それでは、良いSREライフを!
コメント