背景
過去、デプロイ時やPodの再配置時のエラーは
- GoでGraceful Shutdown - Carpe Diem
- KubernetesのPodを安全に終了する - Carpe Diem
- KubernetesのPodを安全に終了する(istio-proxy編) - Carpe Diem
- GoでShutdown Hook - Carpe Diem
といった対応を行って解消していました。
しばらくはこれで安定していたのですが、GKEでIstio(Anthos Service Mesh)のアップデートがされてから再び出るようになってしまったのでその時の対応策を共有します。
環境
- Anthos Service Mesh v1.20.8-asm.33
課題
今回のケースはエッジケースだと思っているので、状況をなるべく詳細に説明します。
事象
- デプロイ時・Podの再配置時に503エラーが稀に発生する
- マイクロサービスA → マイクロサービスB という依存関係で、Bの入れ替わり時にAでエラーが発生
- 具体的なエラーとしては以下
upstream request timeoutupstream connect error or disconnect/reset before headers. retried and the latest reset reason: remote connection failure, transport failure reason: delayed connect error: 111
- Istioのエラーコードでは
UR,URX
調査
起動時か終了時か
で説明したように、このエラーは起動時・終了時両方起きる可能性があります。
今回はGKEやアプリケーションログから、次のような時系列が分かりました。
- 15:23:10 マイクロサービスBのistio-sidecarがready
- 15:23:11 マイクロサービスAで
upstream request timeoutが頻発 - 15:23:12 マイクロサービスBのアプリケーションコンテナが起動開始
- 15:23:13 マイクロサービスBでしばらくstartup probeが失敗(grpcサーバが起動完了していない)
- 15:23:19 マイクロサービスBでアプリケーションコンテナがready
- 15:23:30 マイクロサービスAで
upstream request timeoutが止む
なので起動時に発生していると問題の切り分けができました。
probeは設定されているか
Istioがある場合のprobeは設定値によって挙動が変わるため、過去にまとめたことがあります。
これを元に現状の設定を確認しましたが問題はありませんでした。
なので前述したように常に起きるわけでなく、たまに起きるという状況で再現ができませんでした。
Podがreadyになる前にKubernetes Serviceがトラフィックを流すことはあるのか
逆パターン(アプリ起動後サイドカー起動)ではありますが、、どうやら過去にも同様の事象(全てがreadyになってないのにトラフィック流れてる)は報告されていました。
Initial check for container being ready in a pod · Issue #82482 · kubernetes/kubernetes
こちらも同様な問題に対して、Podでコンテナ起動順に依存関係を持たせられないかというIssueです。
後者ではkubexitといツールがワークアラウンドとして提案されていましたが、Istioのように自動で注入されるケースは想定されていない(全部自分で管理するようなケース)ようだったので今回の解決策としては使えなそうでした。
調査結果
これらのファクトから、Pod内で
- Istio-sidecarはready
- アプリケーションコンテナはreadyでない
という状態にも関わらずトラフィックがPodに流れることによって upstream request timeout が発生していると考えられました。
これは本来Kubernetes Serviceに期待される挙動ではないため、何らかの条件を満たしたときのバグ(なので必ずしも起きる訳ではない再現性の難しさが含まれる)と考えました。
対応方針
適切なパラメータ・構成を取っていても稀に異常系が発生するため、根本対応を取ることが難しいです。
そこで今回は緩和策として、Virtual Serviceにおける自動リトライと Outlier Detection を使ってエラーを低減する方針を取りました。


Outlier Detection とは
こちらで詳細を説明していますが、
今回は
- Readyになる前にServiceからトラフィック流れてくる
- ただし実際にPodからは5xxが返る
- 通常リクエストは流れ続けてエラーが継続する
- そこでOutlier Detectionによって早期にPodを切り離す
- そして自動リトライによって失敗したリクエストを正常のPodに再ルーティングさせる
といった方針です。
設定
Outlier Detection と自動リトライの設定方法です。
Outlier Detection
DestinationRuleとして次のような設定を入れます。
apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: specific-outlier-detection spec: host: my-service # 対象のサービス名に書き換えてください trafficPolicy: outlierDetection: splitExternalLocalOriginErrors: true consecutiveLocalOriginFailures: 3 consecutive5xxErrors: 0 consecutiveGatewayErrors: 5 baseEjectionTime: 15s interval: 5s maxEjectionPercent: 50 minHealthPercent: 50
パラメータは前回の説明を参考にしてください。
自動リトライ
Virtual Serviceで設定します。
apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: my-service-vs spec: hosts: - my-service # DestinationRuleのhostと一致させる http: - route: - destination: host: my-service # ここへルーティングされる時にDestinationRuleが適用される retries: attempts: 3 perTryTimeout: 2s # DestinationRuleで検知しようとしているエラー種別に合わせるのが効果的 retryOn: connect-failure,gateway-error,reset,refused-stream,unavailable # --- 全体のタイムアウト --- timeout: 10s
retryOnの設定値の目的は以下です。
| retryOn 設定値 | 対応するエラーログ / 事象 | 解説 |
|---|---|---|
connect-failure |
delayed connect error: 111(Connection Refused) |
アプリのプロセスは立ち上がっているが、まだポートをリッスンしていない状態を検知してリトライ。 |
gateway-error |
upstream request timeout(502/503/504) |
Envoyがバックエンドへの接続や応答を待ちきれずにタイムアウトしたケースを拾う。 |
reset |
disconnect/reset before headers |
TCP接続は確立できたものの、ヘッダー送信前(通信開始直後)に切断された状態を拾う。 |
unavailable |
gRPC Status: UNAVAILABLE |
アプリケーション(gRPCサーバー)が起動シーケンス中などで、明示的に「今は利用不可」と返してきたケースを対応。 |
deadline-exceeded |
gRPC Status: DEADLINE_EXCEEDED |
Podが準備不足で応答を返さず、クライアントやEnvoyの待ち時間を超過した場合に、次のPodへ切り替えるために使用します。 |
注意として、一般には timeout は高負荷で重い状況に起きます。
その状態でさらにリトライで負荷を与えるとサーバにとどめを刺すことになるので、perTryTimeoutやtimeout設定で早期に失敗させるフォローが必要です。
とはいえ今回はPodが起動処理中などで沈黙しているだけなので、リトライして別の稼働済みPodに当たれば即座に正常処理されシステムへの悪影響は小さいと考えます。
結果
期待通り、
- 早期にReadyでないPodを外す
- ↑のPodにルーティングされて失敗したリクエストを自動リトライで成功させる
がうまくいったようで、このケースにおけるエラーは出なくなりました。
まとめ
エッジケースではありますが、ReadyでないPodにリクエストが流れ込んでしまったとしても、Outlier Detectionと自動リトライを使うことでエラーを低減することができるようになりました。