Envoy Proxy で発生した503エラーを解決:アイドルタイムアウト設定の最適化
はじめに
Envoy Proxy をリバースプロキシとして使用している Cloud Run サービスを運用している際に、たまに 503 UC(UpstreamConnectionTermination)エラーが発生する問題に遭遇しました。この記事では、その原因調査から解決策の実装まで、実際の経験を基に詳しく解説します。
アーキテクチャ
Client
|
v
ロードバランサ
|
v
Cloud Run (マルチコンテナ)
┗━━(コンテナ1)Envoy Proxy
┗━━(コンテナ2)アップストリームサーバー
使用バージョン
- Envoy Proxy: 1.35.1
問題の概要
発生していたエラーの一部を抜粋すると、以下のような内容でした。
[2025-0X-XXT04:47:09.919Z] "GET / HTTP/1.1" 503 UC 0 95 376 - ..."
- エラー内容: 503 UC エラー
- 発生頻度: たまに(間欠的)
- 環境: Cloud Run (Envoy Proxy + Nuxt のマルチコンテナ環境)
UCは、UpstreamConnectionTermination の頭文字を表していて、アップストリームサーバへの接続が失敗した場合に発生するエラーです。
原因分析
一般的に、UpstreamConnectionTermination が発生する具体的な状況は、大きく分けて以下の3つのパターンが考えられます。
1. アップストリームサーバー側の問題
バックエンドサービス自体が、接続を維持できずに切断してしまうケースです。
- サービスのクラッシュや再起動: 処理中にアプリケーションがエラーで停止したり、デプロイや設定変更によってPodが再起動したりすると、プロキシとのTCP接続が強制的に切断されます。
- 高負荷による処理遅延: アクセスが集中し、サービスが過負荷状態になると、新しいリクエストを処理できなくなったり、既存の接続をタイムアウトで切断したりすることがあります。
- ヘルスチェックの失敗: ロードバランサやサービスメッシュがサービスのヘルスチェックを行い、異常を検知して通信経路から切り離す際に接続が切断されます。
- アプリケーションの内部エラー: データベース接続の失敗や、特定の処理におけるバグが原因で、アプリケーションがレスポンスを返さずに接続を閉じてしまう場合があります。
2. プロキシ(Envoyなど)の設定の問題
プロキシ自体の設定が原因で接続が切断されるケースです。
- タイムアウト設定が短い: プロキシに設定されているコネクションタイムアウトやアイドルタイムアウトの値が、アップストリームサーバーの通常の処理時間よりも短く設定されている場合、処理が終わる前にプロキシ側が接続を切断してしまいます。例えば、ファイルのアップロードなど、時間のかかる処理で発生しやすくなります。
- コネクションプールの設定: プロキシがアップストリームサーバーへの接続を管理するコネクションプールの上限に達した場合、新しい接続を確立できずにエラーとなることがあります。
3. ネットワークの問題
プロキシとアップストリームサーバー間のネットワーク経路に問題があるケースです。
- ファイアウォールによる切断: ネットワーク経路上にあるファイアウォールやセキュリティグループが、特定の通信(例:長時間続いているアイドル接続)をセキュリティ上の理由で強制的に切断することがあります。
- ネットワーク機器の障害: ルーターやスイッチといった中間にあるネットワーク機器の障害や設定ミスにより、通信が途絶えるケースです。
- TCP Keep-aliveの不一致: プロキシとアップストリームサーバー間でのTCP Keep-alive(接続が有効か確認する仕組み)の設定値が異なると、片方が接続を有効だと思っていても、もう片方がタイムアウトと判断して切断してしまうことがあります。
分析結果
調査の過程で、 Fixing 503 Errors When Using Istio Envoy も参考にして、TCP Keep-aliveの不一致が原因ではないかという仮説を立て、その後の対応を進めました。
Node.js のデフォルト設定:
- HTTP server の
server.keepAliveTimeout
のデフォルト値: 5秒 (Node.jsドキュメント) - 最後のレスポンス後、5秒間アイドル状態が続くと接続を閉じる
Envoy のデフォルト設定:
- Upstream クラスターのアイドルタイムアウト: 1時間
- 接続を長時間保持し続ける
問題のメカニズム
- アップストリームサーバー側(Node.js): 5秒で接続を閉じる
- クライアント側(Envoy): 1時間は接続が有効だと思っている
- タイミングのずれ: Envoyが接続を使おうとした時、既にサーバー側で接続が閉じられている
- 結果: 503 UC エラーが発生
このように、Cloud Run内コンテナ間の通信における、タイムアウト設定の不整合が起因となり、503エラーが発生する構成になっていることがわかりました。
最初に試したこと
「クライアント側(Envoy)のアイドルタイムアウト < アップストリームサーバー側の Keep-Alive タイムアウト」を満たすように設定を行うことにしました。
まず最初に、Envoyの設定ファイルに以下の設定を追加しました:
clusters:
- name: application_server
# アップストリームサーバーの Node.js (デフォルト HTTP server) の Keep-Alive タイムアウト5sより短い、4sに設定
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
'@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
common_http_protocol_options:
idle_timeout: 4s
ここでは、以下の設定を追加しました。
- idle_timeout: 4s: Node.jsの5秒より短い4秒に設定
実装時の課題と解決
しかしながら、この設定を適用したところ、以下のエラーが発生しました。
Envoyのバリデーションエラー
エラーログを抜粋すると、以下のような内容でした。
common_http_protocol_options {
idle_timeout {
seconds: 4
}
}
: Proto constraint validation failed (field: "upstream_protocol_options", reason: is required)
これは、EnvoyのIssue #20628を参考に、upstream_protocol_options
のバリデーションエラーが発生していることが原因でした。upstream_protocol_options
では explicit_http_config
、use_downstream_protocol_config
、auto_config
のいずれかを設定する必要があります。
解決方法
これは、先ほどの設定にexplicit_http_config
セクションを追加することで解決しました。
clusters:
- name: application_server
# アップストリームサーバーの Node.js (デフォルト HTTP server) の Keep-Alive タイムアウト5sより短い、4sに設定
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
'@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
common_http_protocol_options:
idle_timeout: 4s
explicit_http_config: #<-ここに追加
# upstream_protocol_options のバリデーション通過のため、デフォルト値を読み込みする
http_protocol_options: {} #<-ここに追加
ここでは、以下の設定を追加しました。
- explicit_http_config: Envoyのバリデーション通過のため必要
- http_protocol_options: {}: デフォルト値の読み込み
結果
この設定を適用した後、503 UC エラーは完全に解消されました。
学んだこと
1. 複数コンテナ環境での設定整合性の重要性
- プロキシとアップストリームサーバー間のタイムアウト設定の整合性が重要
- デフォルト値に依存せず、明示的に設定する考慮が必要
2. Envoyの設定の癖
- 公式ドキュメントだけでは把握できないバリデーション項目がある
どれも当たり前のことかもしれませんが、個人的には学びが多く、形に残しておきたいと思い記事にまとめました。
まとめ
Cloud Run (Envoy Proxy + Nuxt のマルチコンテナ環境)での503エラーは、タイムアウト設定の不整合が原因でした。適切なアイドルタイムアウト設定により、問題を完全に解決することができました。この経験から、各コンポーネント間の設定整合性について考慮を行うことが重要であることが分かりました。