こんにちは!SREとして日夜クラウドの海を泳いでいる筆者です。
AWSやGCPといったパブリッククラウドを使っていると、「パブリックサブネット」や「プライベートサブネット」、そして「NATゲートウェイ」という言葉に必ず出会いますよね。
「なんだか難しそう……」
「外のインターネットと通信するための、ただの通路でしょ?」
そう思っていませんか?実はこのNATゲートウェイ、システムが巨大化して秒間数百万というモンスター級のリクエストを処理し始めるやいなや、インフラエンジニアの前に立ちはだかる最強のボスのひとりに変貌するんです。
今回は、このNATゲートウェイの「知られざる限界」と、それを華麗に乗りこなすための分散設計について、身近な例えを交えながら一歩ずつ優しく紐解いていきましょう!
—
1. NATゲートウェイって、現実世界でたとえると何?
まずは、パブリックサブネット、プライベートサブネット、そしてNATゲートウェイの関係を、私たちの身近な世界にたとえてみましょう。
社外(インターネット)から注文を受け付ける巨大な会社を想像してください。
- プライベートサブネット(社内の機密オフィス):
ここにいる従業員(サーバーたち)は、外部の怪しい人から直接話しかけられたくありません。セキュリティをガチガチに固めた安全な部屋で、もくもくと仕事(データ処理)をしています。外の住所も持っていません。
- パブリックサブネット(受付・玄関口):
こちらは外の世界と直接つながっている場所です。外部からの宅配便を受け取ったり、外へ荷物を送り出したりする窓口です。
- NATゲートウェイ(敏腕な「おつかい係」):
さあ、社内の機密オフィスにいる従業員が、「外部のデータベースから最新情報を取ってきて!」と頼まれました。でも、彼らは外の世界に出るパスポート(グローバルIPアドレス)を持っていません。
そこで登場するのが、おつかい係であるNATゲートウェイです!
従業員から荷物(パケット)を受け取ったおつかい係は、こう言います。
> 「よし、宛先は私宛にしておきなさい。外の世界には、私(NATゲートウェイ)の名前で問い合わせて、返ってきたら君に渡してあげるからね!」
この仕組みのおかげで、プライベートオフィスの従業員は、安全を保ったまま外の世界とやり取りができるというわけですね。一歩ずつ、仕組みがイメージできてきましたでしょうか?
—
2. なぜNATゲートウェイでつまずくのか?(スケール上限の壁)
システムが小さいうちは、このおつかい係(NATゲートウェイ)は1人もいれば十分です。しかし、サービスがバズったり、大規模なECサイトのセールが始まったりして、秒間数百万件ものリクエストが押し寄せてくるとどうなるでしょうか?
おつかい係が1人しかいないと、想像してみてください。
- キャパシティの限界(帯域幅の壁):
秒間数百万件の荷物が一気にドッと押し寄せます。おつかい係が通る廊下(帯域幅)の幅には限界があります(クラウドサービスによりますが、最大でも45Gbps程度など)。これが「帯域幅の上限」です。
- ポート番号の枯渇(同時におつかいに行ける限界):
おつかい係が同時に覚えられるメモの数(ポート番号)には物理的な限界があります。「誰から預かったどの荷物だっけ?」とパニックを起こしてしまう現象が、いわゆるポート枯渇(SNATポート枯渇)です。
結果として、おつかい係のところで大渋滞が起き、外の世界との通信がタイムアウトしたり、エラーが続出したりする大惨事につながります。これが、大規模システムにおけるNATゲートウェイのパフォーマンスの壁なのです。
—
3. 巨大な波を乗りこなす!分散設計のテクニック
では、この「おつかい係の限界」をどうやって突破すればよいのでしょうか?
答えはシンプルです。「おつかい係をたくさん雇って、仕事を分散させる」のです!
クラウドの世界では、これを「マルチNATゲートウェイ構成(アベイラビリティゾーンごとの分散)」や「リソースの水平分散」と呼びます。
具体的なアーキテクチャのアプローチ
1. アベイラビリティゾーン(AZ)ごとにNATゲートウェイを配置する
東京リージョンであれば、ap-northeast-1a、1c、1d のように複数のデータセンター(AZ)をまたいでシステムを構築します。それぞれのAZに専用のNATゲートウェイを置き、プライベートサブネットからのルートテーブルを適切に振り分けます。
2. 接続先(宛先)ごとのコネクションプーリング
データベースや外部APIへ接続する際、毎回新しいおつかいをお願いするのではなく、コネクションを使い回す(プーリングする)ことで、おつかい係の負担を劇的に減らします。
—
4. 実践!インフラコードでの設定例と解説
言葉だけではイメージしにくいと思いますので、AWSのインフラをコードで管理する際によく使われる「Terraform」を例に、冗長化されたNATゲートウェイの設定を見てみましょう。
難しい部分はコメントで丁寧に解説するので、リラックスして眺めてみてくださいね。
# ==========================================
# 複数のAZにNATゲートウェイを分散配置する設定例
# ==========================================
# 1つ目のAZ(ap-northeast-1a)用のパブリックサブネットとNATゲートウェイ
resource "aws_subnet" "public_a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "ap-northeast-1a"
tags = { Name = "public-subnet-1a" }
}
resource "aws_eip" "nat_a" {
domain = "vpc"
tags = { Name = "nat-eip-1a" }
}
resource "aws_nat_gateway" "gw_a" {
allocation_id = aws_eip.nat_a.id
subnet_id = aws_subnet.public_a.id
tags = { Name = "nat-gw-1a" } # 1つ目のおつかい係
}
# 2つ目のAZ(ap-northeast-1c)用のパブリックサブネットとNATゲートウェイ
resource "aws_subnet" "public_c" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24"
availability_zone = "ap-northeast-1c"
tags = { Name = "public-subnet-1c" }
}
resource "aws_eip" "nat_c" {
domain = "vpc"
tags = { Name = "nat-eip-1c" }
}
resource "aws_nat_gateway" "gw_c" {
allocation_id = aws_eip.nat_c.id
subnet_id = aws_subnet.public_c.id
tags = { Name = "nat-gw-1c" } # 2つ目のおつかい係(負荷分散の要!)
}
このように、複数のAZにそれぞれNATゲートウェイを配置することで、トラフィックが分散され、片側が万が一パンクしても、もう片方がシステム全体の崩壊を防いでくれます。
—
5. アプリケーション側からのアプローチ(プログラミング設定例)
インフラ側でどれだけNATゲートウェイを増やしても、アプリケーション側(PHPやPythonなど)の作りが悪いと、ポート枯渇を引き起こしてしまいます。
例えば、Pythonのrequestsライブラリなどで外部APIを叩く際、毎回新しい接続を作ってしまうコードの書き方をしていませんか?ここでは、コネクションを効率よく使い回すための「セッション(Session)」を利用した実装例を見てみましょう。
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
def create_robust_session():
"""
NATゲートウェイのポート枯渇を防ぐため、
コネクションを再利用(プーリング)するセッションを作成する関数です。
"""
session = requests.Session()
# リトライ設定(通信エラーが起きても安全に再試行する)
retries = Retry(
total=3,
backoff_factor=0.3,
status_forcelist=[500, 502, 503, 504]
)
# アダプターを設定して、同じTCPコネクションを何度も使い回せるようにする
# これにより、NATゲートウェイのポート消費を最小限に抑えられます!
adapter = HTTPAdapter(pool_connections=100, pool_maxsize=100, max_retries=retries)
session.mount("https://", adapter)
session.mount("http://", adapter)
return session
# メインの処理
if __name__ == "__main__":
api_session = create_robust_session()
# 外部APIへのリクエスト(コネクションが効率よく再利用されます)
try:
response = api_session.get("https://api.example.com/v1/data", timeout=5)
print(f"通信成功! ステータスコード: {response.status_code}")
except requests.exceptions.RequestException as e:
print(f"通信エラーが発生しました: {e}")
このように、インフラの設計(NATゲートウェイの分散)と、アプリケーションの設計(コネクションの使い回し)の両面からアプローチすることで、秒間数百万リクエストという巨大な負荷にも耐えうる、強靭なシステムが完成します。
—
おわりに
今回は、大規模WebシステムにおけるNATゲートウェイのパフォーマンスとスケール上限、そしてその対策について、身近な例えを交えて解説しました。
「ネットワークやインフラは難しそう」と感じていた方も、おつかい係の例えを通じて、トラフィックが集中したときにどこで渋滞が起きるのか、どうやって道を分ければいいのかがイメージできたのではないでしょうか?
現場でトラブルに直面したときや、これから大規模なシステム設計に挑むときは、ぜひこの「おつかい係の分散と効率化」を思い出してみてくださいね。
それでは、快適なクラウドライフを!SREチームより愛を込めて。
コメント