【実務・中級編】 ポート枯渇(Port Exhaustion)エラーの発生メカニズムとCloudWatch/Azure Monitorでのメトリクス監視 – クラウド&コンテナネットワーク実践ガイド

悪夢の「ポート枯渇」を乗りこなせ: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ライフを!

コメント

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