Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Implementing graceful shutdown in Go HTTP servers

2026年10月07日に「Dev.to」が公開したITニュース「Implementing graceful shutdown in Go HTTP servers」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

GoのHTTPサーバーは停止時に処理中のリクエストを強制終了させ、エラーの原因となる。これを防ぐには、`srv.Shutdown(ctx)`を使い、処理中のリクエストが完了するまで待つ「グレースフルシャットダウン」が必要だ。ハンドラやバックグラウンド処理も中断信号に対応させ、安全なサービス停止を実現する。

ITニュース解説

システムエンジニアを目指す皆さんにとって、サーバーの安定稼働は非常に重要なテーマです。特に、サーバーを停止する際に、それがきちんと行われるかどうかは、サービスの品質に直結します。ここでは、ゴー(Go)言語で書かれたHTTPサーバーを安全に停止させるための「グレースフルシャットダウン」という考え方と、その具体的な実装方法について解説します。

Webサーバーは、ユーザーからのリクエストを受け付けて処理する役割を担っています。例えば、Webサイトの閲覧、ファイルのアップロード、データベースへのデータ保存などがこれにあたります。もし、サーバーがこれらの処理の途中で突然停止してしまったらどうなるでしょうか。ユーザーはエラー画面を目にし、アップロード中のファイルは失われ、データベースには不完全なデータが残ってしまうかもしれません。これは、サービスの信頼性を著しく損なう事態です。ゴーのデフォルトのHTTPサーバーに停止信号(SIGTERM)を送ると、進行中のリクエストが瞬時に強制終了されてしまうことがあります。これにより、API呼び出しの失敗、データベースへの書き込み途中のデータ、そしてクライアント側での不要なエラーリトライが発生し、クリーンなデプロイメントではなく障害につながる可能性が高いのです。

なぜ、このようにサーバーが突然停止すると問題が起きるのでしょうか。ゴーのnet/httpパッケージが提供するデフォルトのサーバーは、「現在処理中の接続が完了するのを待つ」という機能を標準では持っていません。プロセスが強制終了されると、オペレーティングシステムはサーバーが使用していたすべてのファイルディスクリプタ(ネットワーク接続やファイルなどへのアクセス経路)を閉じます。その結果、処理中だったHTTP接続はTCP RST(リセット)という信号を受け取り、クライアントは接続エラーを検出します。データベース側では、サーバーからの応答がないため、進行中のトランザクションが途中で放棄されたと見なされてしまいます。このような突然の停止が特に問題となるのは、ゼロダウンタイムデプロイメント(サーバーを停止せずに新しいバージョンに切り替える方式、Kubernetesなどのコンテナオーケストレーションシステムでよく用いられる)、ファイルアップロードやバッチ処理、サーバー送信イベント(SSE)のような長時間かかるリクエスト、そしてHTTPリクエストをきっかけにバックグラウンドで実行される処理がある場合です。

これらの問題を解決するために、「グレースフルシャットダウン」が不可欠です。グレースフルシャットダウンとは、サーバーが停止を指示された際に、新しいリクエストの受け付けを停止し、現在処理中のリクエストがすべて完了するのを待ってから、安全にプロセスを終了させる一連の仕組みを指します。ゴーでは、バージョン1.8以降でhttp.Server構造体にShutdownメソッドが追加され、この機能が利用できるようになりました。

基本的なグレースフルシャットダウンの実装は、次のようになります。まず、サーバー本体を通常のプログラムの流れとは別の「ゴルーチン」(Goにおける軽量な並行処理の仕組み)で起動します。これにより、メインのプログラムはサーバーの起動でブロックされず、他の処理を実行できるようになります。次に、オペレーティングシステムからサーバー停止の信号(syscall.SIGTERMやsyscall.SIGINTなど)を受け取るための準備をします。これらの信号が送られてきたら、それを検知してサーバーにシャットダウンを開始するよう指示するのです。具体的には、srv.Shutdown(ctx)というメソッドを呼び出します。ここで重要なのがctx、つまり「コンテキスト」です。このコンテキストには、シャットダウン処理が完了するまでの最大許容時間(タイムアウト)を設定します。Shutdownメソッドは、新しい接続の受け入れを停止し、指定されたタイムアウト期間内、または現在アクティブなすべての接続が完了するまでのいずれか早い方まで待ちます。もしShutdownが呼ばれた後にサーバーのListenAndServeメソッドがエラーを返した場合、それがhttp.ErrServerClosedであれば、サーバーが正常にシャットダウンされたことを示す予期されたエラーなので、特に問題なく無視して構いません。

しかし、この基本的なパターンだけでは不十分な場合があります。それは、リクエストの処理に時間がかかる「長時間リクエスト」です。例えば、大きなファイルの処理や、外部APIへの問い合わせ、複雑なデータベースクエリなどです。もしハンドラ(リクエストを処理する関数)が、サーバーから送られてくる「シャットダウン中なので早く処理を終えてください」というシグナルを無視して処理を続けてしまったら、設定したシャットダウンタイムアウトが切れるまでサーバーは停止できません。このような事態を避けるためには、各ハンドラがリクエストに紐づけられた「コンテキスト」(r.Context())のキャンセルシグナルを尊重するよう実装する必要があります。具体的には、ハンドラ内で時間のかかる処理を実行するゴルーチンを起動し、その処理の完了を待つか、あるいはr.Context().Done()がシグナルを送るのを待つselect文を使って、どちらかが先に起きたら対応する、というパターンです。もしr.Context().Done()がシグナルを送ってきたら、それはサーバーがシャットダウン中であり、このリクエストの処理はもう続ける必要がないことを意味します。この場合、ハンドラは早期に処理を終了し、クライアントに応答を書き込まないようにします。データベースアクセスの場合も同様で、db.QueryRowContext(r.Context(), ...)のようにリクエストコンテキストをデータベースクエリに直接渡すことで、サーバーシャットダウン中にDB処理がキャンセルされ、不要なリソース消費を防ぐことができます。

HTTPリクエストの処理だけでなく、サーバー内でバックグラウンドで動くワーカー(例えば、メール送信やファイルの非同期処理など)がある場合も考慮が必要です。これらのワーカーがHTTPハンドラからトリガーされ、独自のゴルーチンとして動いている場合、サーバーがシャットダウンしてもワーカーがまだ処理中であれば、途中で強制終了されるか、あるいはサーバープロセスが終了した後も幽霊のように動き続けてしまう可能性があります。これを防ぐためには、sync.WaitGroupという仕組みが非常に役立ちます。WaitGroupは、複数のゴルーチンが完了するのを待機するために使われるものです。バックグラウンドジョブを開始する直前にwg.Add(1)でカウンターを増やし、ジョブが完了したらdefer a.wg.Done()でカウンターを減らすようにします。そして、サーバーのシャットダウン処理の中で、HTTPサーバーの停止後、このWaitGroupがすべてのジョブの完了を通知するまで待機します。この際、HTTPサーバーのシャットダウンとバックグラウンドジョブの完了を同じタイムアウトを持つコンテキストで管理することで、サーバー全体のシャットダウン時間を予測可能に保つことができます。wg.Add(1)をゴルーチンが開始する前に呼び出すことは非常に重要で、もしゴルーチン内で呼び出してしまうと、wg.Wait()がすでに呼ばれている場合に競合状態が発生する可能性があります。

さらに、現代のクラウド環境、特にKubernetesのようなコンテナオーケストレーションシステムでサービスをデプロイする場合、グレースフルシャットダウンには特別な考慮が必要です。Kubernetesでは、コンテナ(Pod)が停止される際、まずロードバランサーからそのPodへの新しいトラフィックのルーティングが停止されます(Endpointsから削除)。同時に、コンテナには停止信号(SIGTERM)が送られます。しかし、この二つのステップは厳密には並行して行われるため、ロードバランサーが新しいトラフィックを完全にルーティングしなくなる前に、サーバーがSIGTERMを受け取ってしまう可能性があります。つまり、シャットダウンプロセスを開始したにもかかわらず、まだ数秒間は新しいリクエストがサーバーに届いてしまうことがあるのです。この問題に対処する実用的な方法として、SIGTERMを受け取った後、すぐにsrv.Shutdown(ctx)を呼び出すのではなく、数秒間(例えば5秒程度)待機する時間を設けることが有効です。この待機時間中に、ロードバランサーの設定変更(iptablesやIPVSの伝播)が完了し、新しいリクエストがサーバーに到達しなくなります。また、SIGTERMを受け取った直後に、ヘルスチェックやレディネスプローブ(サーバーがリクエストを処理する準備ができているかを示すチェック)で「サービス利用不可(503)」を返すように設定することも、ロードバランサーがより早くそのサーバーへのトラフィックを停止するのに役立ちます。

まとめると、ゴーのHTTPサーバーで安定したグレースフルシャットダウンを実現するためには、三つの要素が連携して機能する必要があります。一つ目は、オペレーティングシステムからのサーバー停止信号(SIGTERMなど)を適切に捕捉すること。二つ目は、http.ServerのShutdownメソッドを適切なタイムアウトを設定したコンテキストとともに呼び出し、現在処理中のリクエストの完了を待機すること。そして三つ目は、すべてのHTTPハンドラとバックグラウンドワーカーが、リクエストコンテキストのキャンセルシグナルを尊重し、シャットダウン時には速やかに処理を終了するように実装することです。これらのいずれかが欠けても、リクエストが途中で失われたり、サーバーが適切に終了せず、システムリソースを消費し続ける「ゴルーチンリーク」などの問題が発生する可能性があります。今回紹介したパターンは、ほとんどの一般的なユースケースをカバーしますが、サーバー送信イベント(SSE)のように非常に長い間接続を維持するケースや、Unixドメインソケット、ストリーミングレスポンスなどの特殊なケースでは、これらの基本原則を基盤としつつ、それぞれの状況に応じた明示的なストリーム終了ロジックを追加で実装する必要があります。この基礎を理解することで、より堅牢で信頼性の高いサーバーアプリケーションを開発する能力を身につけることができるでしょう。

関連コンテンツ

関連IT用語