基於go的微服務搭建(六) - health check

第六節:health check
原文地址
轉載請註明原文及翻譯地址linux

當咱們的微服務愈來愈複雜,讓docker swarm知道咱們的服務運行良好與否很重要.下面咱們來看一下如何查看服務運行情況.
例如,咱們的accountservice服務將沒用若是不能 服務http或者連接數據庫.
最好的辦法就是提供一個healthcheck接入點.咱們基於http,因此映射到/health,若是運行良好,返回http 200同事一些解釋什麼是良好的信息.若是有問題,非http 200返回,並解釋哪裏很差.有人認爲都應該返回200,以後返回錯誤信息.我贊成,可是這個簡單例子中,我會用非200返回.git

代碼


同樣,你能夠直接branch到這部分github

git checkout P6

加入BoltDB的檢查

咱們的服務若是不能鏈接database將沒有用,所以咱們加入函數 Check()docker

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的形式npm

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

加入/health路徑


很直接,咱們在routes.go中加入json

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中才能用這個結構.咱們也提取出返回結果的代碼進一個函數來讓咱們不重複代碼.segmentfault

運行


在blog/accountservice文件夾中,運行:api

> 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


clipboard.png

接下來,咱們用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,咱們會關注微服務兩個架構:服務發現和負載均衡.

相關文章
相關標籤/搜索