Support all 2xx HTTP status code for health check.
This commit is contained in:
parent
5c4692a0df
commit
e2bac47a0a
3 changed files with 36 additions and 32 deletions
|
@ -454,13 +454,12 @@ The deprecated way:
|
||||||
|
|
||||||
#### Health Check
|
#### 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).
|
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.
|
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.
|
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
|
A recovering backend returning `2xx` responses again is being returned to the LB rotation pool.
|
||||||
LB rotation pool.
|
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
```toml
|
```toml
|
||||||
|
|
|
@ -174,6 +174,7 @@ func (b *BackendHealthCheck) addHeadersAndHost(req *http.Request) *http.Request
|
||||||
if b.Options.Hostname != "" {
|
if b.Options.Hostname != "" {
|
||||||
req.Host = b.Options.Hostname
|
req.Host = b.Options.Hostname
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range b.Options.Headers {
|
for k, v := range b.Options.Headers {
|
||||||
req.Header.Set(k, v)
|
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
|
// 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.
|
// a non-nil error with a meaningful description why the health check failed.
|
||||||
func checkHealth(serverURL *url.URL, backend *BackendHealthCheck) error {
|
func checkHealth(serverURL *url.URL, backend *BackendHealthCheck) error {
|
||||||
client := http.Client{
|
|
||||||
Timeout: backend.requestTimeout,
|
|
||||||
Transport: backend.Options.Transport,
|
|
||||||
}
|
|
||||||
req, err := backend.newRequest(serverURL)
|
req, err := backend.newRequest(serverURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create HTTP request: %s", err)
|
return fmt.Errorf("failed to create HTTP request: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req = backend.addHeadersAndHost(req)
|
req = backend.addHeadersAndHost(req)
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
client := http.Client{
|
||||||
if err == nil {
|
Timeout: backend.requestTimeout,
|
||||||
defer resp.Body.Close()
|
Transport: backend.Options.Transport,
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
resp, err := client.Do(req)
|
||||||
case err != nil:
|
if err != nil {
|
||||||
return fmt.Errorf("HTTP request failed: %s", err)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,14 +19,14 @@ const healthCheckInterval = 100 * time.Millisecond
|
||||||
|
|
||||||
type testHandler struct {
|
type testHandler struct {
|
||||||
done func()
|
done func()
|
||||||
healthSequence []bool
|
healthSequence []int
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetBackendsConfiguration(t *testing.T) {
|
func TestSetBackendsConfiguration(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
startHealthy bool
|
startHealthy bool
|
||||||
healthSequence []bool
|
healthSequence []int
|
||||||
expectedNumRemovedServers int
|
expectedNumRemovedServers int
|
||||||
expectedNumUpsertedServers int
|
expectedNumUpsertedServers int
|
||||||
expectedGaugeValue float64
|
expectedGaugeValue float64
|
||||||
|
@ -34,7 +34,15 @@ func TestSetBackendsConfiguration(t *testing.T) {
|
||||||
{
|
{
|
||||||
desc: "healthy server staying healthy",
|
desc: "healthy server staying healthy",
|
||||||
startHealthy: true,
|
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,
|
expectedNumRemovedServers: 0,
|
||||||
expectedNumUpsertedServers: 0,
|
expectedNumUpsertedServers: 0,
|
||||||
expectedGaugeValue: 1,
|
expectedGaugeValue: 1,
|
||||||
|
@ -42,7 +50,7 @@ func TestSetBackendsConfiguration(t *testing.T) {
|
||||||
{
|
{
|
||||||
desc: "healthy server becoming sick",
|
desc: "healthy server becoming sick",
|
||||||
startHealthy: true,
|
startHealthy: true,
|
||||||
healthSequence: []bool{false},
|
healthSequence: []int{http.StatusServiceUnavailable},
|
||||||
expectedNumRemovedServers: 1,
|
expectedNumRemovedServers: 1,
|
||||||
expectedNumUpsertedServers: 0,
|
expectedNumUpsertedServers: 0,
|
||||||
expectedGaugeValue: 0,
|
expectedGaugeValue: 0,
|
||||||
|
@ -50,7 +58,7 @@ func TestSetBackendsConfiguration(t *testing.T) {
|
||||||
{
|
{
|
||||||
desc: "sick server becoming healthy",
|
desc: "sick server becoming healthy",
|
||||||
startHealthy: false,
|
startHealthy: false,
|
||||||
healthSequence: []bool{true},
|
healthSequence: []int{http.StatusOK},
|
||||||
expectedNumRemovedServers: 0,
|
expectedNumRemovedServers: 0,
|
||||||
expectedNumUpsertedServers: 1,
|
expectedNumUpsertedServers: 1,
|
||||||
expectedGaugeValue: 1,
|
expectedGaugeValue: 1,
|
||||||
|
@ -58,7 +66,7 @@ func TestSetBackendsConfiguration(t *testing.T) {
|
||||||
{
|
{
|
||||||
desc: "sick server staying sick",
|
desc: "sick server staying sick",
|
||||||
startHealthy: false,
|
startHealthy: false,
|
||||||
healthSequence: []bool{false},
|
healthSequence: []int{http.StatusServiceUnavailable},
|
||||||
expectedNumRemovedServers: 0,
|
expectedNumRemovedServers: 0,
|
||||||
expectedNumUpsertedServers: 0,
|
expectedNumUpsertedServers: 0,
|
||||||
expectedGaugeValue: 0,
|
expectedGaugeValue: 0,
|
||||||
|
@ -66,7 +74,7 @@ func TestSetBackendsConfiguration(t *testing.T) {
|
||||||
{
|
{
|
||||||
desc: "healthy server toggling to sick and back to healthy",
|
desc: "healthy server toggling to sick and back to healthy",
|
||||||
startHealthy: true,
|
startHealthy: true,
|
||||||
healthSequence: []bool{false, true},
|
healthSequence: []int{http.StatusServiceUnavailable, http.StatusOK},
|
||||||
expectedNumRemovedServers: 1,
|
expectedNumRemovedServers: 1,
|
||||||
expectedNumUpsertedServers: 1,
|
expectedNumUpsertedServers: 1,
|
||||||
expectedGaugeValue: 1,
|
expectedGaugeValue: 1,
|
||||||
|
@ -77,6 +85,7 @@ func TestSetBackendsConfiguration(t *testing.T) {
|
||||||
test := test
|
test := test
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// The context is passed to the health check and canonically cancelled by
|
// The context is passed to the health check and canonically cancelled by
|
||||||
// the test server once all expected requests have been received.
|
// the test server once all expected requests have been received.
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
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:]...)
|
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{
|
handler := &testHandler{
|
||||||
done: done,
|
done: done,
|
||||||
healthSequence: healthSequence,
|
healthSequence: healthSequence,
|
||||||
|
@ -328,21 +337,14 @@ func newTestServer(done func(), healthSequence []bool) *httptest.Server {
|
||||||
return httptest.NewServer(handler)
|
return httptest.NewServer(handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP returns 200 or 503 HTTP response codes depending on whether the
|
// ServeHTTP returns HTTP response codes following a status sequences.
|
||||||
// current request is marked as healthy or not.
|
// It calls the given 'done' function once all request health indicators have been depleted.
|
||||||
// It calls the given 'done' function once all request health indicators have
|
|
||||||
// been depleted.
|
|
||||||
func (th *testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (th *testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
if len(th.healthSequence) == 0 {
|
if len(th.healthSequence) == 0 {
|
||||||
panic("received unexpected request")
|
panic("received unexpected request")
|
||||||
}
|
}
|
||||||
|
|
||||||
healthy := th.healthSequence[0]
|
w.WriteHeader(th.healthSequence[0])
|
||||||
if healthy {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
} else {
|
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
|
||||||
}
|
|
||||||
|
|
||||||
th.healthSequence = th.healthSequence[1:]
|
th.healthSequence = th.healthSequence[1:]
|
||||||
if len(th.healthSequence) == 0 {
|
if len(th.healthSequence) == 0 {
|
||||||
|
|
Loading…
Add table
Reference in a new issue