Kubernetes Deployment の rollout status の罠

タイトル: Kubernetes Deployment の rollout status の罠

こんにちは、はじめまして。

グローバル向けAWS版kintoneのバックエンドエンジニアをしている齋藤 ( K.Saito (@SightSeekerTw) / X ) です。

突然ですが、Kubernetes にWebアプリケーションのコンテナをデプロイするために Deployment を作成し、 Deployment によってデプロイされた Pod に Service を経由してリクエストを転送していることが多いと思います。

Deployment のローリングアップデート (strategy type が RollingUpdate) により更新を行った際には紐づく ReplicaSet が新たに作成され、その ReplicaSet に紐づく Pod がデプロイされ、更新前の ReplicaSet の Pod が停止/削除されていくわけですが、皆様は何をもって新しい Deployment の状態に更新が完了したと判定していますでしょうか。

新しい Pod に置き換わったと思っていたのに

kubectl rollout statussuccessfully rolled out と表示されたら新しい ReplicaSet の Pod にすべて置き換わったと思ってませんか? 新しい Pod でリクエストが処理されると思っていませんか?

実は以下のように更新前の ReplicaSet の Pod にもリクエストが転送される場合があります。私達もロールアウト完了と判定後に開始した処理がなぜか古い Pod で行われてしまうという罠にハマってしまいました。

ロールアウト完了した?

アプリのコンテナイメージを v1 から v2 に置き換え、その直後にマイグレーション処理を行いたかったのですが、 v2 (新しいバージョン) に完全に置き換わったという判定に「ロールアウト完了」を使用し、その判定の後にマイグレーション処理を実行する API を呼び出しておりました。

しかし、想像とは裏腹に、稀に v1 の Pod に対してリクエストしてしまっており、マイグレーション処理が行われず不整合が発生してしまいました。

Successfully rolled out (ロールアウト完了) の状態とは

ではなぜ、 successfully rolled out と出力されているのに更新前の Pod にルーティングされてしまったのかという話になります。

これは、新しい Pod が「期待する数 (replicas に設定した数) だけ Ready になった」というだけで、実は更新前の Pod は Terminating に移行している状態で残っていることが原因でした。

参考情報: GitHub kubernetes/kubectl v0.27.5: kubectl の Deployment の rolled out 判定処理実装部

ロールアウト完了時の Pod の状態

Terminating な Pod にもリクエストが転送される可能性があるワケ

Pod の Status が Running から Terminating になったからと言って、 Service から外れた訳でもなく、リクエストが転送されなくなったというわけでもありません。

Terminating になったときに何が行われるのかを見てみると次のようなタイムラインになっているようです。

Pod が Terminating になってから Pod にリクエストがルーティングされなくなる流れ

このように、 Terminating に移行した直後は Service から Pod が切り離されておらず、 kube-proxy による iptables の更新も完了していない場合もあります。

まとめ

kubectl rollout status コマンドでロールアウトが完了したと行っても、新しい Deployment に対応する Pod の展開が完了したというだけにすぎない。

実際には iptables の更新が完了するまでの時間は多くの場合で1秒未満で完了することが多いため問題になることは少ないと思われますが、何か新しい Pod でしかリクエストを処理させたくないということであるならば、「(RollingUpdate の Deployment の) ロールアウト完了」を判定に使うのは条件として足りていないということになります。

RollingUpdateのロールアウト完了時の状態

シンプルに K8s が持つステータスだけで、新しい Pod でしかリクエストが処理されない状態になったかどうかという判定のためには、更新前の ReplicaSet の Pod が一つもなくなったかどうかを確認するのが確実であると考えられます。

あるいは、このような要件がある場合には、 Deployment の strategy type に RollingUpdate を選択するのではなく、更新前の Pod をすべて廃棄してから、新しい Pod を起動するという Recreate を選択をするか、 StatefulSet を採用してロールアウト完了の判定を用いるべきかもしれません。