Go microservices-part sixth-health check

Source: Internet
Author: User
Tags docker ps docker swarm
This is a creation in Article, where the information may have evolved or changed.

Part VI: Go microservices-Health Check

As our microservices become more and more complex, it is important to have a mechanism that lets Docker swarm know that a service is healthy. Therefore, this article focuses on how to add a health check for a microservices service.

If the Accountservice micro-service does not have the following capabilities, it is useless:

    • provides HTTP services.
    • Connect to its own database.

Health Terminal monitoring mode

Implement feature checking in your application, and external tools can be implemented with regular access through exposed endpoints. This helps verify that the application and the service are performing correctly.

The usual way to handle these situations in microservices is to provide health check routing (a good article for Azure Docs), in our case, since we are HTTP-based, we should map a/health, and respond 200 if it will, You may need to bring some machine possibilities to explain what is OK. If there is a problem, return a non-200 response, as well as possible unhealthy causes. Note that some people should return 200 for the check failure and the information to attach the error in the response payload. I agree with that, but in order to simplify, we use non-200来 checks directly in this article. So we need to add a/health route in the Accountservice.

Source

As always, we can check out the corresponding branch from git to get some of the changed code.

Https://github.com/callistaen ...

Add a check to Boltdb

Our service is useless if we cannot access the underlying database properly. Therefore, we need to add an interface check () to Iboltclient.

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

The check method may seem simple, but it still works in this article. It returns TRUE or false depending on whether the boltdb is available.

Our check implementation in the Boltclient.go file is not very realistic, but it is enough to explain the concept.

// Naive healthcheck, just makes sure the DB connection has been initialized.func (bc *BoltClient) Check() bool {    return bc.boltDB != nil}

The simulation implementation in Mockclient.go also follows our extension/test (stretchr/testify) Standard mode:

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

Add/health Route

It's very straightforward here. We add the following route directly inside the Service/routes.go:

var routes = Routes{    Route{        "GetAccount",             // Name        "GET",                    // HTTP method        "/accounts/{accountId}",  // Route pattern        GetAccount,    },    Route{        "HealthCheck",        "GET",        "/health",        HealthCheck,    },}

/health request for Healthcheck to handle. Here's what's 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"`}

The Healthcheck function agent checks the DB state, which is the check () method added in Dbclient. If OK, we create a healthcheckresponse struct. Note that the first letter is lowercase and is visible only within the scope of the module. We also implemented a method of writing HTTP responses so that the code would look concise.

Run

Run the modified code:

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

Then open a new window and use Curl to access the/health interface:

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

It works!

Docker Health Check-up

Next, we'll use Docker's healthcheck mechanism to let Docker swarm check our service activity. This is done by adding a line to the Dockerfile file:

FROM iron/baseEXPOSE 6767ADD accountservice-linux-amd64 /ADD healthchecker-linux-amd64 /HEALTHCHECK --interval=1s --timeout=3s CMD ["./healthchecker-linux-amd64", "-port=6767"] || exit 1ENTRYPOINT ["./accountservice-linux-amd64"]

What is HEALTHCHECKER-LINUX-AMD64? We need to help docker a little bit because Docker does not provide us with HTTP clients or something like that to perform health checks. Instead, the Healthcheck directive in dockerfile specifies a command (CMD) that should perform a call to the/health route. Depending on the exit code of the running program, Docker determines whether the service is healthy. If too many subsequent health checks fail, Docker swarm will kill the container and start a new container.

The most common way to implement a real health check looks like curl. However, this requires our base Docker image to actually install curl (or any underlying dependency), and at this point we don't really want to handle it. Instead, let the go language brew our own health Check applet.

Create a Health Check program

It's time to create a new sub-project under Src/github.com/callistaenterprise/goblog.

mkdir healthchecker

Then create a main.go file under this directory with the following contents:

package mainimport (    "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)}

The amount of code is not very large, what does it do?

    • Use the flag package to read the-port command-line arguments. If not specified, fallback uses the default value of 80.
    • Executes an HTTP GET request http://127.0.0.1:[port]/health.
    • If an HTTP request error occurs, the status code is not 200 and exits with the eject code 1. Otherwise exit with exit code 0. 0 = = Success, 1 = = fail.

Let's try, if we've stopped Accountservice, run it again and run Healthchecker.

go build./accountservice

Then run this program:

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

We forgot to specify the port number above, so it uses the default 80 port. Let's do it again:

> go run *.go -port=6767>

There is no output here to indicate that our request was successful. Well, then we build a linux/amd64 binary and add it to accountservice by adding Healthchecker binary to the Dockerfile file. We continue to rebuild and deploy automatically using copyall.sh scripts.

#!/bin/bashexport GOOS=linuxexport CGO_ENABLED=0cd accountservice;go get;go build -o accountservice-linux-amd64;echo built `pwd`;cd ..// NEW, builds the healthchecker binarycd healthchecker;go get;go build -o healthchecker-linux-amd64;echo built `pwd`;cd ..export GOOS=darwin   // NEW, copies the healthchecker binary into the accountservice/ foldercp healthchecker/healthchecker-linux-amd64 accountservice/docker build -t someprefix/accountservice accountservice/

Finally, we need to do one more thing, is to update the Accountservice dockerfile. Its full content is as follows:

FROM iron/baseEXPOSE 6767ADD accountservice-linux-amd64 /# NEW!! ADD healthchecker-linux-amd64 /HEALTHCHECK --interval=3s --timeout=3s CMD ["./healthchecker-linux-amd64", "-port=6767"] || exit 1ENTRYPOINT ["./accountservice-linux-amd64"]

We have attached the following content:

    • Adding an Add directive ensures that the Healthchecker binary is included in the image.
    • The Healthcheck statement specifies our binaries and parameters, telling Docker to perform a health check every 3 seconds and accept a 3-second timeout.

Deploy Health Check Service

Now we are ready to deploy our updated Accountservice service with Healthchecker. If you want to be more automatic, add these two lines to the copyall.sh file, and each time it runs, it automatically deletes the accountservice from the Docker swarm and re-creates it.

docker service rm accountservicedocker service create --name=accountservice --replicas=1 --network=my_network -p=6767:6767 someprefix/accountservice

So now run./copyall.sh, wait a few seconds, all build updated well. Then we use Docker PS to check the status of the container and we can enumerate all the running containers.

> docker psCONTAINER ID        IMAGE                             COMMAND                 CREATED        STATUS1d9ec8122961        someprefix/accountservice:latest  "./accountservice-lin"  8 seconds ago  Up 6 seconds (healthy)107dc2f5e3fc        manomarks/visualizer              "npm start"             7 days ago     Up 7 days

We look for the "(healthy)" text below the status header. The service does not have a health instruction configured Healthcheck completely.

Deliberately causing a failure

To make things a little more interesting, we add a testable API that allows us to let endpoints play unhealthy purposes. In the Routes.go file, declare another route.

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,    },}

This route (do not have such a route in the production service!) Provides a rest-ish route for the purpose of troubleshooting health checks. The Sethealthystate function is in the Goblog/accountservice/handlers.go file with the following code:

var isHealthy = true // NEWfunc 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)}

Finally, Ishealthy Boolean is used as the check condition for the Healthcheck function:

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(                ...        ...        }

Restart Accountservice.

> cd $GOPATH/src/github.com/callistaenterprise/goblog/accountservice> go run *.goStarting accountserviceSeeded 100 fake accounts...2017/03/03 21:19:24 Starting HTTP service at 6767

A new Healthcheck call is then generated in the new window.

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

The first attempt succeeds, and then we change the state of Accountservice by using the following Curl request testability.

> curl localhost:6767/testability/healthy/false> go run *.go -port=6767exit status 1

It's working! Then we run it in the Docker swarm. Use copyall.sh to rebuild and redeploy accountservice.

> cd $GOPATH/src/github.com/callistaenterprise/goblog> ./copyall.sh

As always, wait for Docker swarm to redeploy "Accountservice", using the newly built "Accountservice" container image. Then, run Docker PS to see if the service is up and running with health.

> docker psCONTAINER ID    IMAGE                            COMMAND                CREATED         STATUS 8640f41f9939    someprefix/accountservice:latest "./accountservice-lin" 19 seconds ago  Up 18 seconds (healthy)

Note the container ID and created fields. You can increase the testability API on your Docker swarm. (My IP is: 192.168.99.100).

> curl $ManagerIP:6767/testability/healthy/false>

Then we run the Docker PS command again in a few seconds.

> docker psCONTAINER ID        IMAGE                            COMMAND                CREATED         STATUS                                                             NAMES0a6dc695fc2d        someprefix/accountservice:latest "./accountservice-lin" 3 seconds ago  Up 2 seconds (healthy)

As you can see, here's a new container ID and created and status. What really happens is that Docker swarm monitors three consecutive failed health checks (default values for retries) and immediately determines that the service is unhealthy and needs to be replaced with a new instance, which happens entirely without any management intervention.

Summarize

In this section, we use a simple/health Routing and Healthchecker program combined with Docker's healthcheck mechanism to demonstrate how this mechanism allows Docker swarm to automatically handle unhealthy services for us.

In the next chapter, we delve into the mechanism of Docker swarm, where we focus on two key areas-service discovery and load balancing.

Reference links

    • 6th part of English
    • Health Endpoint Check
    • Topic Home
    • Next section
Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.