こんにちは! 2025年度に新卒エンジニアとして株式会社レアゾン・ホールディングスに入社しました、鈴木 健介です。
入社後はmenu株式会社に配属され、マイクロサービスチームでGCPを用いたクラウドインフラおよびマイクロサービスの開発・保守・運用をしています。
はじめに
menu社では、マイクロサービス基盤としてGoogle Kubernetes Engine(GKE)のAutopilotモードを利用しています。
GKE Autopilotモードは、ノードの管理やスケーリングをGoogleが自動で行ってくれるフルマネージドなKubernetes環境です。ノードのプロビジョニングやOSのパッチ適用といった保守運用をGoogleに任せられるため、比較的少人数のチームでもKubernetesクラスタの運用が可能になるという大きなメリットがあります。Kubernetesのバージョンアップグレードも自動で行われるため、運用負荷を大幅に軽減できます。
また、GKEはマネージドのService Mesh機能(Cloud Service Mesh)を提供しており、menu社ではこちらも利用しています。これにより、マイクロサービス間の通信制御やトラフィック管理をIstioベースの仕組みで実現しています。
今回は、このGKE環境でGKEの自動アップグレード時にマイクロサービスへの通信が瞬断するという問題に遭遇し、原因調査から解決までを行った話になります。
問題の発生
ある日、マイクロサービス間の通信がごく稀に失敗することがあることを検知しました。
エラーの頻度は非常に低く、常に発生するわけではありません。しかし、サービスの信頼性に直結する問題であるため、原因の特定に乗り出しました。
原因調査
まず、通信が失敗したタイミングのログを詳しく確認しました。すると、通信が失敗した時間と、GKEの自動アップグレードが発生している時間が一致していることが分かりました。
GKE Autopilotモードでは、Kubernetesのバージョンアップグレードが自動的に実行されます(メンテナンスウィンドウで曜日や時間帯を指定することも可能です)。このアップグレードの過程では、GKEがサージアップグレードを実行します。参考: Autopilot cluster upgrades)。
「アップグレード時にPodが退避・再作成される過程で、一時的にリクエストが失われているのではないか?」と仮説を立てました。
Istio Ingress Gatewayへの疑い
マイクロサービスへの通信は、Istio Ingress Gatewayを経由しています。Istio Ingress Gatewayはクラスタ外部からのトラフィックを受け付ける入口であり、ここが瞬断すればサービス全体に影響が出ます。
そこで、Istio Ingress GatewayのPodが終了するタイミングで瞬断が発生しているのではないかと考え、この仮説を検証することにしました。
再現実験
仮説を検証するために、以下のような再現実験を行いました。
- Istio Ingress Gatewayが存在するクラスタAとは別に、検証用のクラスタBを準備
- クラスタBからクラスタAのIstio Ingress Gatewayに対して、継続的にリクエストを送信
- リクエストを送信し続けながら、クラスタAのIstio Ingress GatewayのPodに対してローリングアップデートを実行
GKEのサージアップグレードでも、Podが終了する際のライフサイクル(Endpointsからの削除やSIGTERMの送信)はKubernetesの共通の仕組みです。そのため、ローリングアップデートでPodを再起動させることで、サージアップグレード時に起きる瞬断を擬似的に再現しました。
結果、ごく少数のリクエストで瞬断が再現されました。
やはり、Istio Ingress GatewayのPodが終了する際に、一部のリクエストがドロップされていたのです。
なぜ瞬断が起きるのか?
ここで、「アプリケーション側でグレースフルシャットダウンが実装されていれば、Podの終了時にも安全にリクエストを捌けるのでは?」と疑問に思う方もいるかもしれません。私も最初はそう考えました。
しかし、Kubernetesにおいては、アプリケーション側のグレースフルシャットダウンだけでは不十分なケースがあります。
KubernetesがPodを終了する際、以下の処理が並行して実行されます。
- Endpointsからの削除: Podをサービスのエンドポイントリストから削除し、新しいリクエストがルーティングされないようにする
- SIGTERMの送信: Pod内のコンテナにSIGTERMシグナルを送り、終了処理を開始させる
ここでポイントとなるのが、この2つの処理が同時に発生するという点です。
SIGTERMを受け取ったアプリケーションがグレースフルシャットダウンを開始しても、エンドポイントリストの更新がロードバランサーやプロキシに伝播するまでにはわずかなタイムラグがあります。この間に新たなリクエストが終了処理中のPodに到達してしまうと、接続エラーが発生する。
[リクエスト送信] ──→ [ロードバランサー] ──→ [終了処理中のPod] ✗ 接続エラー
│
└─ エンドポイントリストの更新がまだ反映されていない
解決策:preStopフックによる終了遅延
この問題の解決策として、KubernetesのライフサイクルフックであるpreStopを利用しました。
preStopフックは、コンテナにSIGTERMが送信される前に実行される処理を定義できる仕組みです。ここにスリープ処理を挟むことで、エンドポイントリストの更新が各コンポーネントに伝播するまでの時間を確保できます。
lifecycle:
preStop:
sleep:
seconds: 30
この設定により、Podの終了フローは以下のようになります。
- KubernetesがPodの終了を決定
- Endpointsからの削除が開始される(ロードバランサーへの伝播が始まる)
- preStopフックが実行され、30秒間スリープ(この間にエンドポイントの更新が伝播)
- スリープ終了後、SIGTERMが送信される
- アプリケーションがグレースフルシャットダウンを実行
preStopで待機している間にエンドポイントリストの更新が行き渡るため、新しいリクエストが終了処理中のPodに届くことを防げます。
再現実験での検証
preStopフックの設定を追加した上で、先ほどと同じ再現実験を再度行いました。
クラスタBからリクエストを送信し続けながら、クラスタAのIstio Ingress GatewayのPodをローリングアップデートしたところ、瞬断は発生しなくなりました。
まとめ
今回は、GKEの自動アップグレード時にIstio Ingress Gatewayで瞬断が発生する問題と、その解決策について紹介しました。
- GKEのサージアップグレード時にIstio Ingress GatewayのPodが終了すると、エンドポイント更新のタイムラグにより瞬断が発生することがある
- アプリケーション側でグレースフルシャットダウンを実装していても、KubernetesのPod終了フローにおけるレースコンディションが原因で防ぎきれない
- preStopフックでPodの終了を遅延させることで、エンドポイントの更新が伝播する時間を確保し、瞬断を解消できる
GKE Autopilotモードは運用負荷を大幅に削減してくれる非常に便利なサービスですが、自動アップグレードという便利さの裏側にこのような落とし穴があることを学びました。
参考文献
▼新卒エンジニア研修のご紹介
レアゾン・ホールディングスでは、2025年新卒エンジニア研修にて「個のスキル」と「チーム開発力」の両立を重視した育成に取り組みました。 実際の研修の様子や、若手エンジニアの成長ストーリーは以下の記事で詳しくご紹介していますので、ぜひご覧ください!
https://media.reazon.jp/articles/NewGraduate_engineer4_2025?utm_source=qiita&utm_medium=referral&utm_campaign=new-grads_dev_qiita
▼採用情報
レアゾン・ホールディングスは、「世界一の企業へ」というビジョンを掲げ、「新しい"当たり前"を作り続ける」というミッションを推進しています。
現在、エンジニア採用を積極的に行っておりますので、ご興味をお持ちいただけましたら、ぜひ下記リンクからご応募ください。