From 13e8a875cf05e820f879971d8bb75813ed8e67a2 Mon Sep 17 00:00:00 2001 From: Brian Akins Date: Wed, 10 May 2017 14:28:57 -0400 Subject: [PATCH] Allow overriding port for backend healthchecks --- docs/basics.md | 34 +++++++++++------ healthcheck/healthcheck.go | 26 ++++++++++++- healthcheck/healthcheck_test.go | 67 +++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 13 deletions(-) diff --git a/docs/basics.md b/docs/basics.md index c7b0f6043..47a606df4 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -296,8 +296,9 @@ 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. 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. +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. @@ -311,6 +312,16 @@ For example: interval = "10s" ``` +To use a different port for the healthcheck: +```toml +[backends] + [backends.backend1] + [backends.backend1.healthcheck] + path = "/health" + interval = "10s" + port = 8080 +``` + ## Servers Servers are simply defined using a `URL`. You can also apply a custom `weight` to each server (this will be used by load-balancing). @@ -346,17 +357,17 @@ Here is an example of backends and servers definition: # Configuration -Træfik's configuration has two parts: +Træfik's configuration has two parts: -- The [static Træfik configuration](/basics#static-trfk-configuration) which is loaded only at the beginning. +- The [static Træfik configuration](/basics#static-trfk-configuration) which is loaded only at the beginning. - The [dynamic Træfik configuration](/basics#dynamic-trfk-configuration) which can be hot-reloaded (no need to restart the process). ## Static Træfik configuration -The static configuration is the global configuration which is setting up connections to configuration backends and entrypoints. +The static configuration is the global configuration which is setting up connections to configuration backends and entrypoints. -Træfik can be configured using many configuration sources with the following precedence order. +Træfik can be configured using many configuration sources with the following precedence order. Each item takes precedence over the item below it: - [Key-value Store](/basics/#key-value-stores) @@ -398,18 +409,18 @@ Træfik supports several Key-value stores: - [Consul](https://consul.io) - [etcd](https://coreos.com/etcd/) -- [ZooKeeper](https://zookeeper.apache.org/) +- [ZooKeeper](https://zookeeper.apache.org/) - [boltdb](https://github.com/boltdb/bolt) Please refer to the [User Guide Key-value store configuration](/user-guide/kv-config/) section to get documentation on it. ## Dynamic Træfik configuration -The dynamic configuration concerns : +The dynamic configuration concerns : - [Frontends](/basics/#frontends) -- [Backends](/basics/#backends) -- [Servers](/basics/#servers) +- [Backends](/basics/#backends) +- [Servers](/basics/#servers) Træfik can hot-reload those rules which could be provided by [multiple configuration backends](/toml/#configuration-backends). @@ -427,7 +438,7 @@ List of Træfik available commands with description :              - `version` : Print version  - `storeconfig` : Store the static traefik configuration into a Key-value stores. Please refer to the [Store Træfik configuration](/user-guide/kv-config/#store-trfk-configuration) section to get documentation on it. -Each command may have related flags. +Each command may have related flags. All those related flags will be displayed with : ```bash @@ -439,4 +450,3 @@ Note that each command is described at the beginning of the help section: ```bash $ traefik --help ``` - diff --git a/healthcheck/healthcheck.go b/healthcheck/healthcheck.go index a1282c3b6..25d226e8f 100644 --- a/healthcheck/healthcheck.go +++ b/healthcheck/healthcheck.go @@ -3,8 +3,10 @@ package healthcheck import ( "context" "fmt" + "net" "net/http" "net/url" + "strconv" "sync" "time" @@ -27,6 +29,7 @@ func GetHealthCheck() *HealthCheck { // Options are the public health check options. type Options struct { Path string + Port int Interval time.Duration LB LoadBalancer } @@ -127,11 +130,32 @@ func checkBackend(currentBackend *BackendHealthCheck) { } } +func (backend *BackendHealthCheck) newRequest(serverURL *url.URL) (*http.Request, error) { + if backend.Options.Port == 0 { + return http.NewRequest("GET", serverURL.String()+backend.Path, nil) + } + + // copy the url and add the port to the host + u := &url.URL{} + *u = *serverURL + u.Host = net.JoinHostPort(u.Hostname(), strconv.Itoa(backend.Options.Port)) + u.Path = u.Path + backend.Path + + return http.NewRequest("GET", u.String(), nil) +} + func checkHealth(serverURL *url.URL, backend *BackendHealthCheck) bool { client := http.Client{ Timeout: backend.requestTimeout, } - resp, err := client.Get(serverURL.String() + backend.Path) + req, err := backend.newRequest(serverURL) + if err != nil { + log.Errorf("Failed to create HTTP request [%s] for healthcheck: %s", serverURL, err) + return false + } + + resp, err := client.Do(req) + if err == nil { defer resp.Body.Close() } diff --git a/healthcheck/healthcheck_test.go b/healthcheck/healthcheck_test.go index 5791c2058..e9ef67dd3 100644 --- a/healthcheck/healthcheck_test.go +++ b/healthcheck/healthcheck_test.go @@ -193,6 +193,73 @@ func TestSetBackendsConfiguration(t *testing.T) { } } +func TestNewRequest(t *testing.T) { + tests := []struct { + desc string + host string + port int + path string + expected string + }{ + { + desc: "no port override", + host: "backend1:80", + port: 0, + path: "/test", + expected: "http://backend1:80/test", + }, + { + desc: "port override", + host: "backend2:80", + port: 8080, + path: "/test", + expected: "http://backend2:8080/test", + }, + { + desc: "no port override with no port in host", + host: "backend1", + port: 0, + path: "/health", + expected: "http://backend1/health", + }, + { + desc: "port override with no port in host", + host: "backend2", + port: 8080, + path: "/health", + expected: "http://backend2:8080/health", + }, + } + + for _, test := range tests { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + backend := NewBackendHealthCheck( + Options{ + Path: test.path, + Port: test.port, + }) + + u := &url.URL{ + Scheme: "http", + Host: test.host, + } + + req, err := backend.newRequest(u) + if err != nil { + t.Fatalf("failed to create new backend request: %s", err) + } + + actual := req.URL.String() + if actual != test.expected { + t.Fatalf("got %s for healthcheck URL, wanted %s", actual, test.expected) + } + }) + } + +} + func MustParseURL(rawurl string) *url.URL { u, err := url.Parse(rawurl) if err != nil {