Go微服务 - 第六部分 - 健康检查

第六部分: Go微服务 - 健康检查

随着咱们的微服务愈来愈多,愈来愈复杂, 须要一种可让Docker Swarm知道服务是否健康的机制就变得十分重要了。所以,本文重点看看如何为微服务加入健康检查。linux

假如accountservice微服务不具有下面的能力,就毫无用处了:git

  • 提供HTTP服务。
  • 链接到它本身的数据库。

在微服务中处理这些状况的惯用方式是提供健康检查路由(Azure Docs的好文章)。
在咱们例子中,咱们使用HTTP的,所以简单创建一个/health路由。若是健康就返回200, 也能够返回机器可能性相关的消息来解释什么OK。若是有问题,返回非200响应码,也能够带上一些不健康的缘由。 github

注意,有些人认为检查失败应该也使用200,带上说明不健康的缘由信息,我也赞成那样作。不过为了简化,本文中就直接使用非200来检查不健康。docker

所以咱们须要在accountservice中添加一个/health路由。数据库

源代码

向往常同样,咱们同样能够从git中签出对应的分支,以获取部分更改的代码。npm

https://github.com/callistaen...json

给boltdb添加检查

咱们的服务若是不能正常访问底层数据库的话,就没有什么用。所以,咱们须要给IBoltClient添加一个接口Check().小程序

type IBoltClient interface {
    OpenBoltDb()
    QueryAccount(accountId string) (model.Account, error)
    Seed()
    Check() bool              // NEW!
}

Check方法看起来可能比较单纯,可是它在本文中仍是很起做用的。它根据BoltDB是否可用而相应的返回true或false。bash

咱们在boltclient.go文件中Check的实现没有现实意义,可是它已经足够解释问题了。app

// Naive healthcheck, just makes sure the DB connection has been initialized.
func (bc *BoltClient) Check() bool {
    return bc.boltDB != nil
}

mockclient.go里边的模拟实现也遵循咱们的延伸/测试(stretchr/testify)标准模式:

func (m *MockBoltClient) Check() bool {
    args := m.Mock.Called()
    return args.Get(0).(bool)
}

添加/health路由

这里很是直接。 咱们直接在service/routes.go里边添加下面的路由:

var routes = Routes{
    Route{
        "GetAccount",             // Name
        "GET",                    // HTTP method
        "/accounts/{accountId}",  // Route pattern
        GetAccount,
    },
    Route{
        "HealthCheck",
        "GET",
        "/health",
        HealthCheck,
    },
}

/health请求让HealthCheck来处理。下面是HealthCheck的内容:

func HealthCheck(w http.ResponseWriter, r *http.Request) {
    // Since we're here, we already know that HTTP service is up. Let's just check the state of the boltdb connection
    dbUp := DBClient.Check()
    if dbUp {
        data, _ := json.Marshal(healthCheckResponse{Status: "UP"})
        writeJsonResponse(w, http.StatusOK, data)
    } else {
        data, _ := json.Marshal(healthCheckResponse{Status: "Database unaccessible"})
        writeJsonResponse(w, http.StatusServiceUnavailable, data)
    }
}

func writeJsonResponse(w http.ResponseWriter, status int, data []byte) {
    w.Header().Set("Content-Type", "application/json")
    w.Header().Set("Content-Length", strconv.Itoa(len(data)))
    w.WriteHeader(status)
    w.Write(data)
}

type healthCheckResponse struct {
    Status string `json:"status"`
}

HealthCheck函数代理检查DB状态, 即DBClient中添加的Check()方法。若是OK, 咱们建立一个healthCheckResponse结构体。 注意首字母小写,只在模块内可见的做用域。咱们同时实现了一个写HTTP响应的方法,这样代码看起来简洁一些。

运行

运行修改后的代码:

> go run *.go
Starting accountservice
Seeded 100 fake accounts...
2017/03/03 21:00:31 Starting HTTP service at 6767

而后打开新的窗口,使用curl访问/health接口:

> curl localhost:6767/health
{"status":"UP"}

It works!

Docker的健康检查

clipboard.png

接下来,咱们将使用Docker的HEALTHCHECK机制让Docker Swarm检查咱们的服务活跃性。 这是经过在Dockerfile文件中添加一行来实现的:

FROM iron/base
EXPOSE 6767

ADD accountservice-linux-amd64 /
ADD healthchecker-linux-amd64 /

HEALTHCHECK --interval=1s --timeout=3s CMD ["./healthchecker-linux-amd64", "-port=6767"] || exit 1
ENTRYPOINT ["./accountservice-linux-amd64"]

healthchecker-linux-amd64是什么东西?咱们须要稍微帮助一下Docker, 由于Docker本身没有为咱们提供HTTP客户端或相似的东西来执行健康检查。 而是,Dockerfile中的HEALTHCHECK指令指定了一种命令(CMD), 它应该执行调用/health路由。 依赖运行程序的退出码,Docker会肯定服务是否健康。 若是太多后续健康检查失败,Docker Swarm将杀死这个容器,并启动一个新的容器。

最多见的实现真实健康检查的方式看起来相似curl。 然而,这须要咱们的基础docker映像来实际安装有curl(或者任何底层依赖), 而且在这个时候咱们不会真正但愿处理它。取而代之的是让Go语言来酿造咱们本身的健康检查小程序。

建立健康检查程序

是时候在src/github.com/callistaenterprise/goblog下面建立一个新的子项目了。

mkdir healthchecker

而后在这个目录下面建立一个main.go文件, 其内容以下:

package main

import (
    "flag"
    "net/http"
    "os"
)

func main() {
    port := flag.String("port", "80", "port on localhost to check")
    flag.Parse()

    resp, err := http.Get("http://127.0.0.1:" + *port + "/health") // 注意使用 * 间接引用

    // If there is an error or non-200 status, exit with 1 signaling unsuccessful check.
    if err != nil || resp.StatusCode != 200 {
        os.Exit(1)
    }
    os.Exit(0)
}

代码量不是很大,它作了些什么?

  • 使用flag包读取-port命令行参数。若是没有指定,回退使用默认值80。
  • 执行HTTP GET请求http://127.0.0.1:[port]/health。
  • 若是HTTP请求发生错误,状态码为非200,以推出码1退出。 不然以退出码0退出。0 == success, 1 == fail.

让咱们试试看,若是咱们已经把accountservice停掉了,那么从新运行它,而后运行healthchecker。

go build
./accountservice

而后运行这个程序:

> cd $GOPATH/src/github.com/callistaenterprise/goblog/healtchecker
> go run *.go
exit status 1

上面咱们忘记指定端口号了,所以它使用的是默认80端口。让咱们再来一次:

> go run *.go -port=6767
>

这里没有输出,表示咱们请求是成功的。 很好,那么咱们构建一个linux/amd64的二进制,而后将它添加到accountservice中,经过添加healthchecker二进制到Dockerfile文件中。 咱们继续使用copyall.sh脚原本自动完成从新构建和部署。

#!/bin/bash
export GOOS=linux
export CGO_ENABLED=0

cd accountservice;go get;go build -o accountservice-linux-amd64;echo built `pwd`;cd ..

// NEW, builds the healthchecker binary
cd healthchecker;go get;go build -o healthchecker-linux-amd64;echo built `pwd`;cd ..

export GOOS=darwin
   
// NEW, copies the healthchecker binary into the accountservice/ folder
cp healthchecker/healthchecker-linux-amd64 accountservice/

docker build -t someprefix/accountservice accountservice/

最后咱们还须要作一件事,就是更新accountservice的Dockerfile。它完整内容以下:

FROM iron/base
EXPOSE 6767

ADD accountservice-linux-amd64 /

# NEW!! 
ADD healthchecker-linux-amd64 /
HEALTHCHECK --interval=3s --timeout=3s CMD ["./healthchecker-linux-amd64", "-port=6767"] || exit 1

ENTRYPOINT ["./accountservice-linux-amd64"]

咱们附加了以下内容:

  • 添加ADD指令,确保healthchecker二进制包含到镜像中。
  • HEALTHCHECK语句指定咱们的二进制文件以及参数,告诉Docker每隔3秒去执行一次健康检查, 并接受3秒的超时。

部署健康检查服务

如今咱们准备部署咱们更新后的带healthchecker的accountservice服务了。若是要更加自动,将这两行添加到copyall.sh文件中,每次运行的时候,它会从Docker Swarm中自动删除accountservice而且从新建立它。

docker service rm accountservice
docker service create --name=accountservice --replicas=1 --network=my_network -p=6767:6767 someprefix/accountservice

那么如今运行./copyall.sh, 等几秒钟,全部构建更新好。而后咱们再使用docker ps检查容器状态, 就能够列举出全部运行的容器。

> docker ps
CONTAINER ID        IMAGE                             COMMAND                 CREATED        STATUS
1d9ec8122961        someprefix/accountservice:latest  "./accountservice-lin"  8 seconds ago  Up 6 seconds (healthy)
107dc2f5e3fc        manomarks/visualizer              "npm start"             7 days ago     Up 7 days

咱们查找STATUS头下面的"(healthy)"文本。服务没有配置healthcheck的彻底没有health指示。

故意形成失败

要让事情稍微更加有意思, 咱们添加一个可测试API, 能够容许咱们让端点扮演不健康的目的。在routes.go文件中,声明另一个路由。

var routes = Routes{
    Route{
        "GetAccount", // Name
        "GET",        // HTTP method
        "/accounts/{accountId}", // Route pattern
        /*func(w http.ResponseWriter, r *http.Request) {
            w.Header().Set("Content-Type", "application/json; charset=UTF-8")
            w.Write([]byte("{\"result\":\"OK\"}"))
        },*/
        GetAccount,
    },
    Route{
        "HealthCheck",
        "GET",
        "/health",
        HealthCheck,
    },
    Route{
        "Testability",
        "GET",
        "/testability/healthy/{state}",
        SetHealthyState,
    },
}

这个路由(在生产服务中不要有这样的路由!)提供了一种REST-ish路由的故障健康检查目的。SetHealthyState函数在goblog/accountservice/handlers.go文件中,代码以下:

var isHealthy = true // NEW

func SetHealthyState(w http.ResponseWriter, r *http.Request) {
    // Read the 'state' path parameter from the mux map and convert to a bool
    var state, err = strconv.ParseBool(mux.Vars(r)["state"])
    
    // If we couldn't parse the state param, return a HTTP 400
    if err != nil {
        fmt.Println("Invalid request to SetHealthyState, allowed values are true or false")
        w.WriteHeader(http.StatusBadRequest)
        return
    }
    
    // Otherwise, mutate the package scoped "isHealthy" variable.
    isHealthy = state
    w.WriteHeader(http.StatusOK)
}

最后,将isHealthy布尔做为HealthCheck函数的检查条件:

func HealthCheck(w http.ResponseWriter, r *http.Request) {
        // Since we're here, we already know that HTTP service is up. Let's just check the state of the boltdb connection
        dbUp := DBClient.Check()
        
        if dbUp && isHealthy {              // NEW condition here!
                data, _ := json.Marshal(
                ...
        ...        
}

重启accountservice.

> cd $GOPATH/src/github.com/callistaenterprise/goblog/accountservice
> go run *.go
Starting accountservice
Seeded 100 fake accounts...
2017/03/03 21:19:24 Starting HTTP service at 6767

而后在新窗口产生一个新的healthcheck调用。

> cd $GOPATH/src/github.com/callistaenterprise/goblog/healthchecker
> go run *.go -port=6767

第一次尝试成功,而后咱们经过使用下面的curl请求testability来改变accountservice的状态。

> curl localhost:6767/testability/healthy/false
> go run *.go -port=6767
exit status 1

起做用了!而后咱们在Docker Swarm中运行它。使用copyall.sh重建并从新部署accountservice。

> cd $GOPATH/src/github.com/callistaenterprise/goblog
> ./copyall.sh

向往常同样,等待Docker Swarm从新部署"accountservice", 使用最新构建的"accountservice"容器映像。而后,运行docker ps来看是否启动并运行了带有健康的服务。

> docker ps
CONTAINER ID    IMAGE                            COMMAND                CREATED         STATUS 
8640f41f9939    someprefix/accountservice:latest "./accountservice-lin" 19 seconds ago  Up 18 seconds (healthy)

注意CONTAINER ID和CREATED字段。能够在你的Docker Swarm上调用testability API。(个人IP是: 192.168.99.100)。

> curl $ManagerIP:6767/testability/healthy/false
>

而后,咱们在几秒时间内再次运行docker ps命令.

> docker ps
CONTAINER ID        IMAGE                            COMMAND                CREATED         STATUS                                                             NAMES
0a6dc695fc2d        someprefix/accountservice:latest "./accountservice-lin" 3 seconds ago  Up 2 seconds (healthy)

你能够看到,这里有了全新的CONTAINER ID和CREATED和STATUS. 真正发生的是Docker Swarm监测到三个(重试的默认值)连续失败的健康检查, 并当即肯定服务变得不健康, 须要用新的实例来代替, 这彻底是在没有任何管理人员干预的状况下发生的。

总结

在这一部分中,咱们使用一个简单的/health路由和healthchecker程序结合Docker的HEALTHCHECK机制,展现了这个机制如何让Docker Swarm自动为咱们处理不健康的服务。

下一章,咱们深刻到Docker Swarm的机制, 咱们聚焦两个关键领域 - 服务发现和负载均衡。

参考连接

相关文章
相关标签/搜索