1クール続けるブログ

とりあえず1クール続けるエンジニアの備忘録

pod(Kubernetes)のlifecycle.prestopの挙動

コンテナ削除時すぐにコンテナをSTOPされると困ることありません?

 

例えば….?

ApacheやNginxなどのWebサーバはSIGTERMが送られても、処理中のコネクションがCloseされないまま終了してしまう。 理想としては、残っている接続済みのコネクションを終了してからコンテナがSTOPして欲しいですよね。

Prestopを設定する

SIGTERMでの挙動は以下のようなもの(https://httpd.apache.org/docs/2.2/ja/stopping.htmlより引用)

TERM あるいは stop シグナルを親プロセスに送ると、即座に子プロセス全てを kill しようとします。 子プロセスを完全に kill し終わるまでに数秒かかるかもしれません。 その後、親プロセス自身が終了します。 処理中のリクエストは全て停止され、もはやリクエストに対する 応答はされません。

これでは、まずいのでPrestopフックを入れます。PrestopフックはPodのシャットダウンプロセスの最初に実行されるものです。Deploymentの.spec.template.spec.containers.lifecycle.preStopに処理を記載していきます。httpGet等でhttpリクエストを引っ掛けることもできますが、基本的にはexecで記述することが多いように思えます。execは記述したコマンドの同一のコンテナで実行します。

Apacheのwebサーバの場合だと、httpd -k graceful-stopをDeploymentの.spec.template.spec.containers.lifecycle.preStop.exec.commandに書いていくような形となるかと思います。Pod preStop フックの実行と Service エンドポイントからの Pod IP アドレス削除は並列して実行されるため、preStop フックの実行が先に実行されてしまいアクセスがまだ来る状態でコネクション作成をやめてしまうことになります。 なので、httpd -k graceful-stopの前に数秒置く必要があります。また、指定したコマンドが非同期の場合には、勝手にpreStopフックが完了されたとみなし、SIGTERMがPodのコンテナに送られてしまいます。そのため、非同期のコマンドのあとにはそのコマンドでかかる時間sleepしておくなどするべきです。

もしapacheのリクエストのタイムアウト時間を30秒としているならば、以下のような設定となるかと思います。 terminationGracePeriodSecondsはpodのシャットダウンプロセスが始まってからSIGKILLが送られるまでの時間です。preStopの設定を入れている場合は、preStop処理が終了後にSIGTERMが送られ、シャットダウンプロセスが始まってからterminationGracePeriodSecondsで指定した秒数が経過してもプロセスが完了していない場合にはSIGLKILLが送られます。デフォルトは30秒となっているはずです。

apiVersion: v1
kind: Pod
metadata:
  name: lifecycle-demo
spec:
  containers:
  - name: lifecycle-demo-container
    image: httpd
    lifecycle:
      preStop:
        exec:
          command: ["sh", "-c", "sleep 2; httpd -k graceful-stop; sleep 30"]
  terminationGracePeriodSeconds: 40

kubernetesソースコード上では以下のように、preStopが実装されてます。 preStopの定義がDeploymentにない場合は、すぐにStopContainerが送られてしまうのがわかると思います。

        // Run the pre-stop lifecycle hooks if applicable and if there is enough time to run it
    if containerSpec.Lifecycle != nil && containerSpec.Lifecycle.PreStop != nil && gracePeriod > 0 {
        gracePeriod = gracePeriod - m.executePreStopHook(pod, containerID, containerSpec, gracePeriod)
    }
    // always give containers a minimal shutdown window to avoid unnecessary SIGKILLs
    if gracePeriod < minimumGracePeriodInSeconds {
        gracePeriod = minimumGracePeriodInSeconds
    }
    if gracePeriodOverride != nil {
        gracePeriod = *gracePeriodOverride
        glog.V(3).Infof("Killing container %q, but using %d second grace period override", containerID, gracePeriod)
    }

    err := m.runtimeService.StopContainer(containerID.ID, gracePeriod)
    if err != nil {
        glog.Errorf("Container %q termination failed with gracePeriod %d: %v", containerID.String(), gracePeriod, err)
    } else {
        glog.V(3).Infof("Container %q exited normally", containerID.String())
    }

Prestopフックですが、標準出力などに吐き出してもStackdriverなどでは拾えないので注意です。なお失敗した場合には、kubectl describe <pod>のイベントのところに表示されます。