Cloud NATの深淵:ポート枯渇と戦うSREのための「パケットの行方」
クラウドネイティブな環境において、パブリックIPを持たないVMがインターネットへアクセスする際、私たちは当然のように Cloud NAT に頼り切っています。しかし、大規模なマイクロサービスや、高頻度な外部APIコールが交差する環境で、突如として発生する「接続タイムアウト」に頭を抱えたことはないでしょうか。
「なぜ、まだ帯域には余裕があるのに、パケットが捨てられるのか?」
その答えは、ネットワークの表面的な帯域幅ではなく、トランスポート層における「ポートの生存期間」という極めてシビアな制限にあります。今日は、Cloud NATの内部挙動を紐解き、ポートオーバーサブスクリプションの罠を回避するプロフェッショナルな知見を共有しましょう。
—
1. Cloud NATのSNATアルゴリズム:ポートという名の「命綱」
Cloud NATは、単なるゲートウェイではありません。それは数千ものVMから送られてくるパケットに対し、Source IP と Source Port を書き換え、戻りパケットを正しい宛先へルーティングする「ステートフルな変換機」です。
5タプルの一致とポート割り当て
パケットがインターネットへ出る際、Cloud NATは (Source IP, Source Port, Destination IP, Destination Port, Protocol) の5タプルを監視します。ここでのボトルネックは、NATゲートウェイが1つのパブリックIPあたり最大64,512個のポートしか保持できないという物理的制約です。
特定の宛先(例えば外部の決済APIなど)に対して、短時間に大量のコネクションを張ると、一瞬で「ポート枯渇」が訪れます。これが、パケットが DROP される根本原因です。
—
2. ポートオーバーサブスクリプション:なぜ「繋がらない」のか
Cloud NATは「ポートの動的割り当て(Dynamic Port Allocation)」を行います。しかし、接続数が急増すると、新規コネクションに対するポートの払い出しが追いつかず、TCP SYN パケットが黙殺されます。
回避策のアーキテクチャ
この状況を打破するためには、以下の3つのアプローチを検討すべきです。
1. 最小ポート数を増やす:
デフォルトの割り当て数では足りない高負荷環境では、min-ports-per-vm を調整します。
2. IPプールの拡張:
複数のパブリックIPをNATゲートウェイに割り当て、負荷を分散させます。
3. コネクションプールの活用:
アプリケーション層で HTTP Keep-Alive を適切に設定し、TCPコネクションの再利用を強制します。
# 現在のNAT設定を確認するコマンド
gcloud compute routers nats describe [NAT_NAME] \
--region=[REGION] \
--router=[ROUTER_NAME] \
--format="yaml(rules, minPortsPerVm)"
# パフォーマンス改善のために最小ポート数を引き上げる例
gcloud compute routers nats update [NAT_NAME] \
--router=[ROUTER_NAME] \
--region=[REGION] \
--min-ports-per-vm=1024 # デフォルトの64から増やし、ポート不足のバーストを許容する
—
3. TCPバッファチューニングとTLSハンドシェイクの最適化
ポート枯渇が起きていないにもかかわらずレイテンシが高い場合、ボトルネックはカーネルのTCPスタックにあります。
Linuxカーネルの調整
VM上の sysctl 設定を見直すことで、TIME_WAIT状態のソケットを効率的に再利用し、ポートの回転率を上げることができます。
# /etc/sysctl.conf に追記し、再利用を加速させる
# タイムアウトしたコネクションを再利用可能にする
net.ipv4.tcp_tw_reuse = 1
# TCP FIN-WAIT-2のタイムアウト時間を短縮し、ソケット開放を早める
net.ipv4.tcp_fin_timeout = 15
# 最大接続待機列を増やす
net.core.somaxconn = 1024
また、TLSのハンドシェイクにおいて、TLS 1.3 への強制移行は必須です。1.3ではハンドシェイクが1往復(1-RTT)で完結するため、レイテンシが劇的に改善します。さらに、OCSP Stapling を有効にすることで、クライアント側での証明書検証に伴う外部通信を削減し、ネットワークの負荷を軽減できます。
—
4. 現場で使える「死なない」設計の心得
最後に、SREとして「大規模なトラフィックに耐え抜く」ためのプラクティスを記します。
- コネクションのライフサイクル管理:
アプリケーションコード内でコネクションを使い回す際、Connection: keep-alive ヘッダーを必須にしてください。これにより、TCP接続を何度も確立・切断するコスト(とポート消費)を回避できます。
- モニタリングの重要性:
Cloud Monitoringで nat/port_usage を常に監視し、80%を超えたら自動的にアラートが飛ぶようにしてください。ポート枯渇は「沈黙する障害」であり、事後にログを追うのは非常に困難です。
- TCPゼロコピーとヘッダー圧縮:
もしGoやRustで高負荷なリクエストを処理しているなら、gRPC を採用することを強く推奨します。HTTP/2ベースの多重化により、単一のTCP接続で複数のリクエストを同時に処理できるため、NATのポート消費を劇的に抑えられます。
ネットワークは魔法ではありません。すべてはカーネルのメモリ、パケットの5タプル、そして物理的な制約の積み重ねの上に成り立っています。この深淵を覗き込む勇気を持つことこそが、真のSREへの第一歩です。
皆さんのインフラが、今日も静かに、そして力強くパケットを運び続けることを願っています。
コメント