こんにちは!SREとして日夜クラウドの海を泳ぎ回っている筆者です。
AWSやGCPなどのパブリッククラウドを使っていると、最初は「ボタンひとつでサーバーが立ち上がってすごい!」と感動しますよね。でも、システムが成長してアクセスが爆発的に増えたとき、突然「あれ?外への通信ができなくなったぞ…?」という不可解なトラブルに直面することがあります。
その犯人の多くが、今回テーマにする「NATゲートウェイのポート枯渇(Port Exhaustion)」なんです。
なんだか名前からして難しそうですよね。でも大丈夫です!今回は、パケットの細かい難しい話は一旦置いておいて、私たちの身近な世界に置き換えながら、一歩ずつ優しく紐解いていきましょう!
—
1. NATゲートウェイって、現実世界で言うと「郵便局の窓口」なんです
まず、プライベートサブネットにある私たちのサーバーたち(コンテナやEC2インスタンスなど)が、どうやってインターネットの海へ出ているのかをイメージしてみましょう。
プライベートサブネットにいるサーバーには、原則として外の世界から直接見えない「内線番号(プライベートIPアドレス)」しか割り当てられていません。セキュリティを保つためには素晴らしいことなのですが、このままだと「外部のAPIを叩きたい」「OSのアップデートをダウンロードしたい」といった、外への用事ができません。
そこで登場するのが、NATゲートウェイです。
これは現実世界に例えるなら、「町中のプライベートなマンションの住人が、外の世界へ手紙を出すときの『たった一つの大きな郵便局の窓口』」のようなものです。
マンションの住人(プライベートIP)はたくさんいますが、外の世界(インターネット)へ手紙(リクエスト)を出すときは、必ずこの郵便局の窓口(NATゲートウェイの持つグローバルIPアドレス)を経由します。そして、宛先から返事が届いたとき、窓口の係員さんが「これは何号室の誰宛ての手紙だっけ?」と確認して、元の住人のもとに届けてくれるわけですね。
—
2. なぜ「ポート枯渇」は起きるのか?(窓口の受付表が満杯になる日)
さて、ここからが本題です。
この郵便局の窓口には、実は「同時に受付できる整理券の数(使えるポート番号の数)」に限界があります。
コンピュータの世界では、ひとつのIPアドレスあたり、外と通信するための「受付番号(ポート番号)」が理論上65,535個まで用意されています。窓口の係員さんは、外へ手紙を出すときに、この65,535個の中から空いている番号を一つずつ割り振っていくのです。
通常、数台のサーバーがちょこちょこと通信している分には、この65,535個という数字は使い切れないほど巨大です。しかし、システムが大規模化し、何百・何千というコンテナが同時に、何万もの外部APIやデータベースへ猛烈なリクエストを送り始めたらどうなるでしょうか?
あっという間に、65,535個の受付番号がすべて誰かに貸し出されてしまい、空きがゼロになってしまうのです。
これが「ポート枯渇」の瞬間です!
新しく外へ手紙を出したいサーバーがあっても、窓口の係員さんから「現在、すべての受付番号が埋まっていて発券できません!」と門前払いされてしまいます。結果として、外への通信がタイムアウトしたり、パケットがプッツリと途切れてしまうパケットドロップが発生してしまうのですね。
—
3. 恐怖の「ポート枯渇」はどうやって検知するの?
このトラブルの嫌なところは、CPU使用率やメモリ使用率はピカピカの「正常」なのに、外向きの通信だけが突然死するという、インフラ担当者にとって悪夢のようなサイレントキラーである点です。
現場のSREたちは、次のようなクラウドの監視メトリクスを常に見張ることで、この危機を事前に察知しています。
- AWS (Amazon VPC) の場合: CloudWatchメトリクスの
ErrorPortAllocation(NATゲートウェイがポートを割り当てられずにドロップしたパケットの数)を監視します。これが0より大きくなった瞬間が、まさにポート枯渇の悲鳴です。 - GCP (Cloud NAT) の場合: Cloud Monitoringで
nat/port_utilization(ポート使用率)のグラフを監視します。これが80%や90%を超えて張り付いてきたら、黄色信号です。
もし、ご自身の環境で「外部APIとの通信エラーが急増したぞ」というときは、まずこのポートの割り当て状況を確認してみてくださいね。
—
4. 現場で使える!ポート枯渇を防ぐための具体的な処方箋
もし実際にポート枯渇が起きてしまったら、あるいは将来の大規模トラフィックに備えて予防したいときは、どうすればよいのでしょうか。現場でよく使われる実践的なアプローチをいくつかご紹介します。
① NATゲートウェイの数を増やす(スケールアウト)
一番シンプルで確実な解決策の一つが、NATゲートウェイの「頭数」を増やすことです。
例えば、VPCの可用性ゾーン(AZ)ごとにNATゲートウェイを配置し、サブネットからのトラフィックを分散させます。
AWSのTerraformを例に、複数のNATゲートウェイを綺麗に配置する構成を見てみましょう。
# 各アベイラビリティゾーン(AZ)ごとにElastic IPとNATゲートウェイを定義する例
resource "aws_eip" "nat_a" {
domain = "vpc"
tags = {
Name = "prod-nat-eip-ap-northeast-1a"
}
}
resource "aws_nat_gateway" "gw_a" {
allocation_id = aws_eip.nat_a.id
subnet_id = aws_subnets.public_a.id # パブリックサブネットAに配置
tags = {
Name = "prod-nat-gw-ap-northeast-1a"
}
}
# 同様にゾーンBやCにも展開し、プライベートサブネットからのルートを分散させます
このようにIPアドレスの数を物理的に増やすことで、使える「受付番号」の総数を掛け算式に増やすことができます(※ただし、単一の宛先IPへの接続数制限など、クラウド特有の仕様には注意が必要です)。
② アプリケーション側の「コネクションの使い回し」を見直す
インフラ側で力技で耐えるだけでなく、アプリケーション(コンテナ内のコード)側の行儀を良くすることも極めて重要です。
例えば、Pythonの requests や Node.js、PHPなどで外部APIを呼び出す際、「リクエストのたびに新しいTCPコネクションを張って、終わったらすぐ捨てる」という実装になっていませんか? これをやると、瞬時に膨大なポートを消費してしまいます。
HTTPクライアントを使うときは、必ずコネクションプーリング(Keep-Alive)を有効にし、同じ接続を使い回すようにコードを書き換えましょう。
Pythonの requests ライブラリを例に、セッションを維持してポートの無駄遣いを防ぐコードの書き方です。
import requests
# 毎回 requests.get() を呼ぶのではなく、Sessionオブジェクトを生成して使い回す
# これにより、TCPコネクションが維持され(Keep-Alive)、NATポートの無駄な消費を防げます
session = requests.Session()
def call_external_api(api_url):
try:
# 同じセッションを使い続けることで、コネクションが確立された状態を保持します
response = session.get(api_url, timeout=5)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"API呼び出しに失敗しました: {e}")
return None
ほんの少しのコードの工夫ですが、これだけでNATゲートウェイにかかる負荷劇的に減らすことができます。
—
まとめ:ネットワークの仕組みを知れば、トラブルは怖くない!
今回は、大規模トラフィックの裏側に潜む「NATゲートウェイのポート枯渇」について、郵便局の窓口に例えながらお話ししてきました。
- NATゲートウェイは、外へ行く通信の「受付窓口」。
- 1つのIPアドレスが持つ受付番号(ポート)には「65,535個」という限界がある。
- コンテナやサーバーが大量の通信をばら撒くと、窓口がパンクしてパケットが届かなくなる。
- 解決策としては、NATゲートウェイを増やす(スケールアウト)ことと、アプリ側でコネクションを使い回す(Keep-Alive)ことのダブルアプローチが鉄則!
難解に見えるクラウドのネットワークも、私たちの日常のルールに置き換えてみると、どこでボトルネックが起きているのかがスッと見えてきますよね。
インフラやネットワークの世界は、こうした「目に見えない流れ」を想像する面白さに満ちています。もし皆さんの現場で突然の通信エラーに悩んだときは、ぜひこの記事の「郵便局の窓口」を思い出してみてください。
それでは、快適なクラウド&コンテナライフを!また次回の技術コラムでお会いしましょう!
コメント