基于go的微服务搭建(六) - health check

第六节:health check
原文地址
转载请注明原文及翻译地址

当我们的微服务越来越复杂,让docker swarm知道我们的服务运行良好与否很重要.下面我们来看一下如何查看服务运行状况.
例如,我们的accountservice服务将没用如果不能 服务http或者链接数据库.
最好的办法就是提供一个healthcheck接入点.我们基于http,所以映射到/health,如果运行良好,返回http 200同事一些解释什么是良好的信息.如果有问题,非http 200返回,并解释哪里不好.有人认为都应该返回200,之后返回错误信息.我同意,但是这个简单例子中,我会用非200返回.

代码


一样,你可以直接branch到这部分

git checkout P6

加入BoltDB的检查

我们的服务如果不能连接database将没有用,因此我们加入函数 Check()

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

这个函数可能很简单,但是足够了,他将根据BoltDb能否连接而返回true/false.

func (bc *BoltClient) Check() bool {
    return bc.boltDB != nil
}

mocked代码在mackclient.go遵从stretchr/testify的形式

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

加入/health路径


很直接,我们在routes.go中加入

Route{
    "HealthCheck",
    "GET",
    "/health",
    HealthCheck
},

我们用函数HealthCheck来处理请求,我们把这个函数加到handler.go中:

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函数用Check()函数来检查数据库情况.如果正常,我们返回healthCheckResponse结构的实例.注意这个小写的首字母,这样只有在这个package中才能用这个结构.我们也提取出返回结果的代码进一个函数来让我们不重复代码.

运行


在blog/accountservice文件夹中,运行:

> 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"}

docker healthcheck


基于go的微服务搭建(六) - health check_第1张图片

接下来,我们用docker的健康检查机制来检查我们的服务.加入下面命令在Dockerfile:

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

healthchecker-linux-amd64是什么?docker自己不知道怎样做这个健康检查,我们需要帮一下,我们在CMD命令输入来指引到/health路径.根据exit code,docker会判断服务良好与否.如果太多的检查失败,swarm会关掉容器并开启新的实例
最常见的健康检查使用curl,然而这要求我们的docker镜像安装curl.这里我们会用go来执行这个小程序.

创建helathchecker程序


在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")    // Note pointer dereference using *
    
    // 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)
}

代码不多,主要做:

  • 用内置flags读取-port=NNNN命令参数,如果没有,用默认端口80
  • 开始http get请求127.0.0.1:[port]/health
  • 如果有错误或者返回状态非200,退出同一个非0值,0==成功,>0==失败

试一下,如果你停止了accountservice,用go run *.go启动,或者编译它go build ./accountservice
之后回到后台运行healthchecker

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

哎呀!我们忘记给端口号了.再试一次

> 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每3s执行一次,超时为3s

部署healthcheck


现在我们能部署带有healthchecking的accountservice了.自动化来做这些事,加入两行到copyall.sh中:

docker service rm accountservice
docker service create --name=accountservice --replica=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

我们看到(healthy)字段在status栏,没有健康检查的服务不会有这个提示.

看一下失败的情形


让我们加入可以测试的api来让路径表现的不健康.在routes.go中,加入新路径:

Route{
        "Testability",
        "GET",
        "/testability/healthy/{state}",
        SetHealthyState,
},    

这个路径(你不应该包括他在生产环境)提供我们一个让健康检查失败的方法.SetHealthyState函数在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)
}

重启accountservice

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(
                ...
        ...        
}

重新请求healthcheck

> 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

第一次尝试成功,现在改变accountservice用curl请求到测试路径

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

工作正常,让我们在docker swarm中运行,用copyall.sh重新编译和部署

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

等一会,之后运行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.请求测试api,我的是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时间戳.因为swarm每三秒会检查一次,之后发现服务不健康,所以用一个新的服务替换掉,并且不需要管理员的插手

总结


我们加入一个简单的/health路径和一些docker的健康检查机制.展示swarm是如何控制非健康服务的.
下一节,我们会深入swarm,我们会关注微服务两个架构:服务发现和负载均衡.

你可能感兴趣的:(基于go的微服务搭建(六) - health check)