隨着咱們的微服務愈來愈多,愈來愈複雜, 須要一種可讓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的機制, 咱們聚焦兩個關鍵領域 - 服務發現和負載均衡。