【実務・中級編】 NATゲートウェイのパフォーマンスメトリクス監視(CloudWatchメトリクス) – クラウド&コンテナネットワーク実践ガイド

はじめに:なぜ、あなたのNATゲートウェイは「突然」沈黙するのか

「おい、また本番環境のWeb APIから外部決済サービスへのリクエストがタイムアウトし始めたぞ!」

深夜のSlackに飛び込んできた、背筋が凍るようなアラート。慌ててダッシュボードを開き、アプリケーションログとKubernetesのPodの死活監視を確認するものの、アプリ側には特にエラーが出ていない。リトライを入れても、状況は悪化するばかり……。

多くのクラウドネイティブなエンジニアが、この絶望的なシナリオを一度は経験したことがあるはずです。コンテナやサーバーレス全盛の現代において、パブリックサブネットに鎮座するNATゲートウェイ(NAT Gateway)は、プライベートな世界とインターネットを繋ぐ唯一の「細い裏口」です。普段は黒衣として黒子に徹し、その存在すら忘れられがちですが、いざトラフィックが急増したり、コネクションが枯渇したりすると、システム全体の急所となって牙を剥きます。

今回は、AWSのAmazon VPC NAT Gatewayを主役に据え、CloudWatchメトリクスを武器にして、パケットの挙動から逆算した「本当に使える監視とトラブルシューティングの極意」を徹底的に解説していこう。教科書通りの死活監視(Ping等)だけでは絶対に気づけない、SREの現場のリアルな知見を共有する。

—

パケットの旅路とNATゲートウェイの「知られざる制約」

まずは、プライベートサブネットから外部APIへの通信が、NATゲートウェイを通過してどのように流れていくのか、その背後のメカニズムを整理しておこう。

[K8s Pod (Private Subnet)] 
       │
       ▼ (SNAT: ソースIPの書き換え)
[NAT Gateway (Public Subnet)] 
       │
       ▼ (インターネットの荒野へ)
[External API Server]

プライベートサブネット内で稼働するKubernetesのPodやECSタスクから、外部の決済APIへHTTPSリクエストが飛ぶとき、パケットの送信元IPアドレスはプライベートIPのままです。そのままではインターネット側からルーティングできないため、NATゲートウェイはこのプライベートIPを、自身にアタッチされたパブリックIP(Elastic IP)へと書き換えます。これが SNAT(Source Network Address Translation) です。

ここで、インターネットの通信規格(RFC 793など)に準拠したTCPのルールを思い出してほしい。TCPコネクションは、以下の4つの要素(4タプル)の組み合わせで一意に識別されます。

1. 送信元IPアドレス(Source IP)
2. 送信元ポート番号(Source Port)
3. 宛先IPアドレス(Destination IP)
4. 宛先ポート番号(Destination Port)

NATゲートウェイは、1つのパブリックIPあたり 最大64,000個 のポートを同時に処理できます。もし、あなたのアプリケーションが外部APIに対して短時間で膨大な数のTCPコネクションを張った場合、このポートのプールを使い果たしてしまう瞬間が訪れます。これが、インフラエンジニアの悪夢である 「ポート枯渇」 の正体です。

—

押さえておくべき主要CloudWatchメトリクス4選

CloudWatchの AWS/NATGateway 名前空間には多くのメトリクスが用意されていますが、実務の現場で常時モニタリングし、異常の兆候をいち早く察知すべきなのは、実質的に以下の4つに絞られます。それぞれの意味と、現場での見方を深掘りしていきましょう。

1. BytesOutToDestination / BytesInFromDestination

  • 意味: NATゲートウェイを通過してインターネット側へ送信されたデータ量(Out)、およびインターネット側からプライベート側へ受信されたデータ量(In)です。
  • SREの視点: 単なるトラフィック量としての把握はもちろんですが、これらが「想定外に跳ね上がっていないか」を監視します。もし外部のログ収集基盤やS3などへの転送量が急増していれば、アプリケーション側での不要なデータ送信や、DDoS的なリクエストの反射が起きている可能性があります。

2. PacketDropCount

  • 意味: NATゲートウェイの内部で、何らかの理由により破棄されたパケットの総数です。
  • SREの視点: これがゼロ以外で常時カウントされ始めたら、ネットワーク層やセキュリティグループのミスマッチ、あるいは後述するポートアロケーションのエラーが発生している危険信号です。真っ先に疑うべき重要メトリクスです。

3. ErrorPortAllocation

  • 意味: NATゲートウェイが、プライベートIPからのリクエストに対して利用可能な一時ポート(Ephemeral Port)を割り当てられなかった回数(エラー数)を示します。
  • SREの視点: これが最も重要です。 このメトリクスが1回でも「1以上」を記録した場合、それはシステムがポート枯渇を起こし、外部への新規通信が失敗(接続拒否やタイムアウト)している決定的な証拠です。

4. IdleTimeoutCount

  • 意味: TCPコネクションが一定時間(デフォルトで350秒)アイドル状態だったために、NATゲートウェイ側でセッションテーブルからエントリが削除された回数です。
  • SREの視点: アプリケーション側が「まだコネクションが生きている」と勘違いして古いソケットでデータを送ろうとした際、NATゲートウェイ側ですでに破棄されているため、通信エラー(RSTパケットの返送など)を引き起こします。

—

実践:ポート枯渇を検知・検証するコードと設定例

百聞は一見に如かず。実際にアプリケーションから外部APIへリクエストを大量に送り込み、コネクションを酷使するシチュエーションを考えてみましょう。

以下のPython(requests および urllib3)のコードは、意図的にコネクションプールの再利用を行わず、毎回新しいTCPコネクションを強制的に張ることで、NATゲートウェイのポート消費スピードを加速させる検証スクリプトのサンプルです。

import concurrent.futures
import requests
import time

# 接続先のダミー外部APIエンドポイント(例としてhttpbinを使用)
TARGET_URL = "https://httpbin.org/get"

def send_request(request_id):
    """
    意図的にセッションを使い回さず、毎回新しいTCPコネクションを確立する関数。
    これにより、NATゲートウェイのポート(Ephemeral Port)を急速に消費させます。
    """
    try:
        # requests.getは内部で一時的なセッションを作るため毎回ポートを消費しやすい
        response = requests.get(TARGET_URL, timeout=5)
        print(f"Request {request_id}: Status {response.status_code}")
    except requests.exceptions.RequestException as e:
        print(f"Request {request_id} Failed: {e}")

def load_test_nat_gateway():
    print("NATゲートウェイのポート枯渇シミュレーションを開始します...")
    # 同時並行で大量のリクエストを送信する
    with concurrent.futures.ThreadPoolExecutor(max_workers=200) as executor:
        futures = [executor.submit(send_request, i) for i in range(2000)]
        concurrent.futures.as_completed(futures)

if __name__ == "__main__":
    load_test_nat_gateway()

このようなスクリプトを実行した際、AWSマネジメントコンソールやCloudWatchのダッシュボードで ErrorPortAllocation がスパイク状に跳ね上がる様子を確認できます。

正しいWeb API設計とインフラ側の対策

もし ErrorPortAllocation が検知された場合、根本的な対策はアプリケーション層とインフラ層の両方からアプローチする必要があります。

1. コネクションプールの適切な維持とKeep-Aliveの設定
毎回TCPハンドシェイク(SYN -> SYN-ACK -> ACK)を行うのではなく、HTTP Keep-Aliveを有効にしてコネクションを維持(Reuse)します。Node.jsの http.Agent や Pythonの requests.Session() を適切に活用し、ポートの無駄な消費を防ぎます。
2. NATゲートウェイのスケールアウト(複数配置)
AWSのNATゲートウェイはそれ自体がマネージドで自動スケーリングしますが、単一のNATゲートウェイが持つポートの限界(64,000ポート/IP)は超えられません。複数のAZ(アベイラビリティゾーン)にそれぞれNATゲートウェイを配置し、プライベートサブネットのルートテーブルを分けることで、トラフィックとポートを分散させます。

—

現場で役立つCloudWatchアラームの推奨設定(Terraform例)

監視していない障害は、障害ではない。ErrorPortAllocation や PacketDropCount を検知したら即座にPagerDutyやSlackへ通知が飛ぶよう、Terraformで堅牢なアラームを設定しておきましょう。

以下のTerraformコードは、実務でそのまま使える ErrorPortAllocation の監視リソースの定義例です。

# NATゲートウェイのポート枯渇(ErrorPortAllocation)を検知するCloudWatchアラーム
resource "aws_cloudwatch_metric_alarm" "nat_gateway_port_allocation_error" {
  alarm_name          = "prod-nat-gateway-port-allocation-error"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 1
  metric_name         = "ErrorPortAllocation"
  namespace           = "AWS/NATGateway"
  period              = 60 # 1分間の集計
  statistic           = "Sum"
  threshold           = 1  # 1回でもエラーが発生したら発報
  treat_missing_data  = "notBreaching"

  dimensions = {
    NatGatewayId = "nat-0123456789abcdef0" # 実際のNATゲートウェイIDに置き換えてください
  }

  alarm_description = "警告: NATゲートウェイでポート枯渇(ErrorPortAllocation)が発生しました。外部通信がブロックされている可能性があります。"
  
  # 障害通知用のSNSトピックARNを指定
  alarm_actions = [aws_sns_topic.sre_alerts.arn]
}

このアラームが鳴った瞬間に「あ、アプリのコネクションリークか、外部APIの応答遅延によるコネクション詰まりだな」と当たりをつけられるかどうかが、シニアエンジニアとジュニアエンジニアの分水嶺となります。

—

おわりに:メトリクスはパケットの「心の声」である

CloudWatchのグラフ画面に並ぶ無機質な折れ線グラフは、単なる数字の羅列ではありません。それは、今この瞬間もあなたのクラウド環境を駆け巡り、懸命に外界と通信しようとしているパケットたちの「心の声」です。

NATゲートウェイのメトリクスを正しく理解し、適切な監視とコードの最適化を行うことで、突然のネットワーク障害という恐怖から解放されます。「動いているから大丈夫」ではなく、「なぜその数字で安定しているのか」を語れるエンジニアを目指し、今日のインフラストラクチャを見直してみませんか。

コメント

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