はじめに
Kubernetesを使ってアプリケーションを公開する場合、Podという形で実行されます。
そのPodの中に複数のContainerが存在し、そのContainerの中でプログラムが実行されます。
Podは一時的なリソース( relatively ephemeral (rather than durable) entities
)です。
なので、中に含まれるContainerも一時的なリソースです。
永続的なアプリケーションを提供する場合、一時的なリソースであるいう部分に気を付けなければなりません。
そのアプリケーションの一部のPodが削除されることを想定しなければなりません。
削除された後に再度Podが立ち上がる機能はKubernetesが Deployment 、 StatefulSet や DaemonSet で提供しています。
という訳でPodが削除される際のKubernetes上での処理の流れをPodへの設定値とともに追いかけたいと思います。
なお、その項目に関する公式ドキュメントは Podの終了 にあります。
この記事内で使用するソースコードは、 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の削除処理が始まってからの生存期間を表しています。
Pod内のContainerは、Podの削除処理が始まってから terminationGracePeriodSeconds
で定義される猶予時間(grace period)の間に実行処理を終了させなけれなりません。
もしContainerが終了しなかった場合は、Containerの実行処理に対して SIGKILL
が送信され、強制終了させられることとなります。
但しこれには例外があり、猶予時間が来た時にまだ TERM
シグナルを送信されていなかった場合には、 2秒間
の猶予が与えられた上で TERM
シグナルが送信されます。
実際に試してみる
以下の例では、全て猶予時間を 15秒
に設定しています。
また、 SIGTERM
を受信してからは 1秒ごと
にログを出力するようにしています。
この検証で使用したものは以下です。
- ソースコード
- Dockerイメージ
- Kubernetes
- バージョン(サーバ):
v1.18.0
- バージョン(サーバ):
preStopの処理時間 ≒ 猶予時間
preStopの処理時間を 15秒
として実行しました。
内部処理の実行速度の関係でログの順序は逆転していますが、 preStopを実行してから15秒後のログと SIGTERM
受信のログはほぼ同時刻に送信されていることが分かります。
また、猶予時間の15秒を経過してからも多少の猶予時間があることが確認できます。
preStopの処理時間 < 猶予時間
preStopの処理時間を 10秒
として実行しました。
preStopを実行してから15秒という時間を待つことなく、preStopの処理が完了後に SIGTERM
受信のログが送信されています。
また、preStopを実行してから15秒後にはログが送信されなくなりました。猶予時間が伸びていないことが確認できます。
preStopの処理時間 > 猶予時間
preStopの処理時間を 20秒
として実行しました。
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
とした場合のログです。
SIGNAL SIGTERM を受信しました。
というログが送信されてきませんでした。
終了処理をTERMシグナルを使って実装しようとしていた場合には注意が必要です。