ヘルスチェックエンドポイントの実装

さてさて、今日はクラウドネイティブアプリらしく、ヘルスチェックのエンドポイントを実装していきましょう。

## [utilitywarehouse/go-operational](https://github.com/utilitywarehouse/go-operational)

弊社では公開はしていませんが各アプリが実装すべき各種エンドポイントを仕様としてまとめています。ヘルスチェックにとどまらず、メトリクスなど運用に必要な情報一般を含んだものです。
この共通仕様のおかげで、運用に有用な情報の公開の仕方をチーム間で統一することができます。

さらに仕様頻度の高い言語にはライブラリも用意されており、Goの場合は [utilitywarehouse/go-operational](https://github.com/utilitywarehouse/go-operational) という形で公開されています。

今回はこのライブラリを使い、エンドポイントを実装していきます。

## newOpHandler関数

http.Handler を返すメソッド`newOpHandler`として実装していきましょう。

```go
func newOpHandler() http.Handler {
	return http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {})
}
```

## ハンドラの作成

新しいハンドラの作成に`NewHandler()`というAPIが公開されており、引数として公開する情報を渡します。
公開したい情報は`*Status`という形で抽象化されており、このポインタがもつAPIは自身を返すようになっているため、メソッドをチェインして書くことができるようになっています。
手始めに`NewStatus()`を呼び出し、アプリケーション名と説明を設定してみましょう。

```go
func newOpHandler() http.Handler {
	return op.NewHandler(op.NewStatus(appName, appDesc))
}
```

これまでダミーのハンドラを設定していたところに新しいハンドラを設定します。

```diff
- http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
-         fmt.Fprintln(w, "Hello, world")
- })
+ http.Handle("/__/", newOpHandler())
```

`/__/about`というエンドポイントをみてみましょう。

```sh
$ ./qiita-advent-calendar-2019 
INFO[0000] Hello, world                                  git_hash=c9608991b89e925eaa7f335ceebe39c5342edd46

$ curl localhost:8080/__/about
{
    "name": "qiita-advent-calendar-2019",
    "description": "The sample micro service app",
    "owners": null,
    "build-info": {
      "revision": ""
    }
}
```

設定した情報が取得できるのが確認できました。

## メトリクス情報

実はこの時点ですでにメトリクス情報も公開されています。`/__/metrics`エンドポイントを叩いてみましょう。

```sh
$ curl localhost:8080/__/metrics
# HELP go_gc_duration_seconds A summary of the GC invocation durations.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 0
go_gc_duration_seconds{quantile="0.25"} 0
go_gc_duration_seconds{quantile="0.5"} 0
go_gc_duration_seconds{quantile="0.75"} 0
go_gc_duration_seconds{quantile="1"} 0
go_gc_duration_seconds_sum 0
go_gc_duration_seconds_count 0
    ...
```

Prometheusフォーマットで公開されているので、Prometheusを使う際にはこのエンドポイントをスクレイピングするように設定しましょう。

## ヘルスチェックの追加

ヘルスチェックを追加する際には`AddChecker()`APIを利用します。

第一引数にはヘルスチェック項目の名前を与え、第二引数はヘルス結果を報告するための関数を渡します。
この関数の引数で渡される、`*CheckResponse`には`Healthy`,`Degraded`, `Unhealthy`というメソッドが公開されているので、状況に応じて呼び分けましょう。今回はダミーとしてHealthyを報告してみます。

```go
func newOpHandler() http.Handler {
	return op.NewHandler(op.
		NewStatus(appName, appDesc).
		AddChecker("dummy health check", func(cr *op.CheckResponse) {
			cr.Healthy("I'm healthy!")
		}))
}
```

では、`/__/health`エンドポイントにアクセスしてみます。

```sh
$ curl localhost:8080/__/health
{
    "name": "qiita-advent-calendar-2019",
    "description": "The sample micro service app",
    "health": "healthy",
    "checks": [
      {
        "name": "dummy health check",
        "health": "healthy",
        "output": "I'm healthy!"
      }
    ]
 }
```

設定したとおりに報告されているのが確認できました。

## その他の情報の公開

go-operational ライブラリには他にも便利なAPIが用意されているのでいくつか紹介します。

- Readinessチェック

`Ready*`系の関数ではレディネスチェック用のエンドポイントを実装することができます。自分で定義、ヘルスチェックの結果を利用、常にReadyを返す、などから状況に応じて選ぶことができます。

```go
func newOpHandler() http.Handler {
	return op.NewHandler(op.
		NewStatus(appName, appDesc).
		AddChecker("dummy health check", func(cr *op.CheckResponse) {
			cr.Healthy("I'm healthy!")
		}).
		ReadyUseHealthCheck())
}
```

```sh
$ curl localhost:8080/__/ready
ready
```

- より詳細なアプリ情報

`AddOwner()`によりチーム情報とチームが属するスラックのチャンネル、`SetRevision()`によりアプリのバージョン情報を公開します。

```go
func newOpHandler() http.Handler {
	return op.NewHandler(op.
		NewStatus(appName, appDesc).
		AddOwner("qiita-advent-calendar-team", "#qiita-advent-calendar-2019").
		SetRevision(gitHash).
		AddChecker("dummy health check", func(cr *op.CheckResponse) {
			cr.Healthy("I'm healthy!")
		}).
		ReadyUseHealthCheck())
}
```

```sh
$ curl localhost:8080/__/about
{
    "name": "qiita-advent-calendar-2019",
    "description": "The sample micro service app",
    "owners": [
      {
        "name": "qiita-advent-calendar-team",
        "slack": "#qiita-advent-calendar-2019"
      }
    ],
    "build-info": {
      "revision": "67de87c0517ed2d477d2d27a6e2f250b2230ca93"
    }
}
```

- ヘルスチェック結果をメトリクスとして公開

`WithInstrumentedChecks()`メソッドによりヘルスチェックの結果をメトリクスとして公開します。サービスがUnhealthyなときに、実際にどのヘルスチェックがいつから不調なのかを調べる際に便利です。

```go
func newOpHandler() http.Handler {
	return op.NewHandler(op.
		NewStatus(appName, appDesc).
		AddOwner("qiita-advent-calendar-team", "#qiita-advent-calendar-2019").
		SetRevision(gitHash).
		AddChecker("dummy health check", func(cr *op.CheckResponse) {
			cr.Healthy("I'm healthy!")
		}).
		ReadyUseHealthCheck().
		WithInstrumentedChecks())
}
```

```sh
$ curl localhost:8080/__/metrics | grep health
# HELP healthcheck_status Meters the healthcheck status based for each check and for each result
# TYPE healthcheck_status gauge
healthcheck_status{healthcheck_name="dummy_health_check",healthcheck_result="degraded"} 0
healthcheck_status{healthcheck_name="dummy_health_check",healthcheck_result="healthy"} 1
healthcheck_status{healthcheck_name="dummy_health_check",healthcheck_result="unhealthy"} 0
```

---

さて、こんな感じでヘルスチェックを実装してみました。
公開することももちろん大事ですが、Kubernetesのヘルスチェック機構やPrometheusと組み合わせて真価を発揮する部分です。必要に応じて実装していくとよいでしょう。その際はぜひ go-operational ライブラリを参考にしてみてください。

明日からはgRPCの実装をしていきたいと思います。だんだん楽しくなってきましたね😉笑