Carpe Diem

備忘録

KubernetesのPodを安全に終了する(istio-proxy編)

概要

前回KubernetesのPodの安全な終了方法について図を交えて解説しました。

christina04.hatenablog.com

今回はistio-proxy(envoy sidecar)がある場合の安全な終了方法について説明します。

環境

事前知識

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を返してコネクションを閉じるようにする
  • drain期間後も既存のリクエストは処理する

ref: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/draining#arch-overview-draining

ドキュメントだとちょっと分かりづらいですが、この仕様を決めた時の↓のコメントだと分かりやすいです。

github.com

LBと違ってSidecarはリバランシングすることができないので、Serviceから外れるまでの新規コネクションはエラーにせずきちんと捌くという思想のようです。

drain期間後も既存のリクエストは処理する

drain期間後の挙動については、ローカルで動作検証したところ新規リクエストはエラーになりましたがsleepで保持させた既存リクエストはきちんと返ってきました。

drain-time-s 30sでdrain開始後、10秒待ってから30秒間リクエストを1rpsで流した

drain-time-s後の残りの10リクエスト程度はエラーになりましたが、5秒sleepで処理中だったリクエストは返ってきました。

検証用コードはこちらです。

github.com

図解

言葉だけだと分かりづらいので図示します。

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へのパラメータ

https://github.com/istio/istio/blob/ed06c8aeab07f00ec331ee5fec81ebdada8fdfe0/pkg/envoy/proxy.go#L122-L123

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の安全な終了方法について説明しました。

参考