【実務・中級編】 マルチAZ配置におけるNATゲートウェイの冗長化とゾーン障害耐性 – クラウド&コンテナネットワーク実践ガイド

NATゲートウェイは「贅沢に」使い倒せ:マルチAZ設計における可用性の極意

現場でIaC(TerraformやCDK)のコードレビューをしていると、コスト削減の名の下に「NATゲートウェイ(NAT GW)を1つのAZにしか配置していない」設計に遭遇することがあります。

正直に言います。その設計、数年後に必ず泣きを見ます。

クラウドの世界では「AZは独立した障害ドメインである」というのが大前提です。もしNAT GWを置いているAZがダウンしたら、そこを通っていた全パケットはロストし、あなたのWeb APIは即座に「外の世界(外部APIやDB、S3など)」と遮断されます。今日は、SREとして現場で培った「NAT GWの冗長化」における泥臭い知見を共有しましょう。

—

1. なぜ「NAT GW」をAZごとに独立させるのか

NAT GWは単なるルーターではありません。AWSであればマネージドサービスですが、裏側では特定のENI(Elastic Network Interface)を経由するステートフルな通信制御を行っています。

もし、AZ-aにしかNAT GWを置いていない状態でAZ-aが「死んだ」場合、AZ-bにいるあなたのサーバー群は、インターネットへ向かう出口を完全に失います。たとえALBがマルチAZで生きていても、バックエンドが外部APIを叩けなければ、Web APIは無力です。

冗長化の黄金律

「NAT GWはAZごとに独立して配置し、各AZのプライベートサブネットは、必ず『同じAZ内のNAT GW』へ向かうようにルーティングする」

これを守ることで、仮にAZ-aが物理的に消失しても、AZ-bの通信はAZ-bのNAT GWを通って安全に保護されます。

—

2. アーキテクチャの基本フロー

通信の流れを可視化してみましょう。

1. Request: プライベートサブネットのEC2/Fargateが、外部API(例: api.stripe.com)へリクエスト。
2. Routing: ルートテーブルが 0.0.0.0/0 を nat-gw-a(自分と同じAZ内のGW)へ転送。
3. SNAT: NAT GWが自身の持つ固定パブリックIPにソースIPを変換。
4. Response: 外部APIからのレスポンスは、NAT GWのENIを経由して元のPod/EC2へ帰還。

ここで重要なのは、「自分と同じAZ内のNAT GW以外を使ってはいけない」という点です。AZを跨いだ通信にはデータ転送コスト(AZ間転送費用)が発生しますし、何よりAZ障害時に道連れになるリスクが高まります。

—

3. 実践:IaCによる設計の実装(Terraform例)

TerraformでNAT GWを構築する際、冗長化を意識したコードは以下のようになります。各AZに対してリソースをループで回すのが定石です。

# 各AZに対応するNATゲートウェイを生成
resource "aws_nat_gateway" "nat" {
  for_each      = var.availability_zones
  allocation_id = aws_eip.nat[each.key].id
  subnet_id     = aws_subnet.public[each.key].id # 必ずパブリックサブネットに配置

  tags = { Name = "nat-gw-${each.key}" }
}

# AZごとのプライベートルートテーブルを設定
resource "aws_route_table" "private" {
  for_each = var.availability_zones
  vpc_id   = aws_vpc.main.id

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.nat[each.key].id # 同じAZのNAT GWを指定
  }
}

—

4. トラブルシューティング:接続が切れたらどこを見るか?

「APIがタイムアウトする」というアラートが上がったとき、SREがまず疑うべきはルートテーブルの不整合です。

デバッグコマンドの鉄板

まずは、コンテナ内やEC2内から traceroute や curl での挙動を追います。

# 外部APIへの疎通確認(verboseでヘッダーも確認)
curl -Iv https://api.example.com/v1/health

# パケットがどこで止まっているか確認
# 途中でタイムアウトする場合、NAT GWの経路が怪しい
traceroute -T -p 443 api.example.com

Pythonで確認する(コネクションタイムアウトの深掘り)

アプリケーションコード内で、特定のNAT GW経由であることを意識した疎通確認スクリプトを用意しておくと、障害時に即座に切り分けができます。

import requests

def check_external_connectivity():
    url = "https://api.example.com/health"
    try:
        # タイムアウトを短めに設定して即座に異常を検知
        response = requests.get(url, timeout=3)
        print(f"Status Code: {response.status_code}")
    except requests.exceptions.ConnectTimeout:
        print("【障害】NAT GW経由の通信がタイムアウトしました。ルートテーブルを確認してください。")
    except Exception as e:
        print(f"Error: {e}")

if __name__ == "__main__":
    check_external_connectivity()

—

5. 運用上のTips:コストと可用性のバランス

「NAT GWを3つ作ると高すぎる!」という声が聞こえてきそうですが、ここが運用の腕の見せ所です。

  • トラフィックの集約: 開発環境であれば、あえて冗長化を諦めて1つに集約する(コスト削減)。
  • 本番環境の死守: 本番環境では最低2AZ構成を維持する。
  • 監視の徹底: CloudWatchで ErrorPortAllocation や BytesOutToDestination を監視し、特定のNAT GWに負荷が集中していないかチェックしてください。

最後に

ネットワーク設計は、一度作ってしまうと修正が非常に困難です。NAT GWの冗長化は「保険」ではなく「インフラの基本設計」です。

パケットがどのゲートウェイを通って外の世界へ羽ばたき、どう戻ってくるのか。そのパスを常に頭の中で想像し、AZ障害という最悪のシナリオを想定して設計する。これこそが、信頼されるSREへの第一歩です。

皆さんのインフラが、どんな障害にも負けない強靭なものになることを願っています。次は、このNAT GWのボトルネックをどう可視化するかについて語りましょうか。それでは!

コメント

タイトルとURLをコピーしました