「AZ障害に抗う」— NATゲートウェイの冗長化とルーティングの深淵
クラウドインフラの設計において、AWSのNAT Gatewayは「パブリックサブネットへの避難所」として重宝される存在だ。しかし、マネージドという甘美な響きの裏側には、大規模障害時に我々インフラエンジニアを冷や汗まみれにする「ルーティングの静的制約」という現実が潜んでいる。
今回は、単なる教科書的な「AZごとにNAT GWを置こう」という話を遥かに超え、パケットが網の目を縫って駆け抜ける際のカーネルレベルの挙動から、動的なフォールバックの実装まで、現場の最前線から深掘りしていこう。
—
1. 静的ルートの限界とパケットの孤独な旅路
通常、プライベートサブネットのルートテーブルには 0.0.0.0/0 -> nat-gw-id が設定される。ここで一つの重大な課題が生じる。AWSのルートテーブルは、特定のターゲットが「死んでいる」ことを検知して自動的に別のNAT GWへ切り替えるような柔軟性を持っていない。
もし特定のNAT GWが属するAZで障害が発生した場合、そのルートテーブルを参照しているサブネットのパケットは、ブラックホールへ吸い込まれていく。これは、TCPコネクションにおいては SYN パケットが再送を繰り返したのち、最終的に Connection Timeout を迎えるまでの残酷な待ち時間を意味する。
TCPバッファとRTTの観点
この間、アプリケーション層は沈黙する。もし Keep-Alive が有効な接続であれば、カーネルの tcp_retries2 設定にもよるが、OS標準では約15分近くコネクションがハングアップし続ける可能性がある。高頻度なAPI通信を行うマイクロサービスにおいて、これは雪崩的な障害連鎖(Cascading Failure)の引き金となる。
—
2. 動的ルーティングへの挑戦:Lambdaによるフォールバックの実装
AWSのルートテーブルをプログラムで制御するには、ReplaceRoute APIを活用するしかない。以下は、監視対象のNAT GWが死んだ瞬間に、別のNAT GWへルートを書き換えるための論理モデルだ。
import boto3
def lambda_handler(event, context):
ec2 = boto3.client('ec2')
# 障害発生検知時に呼び出すルート書き換えロジック
try:
response = ec2.replace_route(
RouteTableId='rtb-0123456789abcdef0',
DestinationCidrBlock='0.0.0.0/0',
NatGatewayId='nat-0987654321fedcba0' # 正常な別AZのNAT GWへ切り替え
)
print("ルートテーブルの更新に成功しました")
except Exception as e:
print(f"致命的なエラー: {e}")
このアプローチは強力だが、EventBridge の検知から Lambda の実行まで、最短でも数秒から数十秒のタイムラグが発生する。この「空白の時間」をどう埋めるかが、真のプロフェッショナルの腕の見せ所だ。
—
3. パケットレベルの最適化:TLSハンドシェイクとヘッダー圧縮
NAT GWを経由する通信において、ネットワーク遅延を極限まで削るには、アプリケーション層でのチューニングが不可欠だ。
TLS 1.3の活用とRTTの削減
TLS 1.3では 0-RTT(Zero Round Trip Time)がサポートされている。NAT GW経由の通信は、物理的なホップ数が増える分、ハンドシェイクの遅延が顕著になる。0-RTT を活用すれば、クライアントは最初のパケットで暗号化されたデータを送信できるため、NAT GWの通過回数を実質的に減らしたのと同等のレスポンス体感を得られる。
TCP/IPスタックの微調整
Linuxカーネルで以下のパラメータをチューニングすることで、NAT GWのポート枯渇やパケットドロップに対する耐性を高めることができる。
# TCP接続の急増に備え、TIME_WAIT状態のソケットを再利用する
sysctl -w net.ipv4.tcp_tw_reuse=1
# ウィンドウサイズの拡大(帯域幅遅延積の最適化)
sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
sysctl -w net.ipv4.tcp_wmem="4096 65536 16777216"
—
4. セキュリティの深淵:NATゲートウェイ越しに見える世界
NAT GWは単なる中継点ではない。それは SNAT(Source NAT)を行う境界線だ。ここでの注意点は、ポートオーバーロード である。一つのNAT GWは、宛先と送信元の組み合わせにつき最大64,512の同時接続しか持てない。
大規模なトラフィックを捌く際、同一のNAT GWに接続が集中すると、conntrack テーブルが飽和し、パケットが静かに破棄される「サイレントドロップ」が発生する。これを防ぐには、負荷の分散(NLBをNAT GWの前に置く構成や、NAT GWの複数配置による分散)を、設計の初期段階で組み込む必要がある。
—
結びに:複雑性との付き合い方
インフラの可用性を高めることは、同時にシステムの複雑性を増やすことに他ならない。動的なルーティング切り替えを導入すれば、今度は「誤検知による不必要な切り替え」という新しいリスクと戦わねばならない。
私がSREとして推奨するのは、「自動化を追求しつつも、最後はObservability(可観測性)に帰結させる」ことだ。NAT GWのメトリクスを詳細にモニタリングし、ErrorPortAllocation や IdleTimeoutCount が閾値を超えた瞬間にアラートを発報する。そして、自動切り替えを行うか否かの判断を、ビジネスインパクトに基づいて設計する。
ネットワークは、決して「繋がって当たり前」の魔法ではない。無数のパケットが衝突し、再送され、順序制御される。その泥臭い現場の挙動を想像できるエンジニアこそが、最強のクラウドアーキテクトになれると私は信じている。
コメント