前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >了解微服务,第6部分:健康检查

了解微服务,第6部分:健康检查

作者头像
用户2176511
发布2018-06-25 11:35:27
2.7K0
发布2018-06-25 11:35:27

随着我们的微服务和它们运营的环境变得越来越复杂,让我们的服务为Docker Swarm提供一种安全检查机制也变得日益重要。因此,我们将在博客系列的第六部分中介绍如何添加健康检查。

例如,如果我们的“accountservice”微服务不能完成以下功能,那么它就不是很有用:

  • 为HTTP服务
  • 连接到数据库

在我们的案例中,在微服务中处理此问题的惯用方式是提供一个健康检查终结点(来自Azure Docs的优秀文章),——因为我们基于HTTP的服务——如果能够正常访问,则应该回复HTTP状态码200,表示健康,可能与一些机器可解析的消息一起解释什么是没问题的。如果有问题,应该返回一个非200的HTTP状态码 ,可能说明什么是有问题的。请注意,有些人认为失败的检查应返回200 OK,并在响应的负载中指出错误。我也同意这一点,但为了简单起见,在这片博客文章中我们将坚持使用非200。所以,让我们将这样的端点添加到我们的“account”微服务中。

源代码

与往常一样,请随时从git中检查适当的分支,以便事先获得此部分的所有更改:

代码语言:txt
复制
git checkout P6

添加一个检查访问BoltDB

如果无法访问其底层数据库,我们的服务将无法使用。因此,我们将向IBoltClient接口添加一个新函数Check()

代码语言:txt
复制
type IBoltClient interface {
        OpenBoltDb()
        QueryAccount(accountId string) (model.Account, error)
        Seed()
        Check() bool              // NEW!
}

Check方法可能略显愚笨,但是在本博客中,它的目的就在于此。它通过返回“真”或“假”来表明BoltDB

是否可以被访问。

我们在boltclient.go中的_Check()也不太现实,但它应该足以解释这个概念:

代码语言:txt
复制
// 略显愚笨的健康检查,仅仅为了确保数据库连接被初始化。
func (bc *BoltClient) Check() bool {
        return bc.boltDB != nil
}

mockclient.go中的模仿实现遵循我们的标准拉伸/证明模式:

代码语言:txt
复制
func (m *MockBoltClient) Check() bool {
        args := m.Mock.Called()
        return args.Get(0).(bool)
}

添加健康端点

这非常简单。我们将在/ accounts / {accountId}的现有路径下的/accountservice/service/routes.go文件中添加一条新的“健康”路径:

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

我们声明路由应该由名为HealthCheck的函数处理,我们现在将这个函数添加到 /accountservice/service/handlers.go文件中:

代码语言:txt
复制
func HealthCheck(w http.ResponseWriter, r *http.Request) {
        //由于在这里,我们已经知道HTTP启动了,因此只检查boltdb连接的状态
        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"`
}

我们将 Check() 函数添加到DBClient, HealthCheck函数委托Check()函数检查DB状态。如果没有问题,就创建一个healthCheckResponse 结构体实例。注意到第一个字母是小写了吗?那就是我们如何限定这个结构体只能限定在服务包内被访问。我们还提取了“写入http响应”代码,并将它添加到实用程序方法中以使我们保持DRY。

运行

/ goblog / accountservice文件夹生成并运行:

代码语言:txt
复制
> go run *.go
Starting accountservice
Seeded 100 fake accounts...
2017/03/03 21:00:31 Starting HTTP service at 6767

打开一个新的控制台窗口并且测试健康端点:

代码语言:txt
复制
> curl localhost:6767/health
{"status":"UP"}

有用!

Docker 健康检查

接下来,我们将使用Docker HEALTHCHECK机制使Docker Swarm检查我们的服务是否具有活力。这是通过在Dockerfile中添加一行来完成:

代码语言:txt
复制
HEALTHCHECK --interval=5s --timeout=5s CMD ["./healthchecker-linux-amd64", "-port=6767"] || exit 1

这个“healthchecker-linux-amd64”是什么?我们需要帮助Docker进行健康检查,因为Docker本身不会为我们提供HTTP客户端或类似去实际地执行健康检查。相反,Dockerfile中的HEALTHCHECK指令指定应执行对health端点的调用的命令(CMD)。根据运行的程序的退出代码,Docker将确定服务是否健康。如果过多的后续运行状况检查失败,Docker Swarm将终止该容器并启动一个新实例。

Curl似乎是进行实际健康检查的最常见的方法。但是,这需要我们的基础Docker镜像预先安装curl(以及任何基础依赖项),并且此时我们并不真正想要处理这个问题。相反,我们将使用Go来构造我们自己的健康检查程序。

创建Healthchecker程序

现在可以在/src/github.com/callistaenterprise/goblog路径下创建一个新的子项目了:

代码语言:txt
复制
mkdir healthchecker

然后,在/ healthchecker文件夹中创建main.go文件:

代码语言:txt
复制
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")    // 注意*表示指针指向用
// 如果有错误或者是非200状态码,退出1表示不成功的检查
if err != nil || resp.StatusCode != 200 {
os.Exit(1)
}
os.Exit(0)
}

代码量不是很大。它能做什么:

  • 使用golang中的标志支持来读取-port = NNNN命令行参数。如果未指定,则默认回退到端口80。
  • 执行HTTP GET到127.0.0.1:port/health
  • 如果发生错误或返回的HTTP状态不是200 OK,以推出码0推出。等于零表示成功,大于0表示失败。

不妨试试。如果你停止了“accountservice”,则可以通过运行* .go 或通过在“/ goblog / accountservice”目录中新建控制台选项中创建一个新的服务并且重新启动:

代码语言:txt
复制
go build
./accountservice

提醒:如果得到奇怪的编译错误,请检查以确保GOPATH仍设置为Go工作区的根文件夹,例如/src/github.com/callistaenterprise/goblog的父文件夹

然后切换回正常的控制台窗口(在你设置GOPATH的位置)并运行健康检查程序:

代码语言:txt
复制
> cd $GOPATH/src/github.com/callistaenterprise/goblog/healtchecker
> go run *.go
exit status 1

糟糕!我们忘记指定端口号,因此它默认为80端口。让我们再试一次:

代码语言:txt
复制
> go run *.go -port=6767
>

不出意外,我们成功了!好了,现在,我们将构建一个linux / amd64二进制文件,并将其添加到“accountservice”中,方法同在Dockerfile中包含healthchecker二进制文件。我们将继续使用copyall.sh脚本来自动化一些事情:

代码语言:txt
复制
#!/bin/bash
export GOOS=linux
export CGO_ENABLED=0
cd accountservice;go get;go build -o accountservice-linux-amd64;echo built `pwd`;cd ..
// 新建healthchecker二进制文件
cd healthchecker;go get;go build -o healthchecker-linux-amd64;echo built `pwd`;cd ..
export GOOS=darwin
// 新建, 将healthchecker二进制文件复制到accountservice/ folder文件夹下
cp healthchecker/healthchecker-linux-amd64 accountservice/
docker build -t someprefix/accountservice accountservice/

最后一件事,我们需要更新“accountservice” Dockerfile,其全部内容如下所示:

代码语言:txt
复制
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秒的超时。

使用健康检查进行部署

现在我们准备通过健康检查部署我们更新的“accountservice”。为了进一步实现自动化,请将这两行添加到copyall.sh脚本的底部,以便在每次运行Docker Swarm时删除并重新创建帐户服务:

代码语言:txt
复制
docker service rm accountservice
docker service create --name=accountservice --replicas=1 --network=my_network -p=6767:6767 someprefix/accountservice

现在,运行./copyall.sh并等待几秒钟,稍后所有内容都会生成并更新。让我们使用docker ps检查我们的容器的状态,其中列出了所有正在运行的容器:

代码语言:txt
复制
> 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)”的内容。没有配置健康检查的服务根本没有健康指示。

故意制造失败

为了让事情变得更有趣,我们添加一个可测试性API,使端点故意表现得“不健康”。在routes.go文件中,声明一个新的端点:

代码语言:txt
复制
Route{
        "Testability",
        "GET",
        "/testability/healthy/{state}",
        SetHealthyState,
},

这条路径(在生产服务中,你本不应该有的)为我们提供了故意运行健康检查失败的REST端点。该SetHealthyState函数进入goblog /accountsercive/ handlers.go,如下所示:

代码语言:txt
复制
var isHealthy = true // 新建
func SetHealthyState(w http.ResponseWriter, r *http.Request) {
        // 从 mux map读取“state”路径参数并将其转换为bool类型
        var state, err = strconv.ParseBool(mux.Vars(r)["state"])
        // 如果不能解析状态参数,就返回HTTP 400
        if err != nil {
                fmt.Println("Invalid request to SetHealthyState, allowed values are true or false")
                w.WriteHeader(http.StatusBadRequest)
                return
        }
        // 否则,修改‘isHealthy’变量限定的包
        isHealthy = state
        w.WriteHeader(http.StatusOK)
}

最后,将isHealthy 布尔值作为条件添加到HealthCheck函数:

代码语言:txt
复制
func HealthCheck(w http.ResponseWriter, r *http.Request) {
        // 由于在这里,我们已经知道HTTP服务开启,因此只检查连接状态
        dbUp := DBClient.Check()
        if dbUp && isHealthy {              // 新建条件
                data, _ := json.Marshal(
                ...
        ...        
}

重新启动“accontservice”服务

代码语言:txt
复制
> 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

从另一个窗口进行新的健康检查调用:

代码语言:txt
复制
 > cd $GOPATH/src/github.com/callistaenterprise/goblog/healthchecker
 > go run *.go -port=6767
 >

初次尝试:成功。现在使用curl请求将accountservice的状态更改为可测试性端点:

代码语言:txt
复制
> curl localhost:6767/testability/healthy/false
> go run *.go -port=6767
exit status 1

有用!让我们试着在Docker Swarm中运行。使用copyall.sh重建并重新部署“accountservice” :

代码语言:txt
复制
> cd $GOPATH/src/github.com/callistaenterprise/goblog
> ./copyall.sh

与往常一样,Docker Swarm会使用最新版本的“accountservice”容器镜像重新部署“accountservice”服务。然后,运行docker ps文件以查看我们是否启动并运行了健康的服务:

代码语言:txt
复制
> 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群组IP的可测试性API(我的是192.168.99.100):

代码语言:txt
复制
> curl $ManagerIP:6767/testability/healthy/false
>

现在,在几秒钟内再次运行docker ps

代码语言:txt
复制
> 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检测到三个(默认值为重试)连续失败的健康检查,并立即决定该服务变得不健康,需要用新的实例替换,这是在没有管理员干涉的情况下完成的。

概要

在这一部分中,我们使用一个简单健康端点和一小段健康检查程序添加了健康检查功能,结合Docker HEALTHCHECK机制,表明此机制如何允许Docker Swarm自动为我们处理不健康的服务。

在下一部分中,我们将深入探讨Docker Swarm机制,因为我们将重点关注微服务体系结构的两个关键领域 - 服务发现和负载平衡。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 源代码
  • 添加一个检查访问BoltDB
  • 添加健康端点
  • 运行
  • Docker 健康检查
  • 创建Healthchecker程序
  • 使用健康检查进行部署
  • 故意制造失败
  • 概要
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档