TL

グレースフルシャットダウンとコネクションドレイン

Podを止めるたびに5xxやコネクションリセットが出るのは、終了の順序が間違っているからです。SIGTERMから接続排出・新規拒否・LB伝播までの正しい順番を押さえれば、無停止で更新できます。

応用グレースフルシャットダウンコネクションドレインKubernetesロードバランサゼロダウンタイム信頼性最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.正しい終了順は「LBから外す→新規受付を止める→in-flightを待つ→クローズ」。順序を誤ると、まだ届く新規リクエストを閉じたサーバーが受けて5xxやコネクションリセットになる。
  • 2.Kubernetesではエンドポイント削除(LB伝播)とSIGTERM送信が並行・非同期に走るため競合する。preStopフックでsleepを挟み、LBがこのPodを外し切るまで新規を受け続けてから終了を始めるのが定石。
  • 3.ゼロダウンタイム更新の原理は『常に受け口が余分にある』こと。新Podが Ready になってから旧Podを落とし、旧Podは排出が終わるまで既存接続を生かす。terminationGracePeriodSeconds はこの排出時間の上限。

なぜデプロイのたびにエラーが出るのか

ローリング更新(/devops/rolling-update-math/)で旧Podを落とすたびに、クライアント側に散発的な 5xx やコネクションリセットが観測される——これはアプリのバグではなく、終了処理の順序とタイミング の問題であることがほとんどです。

サーバープロセスを「いきなり止める」と何が起きるか。処理中(in-flight)のリクエストは応答を返す前にコネクションが切れ、クライアントは中途半端な結果を受け取ります。さらに厄介なのは、止めた直後にもまだ新規リクエストが届く ことです。ロードバランサ(LB)やサービスのエンドポイント表は即座には更新されないため、すでに close を始めたサーバーにトラフィックが流れ込み、Connection refusedRST を返してしまう。グレースフルシャットダウンとは、この二つ——処理中の完遂新規流入の遮断 ——を正しい順序で行う技術です。

正しい終了順序:外す→止める→待つ→閉じる

直感に反しますが、「新規受付を止める」のが最初ではありません。正しい順序は次の通りです。

グレースフルシャットダウンの正しい順序
1. LB/エンドポイントから自分を外す      ← 新規が来なくなるよう伝播を始める
2. 伝播が浸透するまで新規を受け続ける    ← まだ届く新規を 5xx にしない
3. ヘルスチェック(readiness)を失敗させる ← 新規ルーティングを止める後押し
4. listen を閉じ、新規 accept を停止     ← もう新規は来ない前提
5. in-flight の応答完了を待つ(排出)    ← 処理中を最後までやり切る
6. 下流の接続・リソースを閉じてプロセス終了

核心は 「LBがこのインスタンスを外し切る前に listen を閉じてはいけない」 点にあります。もし手順4を先にやると、まだ自分宛にトラフィックを送っている LB に対して接続を拒否することになり、それがクライアントから見える 5xx になります。だから手順2の「伝播待ち」が要る。LB側の振り分け表からこのインスタンスが消え、進行中のヘルスチェックが失敗判定に切り替わるまで、意図的に少し待ってから 受付を閉じるのが定石です。

readiness を落とすだけでは新規は止まらない

readiness プローブを失敗させればルーティング対象から外れますが、これは「次のチェック周期で・LBの設定が伝播してから」効くもので、即時ではありません。プローブ間隔・失敗閾値・LB の収束時間の分だけ遅延があり、その間も新規は届きます。readiness を落とすことと、伝播を待つことは別物です。前者は引き金、後者は到達確認だと考えてください。

SIGTERM と排出(ドレイン)の実装

プロセスへの終了要求は通常 SIGTERM で届きます(コンテナランタイムやオーケストレータが送る)。アプリはこのシグナルを 捕捉 して、上の順序を実行しなければなりません。捕捉せず既定動作に任せると、SIGTERM は即時終了として扱われ、in-flight が切れます。

SIGTERM ハンドラの骨子(HTTP サーバーの例)
on SIGTERM:
    server.set_not_ready()        # readiness を fail に(伝播の引き金)
    sleep(propagation_delay)      # LB がこのインスタンスを外し切るのを待つ
    server.stop_accepting()       # listen を閉じ、新規 accept を停止
    server.wait_inflight(timeout) # 処理中の応答完了を待つ=ドレイン
    downstream.close()            # DB プール・キュー接続などを解放
    exit(0)

ここで wait_inflightコネクションドレイン の本体です。新規は受けないが、すでに受理したリクエストには最後まで応答する。HTTP/1.1 のキープアライブ接続では、応答ヘッダに Connection: close を付けて「この接続はもう使い回さないでね」とクライアントへ伝え、レスポンス送信後にその接続を閉じます。長時間の接続(WebSocket やストリーミング、gRPC のサーバーストリーム)は、ドレインの猶予内に収まらないことがあるため、GOAWAY(HTTP/2・gRPC)など 正常終了の合図 を送って、クライアントに別インスタンスへ張り直す機会を与えます。

timeout を設けるのは、永遠に終わらない処理で待ち続けない ためです。猶予を過ぎても残っている接続は強制的に閉じます。この上限がオーケストレータ側の猶予(後述の terminationGracePeriodSeconds)を超えると、待ち切る前にプロセスを SIGKILL で殺されてしまい、せっかくのドレインが無に帰す——両者を整合させる必要があります。

Kubernetes での競合:preStop と エンドポイント伝播

Kubernetes でこの問題が顕著なのは、Pod 削除時に二つの処理が並行・非同期に走る からです。Pod が Terminating になると、ほぼ同時に次が起きます。

並行して走る処理担当効果タイミングの問題
コンテナへ SIGTERM 送信kubeletプロセスに終了を要求即座に届く
Endpoints/EndpointSlice から Pod を除去control planeService の振り分け先から外すkube-proxy/Ingress への伝播に遅延がある

問題は、SIGTERM が先に届き、エンドポイント除去の伝播が後になりがち な点です(/devops/kube-proxy-service-vip/)。エンドポイント削除イベントは API サーバー → 各ノードの kube-proxy → iptables/IPVS 規則更新、あるいは Ingress コントローラ → 外部 LB という経路をたどり、ノード数や LB の収束に応じた遅延を持ちます。その間に SIGTERM を受けたアプリが即座に listen を閉じると、まだ自分宛に振り分けてくるトラフィック を拒否してしまう。これがデプロイ時 5xx の正体です。

定石は preStop フックで sleep を挟む ことです。kubelet は preStop フックを完了させてから SIGTERM を送るため、ここに数秒の待機を入れると、エンドポイント除去の伝播が浸透する時間を稼げます。

lifecycle:
  preStop:
    exec:
      command: ["sh", "-c", "sleep 10"]   # 伝播が浸透するまで新規を受け続ける
preStop の sleep とアプリ側の sleep は役割が同じ

「preStop で待つ」のと「SIGTERM ハンドラの冒頭で sleep する」のは、どちらも『エンドポイント伝播を待ってから受付を閉じる』という同じ目的を果たします。preStop を使う利点は、アプリのコードを変えずに(言語非依存で)待機を差し込める点です。重要なのは、この待機中もアプリは正常にリクエストを受け続けていること——だから readiness を落とすのは伝播の引き金として有効でも、listen はまだ閉じてはいけません。

terminationGracePeriodSeconds と SIGKILL

kubelet は SIGTERM 送信後、terminationGracePeriodSeconds(既定30秒)が経過すると SIGKILL で強制終了 します。この猶予は preStop の所要時間も含む 全体の上限である点に注意が必要です。つまり次の不等式が成り立っていなければなりません。

preStop の待機  +  アプリのドレイン上限  <  terminationGracePeriodSeconds
(例)  10秒      +      15秒            <      30秒   → OK

もし preStop の sleep + ドレイン時間 が猶予を超えると、ドレインの途中で SIGKILL が飛び、結局 in-flight が切れます。長い処理を抱えるサービスでは、猶予を 30 秒より長く設定し、それに合わせてアプリのドレインタイムアウトも調整します。

ありがちな失敗

(1) アプリが SIGTERM を無視する設計(シェル経由起動でシグナルが PID 1 に届かない、フレームワーク既定が即時終了)——ドレインが一切走らず毎回 in-flight が切れる。(2) preStop を入れず SIGTERM で即 listen クローズ——エンドポイント伝播前にトラフィックを拒否し 5xx。(3) ドレインタイムアウトが terminationGracePeriodSeconds を超過——途中で SIGKILL されドレインが無意味化。(4) preStop の sleep が長すぎ、ロールアウト全体が遅延してデプロイがタイムアウト。

ゼロダウンタイム更新の原理

ここまでの部品が噛み合うと、無停止更新が成立します。原理は単純で、「常に処理可能な受け口が余分に存在する」 状態を保つことです。ローリング更新では、新Podが readiness を通過して LB の振り分け先に 加わってから、旧Podを Terminating にします。旧Podは preStop でしばらく新規を受け続け、伝播後に新規を断ち、in-flight を排出してから去る。この重なりがある限り、どの瞬間にもトラフィックを受けられる Ready なPodが存在 し、クライアントから見て断絶がありません。

要するにゼロダウンタイムは、(1) 新を先に立ち上げて Ready を確認する 起動側の規律(readiness を正しく実装する)と、(2) 旧を伝播待ち・排出してから落とす 終了側の規律(preStop と SIGTERM ハンドラ)の両輪で成り立ちます。デプロイ戦略(/devops/deployment-strategies/)が Blue-Green であれ Canary であれ、切り替えの瞬間に必ずこのドレインが効いているかどうかが、無停止と「デプロイのたびにエラー」を分けます。

設計レビューの定番論点

「SIGTERM を受けたら停止する」では不十分です。問われるのは——(1) 終了順序が『LBから外す→伝播を待つ→新規受付停止→in-flight排出→クローズ』になっているか、(2) Kubernetes でエンドポイント伝播と SIGTERM の競合を理解し preStop(または同等のsleep)で対処しているか、(3) preStop + ドレイン < terminationGracePeriodSeconds を満たしているか、(4) シグナルがアプリ本体(PID 1)に確実に届く起動構成か、(5) ゼロダウンタイムが『新が Ready→旧を排出』の重なりで成立する原理を説明できるか。

まとめ

  • 終了の正しい順序は「LB/エンドポイントから外す→伝播を待つ→新規受付を停止→in-flight を排出→クローズ」。先に listen を閉じると、まだ届く新規を拒否して 5xx になる。
  • SIGTERM を捕捉し、ドレイン(処理中への応答完了)を実装する。キープアライブには Connection: close、HTTP/2・gRPC には GOAWAY で正常終了を合図する。
  • Kubernetes ではエンドポイント除去の伝播が SIGTERM より遅れて競合する。preStop の sleep(または SIGTERM ハンドラ冒頭の待機)で伝播を待ってから受付を閉じるのが定石。
  • preStop の待機 + ドレイン上限 が terminationGracePeriodSeconds を超えると SIGKILL でドレインが無効化される。両者を整合させる。
  • ゼロダウンタイムの原理は「常に Ready な受け口が余分にある」こと。新が Ready になってから旧を排出して落とす重なりが、無停止を生む。

DevOps/インフラ Article

グレースフルシャットダウンとコネクションドレインを実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

グレースフルシャットダウン

比較で見る軸

難易度: advanced / カテゴリ: DevOps/インフラ / タグ数: 6

導入後に効く点

Kubernetesではエンドポイント削除(LB伝播)とSIGTERM送信が並行・非同期に走るため競合する。preStopフックでsleepを挟み、LBがこのPodを外し切るまで新規を受け続けてから終了を始めるのが定石。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
DevOps/インフラ
タグ数
6

判断チェックリスト

  • 自社の用途が「グレースフルシャットダウン / コネクションドレイン」に近いか確認する。
  • 強みである「正しい終了順は「LBから外す→新規受付を止める→in-flightを待つ→クローズ」。順序を誤ると、まだ届く新規リクエストを閉じたサーバーが受けて5xxやコネクションリセットになる。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

グレースフルシャットダウンコネクションドレインKubernetesロードバランサゼロダウンタイムグレースフルシャットダウンコネクションドレインKubernetes