概要
前回KubernetesのPodの安全な終了方法について図を交えて解説しました。
今回はistio-proxy(envoy sidecar)がある場合の安全な終了方法について説明します。
環境
- Kubernetes v1.21.14
- Istio 1.16.0
- Envoy 1.24.1
事前知識
drain, shutdown系パラメータ
istio-proxyが絡んで来るとdrain, shutdown系パラメータが非常に増えるのでまずは表にまとめます。
パラメータ | 何のパラメータか | 説明 | デフォルト値 |
---|---|---|---|
terminationGracePeriodSeconds | Pod | Podがterminating状態になってからgraceful shutdownするための猶予期間。過ぎるとコンテナにSIGKILLが飛び強制終了させる | 30s |
--drain-time-s | envoy | envoyのdrain状態の期間 | 10m |
--parent-shutdown-time-s | envoy | ホットリスタート時に親プロセスをシャットダウンするまでのenvoyの待ち時間 | 15m |
drainDuration | Istio | envoyの--drain-time-s をistioから渡すためのパラメータ |
45s |
parentShutdownDuration | Istio | envoyの--parent-shutdown-time-s をistioから渡すためのパラメータ |
60s |
terminationDrainDuration | Istio | istioがproxyをkillするまでの時間 | 5s |
EXIT_ON_ZERO_ACTIVE_CONNECTIONS | Istio | drain開始後、1秒おきにアクティブなdownstream connection が残っているかどうかをチェックし、0になったら終了する | false |
MINIMUM_DRAIN_DURATION | Istio | exitOnZeroActiveConnectionsがtrueの際に、drain開始後アクティブなconnectionをチェックする前にsleepする時間 | 5s |
Envoyのdrain状態について
AWSのALBや、Goのnet/httpの場合はdrain状態というと新規リクエストは受け付けず、既存のリクエストを処理して排出する期間です。
しかしEnvoyの場合は以下の仕様になっています。
- drain期間中は新規リクエストも受け付ける
- ただしHTTP/1なら
Connection: close
を、HTTP/2ならGOAWAY
を返してコネクションを閉じるようにする
- ただしHTTP/1なら
- drain期間後も既存のリクエストは処理する
ドキュメントだとちょっと分かりづらいですが、この仕様を決めた時の↓のコメントだと分かりやすいです。
LBと違ってSidecarはリバランシングすることができないので、Serviceから外れるまでの新規コネクションはエラーにせずきちんと捌くという思想のようです。
drain期間後も既存のリクエストは処理する
drain期間後の挙動については、ローカルで動作検証したところ新規リクエストはエラーになりましたがsleepで保持させた既存リクエストはきちんと返ってきました。
drain-time-s後の残りの10リクエスト程度はエラーになりましたが、5秒sleepで処理中だったリクエストは返ってきました。
検証用コードはこちらです。
図解
言葉だけだと分かりづらいので図示します。
EXIT_ON_ZERO_ACTIVE_CONNECTIONS: falseの場合
- terminationGracePeriodSeconds
- drainDuration
- parentShutdownDuration
- terminationDrainDuration
を調節することになります。
parentShutdownDurationとterminationDrainDurationは役割的に同じになるので同じ値で良いでしょう。
Serviceの切り離し時間 < drainDuration ≦ アプリケーションpreStop < アプリケーションがgraceful shutdown完了 < parentShutdownDuration = terminationDrainDuration ≦ terminationGracePeriodSeconds
とします。
注意点としてterminationDrainDurationはその時間sleepするので、延ばすほどデプロイに時間がかかるようになります。
EXIT_ON_ZERO_ACTIVE_CONNECTIONS: trueの場合
- terminationGracePeriodSeconds
- drainDuration
- parentShutdownDuration
- MINIMUM_DRAIN_DURATION
を調節することになります。
MINIMUM_DRAIN_DURATIONは競合状態(アクティブなコネクションが0になって終了した直後に新規コネクションが来るケース)の回避のためなので、drainDurationと同じ値が良いでしょう。
あとはterminationDrainDurationのように手動で待たずとも、コネクションが0になれば自動的に終了します。
まとめると
Serviceの切り離し時間 < drainDuration = MINIMUM_DRAIN_DURATION ≦ アプリケーションpreStop < アプリケーションがgraceful shutdown完了 < parentShutdownDuration ≦ terminationGracePeriodSeconds
になります。
設定方法
Deploymentで以下のような設定をします。
spec: strategy: rollingUpdate: maxUnavailable: 1 type: RollingUpdate selector: matchLabels: app: nginx template: metadata: labels: app: nginx annotations: proxy.istio.io/config: | drainDuration: '10s' # ここ parentShutdownDuration: '30s' # ここ proxyMetadata: MINIMUM_DRAIN_DURATION: '10s' # ここ EXIT_ON_ZERO_ACTIVE_CONNECTIONS: 'true' spec: terminationGracePeriodSeconds: 30 # ここ containers: - name: nginx lifecycle: preStop: exec: command: ["/bin/sh", "-c", "sleep 10"] # ここ image: nginx:alpine
その他
小ネタ
drain周りのパラメータは機能的に重複したり、他のtimeoutが短いことによってプロセスkillされて意味がなくなるパラメータもあるため↓のようなIssueもあがったり、
ref: https://github.com/istio/istio/issues/34855
挙動を質問された時も「なんでこうしたか覚えていないや」みたいな作った人たちも複雑さに混乱していそうです。
ref: https://github.com/envoyproxy/envoy/issues/19369
調査ログ
後から調査できるようログを残しておきます。
Envoyへのパラメータ
でCLIパラメータとして渡している。
istio-proxyの終了フロー
proxy run https://github.com/istio/istio/blob/ed06c8aeab07f00ec331ee5fec81ebdada8fdfe0/pilot/cmd/pilot-agent/app/cmd.go#L94 ↓ agent.Run(ctx) https://github.com/istio/istio/blob/ed06c8aeab07f00ec331ee5fec81ebdada8fdfe0/pilot/cmd/pilot-agent/app/cmd.go#L171 ↓ agent.terminate() https://github.com/istio/istio/blob/ed06c8aeab07f00ec331ee5fec81ebdada8fdfe0/pkg/envoy/agent.go#L116 ↓ a.proxy.Drain() https://github.com/istio/istio/blob/ed06c8aeab07f00ec331ee5fec81ebdada8fdfe0/pkg/envoy/agent.go#L129 ↓ exitOnZeroActiveConnections check https://github.com/istio/istio/blob/ed06c8aeab07f00ec331ee5fec81ebdada8fdfe0/pkg/envoy/agent.go#L136 ↓ abortCh https://github.com/istio/istio/blob/ed06c8aeab07f00ec331ee5fec81ebdada8fdfe0/pkg/envoy/agent.go#L213 ↓ Process.Kill https://github.com/istio/istio/blob/ed06c8aeab07f00ec331ee5fec81ebdada8fdfe0/pkg/envoy/proxy.go#L165
proxy drain
a.proxy.Drain() ↓ DrainListeners() https://github.com/istio/istio/blob/ed06c8aeab07f00ec331ee5fec81ebdada8fdfe0/pkg/envoy/proxy.go#L103 ↓ post drain api https://github.com/istio/istio/blob/ed06c8aeab07f00ec331ee5fec81ebdada8fdfe0/pkg/envoy/admin.go#L42
Envoyのdrainフロー
handlerDrainListeners https://github.com/envoyproxy/envoy/blob/fb04f39be4f67a210d27416c6057d748661f2d7c/source/server/admin/listeners_handler.cc#L28-L32 ↓ startDrainSequence https://github.com/envoyproxy/envoy/blob/96dd73500ab7910075a5810254234b41389fb813/source/server/drain_manager_impl.cc#L114
まとめ
IstioでEnvoyをサイドカーで用意した場合のPodの安全な終了方法について説明しました。