【図解】なぜNATゲートウェイでパケットが消える?初心者でもわかる「Conntrack(コネクション追跡)の限界」と現場の回避術
みなさん、こんにちは!クラウドやコンテナのネットワーク構築、楽しんでいますか?
「パブリックサブネット」や「プライベートサブネット」、そして外部と安全に通信するための「NAT(ナット)ゲートウェイ」は、インフラ構築の基本中の基本ですよね。
しかし、サービスが急成長して、秒間何万ものアクセスをさばくようになると、ネットワークの裏側で「原因不明の通信エラー(パケットドロップ)」が発生することがあります。
「サーバーのCPUやメモリには余裕があるのに、なぜか外部のAPIとの通信がタイムアウトする……」
そんなミステリアスな現象の裏に潜んでいるのが、今回のテーマである「NATゲートウェイのコネクション追跡(Conntrack)テーブル溢れ」と「ハッシュ衝突」です。
「なんだか難しそうな英語が出てきたな……」と思った方も、安心してください!今回は、パケットの細かいビット数や小難しい専門用語はできるだけ使わず、私たちの身近な「郵便配達」や「アパートの管理人さん」の仕組みに例えて、一歩ずつ丁寧に紐解いていきます。
実務でそのまま使える対策や、Terraformなどの設定例も交えて解説しますので、最後まで一緒に楽しく学んでいきましょう!
—
1. NATゲートウェイの役割を「アパートの管理人さん」で理解しよう
まずは基本のおさらいから始めましょう。
クラウドの世界では、セキュリティのために、データベースやアプリケーションサーバーをインターネットから直接見えない「プライベートサブネット」に配置します。
しかし、これらのサーバーも「セキュリティパッチをダウンロードしたい」「外部の決済APIと通信したい」といった理由で、インターネットに出ていきたいときがありますよね。
そこで登場するのがNATゲートウェイです。
これを現実世界で例えるなら、「外の世界(インターネット)と直接やり取りできない、厳重警備のアパート(プライベートサブネット)の管理人さん」です。
[ アパートの住人 (サーバー) ]
│ (内線でお願い) 「外のショップにお買い物してきて!」
▼
[ 管理人さん (NATゲートウェイ) ]
│ (管理人さんの名前で外へ出発)
▼
[ インターネット (外部APIなど) ]
アパートの住人(プライベートIPを持つサーバー)は、外に直接手紙(パケット)を出せません。そこで、すべての手紙を一度「管理人さん(NATゲートウェイ)」に託します。
管理人さんは、手紙の差出人を「住人の部屋番号」から「管理人さん自身の名前(パブリックIPアドレス)」に書き換えて、外の世界へ送り出します。これが「アドレス変換(NAT)」と呼ばれる仕組みです。
—
2. 帰ってきた手紙を届けるための「Conntrack(コネクション追跡)ノート」
さて、外のショップから、管理人さん宛てに返事の手紙が届きました。
宛先は「管理人さん」になっています。これでは、アパートのどの住人(サーバー)に返せばいいかわかりませんよね。
そこで、管理人さんは手紙を送り出すときに、一冊の「発送記録ノート」にメモを残しています。
このノートのことこそが、ネットワーク用語でいう「コネクション追跡(Conntrack:コネクショントラック)テーブル」です。
ノートには、以下のような「5つの手がかり(5-tuple)」がセットで記録されています。
1. 誰が(送信元IPアドレス):アパートの〇〇号室の住人
2. どの窓口から(送信元ポート番号):住人が使った封筒の整理番号
3. 誰宛てに(宛先IPアドレス):外の〇〇ショップ
4. 相手のどの窓口に(宛先ポート番号):ショップの受付窓口(通常はWebの 80 や 443)
5. どんな方法で(プロトコル):手紙の送り方(TCPやUDPなど)
この「5つの手がかり」が1つのセット(コネクション)となり、ノートに1行ずつ書き込まれます。
【管理人さんのConntrackノート(例)】
[住人IP: 10.0.1.5] [ポート: 50001] ──> [ショップIP: 203.0.113.80] [ポート: 443] (TCP)
返事の手紙が届いたとき、管理人さんはこのノートを上から順にめくって、「あ、この手紙は 10.0.1.5 の住人宛てだな!」と特定し、無事に部屋まで届けることができるのです。
—
3. なぜパケットが消える? 2大トラブルの原因
普段はとても優秀な管理人さんですが、アパートの住人が一斉に、秒間何万通もの手紙を送り始めると、キャパシティの限界を超えてしまいます。
ここで発生するのが、今回の本題である2つのトラブルです。
トラブル①:ノートが真っ黒で書き込めない!「テーブル溢れ(限界突破)」
管理人さんの「発送記録ノート(Conntrackテーブル)」には、書き込める行数の上限が決まっています。
住人(サーバー)たちが、あまりにも大量の接続を一気に作ると、ノートのページがすべて埋まってしまいます。
【満杯になったノート】
1行目: [住人A] ──> [ショップX]
2行目: [住人B] ──> [ショップY]
...
1,000,000行目: [住人Z] ──> [ショップW] (もうこれ以上書けない!)
この状態のときに、さらに新しい手紙の発送を頼まれると、管理人さんは「もうノートに記録できないから、この手紙は送れません!」と、手紙をその場でゴミ箱に捨ててしまいます。
これが、コネクション追跡テーブル溢れによるパケットドロップです。
トラブル②:同じような手紙が多すぎてパニック!「ハッシュ衝突」
「じゃあ、ノートのページ数を無限に増やせば解決するのでは?」と思いますよね。しかし、話はそう単純ではありません。
管理人さんは、手紙が届くたびに、分厚いノートを1ページ目からペラペラとめくって探していたのでは時間がかかりすぎてしまいます。
そこで、特定のルール(ハッシュ関数)を使って、「この特徴の手紙なら、ノートのこの引き出し(ハッシュバケット)に入っているはず」と一瞬で探し出す工夫をしています。
しかし、以下のような状況が重なるとどうなるでしょう?
- アパートの多くの住人が、
- まったく同じ宛先(例えば、大人気の外部決済APIの同一IPアドレス・ポート番号)に対して、
- 同時に大量の手紙を送る。
このとき、用意された引き出し(ハッシュバケット)の中に、同じような特徴を持った「5つの手がかり」のメモが、ギューギューに詰め込まれることになります。
これをネットワークの世界で「ハッシュ衝突(Hash Collision)」と呼びます。
ひとつの引き出しにメモが集中しすぎると、管理人さんは引き出しの中からお目当てのメモを探し出すのに膨大な時間がかかってしまいます。
そして、制限時間内にメモが見つからなかった場合、管理人さんは処理を諦めて手紙を捨ててしまいます。これが、ハッシュ衝突によるパケットドロップの正体です。
—
4. 現場のSREが実践する!3つの具体的な解決策
この「管理人さんのパニック」を防ぐために、実際のシステム開発やインフラ構築ではどのような対策を取るのでしょうか?
実務でそのまま使える、代表的な3つのアプローチをご紹介します。
対策1:通信の「つなぎっぱなし(Keep-Alive)」を有効にする
もっとも効果的で、今すぐアプリケーション側で設定できるのが「Keep-Alive(キープアライブ)」の有効化です。
手紙を送るたびに「封筒を用意して、宛先を書いて、ノートに記録して……」とやるのではなく、「一度つないだパイプ(コネクション)をしばらく維持して、使い回す」という方法です。
これにより、ノートに書き込まれる行数(コネクション数)そのものを劇的に減らすことができます。
例えば、Python の requests ライブラリを使う場合、毎回単発でリクエストを送るのではなく、以下のように Session オブジェクトを使うことで、自動的にコネクションが使い回されます。
import requests
# ✕ 悪い例:毎回新しいコネクションを作り、Conntrackノートを消費する
for _ in range(100):
response = requests.get("https://api.example.com/data")
# ◯ 良い例:Sessionを使い回し、1つのコネクションで何度も通信する
session = requests.Session()
for _ in range(100):
response = session.get("https://api.example.com/data")
対策2:NATゲートウェイに「複数のIPアドレス」を割り当てる(AWSの例)
AWSのNAT Gatewayでは、1つのパブリックIPアドレス(Elastic IP)につき、同時に維持できるコネクション数(送信元ポート数)に上限(約55,000個)があります。
もし特定の宛先に対してこれ以上の接続が必要な場合、NAT Gatewayに複数のIPアドレスを関連付けることで、管理人さんの名前(IPアドレス)を増やし、ノートのキャパシティを倍増させることができます。
以下は、Infrastructure as Code(IaC)ツールの Terraform を使って、1つのNAT Gatewayに2つのElastic IPを割り当てる設定サンプルです。
# 1つ目の Elastic IP
resource "aws_eip" "nat_1" {
domain = "vpc"
tags = {
Name = "nat-eip-1"
}
}
# 2つ目の Elastic IP (ノートのキャパシティを増やすために追加)
resource "aws_eip" "nat_2" {
domain = "vpc"
tags = {
Name = "nat-eip-2"
}
}
# NATゲートウェイの作成
resource "aws_nat_gateway" "example" {
# パブリックサブネットに配置します
subnet_id = aws_subnet.public.id
# 複数のElastic IPを配列で割り当てます(アソシエーション)
allocation_ids = [
aws_eip.nat_1.id,
aws_eip.nat_2.id
]
tags = {
Name = "multi-ip-nat-gateway"
}
}
*※注意:AWSにおいてNAT Gatewayへの複数IP割り当てを行うには、事前にアソシエーションの設定や、利用するルートテーブルの調整が必要な場合があります。公式ドキュメントも併せてご確認ください。*
対策3:そもそもNATゲートウェイを通さない(VPCエンドポイントの活用)
「外部のAPI」ではなく、「AWSのS3やDynamoDB」といった同じクラウド内のサービスと通信する場合、わざわざNATゲートウェイ(管理人さん)を通す必要はありません。
「VPCエンドポイント(プライベートリンク)」という、アパートから各サービスへの「直通のプライベート地下通路」を開通させましょう。
これにより、NATゲートウェイのConntrackノートを1ミリも消費することなく、高速かつ安全に通信ができるようになります。
—
5. まとめ:一歩ずつ強いインフラを作っていきましょう!
最後に、今回のポイントを振り返ってみましょう。
1. NATゲートウェイは、プライベートなサーバーの代わりにパブリックIPに書き換えて通信を仲介する「管理人さん」。
2. 返ってきたパケットを正しいサーバーに戻すため、Conntrack(コネクション追跡)テーブルという「発送記録ノート」を使っている。
3. 同時接続が多すぎると、ノートが満杯になる「テーブル溢れ」や、同じような記録が集中する「ハッシュ衝突」が起き、パケットがドロップ(破棄)されてしまう。
4. 対策として、アプリ側での「Keep-Aliveの有効化」、インフラ側での「NATの複数IP化」や「VPCエンドポイントの導入」が極めて有効。
最初は難しく思えるネットワークの挙動も、こうして「誰が何のために記録をつけているのか」を順を追って見ていくと、すんなりイメージが湧いてきますよね。
インフラのトラブルシューティングは、こうした「見えない裏側の動き」を想像する力が最大の武器になります。
一歩ずつ知識を蓄えて、高トラフィックにもびくともしない、頑丈で美しいシステムを一緒に築いていきましょう!
また次回の記事でお会いしましょう。ハッピー・インフラエンジニアリング!
コメント