随着咱们的微服务愈来愈多,愈来愈复杂, 须要一种可让Docker Swarm知道服务是否健康的机制就变得十分重要了。所以,本文重点看看如何为微服务加入健康检查。linux
假如accountservice微服务不具有下面的能力,就毫无用处了:git
在微服务中处理这些状况的惯用方式是提供健康检查路由(Azure Docs的好文章)。
在咱们例子中,咱们使用HTTP的,所以简单创建一个/health路由。若是健康就返回200, 也能够返回机器可能性相关的消息来解释什么OK。若是有问题,返回非200响应码,也能够带上一些不健康的缘由。 github
注意,有些人认为检查失败应该也使用200,带上说明不健康的缘由信息,我也赞成那样作。不过为了简化,本文中就直接使用非200来检查不健康。docker
所以咱们须要在accountservice中添加一个/health路由。数据库
向往常同样,咱们同样能够从git中签出对应的分支,以获取部分更改的代码。npm
https://github.com/callistaen...json
咱们的服务若是不能正常访问底层数据库的话,就没有什么用。所以,咱们须要给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) }
这里很是直接。 咱们直接在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的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) }
代码量不是很大,它作了些什么?
让咱们试试看,若是咱们已经把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"]
咱们附加了以下内容:
如今咱们准备部署咱们更新后的带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的机制, 咱们聚焦两个关键领域 - 服务发现和负载均衡。