Nuxt 3 × ECS Fargate × ALB 構成で発生した sporadic 500 の原因と解決メモ
ALB 経由でリクエストを受ける Nuxt 3 アプリが sporadic に 500 (timeout) を返していた原因と、Nitro のビルド成果物を書き換えて keep-alive を揃えたときのメモ。
TL;DR
- ALB → ECS Fargate (Node 20) でときどき 500 を返し、ログを見ると
timeoutが原因。 - ALB の
Idle timeout = 60sに対して、Nuxt (Nitro) が生成する Node サーバーのkeepAliveTimeoutが 5s で短すぎた。 - ビルド成果物 (
.output/server/chunks/nitro/*.mjsや.output/server/index.mjs) でserver.listenを呼ぶ前にkeepAliveTimeout/headersTimeoutを延長したら解消。 - Nuxt をアップデートすると成果物のパスやコードが変わるので、ビルド後に
grep "server." -n .output/server -Rで毎回挿し込み位置を確認する。
原因のあたり
AWS 側のメトリクスを見ると ALB の TargetResponseTime は短く、ECS の CPU / メモリも余裕がある。
一方で ELB 504 が並んでいたので、ALB とアプリの keep-alive まわりを疑った。
Node.js (Nuxt Nitro) のデフォルトとALBのデフォルト差分のため、
- ALBが待つ
- Node サーバー側は その間にコネクションを閉じる
- ALB は後段から FIN が来ず「応答が遅い」と判断 → 504 or 500
という矛盾が起きていた説
sed で強制的に上書き
ビルド後のファイルを書き換えられれば良いので、CI/CD の「ビルド→最終イメージ作成」のタイミングで以下を挿し込むだけでも動いた。
sed -i -e "/server\.listen/i server.keepAliveTimeout = 62 * 1000\nserver.headersTimeout = 65 * 1000" \
.output/server/chunks/nitro/nitro.mjs
server.listen の直前に 62s / 65s を突っ込んで、ALB より長めに揃える作戦。
ビルドの仕方によっては .output/server/index.mjs のほうがメインになるので、両方に仕込んでいる。
sed -i -e "/server\.listen/i server.keepAliveTimeout = 62 * 1000\nserver.headersTimeout = 65 * 1000" \
.output/server/index.mjs
ポイント
sedの改行は\nで入れる。server.headersTimeoutはkeepAliveTimeoutより少し長くしないと Node が警告を出す。- CI のログにフラグを残すため、
echo '[patch] applied keepAliveTimeout tweak'を最後に出しておくと安心。
Nuxt のアップデート時に注意
Nuxt 3 の minor アップデートでも Nitro のコード生成は結構変わる。
rg "createServer" -n .output/server や rg "server." -n .output/server を毎回流して、server.listen の位置が変わっていないかチェックする。
もし chunks/nitro/*.mjs がなく server/index.mjs しかない構成なら、そちらだけを対象にすれば良い。
逆に AWS Lambda preset を使うと server/chunks/app/server.mjs のような構成になるので、sed のパスは必ず find .output/server -name '*.mjs' | xargs rg -n "server\.listen" で見つけるようにした。
その後
- 挿し込み後は 1 週間ノーアラート。ALB の 5XX も 0 のまま推移。
- それでも長時間の keep-alive は同時接続数が増えるので、
maxConcurrencyや ECS のタスク数は余裕を持たせる。 - そろそろ Nitro 側で
NITRO_PRESET_KEEP_ALIVEみたいな設定が欲しいが、当面はビルド成果物へのパッチでやり過ごす予定。
最終手段 grepさん で server. を探せば大体見つかるので、ビルド成果物を一度覗いてから sed するのが吉。