【実務・中級編】 NATゲートウェイにおけるTCPタイムアウト仕様とコネクション強制切断 – クラウド&コンテナネットワーク実践ガイド

「なぜ、あのコネクションは突然切れるのか?」NATゲートウェイのTCPタイムアウトと戦うエンジニアへ

現場のSREなら一度は経験があるはずです。「正常に動いていたはずのバッチ処理が、数分間の沈黙の後に突如 Connection reset by peer で落ちる」「APIリクエストがたまにタイムアウトする」。

パケットキャプチャを眺めて絶望する前に、一度立ち止まって考えてみてください。その裏で、AWSの NAT Gateway や GCPの Cloud NAT が、あなたのコネクションを「無駄なゴミ」と判断して強制的に掃除している可能性があることを。

今日は、クラウドネットワークの「静かなる掃除人」、NATゲートウェイのタイムアウト仕様とその防衛術について、現場の視点から深掘りします。

—

NATゲートウェイは「状態」をどこまで覚えているのか

パブリックサブネットにあるインスタンスがインターネットへ出る際、NATゲートウェイは「プライベートIP:送信元ポート」と「グローバルIP:変換後ポート」の対応表(NATテーブル)をメモリ上に保持します。

しかし、このテーブルは有限です。メモリを節約するため、クラウドプロバイダーは「一定時間通信がないコネクション(アイドル状態)」を容赦なく切り捨てます。

各クラウドのタイムアウト仕様(目安)

  • AWS NAT Gateway: 350秒(約6分)でアイドル状態のコネクションを強制切断します。
  • GCP Cloud NAT: デフォルトで600秒(10分)ですが、設定により30秒〜3600秒の間で調整可能です。

この「350秒」や「600秒」という数字。TCPのRFC 793が定義する「コネクションの正常終了(FIN/ACK)」を待たず、NATゲートウェイが一方的にテーブルからエントリを削除することに注意してください。クライアントもサーバーも、「まだ接続は生きている」と信じ込んでいるのに、経路上の関所が勝手に門を閉ざしてしまう。これが「突然の切断」の正体です。

—

パケットの裏側:なぜ「沈黙」が罪なのか

TCPは本来、切断の意思表示(FINやRST)がない限り、コネクションは生き続けます。しかし、NATゲートウェイからすれば、数分間パケットが流れない通信は「相手が死んでいるか、忘れている」とみなされます。

このとき、NATゲートウェイはエントリを消去しますが、クライアントには何も通知しません。 その後にクライアントが再度データを送ろうとすると、NATゲートウェイは「そんなコネクションは知らない」として RST パケットを返します。これが、アプリケーションログに Connection reset by peer が記録されるメカニズムです。

—

防衛術:キープアライブで「死んでない」と主張する

この問題の最も正攻法な解決策は、NATゲートウェイがエントリを削除するよりも短い間隔で、「私はここにいますよ」という生存確認パケットを流すことです。

1. TCP Keepalive をOSレベルで設定する(Pythonの例)

アプリケーションがコネクションを長時間維持する設計の場合、ソケットレベルで SO_KEEPALIVE を有効にします。

import socket

# ソケット作成
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# TCP Keepaliveを有効化
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)

# システム依存の設定例(Linux系の場合)
# 接続がアイドル状態で、どれくらいでKeepaliveパケットを送るか
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 60) # 60秒の沈黙で送信
# Keepaliveパケットの再送間隔
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 10)
# 何回失敗したら切断とみなすか
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 3)

sock.connect(("api.example.com", 443))

2. HTTPレイヤーでの対応(Fetch API / curl)

Web APIの場合、Keep-Alive ヘッダーを制御するのはブラウザやランタイムの責務ですが、接続プール(Connection Pooling)の再利用時間を短く設定するのが賢明です。

例えば、Node.jsの axios や http.Agent を使う場合、keepAliveMsecs をNATゲートウェイのタイムアウトより十分に短い値(例えば60秒)に設定します。

const http = require('http');

const agent = new http.Agent({
  keepAlive: true,
  keepAliveMsecs: 60000, // 60秒毎にキープアライブ
  maxSockets: 256
});

// このエージェントをHTTPクライアントに渡して利用する

3. インフラ運用でのデバッグ手順

もし、あなたの環境で原因不明の切断が多発しているなら、まずは以下のコマンドで「実際にパケットがどうなっているか」を確認してください。

# 特定のポートへの接続状態を監視
sudo tcpdump -i eth0 port 443 -nn -v

# 接続が切れた瞬間にRSTが返ってきていないか確認
# -S: シーケンス番号を確認し、通信の断絶を追跡する

—

アーキテクトからのアドバイス

「タイムアウトを延ばせばいいのでは?」と考えがちですが、それは対症療法です。クラウドのインフラは、基本的に「いつ切れてもおかしくない」という前提でアプリケーションを設計する(Resilienceの向上)のが、真のSREの流儀です。

1. コネクションプーリングの寿命(Max Age)をNATのタイムアウトより短くする。
2. リトライ処理を必ず実装する(指数バックオフを忘れずに)。
3. アプリケーションのログには、タイムアウトを検知した瞬間のIPやポートを記録する。

ネットワークのトラブルは、多くの場合「仕様のすれ違い」から生まれます。NATゲートウェイは意地悪で切断しているわけではありません。ただ、効率のために「過去を忘れる」だけなのです。その性質を理解した上で、いかに「忘れられない努力」をするか。それが、安定したシステムを支えるネットワークエンジニアの腕の見せ所です。

それでは、また次回の障害対応でお会いしましょう。健闘を祈ります。

コメント

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