gRPCのGraceful Shutdown


## はじめに
この記事は[Go 3 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/go3)の21日目です。

gRPCのメトリクスを取得するためにPrometheusを使う際にGraceful Shutdown を行う際の例になります。
やりたいことは「どちらかのサーバでエラーや特定のシグナルが発生した場合すべてのサーバを安全に停止する」です。
## 注意
Goで作るgRPCのServerでのメトリクス取得が対象です。
## 要約
今回は[run](https://github.com/oklog/run)を使ってGraceful Shutdownを作ります。
[errgroup](https://godoc.org/golang.org/x/sync/errgroup)という便利なパッケージもあります。
## ざっくりとした背景
gRPCのメトリクスを取得しようとした際、[go-grpc-prometheus](https://github.com/grpc-ecosystem/go-grpc-prometheus)にならって
以下のようにgPRCのHTTP2なサーバとPrometheus用のHTTPなサーバの2つをたてると思います。[こちらから抜粋](https://github.com/grpc-ecosystem/go-grpc-prometheus/blob/release-1.2/examples/grpc-server-with-prometheus/server/server.go#L78-L86)  

```go
	// メトリクス用のサーバを別のgoroutineで起動
	go func() {
		if err := httpServer.ListenAndServe(); err != nil {
			log.Fatal("Unable to start a http server.")
		}
	}()
	// gRPC用のサーバを起動
	log.Fatal(grpcServer.Serve(lis))
```

ただ、上記のexampleには「Graceful Shutdownを実装していないから本番ではつかわないでね」のような[注意書き](https://github.com/grpc-ecosystem/go-grpc-prometheus/blob/release-1.2/examples/grpc-server-with-prometheus/server/server.go#L51)
があります。  
1つのサーバ起動でのGraceful Shutdownはいろんな参考になる例が存在しますが、2つのサーバをGraceful Shutdownする方法があまりわからなかったところ、社内で聞いた情報を忘れないように記事にしようと思いました。
## Graceful Shutdownのおさらい
特定のsignalを受け取った場合などに以下のメソッドを呼び出してサーバが正常に終了します。
- net/httpの場合
[Server.Shutdown](https://golang.org/pkg/net/http/#Server.Shutdown)
- grpcの場合
[Server.GracefulStop](https://godoc.org/google.golang.org/grpc#Server.GracefulStop)
## runを使ってGraceful Shutdown
errgroupと似ていますが、Group.Add()で実行の関数と割り込み時の関数をGroupに追加して、Group.Run()でgoroutineによって実行の関数が実行され、  
実行中の関数が終了した場合にすべての割り込み時の関数を呼び出す流れになります。
実行と割り込みを別で管理できるので個人的に便利だと思います。
詳しくは[README](https://github.com/oklog/run/blob/master/README.md)をご確認ください
今回の例では以下のようになると思います。

```go
	var g run.Group
	{
		c := make(chan os.Signal, 1)
		signal.Notify(c, syscall.SIGTERM, os.Interrupt)
		cancel := make(chan struct{})
		g.Add(
			func() error {
				select {
				case <-c:
					log.Println("Received signal, exiting gracefully...")
				case <-cancel:
				}
				return nil
			},
			func(err error) {
				close(cancel)
			},
		)
	}
	{
		g.Add(
			func() error {
				if err := httpServer.ListenAndServe(); err != http.ErrServerClosed {
					log.Printf("Failed to serve http server: %v", err)
					return err
				}
				return nil
			},
			func(err error) {
				ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
				defer cancel()
				if err := httpServer.Shutdown(ctx); err != nil {
					log.Printf("Failed to shutdown http server: %v", err)
				}
			},
		)
	}
	{
		g.Add(
			func() error {
				listener, err := net.Listen("tcp", ":9093")
				if err != nil {
					log.Printf("Failed to listen grpc server: %v", err)
					return err
				}
				if err := server.Serve(listener); err != nil {
					log.Printf("Failed to serve grpc server: %v", err)
					return err
				}
				return nil
			},
			func(err error) {
				server.GracefulStop()
			},
		)
	}
	if err := g.Run(); err != nil {
		log.Fatalf("error: %v", err)
	}
```

Signalを受け取る、HTTPサーバを起動する、gRPCサーバを起動する3つのブロックになります。  
runは[prometheus](https://github.com/prometheus/prometheus/blob/master/cmd/prometheus/main.go#L561) でも使われているので参考にしてみてください。
## errgroupを使ってGraceful Shutdown
TBD...
## まとめ
errgroupやrunを使うことで複数のサーバのGraceful Shutdownが簡単にできるので、参考になればと思います。