KubernetesのPodの削除のときの動作を見る

# はじめに

Kubernetesを使ってアプリケーションを公開する場合、Podという形で実行されます。
そのPodの中に複数のContainerが存在し、そのContainerの中でプログラムが実行されます。
Podは一時的なリソース（ `relatively ephemeral (rather than durable) entities` ）です。
なので、中に含まれるContainerも一時的なリソースです。

永続的なアプリケーションを提供する場合、一時的なリソースであるいう部分に気を付けなければなりません。
そのアプリケーションの一部のPodが削除されることを想定しなければなりません。
削除された後に再度Podが立ち上がる機能はKubernetesが [Deployment](https://kubernetes.io/ja/docs/concepts/workloads/controllers/deployment/) 、 [StatefulSet](https://kubernetes.io/ja/docs/concepts/workloads/controllers/statefulset/) や [DaemonSet](https://kubernetes.io/ja/docs/concepts/workloads/controllers/daemonset/) で提供しています。

という訳でPodが削除される際のKubernetes上での処理の流れをPodへの設定値とともに追いかけたいと思います。
なお、その項目に関する公式ドキュメントは [Podの終了](https://kubernetes.io/ja/docs/concepts/workloads/pods/pod/#pod%E3%81%AE%E7%B5%82%E4%BA%86) にあります。

この記事内で使用するソースコードは、 [walk8243/kubernetes-pod-delete](https://github.com/walk8243/kubernetes-pod-delete) にありますので、是非ご利用ください。

# 全体の流れ

仮にPodの中に3つのContainerが含まれているとします。
そのPodにおいて、Podの削除処理が始まりました。
すると、Podのステータスを `Terminating` に変更して、Pod内のそれぞれのContainerにおいて `preStop` で定義されている処理の内容が実行されます。
`preStop` の処理が完了した後、Containerの実行コマンドに対してシグナルの `TERM` が送信されます。
このシグナルはNode.jsの場合、 `process.on('SIGTERM', () => {})` でイベントとして受け取ることができます。
Pod内の全てのContainerにおいて実行処理が終了したときにPodも終了します。

下の図は、上の説明を図化したものです。
青い枠はPodの削除処理が始まってからの生存期間、緑の枠はContainerの削除処理が始まってからの生存期間を表しています。

![全体の流れ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183540/2f97313d-e390-5f50-e55f-df38ddfcf9da.jpeg)

Pod内のContainerは、Podの削除処理が始まってから `terminationGracePeriodSeconds` で定義される猶予時間（grace period）の間に実行処理を終了させなけれなりません。
もしContainerが終了しなかった場合は、Containerの実行処理に対して `SIGKILL` が送信され、強制終了させられることとなります。
但しこれには例外があり、猶予時間が来た時にまだ `TERM` シグナルを送信されていなかった場合には、 `2秒間` の猶予が与えられた上で `TERM` シグナルが送信されます。

![猶予時間の追加.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183540/981e9c69-4eca-42c7-7a3f-20a639a6363c.jpeg)

# 実際に試してみる

以下の例では、全て猶予時間を `15秒` に設定しています。
また、 `SIGTERM` を受信してからは `1秒ごと` にログを出力するようにしています。

この検証で使用したものは以下です。

- ソースコード
    - https://github.com/walk8243/kubernetes-pod-delete
- Dockerイメージ
    - https://hub.docker.com/r/walk8243/kubernetes-pod-delete
- Kubernetes
    - バージョン（サーバ）： `v1.18.0`

## preStopの処理時間 ≒ 猶予時間

preStopの処理時間を `15秒` として実行しました。

![slack-1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183540/02c84e2d-744c-56c9-1690-04624071c89f.jpeg)

内部処理の実行速度の関係でログの順序は逆転していますが、 preStopを実行してから15秒後のログと `SIGTERM` 受信のログはほぼ同時刻に送信されていることが分かります。
また、猶予時間の15秒を経過してからも多少の猶予時間があることが確認できます。

## preStopの処理時間 < 猶予時間

preStopの処理時間を `10秒` として実行しました。

![slack-2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183540/8476bbc3-0afa-f76b-0557-bd8f18297798.jpeg)

preStopを実行してから15秒という時間を待つことなく、preStopの処理が完了後に `SIGTERM` 受信のログが送信されています。
また、preStopを実行してから15秒後にはログが送信されなくなりました。猶予時間が伸びていないことが確認できます。

## preStopの処理時間 > 猶予時間

preStopの処理時間を `20秒` として実行しました。

![slack-3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183540/317e8bb5-b531-8294-cc16-3e4a96ec620b.jpeg)

`preStop が実行されてから20秒が経過しました。` というログが送信されてきませんでした。
つまり、その前にPodが強制終了したということです。
また、preStopの処理時間を15秒に設定したときと同様に、preStopが実行されてから15秒後に `SIGTERM` 受信のログが送信されています。
こちらも15秒以降の猶予時間を確認できました。

# おわりに

正常に処理が終了しないまま終わってしまうと、保持しているデータに不整合が起こり、システムエラーを引き起こしたり復旧に時間がかかったり、最悪の場合復旧不可能なんてことになってしまうかもしれません。
安定稼働のためには、常に正常に終了させる状態を作ることが大切です。

正常に終了させる方法として、Podの削除時、Podの猶予時間を延ばすことで各Containerが正常に処理が終了することを待つようにすることはできます。
しかし、むやみに延ばしてしまうと、ローリングアップデートの時間が長くなってしまいます。
それはリリース時に旧バージョンと新バージョンが入り混じった状態が長くなるということです。
また、いくら猶予時間を延ばしても、延ばすことにも限度はあるでしょう。

なので、大切なことは次の3点でしょう。

- 一連の処理にかかる時間を短くする。
- 処理の途中にセーブポイントを用意し、処理の途中でも正常に終了できるポイントを作ることを考慮する。
- 正常に終了するための処理を作成する。

事故の少ない世の中を作るために。

# おまけ

## TERMシグナルの送信先

`TERM` シグナルの送信先は常にContainerのコマンドで実行しているプロセスです。
そのため、Node.jsの場合の `npm` や `yarn` 経由で実行しているような場合には、処理には `TERM` シグナルが届きません。

以下は猶予時間を15秒、preStopの処理時間を10秒とし、実行コマンドを `yarn run start` とした場合のログです。

![slack-4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183540/7b0be115-aaa8-586c-e3f6-90b0c6cc1f03.jpeg)

`SIGNAL SIGTERM を受信しました。` というログが送信されてきませんでした。
終了処理をTERMシグナルを使って実装しようとしていた場合には注意が必要です。