Blogaomu

WEBアプリケーション開発とその周辺のメモをゆるふわに書いていきます。

EnvoyのイメージをDistrolessベースに替えたときの失敗談

この記事はZOZO Advent Calendar 2023 シリーズ9の7日目の記事です。

私たちの管理するWebシステムではリバースプロキシとしてEnvoyを利用しています。以前はAlpineベースのイメージを利用していましたが、こちらが廃止となったためEnvoyのバージョンアップと同時にDistrolessベースのイメージに切り替えました。この際の失敗についてアドベントカレンダーの記事として供養しておきます。

システムについて簡単に説明

EnvoyはAPIサーバーのサイドカーコンテナとしてPod内に含まれます。PodへのリクエストはまずEnvoyを経由し、APIサーバーにルーティングされます。

また、ArgoCDおよびArgo Rolloutsを利用してカナリアリリースで新しいバージョンのPodをロールアウトします。詳しくは同僚が書いたテックブログを参照ください。

techblog.zozo.com

イメージの切り替え作業で起きたこと

最初に、Envoyイメージを切り替えるロールアウトを行いました。こちらに関しては特に問題無く完了しました。以下の図はロールアウトの流れで、左上から右下にかけて段階が進む様子を示します。Podは既存バージョンを示すStableとラベルのついたものとカナリアのバージョンを示すCanaryとラベルのついたもので色分けしています。また、点線の枠のものはPodが終了中(Terminating)であることを示します。

これで問題無く移行できたと思い、時間が経った後に別の変更のロールアウトを行いました。すると、程なくしてALBのステータス5xxが急増したアラートが鳴り、ロールアウトもカナリアの段階で失敗し既存バージョンに戻ってしまいました。*1この後ステータス5xxの発生が収まりました。

調査したところ、カナリアリリースで既存バージョンのPodを終了させる際にEnvoyコンテナのPreStopフックが失敗したことでリクエストが失敗していたことが分かりました。

私たちのシステムでは、正常なときは以下のような流れでPodが終了します。

  1. Kubernetesのコントロールプレーンに対してPodの終了APIがリクエストされる
  2. EnvoyコンテナのPreStopフックが開始され、並行してALBのターゲットから該当PodのIPアドレスを登録解除するAPIが呼ばれる(処理は前後する可能性あり)
  3. ALBは登録解除のAPIが呼ばれた後一定期間待ってから登録解除が完了する(一定期間内にターゲットへ処理中のリクエストが完了するのを期待している)
  4. EnvoyコンテナはPreStopフックでsleepコマンドを実行し、その間に処理中のリクエストを完了させる
  5. PreStopフックが完了し、コンテナにTERMシグナルが送られる(もしterminationGracePeriodSecondsが経過していればKILLシグナルが送られる)
  6. Podの削除が完了する

ざっくりとした流れの説明なので、詳しくは以下の資料を参考にしてください。

kubernetes.io

docs.aws.amazon.com

話を戻すと、失敗したパターンでは先ほどの4の段階でPreStopフックが失敗しました。これはDistrolessベースのイメージに替えたことでsleepコマンドが存在しなかったためです。preStopフックが失敗するとコンテナは強制終了します。このため、処理中のリクエストが失敗し、ALBから5xxステータスを返しました。以下の図は先ほどのものと似ていますが、StableのPodが終了する際にリクエストが失敗すること、Canaryが失敗しロールバックすること、CanaryのPodが終了する際にリクエストが失敗することの3点が異なります。

今回幸運だったことは、カナリアリリースおよびメトリクスのチェックを行っていたことで不具合の影響を最小限に抑えられたことでした。カナリアリリースでALBのステータス5xxのメトリクスをチェックしており、これが失敗と判定されたことでロールバックできました。また、対処としてはEnvoyイメージをベースとしてbusyboxイメージからsleepコマンドをコピーしてビルドし、このビルドしたイメージをPodで利用するようにしました。

おわりに

イメージの切り替え自体は上手くいっていたので、まさかここでリクエストの失敗が起きるとは正直思っていませんでした。さらに、Envoyとは別の変更をロールアウトしようとしていたので原因に辿り着くまで少し時間が掛かってしまいました。考慮不足といわれればその通りですし、Distrolessではsleep等のユーティリティーコマンドが含まれていないこと自体は知っていたので事前に気づけなかったのは残念です。

さて、時は流れて、Xのタイムラインを見ていたところこんなポストを見つけました。これは、まさに我々が求めていたもの...。

将来的にはsleepコマンドを内包しなくてもやりたいことは実現できそうです。