diff --git a/docs/basics.md b/docs/basics.md index 0b12fe08f..f5243c7ca 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -454,13 +454,12 @@ The deprecated way: #### Health Check -A health check can be configured in order to remove a backend from LB rotation as long as it keeps returning HTTP status codes other than `200 OK` to HTTP GET requests periodically carried out by Traefik. +A health check can be configured in order to remove a backend from LB rotation as long as it keeps returning HTTP status codes other than `2xx` to HTTP GET requests periodically carried out by Traefik. The check is defined by a path appended to the backend URL and an interval (given in a format understood by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration)) specifying how often the health check should be executed (the default being 30 seconds). Each backend must respond to the health check within 5 seconds. By default, the port of the backend server is used, however, this may be overridden. -A recovering backend returning 200 OK responses again is being returned to the -LB rotation pool. +A recovering backend returning `2xx` responses again is being returned to the LB rotation pool. For example: ```toml diff --git a/healthcheck/healthcheck.go b/healthcheck/healthcheck.go index cc5a5edd8..ab9a2108f 100644 --- a/healthcheck/healthcheck.go +++ b/healthcheck/healthcheck.go @@ -174,6 +174,7 @@ func (b *BackendHealthCheck) addHeadersAndHost(req *http.Request) *http.Request if b.Options.Hostname != "" { req.Host = b.Options.Hostname } + for k, v := range b.Options.Headers { req.Header.Set(k, v) } @@ -183,26 +184,28 @@ func (b *BackendHealthCheck) addHeadersAndHost(req *http.Request) *http.Request // checkHealth returns a nil error in case it was successful and otherwise // a non-nil error with a meaningful description why the health check failed. func checkHealth(serverURL *url.URL, backend *BackendHealthCheck) error { - client := http.Client{ - Timeout: backend.requestTimeout, - Transport: backend.Options.Transport, - } req, err := backend.newRequest(serverURL) if err != nil { return fmt.Errorf("failed to create HTTP request: %s", err) } + req = backend.addHeadersAndHost(req) - resp, err := client.Do(req) - if err == nil { - defer resp.Body.Close() + client := http.Client{ + Timeout: backend.requestTimeout, + Transport: backend.Options.Transport, } - switch { - case err != nil: + resp, err := client.Do(req) + if err != nil { return fmt.Errorf("HTTP request failed: %s", err) - case resp.StatusCode != http.StatusOK: - return fmt.Errorf("received non-200 status code: %v", resp.StatusCode) } + + defer resp.Body.Close() + + if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices { + return fmt.Errorf("received non-2xx status code: %v", resp.StatusCode) + } + return nil } diff --git a/healthcheck/healthcheck_test.go b/healthcheck/healthcheck_test.go index c1d65f783..6ede3a847 100644 --- a/healthcheck/healthcheck_test.go +++ b/healthcheck/healthcheck_test.go @@ -19,14 +19,14 @@ const healthCheckInterval = 100 * time.Millisecond type testHandler struct { done func() - healthSequence []bool + healthSequence []int } func TestSetBackendsConfiguration(t *testing.T) { testCases := []struct { desc string startHealthy bool - healthSequence []bool + healthSequence []int expectedNumRemovedServers int expectedNumUpsertedServers int expectedGaugeValue float64 @@ -34,7 +34,15 @@ func TestSetBackendsConfiguration(t *testing.T) { { desc: "healthy server staying healthy", startHealthy: true, - healthSequence: []bool{true}, + healthSequence: []int{http.StatusOK}, + expectedNumRemovedServers: 0, + expectedNumUpsertedServers: 0, + expectedGaugeValue: 1, + }, + { + desc: "healthy server staying healthy (StatusNoContent)", + startHealthy: true, + healthSequence: []int{http.StatusNoContent}, expectedNumRemovedServers: 0, expectedNumUpsertedServers: 0, expectedGaugeValue: 1, @@ -42,7 +50,7 @@ func TestSetBackendsConfiguration(t *testing.T) { { desc: "healthy server becoming sick", startHealthy: true, - healthSequence: []bool{false}, + healthSequence: []int{http.StatusServiceUnavailable}, expectedNumRemovedServers: 1, expectedNumUpsertedServers: 0, expectedGaugeValue: 0, @@ -50,7 +58,7 @@ func TestSetBackendsConfiguration(t *testing.T) { { desc: "sick server becoming healthy", startHealthy: false, - healthSequence: []bool{true}, + healthSequence: []int{http.StatusOK}, expectedNumRemovedServers: 0, expectedNumUpsertedServers: 1, expectedGaugeValue: 1, @@ -58,7 +66,7 @@ func TestSetBackendsConfiguration(t *testing.T) { { desc: "sick server staying sick", startHealthy: false, - healthSequence: []bool{false}, + healthSequence: []int{http.StatusServiceUnavailable}, expectedNumRemovedServers: 0, expectedNumUpsertedServers: 0, expectedGaugeValue: 0, @@ -66,7 +74,7 @@ func TestSetBackendsConfiguration(t *testing.T) { { desc: "healthy server toggling to sick and back to healthy", startHealthy: true, - healthSequence: []bool{false, true}, + healthSequence: []int{http.StatusServiceUnavailable, http.StatusOK}, expectedNumRemovedServers: 1, expectedNumUpsertedServers: 1, expectedGaugeValue: 1, @@ -77,6 +85,7 @@ func TestSetBackendsConfiguration(t *testing.T) { test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() + // The context is passed to the health check and canonically cancelled by // the test server once all expected requests have been received. ctx, cancel := context.WithCancel(context.Background()) @@ -320,7 +329,7 @@ func (lb *testLoadBalancer) removeServer(u *url.URL) { lb.servers = append(lb.servers[:i], lb.servers[i+1:]...) } -func newTestServer(done func(), healthSequence []bool) *httptest.Server { +func newTestServer(done func(), healthSequence []int) *httptest.Server { handler := &testHandler{ done: done, healthSequence: healthSequence, @@ -328,21 +337,14 @@ func newTestServer(done func(), healthSequence []bool) *httptest.Server { return httptest.NewServer(handler) } -// ServeHTTP returns 200 or 503 HTTP response codes depending on whether the -// current request is marked as healthy or not. -// It calls the given 'done' function once all request health indicators have -// been depleted. +// ServeHTTP returns HTTP response codes following a status sequences. +// It calls the given 'done' function once all request health indicators have been depleted. func (th *testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if len(th.healthSequence) == 0 { panic("received unexpected request") } - healthy := th.healthSequence[0] - if healthy { - w.WriteHeader(http.StatusOK) - } else { - w.WriteHeader(http.StatusServiceUnavailable) - } + w.WriteHeader(th.healthSequence[0]) th.healthSequence = th.healthSequence[1:] if len(th.healthSequence) == 0 {