健康检查终端点的实现

好了好了,今天我们来实现一个像云原生应用一样的健康检查端点吧。

实用仓库/ Go 运营

我们公司虽然没有公开,但我们已经整理了每个应用程序应该实现的各种终端点作为规范。这份规范不仅包括健康检查,还包含了运营所需的一般信息,如指标等。

多亏了这个共同规范,我们能够在团队之间统一运营有用信息的公开方式。

并且还为常用的编程语言提供了相应的库,例如针对Go语言的库 utilitywarehouse/go-operational 已经公开发布。

我們將使用這個程式庫來實現端點。

新的操作处理器函数

我们将实现一个返回http.Handler的方法newOpHandler。

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

创建处理器

现在有一个名为NewHandler()的API可以用来创建一个新的控制器,你可以通过参数向其传递想要公开的信息。
想要公开的信息被抽象为*Status的形式,这个指针具有返回自身的API功能,这样可以实现链式调用方法。
首先,我们可以调用NewStatus(),并设置应用程序名称和描述。

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

我們將在之前設置虛擬處理程序的地方設置新的處理程序。

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

我们来看一下关于(/__/about)的端点。

$ ./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终点。

$ 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格式进行发布,因此在使用Prometheus时,请配置它来抓取该端点。

添加健康检查

在添加健康检查时,可以使用AddChecker() API。

将第一个参数作为健康检查项目的名称,并将第二个参数作为用于报告健康结果的函数传递。
在传递给这个函数的参数中,*CheckResponse可以公开Healthy、Degraded和Unhealthy这些方法,根据情况进行调用。这次我们将尝试以健康(Healthy)作为虚拟报告。

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终点。

$ 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等方式。

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())
}
$ curl localhost:8080/__/ready
ready
    より詳細なアプリ情報

通过AddOwner()函数添加团队信息和团队所属的Slack频道,并通过SetRevision()函数发布应用的版本信息。

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())
}
$ 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() 方法将健康检查的结果作为指标公开。当服务处于不健康状态时,这对于实际查明从何时开始哪个健康检查出现问题非常方便。

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())
}
$ 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功能。渐渐地感觉越来越有趣了哦?笑