【実務・中級編】 GCP Cloud NATの最小ポート数(Min Ports Per VM)自動割当アルゴリズム – クラウド&コンテナネットワーク実践ガイド

GCP Cloud NATの「ポート枯渇」と戦う:Min Ports Per VMの自動割当アルゴリズムを深掘りする

SREとして現場に立っていると、深夜のオンコールで必ずと言っていいほど直面するのが「NATポートの枯渇」です。特にGCPの Cloud NAT を利用している環境で、外部APIへのリクエストが急増した瞬間に 503 Service Unavailable や Connection Timeout が頻発する現象は、まさに悪夢の始まりと言えます。

今日は、教科書にはあまり詳しく書かれていない、GCP Cloud NATの「ポート自動割当アルゴリズム」の裏側と、実務で身を守るための設定術について、泥臭い視点で解説します。

—

1. なぜ「ポート」が足りなくなるのか?

TCP通信において、クライアント(VM)が外部のサーバーへ接続する際、送信元IPアドレスと送信元ポート番号の組み合わせが「ソケット」として一意に識別されます。Cloud NATは、VMのプライベートIPをNATゲートウェイのパブリックIPへ変換する際、このポートを動的に割り当てます。

ここで重要なのが、Cloud NATの 「エンドポイント独立マッピング(Endpoint-Independent Mapping)」 です。これは、同じVMから同じ宛先(IPとポート)へ向かう通信に対して、可能な限り同じポートを再利用しようとする仕組みですが、接続先が多岐にわたる場合、ポートは瞬く間に消費されます。

ポート枯渇のシグナル

gcloud コマンドで確認できる nat_dropped_packets_count メトリックが跳ね上がっていたら、それは「割り当て可能なポートが尽きた」というSOSです。

—

2. Min Ports Per VM:自動割当の「生命線」

Cloud NATを構築する際、デフォルトでは min-ports-per-vm が 64 に設定されています。しかし、大規模なマイクロサービス構成では、この値がボトルネックになることが多々あります。

自動割当アルゴリズムの挙動

Cloud NATは、設定された min-ports-per-vm を「最低保証数」として確保します。しかし、負荷が急増した際には、以下のルールで動的に拡張を試みます。

1. 初期確保: VMが起動した瞬間に min-ports-per-vm 分のポートを予約します。
2. 動的拡張: 予約分を使い切ると、NATはさらにポートを要求しますが、プールに空きがあれば「自動的に」追加割り当てを行います。
3. 上限の壁: 全てのVMに割り当てたポートの合計が、NATゲートウェイに紐付けられた外部IPアドレスの全ポート(64,512ポート/IP)を超えると、そこから先は「早い者勝ち」または「ドロップ」が発生します。

—

3. 実践:Terraformでの最適化設定

多くのエンジニアがやりがちなミスは、min-ports-per-vm を過小評価することです。Web APIを頻繁に叩くアプリケーションであれば、少なくとも 256 〜 1024 程度は確保しておくのが安全です。

# TerraformでのCloud NAT設定例
resource "google_compute_router_nat" "main_nat" {
  name                               = "production-nat"
  router                             = google_compute_router.router.name
  region                             = "asia-northeast1"
  nat_ip_allocate_option             = "MANUAL_ONLY" # IPを固定して制御しやすくする
  nat_ips                            = [google_compute_address.nat_ip.self_link]

  # ここが重要!アプリケーションの同時接続数を見積もって設定する
  # 64だと高負荷時に瞬殺されるため、余裕を持って設定
  min_ports_per_vm                   = 512 

  # ポートの最大拡張数(メモリ効率と安全性のバランスをとる)
  max_ports_per_vm                   = 2048 

  enable_endpoint_independent_mapping = true # 同じ宛先への再利用を促進
}

—

4. 現場で使えるデバッグと対応のTips

コード側で「コネクションをどう扱うか」も重要です。Pythonの requests ライブラリを使っている場合、デフォルトのままでは接続のたびに新しいポートを開こうとします。

Pythonでの接続再利用(Connection Pooling)

requests.Session を使うことで、TCPコネクションを使い回し、ポートの消費を劇的に抑えることができます。

import requests

# Sessionオブジェクトを使い回すことで、同じ宛先へのポートを再利用する
session = requests.Session()

def fetch_external_api(url):
    try:
        # TCPコネクションが維持されるため、ポート枯渇のリスクが激減する
        response = session.get(url, timeout=5)
        return response.json()
    except requests.exceptions.ConnectionError as e:
        # ここに到達したらポート枯渇を疑う
        print(f"Connection failed: {e}")

デバッグコマンド

もし障害が発生した際、実際にどの程度ポートを使っているかは、VM内から以下の情報を確認します。

# 現在のコネクションの状態を確認(ESTABLISHEDが多い場合は要注意)
ss -tan | grep ESTAB | wc -l

# もしCloud NATのドロップを確認したいならCloud Monitoringのクエリ
# metric.type="compute.googleapis.com/nat/dropped_packets_count"

—

最後に:SREとしてのアドバイス

「とりあえず大きな値を設定しておけばいいや」というのは、IPアドレスという貴重なリソースの無駄遣いにも繋がります。

1. トラフィック分析: まずは Cloud Monitoring で nat_ports_used を確認し、ピーク時の使用量を把握してください。
2. コネクションプーリング: インフラのポートを増やす前に、アプリケーション側で Keep-Alive が有効か、接続の使い回しができているかを必ず見直してください。
3. スケーリング戦略: 1つのNATゲートウェイに全てのVMを詰め込むのではなく、負荷に応じてNATゲートウェイを分割することも検討の余地があります。

ネットワークは「見えないからこそ、仕組みを想像する」ことが大切です。今日解説したアルゴリズムが、皆さんのシステムの安定稼働の一助となれば幸いです。

コメント

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