【実務・中級編】 Cloud NATのポート枯渇問題(Port Exhaustion)とその検知・対策 – クラウドインフラと仮想化ネットワーク実践ガイド

Cloud NATのポート枯渇に泣く前に:その「接続エラー」の正体を見極めろ

クラウドインフラを設計・運用していると、ある日突然、アプリケーションログに「Connection timed out」や「Connection refused」が散見され始める――そんな悪夢のような経験はないだろうか。特に、複数のマイクロサービスが外部のWeb APIを頻繁に叩くような環境において、GCPの Cloud NAT を使っているなら、真っ先に疑うべきは「ポート枯渇(Port Exhaustion)」だ。

今日は、教科書的な仕様解説ではなく、現場で障害対応にあたるエンジニアが「なぜそこで止まるのか」を腹落ちするための、泥臭いネットワークの話をしよう。

—

1. なぜ「64,000」で限界が来るのか?

TCP/IPの通信において、クライアント側の送信元ポート番号は、0 から 65535 までの範囲で割り当てられる。Cloud NAT は、プライベートIPを持つVMが外部と通信する際、その送信元IPとポートを「NATゲートウェイの外部IP」と「動的に割り当てたポート」に書き換える。

ここで重要な制約がある。GCPの Cloud NAT では、1つの外部IPアドレスあたり最大64,000個のソースポートしか保持できないという仕様だ。

接続の「四元数(4-tuple)」を意識せよ

ネットワーク接続は以下の組み合わせで一意に識別される。

1. 送信元IP(NATゲートウェイのIP)
2. 送信元ポート(NATゲートウェイが割り当てたポート)
3. 送信先IP(APIサーバーのIP)
4. 送信先ポート(APIサーバーのポート)

「特定のAPIサーバーに対して大量のコネクションを張る」場合、送信先IPとポートは固定される。すると、NATゲートウェイは「送信元ポート」の組み合わせだけで通信を識別しなければならない。このとき、NATのポートプールを使い切ってしまうと、新しいコネクションは作れず、 Connection timed out が発生する。

—

2. 現場で「ポート枯渇」を検知する

「なんとなく遅い」で済ませてはいけない。Cloud Monitoringで可視化するのが鉄則だ。

Cloud Monitoringでの監視ポイント

以下のメトリクスをダッシュボードに表示しておくべきだ。

  • nat/gateway/sent_bytes_count
  • nat/gateway/used_nat_ips
  • nat/gateway/allocated_ports (ここが重要!)

特に allocated_ports が64,000に近づいている、あるいはNATの割り当てルール上限に達している場合は、迷わず対策を打つ必要がある。

—

3. 実践:ポート枯渇を解消するテクニック

ポートが足りないなら、単純に「IPを増やす」か「効率を上げる」かの二択だ。

A. 外部IPアドレスの追加(スケーリング)

Cloud NATに複数の外部IPを割り当てることで、理論上の最大ポート数は IP数 × 64,000 にまで拡張できる。

# 既存のNATゲートウェイにIPアドレスを追加するgcloudコマンド例
gcloud compute routers nats update [NAT_NAME] \
    --router=[ROUTER_NAME] \
    --nat-external-ip-pool=[IP_ADDRESS_1],[IP_ADDRESS_2] \
    --region=[REGION]

B. アプリケーション側の改善(コネクションプーリング)

最も健全なのは、インフラを増強する前に「無駄なコネクションを捨てる」ことだ。特にPythonやNode.jsで毎回 requests や fetch を使い捨てていないだろうか?

NGなコード例(毎回接続を切る):

# 毎回セッションを生成すると、TCPハンドシェイクとポート消費が激しい
import requests

def call_api():
    # 毎回接続するため、FIN/ACKのやり取りでポートがTIME_WAIT状態に滞留する
    response = requests.get("https://api.external-service.com/data")
    return response.json()

OKなコード例(Session/Connection Poolを利用):

import requests

# セッションを再利用することで、同一ホストへのコネクションを維持する
session = requests.Session()

def call_api():
    # 既存のコネクションを再利用するため、ポートの再割り当てが発生しにくい
    response = session.get("https://api.external-service.com/data")
    return response.json()

—

4. プロの視点:TCPコネクションの「寿命」を制御する

NATゲートウェイには TCP Established Timeout や TCP Transitory Idle Timeout といった設定値が存在する。これらは「アイドル状態のコネクションをどれくらいの時間保持するか」を定義するものだ。

もし、クライアント側のアプリケーションがコネクションを掴んだまま放置しているなら、これらのタイムアウト値を短く調整することで、不要なポート占有を強制的に解消できる。

  • Established Timeout: 通信中のコネクションをどれだけ維持するか(デフォルトは通常1200秒)。
  • Transitory Idle Timeout: 通信終了後の TIME_WAIT 等の状態をどれだけ維持するか(デフォルト30秒)。

これを短く設定することで、ポートの回転率を上げることができる。ただし、短すぎると頻繁に再接続が発生し、オーバーヘッドが増えるため、サービスの特性(APIのレスポンス頻度)を見極めてチューニングしてほしい。

—

まとめ:インフラは「繋がるのが当たり前」ではない

ポート枯渇問題は、ある日突然やってくる「サイレント・キラー」だ。特に開発環境では問題にならなかったのに、本番環境でトラフィックが増えた瞬間に発生する。

1. まずは監視: allocated_ports をダッシュボードに置く。
2. アプリを見直す: コネクションプーリングが正しく設定されているか確認する。
3. インフラを拡張: それでも足りなければ、Cloud NATの外部IPプールを増やす。

ネットワークのトラブルシューティングは、パケットの行方を想像することから始まる。NATの裏側で何が起きているのか、常にこの「四元数」の考え方を頭の片隅に置いておいてほしい。現場からは以上だ。

コメント

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