350秒の沈黙:AWS NATゲートウェイの「見えない断頭台」とTCPの生存戦略
クラウドアーキテクトとして現場を渡り歩いていると、必ずと言っていいほど遭遇する「不可解なコネクション切断」がある。特にマイクロサービス化されたKubernetes環境において、バックエンドのデータベース接続や、外部APIへの常時接続セッションが、何の前触れもなく Connection reset by peer で死ぬ現象だ。
多くの場合、犯人はAWS NAT Gateway の「350秒」というアイドルタイムアウトだ。これは単なる仕様というより、ステートフルなNATデバイスが管理コストを最適化するために下す、冷徹な「生存判断」である。今日は、パケットレベルの挙動からカーネルチューニングまで、この「350秒の壁」をどう乗り越えるべきか、その深淵を覗いてみよう。
—
なぜ350秒でコネクションは「消される」のか
AWS NAT Gateway は、内部的に膨大な接続を追跡するコネクショントラッキングテーブルを持っている。このテーブルにおいて、350秒間パケットのやり取りがないフローは「もはや死んでいる」と見なされる。
ここで重要なのは、NATゲートウェイはTCPのFINパケットを待たないということだ。タイムアウトに達した瞬間、NATゲートウェイは該当するフロー情報を破棄する。その結果、バックエンド側にはセッションが残っているにもかかわらず、NATゲートウェイ側ではそのマッピングが消失する。
次にパケットが届いた瞬間、NATゲートウェイは「そんな接続は知らない」と RST を返す。これが、アプリケーション層で発生する突然の切断の正体だ。
—
現場で通用する「350秒の壁」突破術
この問題を解決するには、単にアプリ側でタイムアウトを延ばすだけでは不十分だ。ネットワーク層での「生存確認(キープアライブ)」が不可欠となる。
1. TCP Keep-Aliveの最適化
Linuxカーネルのデフォルト設定は、あまりに腰が重すぎる。net.ipv4.tcp_keepalive_time がデフォルトの7200秒(2時間)のままでは、350秒で切断されるNATゲートウェイに対しては無力だ。
これを回避するために、接続先のアプリケーションやカーネルパラメーターで以下のように設定を追い込む必要がある。
# sysctlでのカーネルレベルでのチューニング例
# 最初のプローブを送るまでの時間を300秒に設定
net.ipv4.tcp_keepalive_time = 300
# プローブの間隔を10秒に
net.ipv4.tcp_keepalive_intvl = 10
# 3回失敗したら切断と見なす
net.ipv4.tcp_keepalive_probes = 3
これにより、NATゲートウェイが「死んでいる」と判断する350秒の前に、確実にパケットが流れ、NATゲートウェイ側のタイマーをリセット(再延長)させる。
2. アプリケーション層での制御(Go/Pythonの例)
カーネルの設定をグローバルに変えるのが怖い、あるいは特定の接続だけを制御したい場合は、アプリケーション層で TCP_KEEPALIVE オプションを叩くのが最もスマートだ。
// Go言語でのソケット設定例
// 特定のコネクションに対してKeep-Aliveを有効にする
dialer := &net.Dialer{
KeepAlive: 300 * time.Second, // 300秒ごとにパケットを送信
}
conn, err := dialer.Dial("tcp", "api.example.com:443")
—
パフォーマンスとセキュリティの最適化:その先の視座
単に接続を維持するだけでは「SRE」とは呼べない。我々は、この維持コストを最小化し、同時にレイテンシを極限まで削り取る必要がある。
RTT削減とTLSハンドシェイクの「罠」
頻繁な再接続は、TCP 3-way handshake だけでなく、TLS 1.3 のハンドシェイクオーバーヘッドを伴う。これは特にレイテンシに敏感なマイクロサービス間通信において致命的だ。
- TCP Fast Open (TFO): 3-way handshakeの最初のパケットにデータを載せることで、RTTを1往復削減できる。ただし、ミドルボックス(NATゲートウェイやLB)がTFOに対応していない場合、パケットドロップの要因になるため注意が必要だ。
- TLS Session Resumption:
Pre-Shared Key (PSK)を用いた再開により、ハンドシェイクのRTTを最小化する。
ヘッダー圧縮とバッファチューニング
高負荷な環境では、TCPバッファサイズがボトルネックになることが多い。tcp_rmem および tcp_wmem を適切に設定し、BDP(Bandwidth Delay Product)に見合ったバッファを確保しよう。
# 高速なネットワーク環境でのバッファチューニング例
# 最小値 4KB, 規定値 87KB, 最大値 16MB
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
—
まとめ:ネットワークを「飼い慣らす」ということ
NATゲートウェイの350秒という制限は、クラウドネイティブな世界において避けては通れない「物理法則」のようなものだ。これを「仕様だから」と嘆くのではなく、TCPの特性を理解し、パケットを意図的に流すことで、ネットワークを意のままに飼い慣らす。
現場のトラブルシューティングにおいて、tcpdump を取り、NATゲートウェイ越しにどのようなパケットが流れているかを確認することは、現代のエンジニアにとっての「聴診器」だ。
もしあなたのシステムで突然のセッション切断が起きているなら、まずは netstat -an | grep ESTABLISHED で接続の年齢を眺めてみてほしい。300秒を超えたあたりでパケットが途絶え、その直後に RST が飛んできていないか。その答えこそが、あなたのインフラを一段上の信頼性へと導くはずだ。
コメント