🦁

【Go】WebサーバをGraceful Shutdownしてみる

2022/05/21に公開

はじめに

Webサーバを安全にシャットダウンする方法として「Graceful Shutdown」と呼ばれる概念があります。
Graceful Shutdownというのは、例えばデータベースへの登録処理があったときに、処理中にサーバのシャットダウン等の処理が発生した場合、登録処理が終わるまで待ってからシャットダウンするようなイメージです。もし金銭が関わるような処理中にシャットダウンされてしまったら大変なことになりますからね。

今回はGoでそれを実装してみたので、ご紹介します。

全体コード

GoでGraceful Shutdownをするためには、例えば以下のような方法が考えられます。

main.go

ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, os.Interrupt, os.Kill)
defer stop()

srv := &http.Server{
	Addr:    port,
	Handler: mux,
}

go srv.ListenAndServe()

<-ctx.Done()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

err = srv.Shutdown(ctx)
if err != nil {
	return err
}

コードだけ載せても「なんでそれで安全にシャットダウンできるんや?」となると思うので、コードを詳しく説明していきます。

コード説明

コードを上から順に説明していきます。

ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, os.Interrupt, os.Kill)

ここでは、signal.NotifyContext()を利用しています。今回の例では第一引数としてcontext.Background()を指定し、真っ新なコンテキストに対して実装しています。
第二引数以降は、どの割り込み処理に対してGraceful Shutdownをするのかを定義しています。
syscall.SIGTERMはプロセスの停止、os.Interruptctr + cのような中断処理、os.Killはkillコマンド等の処理ですね。
signal.NotifyContext()の戻り値として、コンテキストとコンテキストのキャンセル関数が渡されます。今回は例として見せるためstopに渡していますが、キャンセル処理を実装する必要がないのであれば、変数に渡す必要はないと思います。

srv := &http.Server{
	Addr:    port,
	Handler: mux,
}

go srv.ListenAndServe()

ここで実行するWebサーバの設定をしています。AddrHundlerは適宜設定してください。
サーバの実行はgoroutineを使っています。

<-ctx.Done()

このコードより下の処理は、コンテキストがDoneされるまで(signal.NotifyContext()に指定した中断処理が実行されるまで)は実行されません。

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)

ここではシャットダウンをするまでのタイムアウト時間をcontext.WithTimeout()を利用して設定しています。今回は5秒と設定しているので、割り込み処理が入って5秒以内にWebサーバの処理が完了しなければ強制的にシャットダウンされます。
本番環境とかだとかなり長めに設定されるのかな?と勝手に思ってます。(もしくは設定しないか)
signal.NotifyContext()同様に、context.WithTimeoutの戻り値として、コンテキストとコンテキストのキャンセル関数が渡されます。今回は例として見せるためcancelに渡していますが、タイムアウトをキャンセルする必要がないのであれば、変数に渡す必要はないと思います。

err = srv.Shutdown(ctx)
if err != nil {
	return err
}

ここでWebサーバのシャットダウン処理が実行されます。引数にctxを指定することで、先ほど設定したタイムアウトを設定できます。

まとめ

今回は上記のような方法でGraceful Shutdownを実現しましたが、他にもやり方は様々です。
signal.NotifyContext()ではなく、signal.Notify()を利用した方法もあります。
自分なりに他の方法も実装してみて理解を深めていくと良いでしょう。

自分は初心者ですが、TechTrainというサービスを利用して勉強しています。経験豊富なメンターの指導のもとコーディング練習ができるので興味のある方は是非ご利用ください、

Discussion