要約
APサーバ(EC2)からDBサーバ(EC2)のOracle DBへの通信が、同じ処理にも関わらず成功したり失敗したりする事象に遭遇しました。
原因は、普段あまり意識することのない「ENIのアイドルタイムアウト」でした。
今後自分も意識できるように記事に起こします。
| 項目 | 内容 |
|---|---|
| 事象 | APサーバ → DBサーバの同じ通信が成功したり失敗したりした |
| 原因 | ENIのアイドルタイムアウトのデフォルトが350秒であり、それを超える処理だとセッションが破棄された。結果として、エフェメラルポートによる戻り通信が拒否されていた。 |
| 気づいた方法 | VPC Flow Logs に、エフェメラルポート宛の REJECT が記録されていた |
| 対処 | Oracle の DCD(sqlnet.ora の SQLNET.EXPIRE_TIME)で定期的にパケットを流し、350秒の無通信状態を作らないようにした |
今回の環境
| 項目 | 内容 |
|---|---|
| APサーバ | EC2 |
| DBサーバ | EC2 上の Oracle Database |
| 経路 | 同一VPC内、ENI間の直接通信(NAT Gateway・NLBは経由しない) |
| ENIの接続追跡設定 | 明示設定なし(=デフォルトの350秒) |
※ 本記事はインフラ観点に絞っています。アプリ側の実装やコネクションプールの設定には踏み込みません。
事象
- APサーバ → DBサーバの通信が、処理によって成功したり失敗したりする
- セキュリティグループのルールは問題ない。
- セキュリティグループでAP→DBの通信で全ポートを許可すると全て成功する
設定は正しいのにたまに落ちる、という、初めは意味不明な現象でした。
前提知識:セキュリティグループの「ステート」には寿命がある
セキュリティグループはステートフルなので、アウトバウンドで許可した通信の戻りは、インバウンドルールがなくても通ります。
今回、戻りパケットの宛先はAPサーバ側のエフェメラルポート(Linuxなら概ね32768〜60999)で、当然そんなポートを開けるインバウンドルールは書いていません。結果、処理が失敗しました。
気づいたきっかけ:VPC Flow Logs
VPC Flow Logs を調査して気付きました。。失敗した時間帯に、DBサーバ → APサーバのエフェメラルポート宛パケットが REJECT で記録されていました。
... 10.0.2.20 10.0.1.10 1521 47812 6 ... REJECT OK
^^^^^ エフェメラルポート宛が拒否されている
セキュリティグループを一切変更していないのに REJECT が出ているというのが、そのまま「ルールではなく状態の問題である」という手がかりになりました。
対処の選択肢
対処は大きく3通りあり、それぞれ効く範囲が違います。
| 対処 | 内容 | メリット | デメリット |
|---|---|---|---|
| ① ENIのタイムアウト延長 | APサーバ側ENIの TcpEstablishedTimeout を延ばす |
インフラ側だけで完結。設定が明示的になる | 追跡エントリを長く保持するのでconntrack枯渇のリスク。AWSは432000秒未満を推奨。ENIごとの管理が必要 |
| ② OSのTCP Keepalive |
net.ipv4.tcp_keepalive_time を短縮 |
DB以外の通信にもまとめて効く | OS全体に影響。デフォルト7200秒なので必ず短縮が必要 |
| ③ Oracle の DCD |
sqlnet.ora の SQLNET.EXPIRE_TIME
|
DB接続に限定して効く。Oracle標準機能で実績も豊富 | DB側の設定変更が必要。プローブ分のわずかな負荷 |
今回は③を採用しました。
(理由は割愛します)
まとめ
- セキュリティグループはステートフルだが、そのステートには寿命がある
- ENIのアイドルタイムアウトは、デフォルトで350秒
- 「エラーが出ずにハングする」「処理によって成否が変わる」ときは、経路上のステートフルな機器のタイムアウトを疑うべき
- 対処は「タイムアウトを延ばす」か「定期的にパケットを流す」かの二択。