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

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

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

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

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

源代码

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

git checkout P6

添加一个检查访问BoltDB

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

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

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

是否可以被访问。

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

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

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

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文件中:

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文件夹生成并运行:

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

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

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

有用!

Docker 健康检查

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

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路径下创建一个新的子项目了:

mkdir healthchecker

然后,在/ 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")    // 注意*表示指针指向用
// 如果有错误或者是非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”目录中新建控制台选项中创建一个新的服务并且重新启动:

go build
./accountservice

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

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

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

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

> go run *.go -port=6767
>

不出意外,我们成功了!好了,现在,我们将构建一个linux / amd64二进制文件,并将其添加到“accountservice”中,方法同在Dockerfile中包含healthchecker二进制文件。我们将继续使用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 ..
// 新建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,其全部内容如下所示:

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时删除并重新创建帐户服务:

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)”的内容。没有配置健康检查的服务根本没有健康指示。

故意制造失败

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

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

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

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函数:

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

重新启动“accontservice”服务

> 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

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

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

初次尝试:成功。现在使用curl请求将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群组IP的可测试性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上新的时间戳。实际发生的事情是,Docker Swarm检测到三个(默认值为重试)连续失败的健康检查,并立即决定该服务变得不健康,需要用新的实例替换,这是在没有管理员干涉的情况下完成的。

概要

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

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

本文的版权归 用户2176511 所有,如需转载请联系作者。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏BinarySec

关于主题暂时更换

0x00 之前使用的主题-Material Material,主题的Demo在https://blog.viosey.com,主题的项目地址https://gi...

3285
来自专栏刘望舒

有关Android插件化思考

最近几年移动开发业界兴起了「 插件化技术 」的旋风,各个大厂都推出了自己的插件化框架,各种开源框架都评价自身功能优越性,令人目不暇接。随着公司业务快速发展,项目...

601
来自专栏CodeSheep的技术分享

Docker容器可视化监控中心搭建

22610
来自专栏Jerry的SAP技术分享

使用Java+SAP云平台+SAP Cloud Connector调用ABAP On-Premise系统里的函数

最近Jerry接到一个原型开发的任务,需要在微信里调用ABAP On Premise系统(SAP CRM On-Premise)里的某些函数。具体场景和我之前的...

982
来自专栏Java架构师学习

把项目迁移到Kubernetes上的5个小技巧

我们将在本文中提供5个诀窍帮你将项目迁移到Kubernetes上,这些诀窍来源于过去12个月中OpenFaas社区的经验。下文的内容与Kubernetes 1....

3538
来自专栏liulun

博客园文章编辑器5.0版本发布(markdown版)

(后来我自己发现了一些问题,于是偷偷发了博客园文章编辑器的4.0.1版本,也没通知大家,不过好在有自动升级功能)

1632
来自专栏Golang语言社区

用 Go 写一个轻量级的 ssh 批量操作工具

大家都知道 Ansible 是功能超级强大的自动化运维工具,十分的高大上。太高大上了以至于在低端运维有点水土不服,在于三点:

1872
来自专栏熊二哥

快速入门系列--Log4net日志组件

Log4net是阿帕奇基金会的非常流行的开源日志组件,是log4j的.NET移植版本,至今已经有11年的历史,使用方便并且非常稳定,此外很重要的一点是其和很多开...

18710
来自专栏phodal

使用 OpenWhisk 自建 Serverless 服务

在尝试了使用 AWS 开发 Serverless 应用之后,我便想尝试使用 OpenWhisk 框架来搭建自己的 Serverless 服务。 Apache O...

2995
来自专栏FreeBuf

无线宝宝wifi热点共享软件刷流量行为分析

近日,腾讯反病毒实验室截获到了大量通过传入特殊参数实现刷流量行为的恶意程序,经过回溯发现,这些恶意程序均是由某wifi热点共享软件下载并解密运行进行传播,感染量...

1948

扫码关注云+社区