マルチAZ時代のNATゲートウェイ設計:パケットが語る冗長化の真実とゾーン障害時の生存戦略
クラウドネイティブなインフラストラクチャにおいて、「可用性」という言葉はもはや単なるマーケティング用語ではない。それは、夜中に pager が鳴り響くのを防ぐための防壁であり、ミリ秒単位のレイテンシー最適化と表裏一体をなすエンジニアリングの極みだ。
特に、インターネットへの外向き通信(Egress)を担うNATゲートウェイの設計は、システム全体のスループットと耐障害性を左右するクリティカルな要素である。今回は、AWSやGCPなどのメガクラウド環境におけるマルチAZ構成のNATゲートウェイ配置と、ゾーン障害時におけるパケットレベルの挙動、そして極限のパフォーマンスを引き出すためのカーネルおよびTCPチューニングの深淵へと踏り込む。
—
1. なぜ「1つでは足りないのか」:マルチAZ NATゲートウェイの必然性
コスト削減の悪魔の囁きに負け、「プライベートサブネットからの外向き通信は、単一のAZに配置した1つのNATゲートウェイに集約すればいいや」と設計したことはないだろうか。そのアーキテクチャは、特定のAZが物理的な障害(電源喪失やバックボーンの断絶)に見舞われた瞬間、システム全体を沈没させる単一障害点(SPOF)へと変貌する。
さらに、単一NATゲートウェイ構成には、可用性の問題だけでなく「AZ間のデータ転送コスト(Cross-AZ Data Transfer Cost)」という致命的な代償が伴う。異なるAZに存在するEC2インスタンスから単一AZのNATゲートウェイへトラフィックを流し込むと、クラウドプロバイダーは容赦なくAZ間転送量を課金してくる。大規模なマイクロサービス群において、これはじわじわとインフラ予算を蝕む隠れた癌となる。
真にレジリエントな設計とは、すべての可用性ゾーン(AZ)に独立したNATゲートウェイをデプロイし、トラフィックを完全に局所化(Zone-Local Routing)させることである。
[ AZ-a ] [ AZ-b ]
EC2 (Private) --(同一AZ内ルーティング)--> NAT Gateway-a --> Internet
| (ゾーン障害時)
v (動的ルーティング切替)
EC2 (Private) --(クロスAZ)-------------> NAT Gateway-b --> Internet
—
2. ゾーン障害発生時、ルートテーブルとパケットはどう動くのか
ある日突然、AZ-a がブラックアウトしたとする。この時、パケットの運命はどうなるのか。その裏側では、クラウドのコントロールプレーンとLinuxカーネルのネットワークスタックが緻密な連携を繰り広げている。
コントロールプレーンの挙動
パブリッククラウドのマネージドNATゲートウェイは、背後で冗長化されたElastic IP(EIP)や外部IPを保持している。特定のAZが物理的に隔離された場合、クラウドの自動修復機能(あるいはBGPベースのルーティング制御)が働き、影響を受けたAZのルートテーブルやエンドポイントのステータスが更新される。
しかし、完全な自動フェイルオーバーを待つだけでは、RTO(目標復旧時間)数秒〜数分の間にパケットがブラックホールに吸い込まれてしまう。ここで重要になるのが、インフラストラクチャ・アイズ・コード(IaC)やカスタムヘルスチェックによる、動的なルートテーブルの書き換えメカニズムだ。
データプレーン(パケットレベル)の挙動
AZ-a にあるプライベートサブネットのルートテーブルが、障害検知トリガーによって以下のように書き換わると仮定しよう。
# 障害発生前のルートテーブル (AZ-a用)
- Destination: 0.0.0.0/0
Target: nat-gateway-az-a (DEAD)
# 障害発生後の動的ルーティング切替後
- Destination: 0.0.0.0/0
Target: nat-gateway-az-b (ALIVE)
この瞬間、Linuxカーネルのルーティングキャッシュおよびコンラート(Conntrack)テーブルはどうなるか。TCPコネクションの確立中にNATゲートウェイが切り替わると、既存のTCPセッションはソースIP/ポートの維持(あるいはステートフルファイアウォールのセッション断)により、基本的にはリセット(RST)されるか、タイムアウトに向かう。
ここで重要なのは、アプリケーション層での再送メカニズム(Exponential Backoff)と、TLSセッションのレジューム(Session Resumption)の設計である。フェイルオーバー発生時、クライアント側のTCPスタックは即座に新しいルートに向けてSYNパケットを再送し、NATゲートウェイのSNAT(Source Network Address Translation)エンジンが新しいエフェメラルポートを割り当てる。
—
3. 極限のパフォーマンスチューニング:RTT削減とTCPバッファ
マルチAZ構成を採用し、クロスAZのオーバーヘッドを排除したとしても、NATゲートウェイ自体のスケーリング限界やLinuxカーネルのデフォルトパラメータに足元をすくわれることがある。数万コネクションを捌く高負荷なEgress環境では、以下のカーネルパラメータとネットワーク設定が命運を分ける。
Linuxカーネルパラメータ(sysctl)の最適化
NATゲートウェイを自前で構築する場合(あるいは高度なコンテナネットワーク制御を行う場合)、/etc/sysctl.conf において以下のチューニングが不可欠となる。
# エフェメラルポートの範囲を極限まで拡大し、ポート枯渇(Port Exhaustion)を防ぐ
net.ipv4.ip_local_port_range = 1024 65535
# TIME_WAIT状態のソケットを迅速に再利用し、高頻度なアウトバウンド通信に対応
net.ipv4.tcp_tw_reuse = 1
# TCPパケットのウィンドウサイズを動的にスケーリングし、大規模なBDP(Bandwidth-Delay Product)に対応
net.ipv4.tcp_window_scaling = 1
# コネクション追跡(Conntrack)テーブルの最大サイズを引き上げ、高トラフィック時のパケットドロップを回避
net.netfilter.nf_conntrack_max = 2097152
net.netfilter.nf_conntrack_buckets = 524288
ポート枯渇の悪夢とSNATの数理
1つのパブリックIPアドレス(NATゲートウェイ)が同時に確立できるTCPコネクション数は、理論上 (65535 - 1024) = 64,511 が上限となる(実際には宛先IP/ポートの組み合わせである4タプル (Source IP, Source Port, Dest IP, Dest Port) で一意に決まるため、同一宛先でなければこの制限を超えることは可能だが、単一宛先への大量リクエストでは容易に枯渇する)。
これを回避するため、マルチAZ構成の各NATゲートウェイに対して複数個のEIPを割り当て、パブリックIPのプールを拡張するアプローチが極めて有効である。AWSのNATゲートウェイはデフォルトでプレフィックス単位の拡張や複数IPの紐付けをサポートしていないケースもあるが、KubernetesのEgress GatewayやEnvoyを用いたカスタムNATレイヤーでは必須の知見となる。
—
4. セキュリティとトランスポート層の最適化
NATゲートウェイを通過するトラフィックの多くは、HTTPS(TLS 1.3)で暗号化されている。ここでインフラエンジニアが意識すべきは、「暗号化そのものがネットワークスタックに与える影響」と「セキュリティ境界の維持」だ。
TLS 1.3とハンドシェイクのレイテンシー
TLS 1.3では、従来のTLS 1.2と比較してハンドシェイクのラウンドトリップ(RTT)が 2-RTT から 1-RTT(さらに0-RTT Resumption)へと短縮された。しかし、NATゲートウェイでパケットの断片化(Fragmentation)やMTUのミスマッチが発生すると、TCPの初期輻輳ウィンドウ(Initial Congestion Window: initcwnd)のサイズを超えるパケットがドロップし、ハンドシェイクが再送のループに陥る。
特に、クラウド環境のMTU(標準的な 1500 バ이트、あるいはJumbo Frameの 9001 バイト)と、インターネット側のMTUの乖離によって発生する「Path MTU Discovery (PMTUD) のブラックホール問題」は、NATゲートウェイのICMPドロップ設定を誤るとデバッグが極めて困難になる。
以下のiptables(またはnftables)ルールを確認し、ICMP Destination Unreachable( Fragmentation Needed)が適切にフォワードされているか確認してほしい。
# PMTUDを維持するため、ICMP Fragmentation Neededパケットのブラックホール化を防ぐ
# (通常、クラウドのマネージドNATでは自動処理されるが、独自構築のルーターやFirewallでは必須)
iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
この設定により、TCPのMSS(Maximum Segment Size)がパスの最小MTUに合わせて自動的にクランプされ、パケットの断片化によるドロップを防ぐことができる。
—
5. 実践:TerraformによるマルチAZ対応NATゲートウェイの模範実装
机上の空論で終わらせないために、インフラストラクチャをコードとして定義する際の実装パターンを示そう。以下のTerraformコードは、可用性とゾーン局所性を担保したマルチAZ NATゲートウェイの設計パターンである。
# 可用性ゾーンのリストを取得
data "aws_availability_zones" "available" {
state = "available"
}
# 各AZごとに独立したEIPとNATゲートウェイをプロビジョニング
resource "aws_eip" "nat" {
count = length(data.aws_availability_zones.available.names)
domain = "vpc"
tags = {
Name = "nat-eip-${data.aws_availability_zones.available.names[count.index]}"
}
}
resource "aws_nat_gateway" "main" {
count = length(data.aws_availability_zones.available.names)
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id # 各AZのパブリックサブネットに配置
tags = {
Name = "nat-gw-${data.aws_availability_zones.available.names[count.index]}"
}
# 依存関係の明示:インターネットゲートウェイの作成完了を待つ
depends_on = [aws_internet_gateway.gw]
}
# 各プライベートサブネットに対応するAZのNATゲートウェイをルーティング
resource "aws_route_table" "private" {
count = length(data.aws_availability_zones.available.names)
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main[count.index].id # ゾーンローカルなNATへルーティング
}
tags = {
Name = "rt-private-${data.aws_availability_zones.available.names[count.index]}"
}
}
この設計により、AZ-a のプライベートインスタンスからのトラフィックは、必ず同一AZ内の aws_nat_gateway.main[0] を通過し、クロスAZの通信コストとレイテンシーの増大を完全に排除する。万が一 AZ-a がダウンした際のマルチAZフェイルオーバー設計と組み合わせることで、堅牢性とパフォーマンスの双方を高次元で両立させることが可能となる。
—
結びにかえて
ネットワークの挙動は、決してブラックボックスではない。パケットは物理的な制約とカーネルのアルゴリズムに従って、極めてロジカルに目的地へとルーティングされている。
マルチAZ構成におけるNATゲートウェイの冗長化は、「ただ落ちないようにする」ためのものではない。それは、ゾーン障害というカオスの中でもパフォーマンスを維持し、コストの最適化とセキュアなトランスポート層の確立を同時に達成するための、インフラエンジニアの美学そのものである。
あなたの設計するネットワークは、次の障害を静かに、そして何事もなかったかのように乗り越えられるだろうか。今一度、ルートテーブルとカーネルのログに目を向けてみてほしい。
コメント