diff --git a/docs/content/reference/dynamic-configuration/docker-labels.yml b/docs/content/reference/dynamic-configuration/docker-labels.yml index 3c91d6f7d..ad2419ca4 100644 --- a/docs/content/reference/dynamic-configuration/docker-labels.yml +++ b/docs/content/reference/dynamic-configuration/docker-labels.yml @@ -153,6 +153,7 @@ - "traefik.http.services.service01.loadbalancer.healthcheck.interval=foobar" - "traefik.http.services.service01.loadbalancer.healthcheck.path=foobar" - "traefik.http.services.service01.loadbalancer.healthcheck.method=foobar" +- "traefik.http.services.service01.loadbalancer.healthcheck.status=42" - "traefik.http.services.service01.loadbalancer.healthcheck.port=42" - "traefik.http.services.service01.loadbalancer.healthcheck.scheme=foobar" - "traefik.http.services.service01.loadbalancer.healthcheck.mode=foobar" diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml index fc571b026..fc2b54961 100644 --- a/docs/content/reference/dynamic-configuration/file.toml +++ b/docs/content/reference/dynamic-configuration/file.toml @@ -56,6 +56,7 @@ mode = "foobar" path = "foobar" method = "foobar" + status = 42 port = 42 interval = "42s" timeout = "42s" diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml index 808528ef9..d97d030f3 100644 --- a/docs/content/reference/dynamic-configuration/file.yaml +++ b/docs/content/reference/dynamic-configuration/file.yaml @@ -61,6 +61,7 @@ http: mode: foobar path: foobar method: foobar + status: 42 port: 42 interval: 42s timeout: 42s diff --git a/docs/content/reference/dynamic-configuration/kv-ref.md b/docs/content/reference/dynamic-configuration/kv-ref.md index 9fa147791..9c0c3e41f 100644 --- a/docs/content/reference/dynamic-configuration/kv-ref.md +++ b/docs/content/reference/dynamic-configuration/kv-ref.md @@ -220,6 +220,7 @@ | `traefik/http/services/Service01/loadBalancer/healthCheck/path` | `foobar` | | `traefik/http/services/Service01/loadBalancer/healthCheck/port` | `42` | | `traefik/http/services/Service01/loadBalancer/healthCheck/scheme` | `foobar` | +| `traefik/http/services/Service01/loadBalancer/healthCheck/status` | `42` | | `traefik/http/services/Service01/loadBalancer/healthCheck/timeout` | `42s` | | `traefik/http/services/Service01/loadBalancer/passHostHeader` | `true` | | `traefik/http/services/Service01/loadBalancer/responseForwarding/flushInterval` | `42s` | diff --git a/docs/content/reference/dynamic-configuration/marathon-labels.json b/docs/content/reference/dynamic-configuration/marathon-labels.json index 5bc8e9e7c..2bd780b0c 100644 --- a/docs/content/reference/dynamic-configuration/marathon-labels.json +++ b/docs/content/reference/dynamic-configuration/marathon-labels.json @@ -153,6 +153,7 @@ "traefik.http.services.service01.loadbalancer.healthcheck.interval": "42s", "traefik.http.services.service01.loadbalancer.healthcheck.path": "foobar", "traefik.http.services.service01.loadbalancer.healthcheck.method": "foobar", +"traefik.http.services.service01.loadbalancer.healthcheck.status": "42", "traefik.http.services.service01.loadbalancer.healthcheck.port": "42", "traefik.http.services.service01.loadbalancer.healthcheck.scheme": "foobar", "traefik.http.services.service01.loadbalancer.healthcheck.mode": "foobar", diff --git a/docs/content/routing/providers/consul-catalog.md b/docs/content/routing/providers/consul-catalog.md index 16e9f531b..6d2ddb8a2 100644 --- a/docs/content/routing/providers/consul-catalog.md +++ b/docs/content/routing/providers/consul-catalog.md @@ -193,6 +193,14 @@ you'd add the tag `traefik.http.services.{name-of-your-choice}.loadbalancer.pass traefik.http.services.myservice.loadbalancer.healthcheck.method=foobar ``` +??? info "`traefik.http.services..loadbalancer.healthcheck.status`" + + See [health check](../services/index.md#health-check) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.healthcheck.status=42 + ``` + ??? info "`traefik.http.services..loadbalancer.healthcheck.port`" See [health check](../services/index.md#health-check) for more information. diff --git a/docs/content/routing/providers/docker.md b/docs/content/routing/providers/docker.md index 0ebdf827f..eacf78956 100644 --- a/docs/content/routing/providers/docker.md +++ b/docs/content/routing/providers/docker.md @@ -347,6 +347,14 @@ you'd add the label `traefik.http.services..loadbalancer.pa - "traefik.http.services.myservice.loadbalancer.healthcheck.method=foobar" ``` +??? info "`traefik.http.services..loadbalancer.healthcheck.status`" + + See [health check](../services/index.md#health-check) for more information. + + ```yaml + - "traefik.http.services.myservice.loadbalancer.healthcheck.status=42" + ``` + ??? info "`traefik.http.services..loadbalancer.healthcheck.port`" See [health check](../services/index.md#health-check) for more information. diff --git a/docs/content/routing/providers/ecs.md b/docs/content/routing/providers/ecs.md index 0dad42781..9cd180641 100644 --- a/docs/content/routing/providers/ecs.md +++ b/docs/content/routing/providers/ecs.md @@ -195,6 +195,14 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa traefik.http.services.myservice.loadbalancer.healthcheck.method=foobar ``` +??? info "`traefik.http.services..loadbalancer.healthcheck.status`" + + See [health check](../services/index.md#health-check) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.healthcheck.status=42 + ``` + ??? info "`traefik.http.services..loadbalancer.healthcheck.port`" See [health check](../services/index.md#health-check) for more information. diff --git a/docs/content/routing/providers/kv.md b/docs/content/routing/providers/kv.md index 8771c4cdb..bce4d7703 100644 --- a/docs/content/routing/providers/kv.md +++ b/docs/content/routing/providers/kv.md @@ -172,6 +172,14 @@ A Story of key & values |-------------------------------------------------------------------|----------| | `traefik/http/services/myservice/loadbalancer/healthcheck/method` | `foobar` | +??? info "`traefik/http/services//loadbalancer/healthcheck/status`" + + See [health check](../services/index.md#health-check) for more information. + + | Key (Path) | Value | + |-------------------------------------------------------------------|-------| + | `traefik/http/services/myservice/loadbalancer/healthcheck/status` | `42` | + ??? info "`traefik/http/services//loadbalancer/healthcheck/port`" See [health check](../services/index.md#health-check) for more information. diff --git a/docs/content/routing/providers/marathon.md b/docs/content/routing/providers/marathon.md index 7c215f1b6..19a8a8dde 100644 --- a/docs/content/routing/providers/marathon.md +++ b/docs/content/routing/providers/marathon.md @@ -222,6 +222,14 @@ For example, to change the passHostHeader behavior, you'd add the label `"traefi "traefik.http.services.myservice.loadbalancer.healthcheck.method": "foobar" ``` +??? info "`traefik.http.services..loadbalancer.healthcheck.status`" + + See [health check](../services/index.md#health-check) for more information. + + ```json + "traefik.http.services.myservice.loadbalancer.healthcheck.status": "42" + ``` + ??? info "`traefik.http.services..loadbalancer.healthcheck.port`" See [health check](../services/index.md#health-check) for more information. diff --git a/docs/content/routing/providers/nomad.md b/docs/content/routing/providers/nomad.md index ccc6316e7..e4d24a942 100644 --- a/docs/content/routing/providers/nomad.md +++ b/docs/content/routing/providers/nomad.md @@ -185,6 +185,14 @@ you'd add the tag `traefik.http.services.{name-of-your-choice}.loadbalancer.pass traefik.http.services.myservice.loadbalancer.healthcheck.path=/foo ``` +??? info "`traefik.http.services..loadbalancer.healthcheck.status`" + + See [health check](../services/index.md#health-check) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.healthcheck.status=42 + ``` + ??? info "`traefik.http.services..loadbalancer.healthcheck.port`" See [health check](../services/index.md#health-check) for more information. diff --git a/docs/content/routing/providers/rancher.md b/docs/content/routing/providers/rancher.md index fef132c4b..7d392889e 100644 --- a/docs/content/routing/providers/rancher.md +++ b/docs/content/routing/providers/rancher.md @@ -228,6 +228,14 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa - "traefik.http.services.myservice.loadbalancer.healthcheck.method=foobar" ``` +??? info "`traefik.http.services..loadbalancer.healthcheck.status`" + + See [health check](../services/index.md#health-check) for more information. + + ```yaml + - "traefik.http.services.myservice.loadbalancer.healthcheck.status=42" + ``` + ??? info "`traefik.http.services..loadbalancer.healthcheck.port`" See [health check](../services/index.md#health-check) for more information. diff --git a/docs/content/routing/services/index.md b/docs/content/routing/services/index.md index 800471cf8..5e2766f0e 100644 --- a/docs/content/routing/services/index.md +++ b/docs/content/routing/services/index.md @@ -316,7 +316,7 @@ On subsequent requests, to keep the session alive with the same server, the clie #### Health Check Configure health check to remove unhealthy servers from the load balancing rotation. -Traefik will consider your HTTP(s) servers healthy as long as they return status codes between `2XX` and `3XX` to the health check requests (carried out every `interval`). +Traefik will consider HTTP(s) servers healthy as long as they return a status code to the health check request (carried out every `interval`) between `2XX` and `3XX`, or matching the configured status. For gRPC servers, Traefik will consider them healthy as long as they return `SERVING` to [gRPC health check v1](https://github.com/grpc/grpc/blob/master/doc/health-checking.md) requests. To propagate status changes (e.g. all servers of this service are down) upwards, HealthCheck must also be enabled on the parent(s) of this service. @@ -333,6 +333,7 @@ Below are the available options for the health check mechanism: - `headers` (optional), defines custom headers to be sent to the health check endpoint. - `followRedirects` (default: true), defines whether redirects should be followed during the health check calls. - `method` (default: GET), defines the HTTP method that will be used while connecting to the endpoint. +- `status` (optional), defines the expected HTTP status code of the response to the health check request. !!! info "Interval & Timeout Format" diff --git a/pkg/config/dynamic/http_config.go b/pkg/config/dynamic/http_config.go index 89cbf7026..746ced781 100644 --- a/pkg/config/dynamic/http_config.go +++ b/pkg/config/dynamic/http_config.go @@ -237,6 +237,7 @@ type ServerHealthCheck struct { Mode string `json:"mode,omitempty" toml:"mode,omitempty" yaml:"mode,omitempty" export:"true"` Path string `json:"path,omitempty" toml:"path,omitempty" yaml:"path,omitempty" export:"true"` Method string `json:"method,omitempty" toml:"method,omitempty" yaml:"method,omitempty" export:"true"` + Status int `json:"status,omitempty" toml:"status,omitempty" yaml:"status,omitempty" export:"true"` Port int `json:"port,omitempty" toml:"port,omitempty,omitzero" yaml:"port,omitempty" export:"true"` Interval ptypes.Duration `json:"interval,omitempty" toml:"interval,omitempty" yaml:"interval,omitempty" export:"true"` Timeout ptypes.Duration `json:"timeout,omitempty" toml:"timeout,omitempty" yaml:"timeout,omitempty" export:"true"` diff --git a/pkg/config/label/label_test.go b/pkg/config/label/label_test.go index 702e9bffe..19d974387 100644 --- a/pkg/config/label/label_test.go +++ b/pkg/config/label/label_test.go @@ -151,6 +151,7 @@ func TestDecodeConfiguration(t *testing.T) { "traefik.http.services.Service0.loadbalancer.healthcheck.interval": "1s", "traefik.http.services.Service0.loadbalancer.healthcheck.path": "foobar", "traefik.http.services.Service0.loadbalancer.healthcheck.method": "foobar", + "traefik.http.services.Service0.loadbalancer.healthcheck.status": "401", "traefik.http.services.Service0.loadbalancer.healthcheck.port": "42", "traefik.http.services.Service0.loadbalancer.healthcheck.scheme": "foobar", "traefik.http.services.Service0.loadbalancer.healthcheck.mode": "foobar", @@ -168,6 +169,7 @@ func TestDecodeConfiguration(t *testing.T) { "traefik.http.services.Service1.loadbalancer.healthcheck.interval": "1s", "traefik.http.services.Service1.loadbalancer.healthcheck.path": "foobar", "traefik.http.services.Service1.loadbalancer.healthcheck.method": "foobar", + "traefik.http.services.Service1.loadbalancer.healthcheck.status": "401", "traefik.http.services.Service1.loadbalancer.healthcheck.port": "42", "traefik.http.services.Service1.loadbalancer.healthcheck.scheme": "foobar", "traefik.http.services.Service1.loadbalancer.healthcheck.mode": "foobar", @@ -655,6 +657,7 @@ func TestDecodeConfiguration(t *testing.T) { Mode: "foobar", Path: "foobar", Method: "foobar", + Status: 401, Port: 42, Interval: ptypes.Duration(time.Second), Timeout: ptypes.Duration(time.Second), @@ -684,6 +687,7 @@ func TestDecodeConfiguration(t *testing.T) { Mode: "foobar", Path: "foobar", Method: "foobar", + Status: 401, Port: 42, Interval: ptypes.Duration(time.Second), Timeout: ptypes.Duration(time.Second), @@ -1147,6 +1151,7 @@ func TestEncodeConfiguration(t *testing.T) { Scheme: "foobar", Path: "foobar", Method: "foobar", + Status: 401, Port: 42, Interval: ptypes.Duration(time.Second), Timeout: ptypes.Duration(time.Second), @@ -1174,6 +1179,7 @@ func TestEncodeConfiguration(t *testing.T) { Scheme: "foobar", Path: "foobar", Method: "foobar", + Status: 401, Port: 42, Interval: ptypes.Duration(time.Second), Timeout: ptypes.Duration(time.Second), @@ -1335,6 +1341,7 @@ func TestEncodeConfiguration(t *testing.T) { "traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Interval": "1000000000", "traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Path": "foobar", "traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Method": "foobar", + "traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Status": "401", "traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Port": "42", "traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Scheme": "foobar", "traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Timeout": "1000000000", @@ -1351,6 +1358,7 @@ func TestEncodeConfiguration(t *testing.T) { "traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Interval": "1000000000", "traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Path": "foobar", "traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Method": "foobar", + "traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Status": "401", "traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Port": "42", "traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Scheme": "foobar", "traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Timeout": "1000000000", diff --git a/pkg/healthcheck/healthcheck.go b/pkg/healthcheck/healthcheck.go index 58cbcf6e4..7a590d3f1 100644 --- a/pkg/healthcheck/healthcheck.go +++ b/pkg/healthcheck/healthcheck.go @@ -173,10 +173,14 @@ func (shc *ServiceHealthChecker) checkHealthHTTP(ctx context.Context, target *ur defer resp.Body.Close() - if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusBadRequest { + if shc.config.Status == 0 && (resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusBadRequest) { return fmt.Errorf("received error status code: %v", resp.StatusCode) } + if shc.config.Status != 0 && shc.config.Status != resp.StatusCode { + return fmt.Errorf("received error status code: %v expected status code: %v", resp.StatusCode, shc.config.Status) + } + return nil } diff --git a/pkg/healthcheck/healthcheck_test.go b/pkg/healthcheck/healthcheck_test.go index b1e6d4f11..5bc08bb94 100644 --- a/pkg/healthcheck/healthcheck_test.go +++ b/pkg/healthcheck/healthcheck_test.go @@ -249,6 +249,7 @@ func TestServiceHealthChecker_Launch(t *testing.T) { testCases := []struct { desc string mode string + status int server StartTestServer expNumRemovedServers int expNumUpsertedServers int @@ -263,6 +264,15 @@ func TestServiceHealthChecker_Launch(t *testing.T) { expGaugeValue: 1, targetStatus: runtime.StatusUp, }, + { + desc: "healthy server staying healthy, with custom code status check", + server: newHTTPServer(http.StatusNotFound), + status: http.StatusNotFound, + expNumRemovedServers: 0, + expNumUpsertedServers: 1, + expGaugeValue: 1, + targetStatus: runtime.StatusUp, + }, { desc: "healthy server staying healthy (StatusNoContent)", server: newHTTPServer(http.StatusNoContent), @@ -287,6 +297,15 @@ func TestServiceHealthChecker_Launch(t *testing.T) { expGaugeValue: 0, targetStatus: runtime.StatusDown, }, + { + desc: "healthy server becoming sick, with custom code status check", + server: newHTTPServer(http.StatusOK), + status: http.StatusServiceUnavailable, + expNumRemovedServers: 1, + expNumUpsertedServers: 0, + expGaugeValue: 0, + targetStatus: runtime.StatusDown, + }, { desc: "healthy server toggling to sick and back to healthy", server: newHTTPServer(http.StatusServiceUnavailable, http.StatusOK), @@ -348,6 +367,7 @@ func TestServiceHealthChecker_Launch(t *testing.T) { config := &dynamic.ServerHealthCheck{ Mode: test.mode, + Status: test.status, Path: "/path", Interval: ptypes.Duration(500 * time.Millisecond), Timeout: ptypes.Duration(499 * time.Millisecond),