From 3e13ebec93ab078865b383bed36c8ec6b24dedf9 Mon Sep 17 00:00:00 2001 From: SALLEYRON Julien Date: Tue, 2 Jan 2018 16:02:03 +0100 Subject: [PATCH 01/13] We need to flush the end of the body when retry is streamed --- middlewares/retry.go | 1 + middlewares/retry_test.go | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/middlewares/retry.go b/middlewares/retry.go index 94ee99686..eee243a29 100644 --- a/middlewares/retry.go +++ b/middlewares/retry.go @@ -56,6 +56,7 @@ func (retry *Retry) ServeHTTP(rw http.ResponseWriter, r *http.Request) { // It's a stream request and the body gets already sent to the client. // Therefore we should not send the response a second time. if recorder.streamingResponseStarted { + recorder.Flush() break } diff --git a/middlewares/retry_test.go b/middlewares/retry_test.go index bb92754a6..4f74efba7 100644 --- a/middlewares/retry_test.go +++ b/middlewares/retry_test.go @@ -134,3 +134,21 @@ type countingRetryListener struct { func (l *countingRetryListener) Retried(req *http.Request, attempt int) { l.timesCalled++ } + +func TestRetryWithFlush(t *testing.T) { + next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(200) + rw.Write([]byte("FULL ")) + rw.(http.Flusher).Flush() + rw.Write([]byte("DATA")) + }) + + retry := NewRetry(1, next, &countingRetryListener{}) + responseRecorder := httptest.NewRecorder() + + retry.ServeHTTP(responseRecorder, &http.Request{}) + + if responseRecorder.Body.String() != "FULL DATA" { + t.Errorf("Wrong body %q want %q", responseRecorder.Body.String(), "FULL DATA") + } +} From 01e17b6c3e1587a9f1abefe4e3da36697059860d Mon Sep 17 00:00:00 2001 From: Timo Reimann Date: Wed, 3 Jan 2018 09:12:03 +0100 Subject: [PATCH 02/13] k8s guide: Leave note about assumed DaemonSet usage. --- docs/user-guide/kubernetes.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/user-guide/kubernetes.md b/docs/user-guide/kubernetes.md index b146515ed..ea272da21 100644 --- a/docs/user-guide/kubernetes.md +++ b/docs/user-guide/kubernetes.md @@ -257,7 +257,7 @@ curl $(minikube ip) 404 page not found ``` -If you decided to use the deployment, then you need to target the correct NodePort, which can be seen then you execute `kubectl get services --namespace=kube-system`. +If you decided to use the deployment, then you need to target the correct NodePort, which can be seen when you execute `kubectl get services --namespace=kube-system`. ```sh curl $(minikube ip): @@ -269,6 +269,8 @@ curl $(minikube ip): !!! note We expect to see a 404 response here as we haven't yet given Træfik any configuration. +All further examples below assume a DaemonSet installation. Deployment users will need to append the NodePort when constructing requests. + ## Deploy Træfik using Helm Chart Instead of installing Træfik via an own object, you can also use the Træfik Helm chart. From f30ad20c9bcdbb7704af23925a7a49f0f35a0a28 Mon Sep 17 00:00:00 2001 From: SALLEYRON Julien Date: Wed, 3 Jan 2018 15:32:03 +0100 Subject: [PATCH 03/13] Use gorilla readMessage and writeMessage instead of just an io.Copy --- glide.lock | 2 +- vendor/github.com/vulcand/oxy/forward/fwd.go | 37 ++++++++++---------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/glide.lock b/glide.lock index 7f49501d5..0bc9610db 100644 --- a/glide.lock +++ b/glide.lock @@ -520,7 +520,7 @@ imports: - name: github.com/VividCortex/gohistogram version: 51564d9861991fb0ad0f531c99ef602d0f9866e6 - name: github.com/vulcand/oxy - version: 7b6e758ab449705195df638765c4ca472248908a + version: 812cebb8c764f2a78cb806267648b8728b4599ad repo: https://github.com/containous/oxy.git vcs: git subpackages: diff --git a/vendor/github.com/vulcand/oxy/forward/fwd.go b/vendor/github.com/vulcand/oxy/forward/fwd.go index ffd226860..2fa33a46a 100644 --- a/vendor/github.com/vulcand/oxy/forward/fwd.go +++ b/vendor/github.com/vulcand/oxy/forward/fwd.go @@ -5,7 +5,6 @@ package forward import ( "crypto/tls" - "io" "net/http" "net/http/httptest" "net/http/httputil" @@ -214,7 +213,7 @@ func (f *Forwarder) ServeHTTP(w http.ResponseWriter, req *http.Request) { if f.log.Level >= log.DebugLevel { logEntry := f.log.WithField("Request", utils.DumpHttpRequest(req)) logEntry.Debug("vulcand/oxy/forward: begin ServeHttp on request") - defer logEntry.Debug("vulcand/oxy/forward: competed ServeHttp on request") + defer logEntry.Debug("vulcand/oxy/forward: completed ServeHttp on request") } if f.stateListener != nil { @@ -333,27 +332,27 @@ func (f *httpForwarder) serveWebSocket(w http.ResponseWriter, req *http.Request, defer targetConn.Close() errc := make(chan error, 2) - replicate := func(dst io.Writer, src io.Reader, dstName string, srcName string) { - _, errCopy := io.Copy(dst, src) - if errCopy != nil { - f.log.Errorf("vulcand/oxy/forward/websocket: Error when copying from %s to %s using io.Copy: %v", srcName, dstName, errCopy) - } else { - f.log.Infof("vulcand/oxy/forward/websocket: Copying from %s to %s using io.Copy completed without error.", srcName, dstName) + + replicateWebsocketConn := func(dst, src *websocket.Conn, dstName, srcName string) { + var err error + for { + msgType, msg, err := src.ReadMessage() + if err != nil { + f.log.Errorf("vulcand/oxy/forward/websocket: Error when copying from %s to %s using ReadMessage: %v", srcName, dstName, err) + break + } + err = dst.WriteMessage(msgType, msg) + if err != nil { + f.log.Errorf("vulcand/oxy/forward/websocket: Error when copying from %s to %s using WriteMessage: %v", srcName, dstName, err) + break + } } - errc <- errCopy + errc <- err } - go replicate(targetConn.UnderlyingConn(), underlyingConn.UnderlyingConn(), "backend", "client") + go replicateWebsocketConn(underlyingConn, targetConn, "client", "backend") + go replicateWebsocketConn(targetConn, underlyingConn, "backend", "client") - // Try to read the first message - msgType, msg, err := targetConn.ReadMessage() - if err != nil { - log.Errorf("vulcand/oxy/forward/websocket: Couldn't read first message : %v", err) - } else { - underlyingConn.WriteMessage(msgType, msg) - } - - go replicate(underlyingConn.UnderlyingConn(), targetConn.UnderlyingConn(), "client", "backend") <-errc } From e83599dd0820fc4b7ef908b549d5bc0daaa117da Mon Sep 17 00:00:00 2001 From: Julien Maitrehenry Date: Thu, 4 Jan 2018 04:34:03 -0500 Subject: [PATCH 04/13] Add a note on how to add label to a docker compose file --- docs/configuration/backends/docker.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/configuration/backends/docker.md b/docs/configuration/backends/docker.md index bad8ad59d..47bed9af6 100644 --- a/docs/configuration/backends/docker.md +++ b/docs/configuration/backends/docker.md @@ -145,6 +145,20 @@ To enable constraints see [backend-specific constraints section](/configuration/ ## Labels: overriding default behaviour +!!! note + If you use a compose file, labels should be defined in the `deploy` part of your service. + + This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file/#labels-1)). + +```yaml +version: "3" +services: + whoami: + deploy: + labels: + traefik.docker.network: traefik +``` + ### On Containers Labels can be used on containers to override default behaviour. @@ -224,11 +238,13 @@ Services labels can be used for overriding default behaviour !!! note - if a label is defined both as a `container label` and a `service label` (for example `traefik..port=PORT` and `traefik.port=PORT` ), the `service label` is used to defined the `` property (`port` in the example). + If a label is defined both as a `container label` and a `service label` (for example `traefik..port=PORT` and `traefik.port=PORT` ), the `service label` is used to defined the `` property (`port` in the example). + It's possible to mix `container labels` and `service labels`, in this case `container labels` are used as default value for missing `service labels` but no frontends are going to be created with the `container labels`. + More details in this [example](/user-guide/docker-and-lets-encrypt/#labels). !!! warning - when running inside a container, Træfik will need network access through: + When running inside a container, Træfik will need network access through: `docker network connect ` From 5b24403c8ed815c8f702c07768aba86ba94d5938 Mon Sep 17 00:00:00 2001 From: SALLEYRON Julien Date: Thu, 4 Jan 2018 11:18:03 +0100 Subject: [PATCH 05/13] Don't panic if ResponseWriter does not implement CloseNotify --- middlewares/error_pages.go | 16 ++++---- middlewares/retry.go | 82 +++++++++++++++++++++++++------------- middlewares/retry_test.go | 50 +++++++++++++++++++++++ 3 files changed, 113 insertions(+), 35 deletions(-) diff --git a/middlewares/error_pages.go b/middlewares/error_pages.go index 3ef49f11d..086192194 100644 --- a/middlewares/error_pages.go +++ b/middlewares/error_pages.go @@ -52,18 +52,18 @@ func NewErrorPagesHandler(errorPage types.ErrorPage, backendURL string) (*ErrorP } func (ep *ErrorPagesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http.HandlerFunc) { - recorder := newRetryResponseRecorder() - recorder.responseWriter = w + recorder := newRetryResponseRecorder(w) + next.ServeHTTP(recorder, req) - w.WriteHeader(recorder.Code) + w.WriteHeader(recorder.GetCode()) //check the recorder code against the configured http status code ranges for _, block := range ep.HTTPCodeRanges { - if recorder.Code >= block[0] && recorder.Code <= block[1] { - log.Errorf("Caught HTTP Status Code %d, returning error page", recorder.Code) - finalURL := strings.Replace(ep.BackendURL, "{status}", strconv.Itoa(recorder.Code), -1) + if recorder.GetCode() >= block[0] && recorder.GetCode() <= block[1] { + log.Errorf("Caught HTTP Status Code %d, returning error page", recorder.GetCode()) + finalURL := strings.Replace(ep.BackendURL, "{status}", strconv.Itoa(recorder.GetCode()), -1) if newReq, err := http.NewRequest(http.MethodGet, finalURL, nil); err != nil { - w.Write([]byte(http.StatusText(recorder.Code))) + w.Write([]byte(http.StatusText(recorder.GetCode()))) } else { ep.errorPageForwarder.ServeHTTP(w, newReq) } @@ -73,5 +73,5 @@ func (ep *ErrorPagesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request, //did not catch a configured status code so proceed with the request utils.CopyHeaders(w.Header(), recorder.Header()) - w.Write(recorder.Body.Bytes()) + w.Write(recorder.GetBody().Bytes()) } diff --git a/middlewares/retry.go b/middlewares/retry.go index eee243a29..9b57ed0a6 100644 --- a/middlewares/retry.go +++ b/middlewares/retry.go @@ -14,7 +14,7 @@ import ( // Compile time validation responseRecorder implements http interfaces correctly. var ( - _ Stateful = &retryResponseRecorder{} + _ Stateful = &retryResponseRecorderWithCloseNotify{} ) // Retry is a middleware that retries requests @@ -48,22 +48,21 @@ func (retry *Retry) ServeHTTP(rw http.ResponseWriter, r *http.Request) { // when proxying the HTTP requests to the backends. This happens in the custom RecordingErrorHandler. newCtx := context.WithValue(r.Context(), defaultNetErrCtxKey, &netErrorOccurred) - recorder := newRetryResponseRecorder() - recorder.responseWriter = rw + recorder := newRetryResponseRecorder(rw) retry.next.ServeHTTP(recorder, r.WithContext(newCtx)) // It's a stream request and the body gets already sent to the client. // Therefore we should not send the response a second time. - if recorder.streamingResponseStarted { + if recorder.IsStreamingResponseStarted() { recorder.Flush() break } if !netErrorOccurred || attempts >= retry.attempts { utils.CopyHeaders(rw.Header(), recorder.Header()) - rw.WriteHeader(recorder.Code) - rw.Write(recorder.Body.Bytes()) + rw.WriteHeader(recorder.GetCode()) + rw.Write(recorder.GetBody().Bytes()) break } attempts++ @@ -115,9 +114,31 @@ func (l RetryListeners) Retried(req *http.Request, attempt int) { } } -// retryResponseRecorder is an implementation of http.ResponseWriter that +type retryResponseRecorder interface { + http.ResponseWriter + http.Flusher + GetCode() int + GetBody() *bytes.Buffer + IsStreamingResponseStarted() bool +} + +// newRetryResponseRecorder returns an initialized retryResponseRecorder. +func newRetryResponseRecorder(rw http.ResponseWriter) retryResponseRecorder { + recorder := &retryResponseRecorderWithoutCloseNotify{ + HeaderMap: make(http.Header), + Body: new(bytes.Buffer), + Code: http.StatusOK, + responseWriter: rw, + } + if _, ok := rw.(http.CloseNotifier); ok { + return &retryResponseRecorderWithCloseNotify{recorder} + } + return recorder +} + +// retryResponseRecorderWithoutCloseNotify is an implementation of http.ResponseWriter that // records its mutations for later inspection. -type retryResponseRecorder struct { +type retryResponseRecorderWithoutCloseNotify struct { Code int // the HTTP response code from WriteHeader HeaderMap http.Header // the HTTP response headers Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to @@ -127,17 +148,19 @@ type retryResponseRecorder struct { streamingResponseStarted bool } -// newRetryResponseRecorder returns an initialized retryResponseRecorder. -func newRetryResponseRecorder() *retryResponseRecorder { - return &retryResponseRecorder{ - HeaderMap: make(http.Header), - Body: new(bytes.Buffer), - Code: http.StatusOK, - } +type retryResponseRecorderWithCloseNotify struct { + *retryResponseRecorderWithoutCloseNotify +} + +// CloseNotify returns a channel that receives at most a +// single value (true) when the client connection has gone +// away. +func (rw *retryResponseRecorderWithCloseNotify) CloseNotify() <-chan bool { + return rw.responseWriter.(http.CloseNotifier).CloseNotify() } // Header returns the response headers. -func (rw *retryResponseRecorder) Header() http.Header { +func (rw *retryResponseRecorderWithoutCloseNotify) Header() http.Header { m := rw.HeaderMap if m == nil { m = make(http.Header) @@ -146,8 +169,20 @@ func (rw *retryResponseRecorder) Header() http.Header { return m } +func (rw *retryResponseRecorderWithoutCloseNotify) GetCode() int { + return rw.Code +} + +func (rw *retryResponseRecorderWithoutCloseNotify) GetBody() *bytes.Buffer { + return rw.Body +} + +func (rw *retryResponseRecorderWithoutCloseNotify) IsStreamingResponseStarted() bool { + return rw.streamingResponseStarted +} + // Write always succeeds and writes to rw.Body, if not nil. -func (rw *retryResponseRecorder) Write(buf []byte) (int, error) { +func (rw *retryResponseRecorderWithoutCloseNotify) Write(buf []byte) (int, error) { if rw.err != nil { return 0, rw.err } @@ -155,24 +190,17 @@ func (rw *retryResponseRecorder) Write(buf []byte) (int, error) { } // WriteHeader sets rw.Code. -func (rw *retryResponseRecorder) WriteHeader(code int) { +func (rw *retryResponseRecorderWithoutCloseNotify) WriteHeader(code int) { rw.Code = code } // Hijack hijacks the connection -func (rw *retryResponseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) { +func (rw *retryResponseRecorderWithoutCloseNotify) Hijack() (net.Conn, *bufio.ReadWriter, error) { return rw.responseWriter.(http.Hijacker).Hijack() } -// CloseNotify returns a channel that receives at most a -// single value (true) when the client connection has gone -// away. -func (rw *retryResponseRecorder) CloseNotify() <-chan bool { - return rw.responseWriter.(http.CloseNotifier).CloseNotify() -} - // Flush sends any buffered data to the client. -func (rw *retryResponseRecorder) Flush() { +func (rw *retryResponseRecorderWithoutCloseNotify) Flush() { if !rw.streamingResponseStarted { utils.CopyHeaders(rw.responseWriter.Header(), rw.Header()) rw.responseWriter.WriteHeader(rw.Code) diff --git a/middlewares/retry_test.go b/middlewares/retry_test.go index 4f74efba7..a8ab483fd 100644 --- a/middlewares/retry_test.go +++ b/middlewares/retry_test.go @@ -7,6 +7,8 @@ import ( "net/http" "net/http/httptest" "testing" + + "github.com/stretchr/testify/assert" ) func TestRetry(t *testing.T) { @@ -152,3 +154,51 @@ func TestRetryWithFlush(t *testing.T) { t.Errorf("Wrong body %q want %q", responseRecorder.Body.String(), "FULL DATA") } } + +func TestNewRetryResponseRecorder(t *testing.T) { + testCases := []struct { + desc string + rw http.ResponseWriter + expected http.ResponseWriter + }{ + { + desc: "Without Close Notify", + rw: httptest.NewRecorder(), + expected: &retryResponseRecorderWithoutCloseNotify{}, + }, + { + desc: "With Close Notify", + rw: &mockRWCloseNotify{}, + expected: &retryResponseRecorderWithCloseNotify{}, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + rec := newRetryResponseRecorder(test.rw) + + assert.IsType(t, rec, test.expected) + }) + } +} + +type mockRWCloseNotify struct{} + +func (m *mockRWCloseNotify) CloseNotify() <-chan bool { + panic("implement me") +} + +func (m *mockRWCloseNotify) Header() http.Header { + panic("implement me") +} + +func (m *mockRWCloseNotify) Write([]byte) (int, error) { + panic("implement me") +} + +func (m *mockRWCloseNotify) WriteHeader(int) { + panic("implement me") +} From 287fb78654f6086b87ce4030074d2d6680204706 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 4 Jan 2018 14:48:03 +0100 Subject: [PATCH 06/13] Split Consul and Consul Catalog documentation --- docs/configuration/backends/consul.md | 98 +------------------- docs/configuration/backends/consulcatalog.md | 93 +++++++++++++++++++ docs/user-guide/kv-config.md | 13 +-- mkdocs.yml | 1 + 4 files changed, 102 insertions(+), 103 deletions(-) create mode 100644 docs/configuration/backends/consulcatalog.md diff --git a/docs/configuration/backends/consul.md b/docs/configuration/backends/consul.md index e59d6ca18..086ea874a 100644 --- a/docs/configuration/backends/consul.md +++ b/docs/configuration/backends/consul.md @@ -1,6 +1,4 @@ -# Consul Backend - -## Consul Key-Value backend +# Consul Key-Value backend Træfik can be configured to use Consul as a backend configuration. @@ -61,97 +59,3 @@ prefix = "traefik" To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific). Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on Traefik KV structure. - -## Consul Catalog backend - -Træfik can be configured to use service discovery catalog of Consul as a backend configuration. - -```toml -################################################################ -# Consul Catalog configuration backend -################################################################ - -# Enable Consul Catalog configuration backend. -[consulCatalog] - -# Consul server endpoint. -# -# Required -# Default: "127.0.0.1:8500" -# -endpoint = "127.0.0.1:8500" - -# Expose Consul catalog services by default in Traefik. -# -# Optional -# Default: true -# -exposedByDefault = false - -# Default domain used. -# -# Optional -# -domain = "consul.localhost" - -# Prefix for Consul catalog tags. -# -# Optional -# Default: "traefik" -# -prefix = "traefik" - -# Default frontEnd Rule for Consul services. -# -# The format is a Go Template with: -# - ".ServiceName", ".Domain" and ".Attributes" available -# - "getTag(name, tags, defaultValue)", "hasTag(name, tags)" and "getAttribute(name, tags, defaultValue)" functions are available -# - "getAttribute(...)" function uses prefixed tag names based on "prefix" value -# -# Optional -# Default: "Host:{{.ServiceName}}.{{.Domain}}" -# -#frontEndRule = "Host:{{.ServiceName}}.{{Domain}}" -``` - -This backend will create routes matching on hostname based on the service name used in Consul. - -To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific). - -### Tags - -Additional settings can be defined using Consul Catalog tags. - -| Tag | Description | -|-----------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `traefik.enable=false` | Disable this container in Træfik | -| `traefik.protocol=https` | Override the default `http` protocol | -| `traefik.backend.weight=10` | Assign this weight to the container | -| `traefik.backend.circuitbreaker=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend, ex: `NetworkErrorRatio() > 0.` | -| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect. | -| `traefik.backend.maxconn.extractorfunc=client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by. Must be used in conjunction with the above label to take effect. | -| `traefik.frontend.rule=Host:test.traefik.io` | Override the default frontend rule (Default: `Host:{{.ServiceName}}.{{.Domain}}`). | -| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. | -| `traefik.frontend.priority=10` | Override default frontend priority | -| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. | -| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` | -| `traefik.backend.loadbalancer=drr` | override the default `wrr` load balancer algorithm | -| `traefik.backend.loadbalancer.stickiness=true` | enable backend sticky sessions | -| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions | -| `traefik.backend.loadbalancer.sticky=true` | enable backend sticky sessions (DEPRECATED) | - -### Examples - -If you want that Træfik uses Consul tags correctly you need to defined them like that: -```json -traefik.enable=true -traefik.tags=api -traefik.tags=external -``` - -If the prefix defined in Træfik configuration is `bla`, tags need to be defined like that: -```json -bla.enable=true -bla.tags=api -bla.tags=external -``` \ No newline at end of file diff --git a/docs/configuration/backends/consulcatalog.md b/docs/configuration/backends/consulcatalog.md new file mode 100644 index 000000000..b528946c2 --- /dev/null +++ b/docs/configuration/backends/consulcatalog.md @@ -0,0 +1,93 @@ +# Consul Catalog backend + +Træfik can be configured to use service discovery catalog of Consul as a backend configuration. + +```toml +################################################################ +# Consul Catalog configuration backend +################################################################ + +# Enable Consul Catalog configuration backend. +[consulCatalog] + +# Consul server endpoint. +# +# Required +# Default: "127.0.0.1:8500" +# +endpoint = "127.0.0.1:8500" + +# Expose Consul catalog services by default in Traefik. +# +# Optional +# Default: true +# +exposedByDefault = false + +# Default domain used. +# +# Optional +# +domain = "consul.localhost" + +# Prefix for Consul catalog tags. +# +# Optional +# Default: "traefik" +# +prefix = "traefik" + +# Default frontEnd Rule for Consul services. +# +# The format is a Go Template with: +# - ".ServiceName", ".Domain" and ".Attributes" available +# - "getTag(name, tags, defaultValue)", "hasTag(name, tags)" and "getAttribute(name, tags, defaultValue)" functions are available +# - "getAttribute(...)" function uses prefixed tag names based on "prefix" value +# +# Optional +# Default: "Host:{{.ServiceName}}.{{.Domain}}" +# +#frontEndRule = "Host:{{.ServiceName}}.{{.Domain}}" +``` + +This backend will create routes matching on hostname based on the service name used in Consul. + +To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific). + +### Tags + +Additional settings can be defined using Consul Catalog tags. + +| Tag | Description | +|-----------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `traefik.enable=false` | Disable this container in Træfik | +| `traefik.protocol=https` | Override the default `http` protocol | +| `traefik.backend.weight=10` | Assign this weight to the container | +| `traefik.backend.circuitbreaker=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend, ex: `NetworkErrorRatio() > 0.` | +| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect. | +| `traefik.backend.maxconn.extractorfunc=client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by. Must be used in conjunction with the above label to take effect. | +| `traefik.frontend.rule=Host:test.traefik.io` | Override the default frontend rule (Default: `Host:{{.ServiceName}}.{{.Domain}}`). | +| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. | +| `traefik.frontend.priority=10` | Override default frontend priority | +| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. | +| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` | +| `traefik.backend.loadbalancer=drr` | override the default `wrr` load balancer algorithm | +| `traefik.backend.loadbalancer.stickiness=true` | enable backend sticky sessions | +| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions | +| `traefik.backend.loadbalancer.sticky=true` | enable backend sticky sessions (DEPRECATED) | + +### Examples + +If you want that Træfik uses Consul tags correctly you need to defined them like that: +```json +traefik.enable=true +traefik.tags=api +traefik.tags=external +``` + +If the prefix defined in Træfik configuration is `bla`, tags need to be defined like that: +```json +bla.enable=true +bla.tags=api +bla.tags=external +``` \ No newline at end of file diff --git a/docs/user-guide/kv-config.md b/docs/user-guide/kv-config.md index dc1542a74..995e50ed2 100644 --- a/docs/user-guide/kv-config.md +++ b/docs/user-guide/kv-config.md @@ -111,7 +111,7 @@ And there, the same global configuration in the Key-value Store (using `prefix = | `/traefik/entrypoints/https/tls/certificates/0/keyfile` | `integration/fixtures/https/snitest.com.key` | | `/traefik/entrypoints/https/tls/certificates/1/certfile` | `--BEGIN CERTIFICATE----END CERTIFICATE--` | | `/traefik/entrypoints/https/tls/certificates/1/keyfile` | `--BEGIN CERTIFICATE----END CERTIFICATE--` | -| `/traefik/entrypoints/other-https/address` | `:4443` +| `/traefik/entrypoints/other-https/address` | `:4443` | | `/traefik/consul/endpoint` | `127.0.0.1:8500` | | `/traefik/consul/watch` | `true` | | `/traefik/consul/prefix` | `traefik` | @@ -341,9 +341,10 @@ And there, the same dynamic configuration in a KV Store (using `prefix = "traefi | Key | Value | |----------------------------------------------------|-----------------------| -| `/traefik/tlsconfiguration/2/entrypoints` | `https,other-https` | +| `/traefik/tlsconfiguration/2/entrypoints` | `https,other-https` | | `/traefik/tlsconfiguration/2/certificate/certfile` | `` | | `/traefik/tlsconfiguration/2/certificate/certfile` | `` | + ### Atomic configuration changes Træfik can watch the backends/frontends configuration changes and generate its configuration automatically. @@ -378,9 +379,9 @@ Here, although the `/traefik_configurations/2/...` keys have been set, the old c | `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` | | `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` | | `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` | -| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` | +| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` | | `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` | -| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` | +| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` | Once the `/traefik/alias` key is updated, the new `/traefik_configurations/2` configuration becomes active atomically. @@ -392,9 +393,9 @@ Here, we have a 50% balance between the `http://172.17.0.3:80` and the `http://1 | `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` | | `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` | | `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.3:80` | -| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` | +| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` | | `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.4:80` | -| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` | +| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` | !!! note Træfik *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik/alias`. diff --git a/mkdocs.yml b/mkdocs.yml index c354faeea..a66bf22b5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -71,6 +71,7 @@ pages: - 'Backend: Web': 'configuration/backends/web.md' - 'Backend: BoltDB': 'configuration/backends/boltdb.md' - 'Backend: Consul': 'configuration/backends/consul.md' + - 'Backend: Consul Catalog': 'configuration/backends/consulcatalog.md' - 'Backend: Docker': 'configuration/backends/docker.md' - 'Backend: DynamoDB': 'configuration/backends/dynamodb.md' - 'Backend: ECS': 'configuration/backends/ecs.md' From 22bdbd249836cb6a8a8e9c438e89d08335d38d7c Mon Sep 17 00:00:00 2001 From: SALLEYRON Julien Date: Thu, 4 Jan 2018 15:22:03 +0100 Subject: [PATCH 07/13] Prepare release 1.5.0-rc4 --- CHANGELOG.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 899357a0d..1a2bead45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Change Log +## [v1.5.0-rc4](https://github.com/containous/traefik/tree/v1.5.0-rc4) (2018-01-04) +[All Commits](https://github.com/containous/traefik/compare/v1.5.0-rc3...v1.5.0-rc4) + +**Bug fixes:** +- **[consulcatalog]** Use prefix for sticky and stickiness tags. ([#2624](https://github.com/containous/traefik/pull/2624) by [ldez](https://github.com/ldez)) +- **[file,tls]** Send empty configuration from file provider ([#2609](https://github.com/containous/traefik/pull/2609) by [nmengin](https://github.com/nmengin)) +- **[middleware,docker,k8s]** Fix custom headers template ([#2621](https://github.com/containous/traefik/pull/2621) by [ldez](https://github.com/ldez)) +- **[middleware]** Don't panic if ResponseWriter does not implement CloseNotify ([#2651](https://github.com/containous/traefik/pull/2651) by [Juliens](https://github.com/Juliens)) +- **[middleware]** We need to flush the end of the body when retry is streamed ([#2644](https://github.com/containous/traefik/pull/2644) by [Juliens](https://github.com/Juliens)) +- **[tls]** Allow deleting dynamically all TLS certificates from an entryPoint ([#2603](https://github.com/containous/traefik/pull/2603) by [nmengin](https://github.com/nmengin)) +- **[websocket]** Use gorilla readMessage and writeMessage instead of just an io.Copy ([#2650](https://github.com/containous/traefik/pull/2650) by [Juliens](https://github.com/Juliens)) + +**Documentation:** +- **[consul,consulcatalog]** Split Consul and Consul Catalog documentation ([#2654](https://github.com/containous/traefik/pull/2654) by [ldez](https://github.com/ldez)) +- **[docker/swarm]** Typo in docker.endpoint TCP port. ([#2626](https://github.com/containous/traefik/pull/2626) by [redhandpl](https://github.com/redhandpl)) +- **[docker]** Add a note on how to add label to a docker compose file ([#2611](https://github.com/containous/traefik/pull/2611) by [jmaitrehenry](https://github.com/jmaitrehenry)) +- **[k8s]** k8s guide: Leave note about assumed DaemonSet usage. ([#2634](https://github.com/containous/traefik/pull/2634) by [timoreimann](https://github.com/timoreimann)) +- **[marathon]** Improve Marathon service label documentation. ([#2635](https://github.com/containous/traefik/pull/2635) by [timoreimann](https://github.com/timoreimann)) + +**Misc:** +- **[etcd,kv,tls]** Add tests for TLS dynamic configuration in ETCD3 ([#2606](https://github.com/containous/traefik/pull/2606) by [dahefanteng](https://github.com/dahefanteng)) +- Merge v1.4.6 into v1.5 ([#2642](https://github.com/containous/traefik/pull/2642) by [ldez](https://github.com/ldez)) + ## [v1.4.6](https://github.com/containous/traefik/tree/v1.4.6) (2018-01-02) [All Commits](https://github.com/containous/traefik/compare/v1.4.5...v1.4.6) From acd0c1bcd50931269fa7047df88eb3413159ce5e Mon Sep 17 00:00:00 2001 From: SALLEYRON Julien Date: Fri, 5 Jan 2018 02:26:03 +0100 Subject: [PATCH 08/13] GzipResponse must implement CloseNotifier if ResponseWriter implement it --- glide.lock | 2 +- vendor/github.com/NYTimes/gziphandler/gzip.go | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/glide.lock b/glide.lock index 0bc9610db..09e1be294 100644 --- a/glide.lock +++ b/glide.lock @@ -426,7 +426,7 @@ imports: repo: https://github.com/ijc25/Gotty.git vcs: git - name: github.com/NYTimes/gziphandler - version: d6f46609c7629af3a02d791a4666866eed3cbd3e + version: 47ca22a0aeea4c9ceddfb935d818d636d934c312 - name: github.com/ogier/pflag version: 45c278ab3607870051a2ea9040bb85fcb8557481 - name: github.com/opencontainers/go-digest diff --git a/vendor/github.com/NYTimes/gziphandler/gzip.go b/vendor/github.com/NYTimes/gziphandler/gzip.go index 901c4554a..b3cb8315b 100644 --- a/vendor/github.com/NYTimes/gziphandler/gzip.go +++ b/vendor/github.com/NYTimes/gziphandler/gzip.go @@ -84,6 +84,14 @@ type GzipResponseWriter struct { contentTypes []string // Only compress if the response is one of these content-types. All are accepted if empty. } +type GzipResponseWriterWithCloseNotify struct { + *GzipResponseWriter +} + +func (w *GzipResponseWriterWithCloseNotify) CloseNotify() <-chan bool { + return w.ResponseWriter.(http.CloseNotifier).CloseNotify() +} + // Write appends data to the gzip writer. func (w *GzipResponseWriter) Write(b []byte) (int, error) { // If content type is not set. @@ -264,7 +272,6 @@ func GzipHandlerWithOpts(opts ...option) (func(http.Handler) http.Handler, error return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add(vary, acceptEncoding) - if acceptsGzip(r) { gw := &GzipResponseWriter{ ResponseWriter: w, @@ -274,7 +281,13 @@ func GzipHandlerWithOpts(opts ...option) (func(http.Handler) http.Handler, error } defer gw.Close() - h.ServeHTTP(gw, r) + if _, ok := w.(http.CloseNotifier); ok { + gwcn := GzipResponseWriterWithCloseNotify{gw} + h.ServeHTTP(gwcn, r) + } else { + h.ServeHTTP(gw, r) + } + } else { h.ServeHTTP(w, r) } From 60fd26e0b7b343940cedb80b6eec24ddb5868b57 Mon Sep 17 00:00:00 2001 From: Julien Maitrehenry Date: Sun, 7 Jan 2018 09:54:03 -0500 Subject: [PATCH 09/13] Add a clustering example with Docker Swarm --- docs/user-guide/cluster-docker-consul.md | 289 +++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 290 insertions(+) create mode 100644 docs/user-guide/cluster-docker-consul.md diff --git a/docs/user-guide/cluster-docker-consul.md b/docs/user-guide/cluster-docker-consul.md new file mode 100644 index 000000000..8b3f70c84 --- /dev/null +++ b/docs/user-guide/cluster-docker-consul.md @@ -0,0 +1,289 @@ +# Clustering / High Availability on Docker Swarm with Consul + +This guide explains how to use Træfik in high availability mode in a Docker Swarm and with Let's Encrypt. + +Why do we need Træfik in cluster mode? Running multiple instances should work out of the box? + +If you want to use Let's Encrypt with Træfik, sharing configuration or TLS certificates between many Træfik instances, you need Træfik cluster/HA. + +Ok, could we mount a shared volume used by all my instances? Yes, you can, but it will not work. +When you use Let's Encrypt, you need to store certificates, but not only. +When Træfik generates a new certificate, it configures a challenge and once Let's Encrypt will verify the ownership of the domain, it will ping back the challenge. +If the challenge is not knowing by other Træfik instances, the validation will fail. + +For more information about challenge: [Automatic Certificate Management Environment (ACME)](https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#tls-with-server-name-indication-tls-sni) + +## Prerequisites + +You will need a working Docker Swarm cluster. + +## Træfik configuration + +In this guide, we will not use a TOML configuration file, but only command line flag. +With that, we can use the base image without mounting configuration file or building custom image. + +What Træfik should do: + +- Listen to 80 and 443 +- Redirect HTTP traffic to HTTPS +- Generate SSL certificate when a domain is added +- Listen to Docker Swarm event + +### EntryPoints configuration + +TL;DR: + +```shell +$ traefik \ + --entrypoints=Name:http Address::80 Redirect.EntryPoint:https \ + --entrypoints=Name:https Address::443 TLS \ + --defaultentrypoints=http,https +``` + +To listen to different ports, we need to create an entry point for each. + +The CLI syntax is `--entrypoints=Name:a_name Address:an_ip_or_empty:a_port options`. +If you want to redirect traffic from one entry point to another, it's the option `Redirect.EntryPoint:entrypoint_name`. + +By default, we don't want to configure all our services to listen on http and https, we add a default entry point configuration: `--defaultentrypoints=http,https`. + +### Let's Encrypt configuration + +TL;DR: + +```shell +$ traefik \ + --acme \ + --acme.storage=/etc/traefik/acme/acme.json \ + --acme.entryPoint=https \ + --acme.email=contact@mydomain.ca +``` + +Let's Encrypt needs 3 parameters: an entry point to listen to, a storage for certificates, and an email for the registration. + +To enable Let's Encrypt support, you need to add `--acme` flag. + +Now, Træfik needs to know where to store the certificates, we can choose between a key in a Key-Value store, or a file path: `--acme.storage=my/key` or `--acme.storage=/path/to/acme.json`. + +For your email and the entry point, it's `--acme.entryPoint` and `--acme.email` flags. + +### Docker configuration + +TL;DR: + +```shell +$ traefik \ + --docker \ + --docker.swarmmode \ + --docker.domain=mydomain.ca \ + --docker.watch +``` + +To enable docker and swarm-mode support, you need to add `--docker` and `--docker.swarmmode` flags. +To watch docker events, add `--docker.watch`. + +### Full docker-compose file + +```yaml +version: "3" +services: + traefik: + image: traefik:1.5 + command: + - "--web" + - "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https" + - "--entrypoints=Name:https Address::443 TLS" + - "--defaultentrypoints=http,https" + - "--acme" + - "--acme.storage=/etc/traefik/acme/acme.json" + - "--acme.entryPoint=https" + - "--acme.OnHostRule=true" + - "--acme.onDemand=false" + - "--acme.email=contact@mydomain.ca" + - "--docker" + - "--docker.swarmmode" + - "--docker.domain=mydomain.ca" + - "--docker.watch" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + networks: + - webgateway + - traefik + ports: + - target: 80 + published: 80 + mode: host + - target: 443 + published: 443 + mode: host + - target: 8080 + published: 8080 + mode: host + deploy: + mode: global + placement: + constraints: + - node.role == manager + update_config: + parallelism: 1 + delay: 10s + restart_policy: + condition: on-failure +networks: + webgateway: + driver: overlay + external: true + traefik: + driver: overlay +``` + +## Migrate configuration to Consul + +We created a special Træfik command to help configuring your Key Value store from a Træfik TOML configuration file and/or CLI flags. + +## Deploy a Træfik cluster + +The best way we found is to have an initializer service. +This service will push the config to Consul via the `storeconfig` sub-command. + +This service will retry until finishing without error because Consul may not be ready when the service tries to push the configuration. + +The initializer in a docker-compose file will be: + +```yaml + traefik_init: + image: traefik:1.5 + command: + - "storeconfig" + - "--web" + [...] + - "--consul" + - "--consul.endpoint=consul:8500" + - "--consul.prefix=traefik" + networks: + - traefik + deploy: + restart_policy: + condition: on-failure + depends_on: + - consul +``` + +And now, the Træfik part will only have the Consul configuration. + +```yaml + traefik: + image: traefik:1.5 + depends_on: + - traefik_init + - consul + command: + - "--consul" + - "--consul.endpoint=consul:8500" + - "--consul.prefix=traefik" + [...] +``` + +!!! note + For Træfik <1.5.0 add `acme.storage=traefik/acme/account` because Træfik is not reading it from Consul. + +If you have some update to do, update the initializer service and re-deploy it. +The new configuration will be stored in Consul, and you need to restart the Træfik node: `docker service update --force traefik_traefik`. + +## Full docker-compose file + +```yaml +version: "3.4" +services: + traefik_init: + image: traefik:1.5 + command: + - "storeconfig" + - "--web" + - "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https" + - "--entrypoints=Name:https Address::443 TLS" + - "--defaultentrypoints=http,https" + - "--acme" + - "--acme.storage=traefik/acme/account" + - "--acme.entryPoint=https" + - "--acme.OnHostRule=true" + - "--acme.onDemand=false" + - "--acme.email=contact@jmaitrehenry.ca" + - "--docker" + - "--docker.swarmmode" + - "--docker.domain=jmaitrehenry.ca" + - "--docker.watch" + - "--consul" + - "--consul.endpoint=consul:8500" + - "--consul.prefix=traefik" + networks: + - traefik + deploy: + restart_policy: + condition: on-failure + depends_on: + - consul + traefik: + image: traefik:1.5 + depends_on: + - traefik_init + - consul + command: + - "--consul" + - "--consul.endpoint=consul:8500" + - "--consul.prefix=traefik" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + networks: + - webgateway + - traefik + ports: + - target: 80 + published: 80 + mode: host + - target: 443 + published: 443 + mode: host + - target: 8080 + published: 8080 + mode: host + deploy: + mode: global + placement: + constraints: + - node.role == manager + update_config: + parallelism: 1 + delay: 10s + restart_policy: + condition: on-failure + consul: + image: consul + command: agent -server -bootstrap-expect=1 + volumes: + - consul-data:/consul/data + environment: + - CONSUL_LOCAL_CONFIG={"datacenter":"us_east2","server":true} + - CONSUL_BIND_INTERFACE=eth0 + - CONSUL_CLIENT_INTERFACE=eth0 + deploy: + replicas: 1 + placement: + constraints: + - node.role == manager + restart_policy: + condition: on-failure + networks: + - traefik + +networks: + webgateway: + driver: overlay + external: true + traefik: + driver: overlay + +volumes: + consul-data: + driver: [not local] +``` diff --git a/mkdocs.yml b/mkdocs.yml index a66bf22b5..9befff6fa 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -98,5 +98,6 @@ pages: - 'Key-value Store Configuration': 'user-guide/kv-config.md' - 'Clustering/HA': 'user-guide/cluster.md' - 'gRPC Example': 'user-guide/grpc.md' + - 'Traefik cluster example with Swarm': 'user-guide/cluster-docker-consul.md' - Benchmarks: benchmarks.md - 'Archive': 'archive.md' From 8a697f7a396115ec29b8737d7769b1d19352621c Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 9 Jan 2018 10:08:03 +0100 Subject: [PATCH 10/13] Fix: timeout integration test --- integration/fixtures/timeout/forwarding_timeouts.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/fixtures/timeout/forwarding_timeouts.toml b/integration/fixtures/timeout/forwarding_timeouts.toml index 800e16332..78c3d098a 100644 --- a/integration/fixtures/timeout/forwarding_timeouts.toml +++ b/integration/fixtures/timeout/forwarding_timeouts.toml @@ -22,7 +22,7 @@ responseHeaderTimeout = "300ms" [backends.backend1.servers.server1] # Non-routable IP address that should always deliver a dial timeout. # See: https://stackoverflow.com/questions/100841/artificially-create-a-connection-timeout-error#answer-904609 - url = "http://10.255.255.1" + url = "http://50.255.255.1" [backends.backend2] [backends.backend2.servers.server2] url = "http://{{.TimeoutEndpoint}}:9000" From 7c227392fac35de50e2e96607d0b26b6e59714c6 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 9 Jan 2018 11:24:03 +0100 Subject: [PATCH 11/13] fix: glide files. --- glide.lock | 2 +- glide.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/glide.lock b/glide.lock index 09e1be294..371ad536f 100644 --- a/glide.lock +++ b/glide.lock @@ -1,4 +1,4 @@ -hash: 2ca4d2b4f55342c6a722f70e0ef2e85ac2a38d8395dc206ad3f71a785b9f050f +hash: cccbfefb9183eb5425bfd242e4ae673d5a2acfa30ce83da377fd795ebcecf315 updated: 2017-12-15T10:34:41.246378337+01:00 imports: - name: cloud.google.com/go diff --git a/glide.yaml b/glide.yaml index b394d797d..4baffe125 100644 --- a/glide.yaml +++ b/glide.yaml @@ -14,7 +14,7 @@ import: - package: github.com/containous/traefik-extra-service-fabric version: v1.0.5 - package: github.com/vulcand/oxy - version: 7b6e758ab449705195df638765c4ca472248908a + version: 812cebb8c764f2a78cb806267648b8728b4599ad repo: https://github.com/containous/oxy.git vcs: git subpackages: From e74a20de244aacd5fbd80450c2b3fddd71c37ed0 Mon Sep 17 00:00:00 2001 From: Timo Reimann Date: Tue, 9 Jan 2018 11:56:02 +0100 Subject: [PATCH 12/13] Document rewrite-target annotation. --- docs/configuration/backends/kubernetes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/configuration/backends/kubernetes.md b/docs/configuration/backends/kubernetes.md index 02e0f4617..b2b5b94f4 100644 --- a/docs/configuration/backends/kubernetes.md +++ b/docs/configuration/backends/kubernetes.md @@ -112,6 +112,8 @@ Annotations can be used on containers to override default behaviour for the whol Override the default frontend endpoints. - `traefik.frontend.passTLSCert: true` Override the default frontend PassTLSCert value. Default: `false`. +- `ingress.kubernetes.io/rewrite-target: /users` + Replaces each matched Ingress path with the specified one, and adds the old path to the `X-Replaced-Path` header. !!! note Please note that `traefik.frontend.redirect.regex` and `traefik.frontend.redirect.replacement` do not have to be set if `traefik.frontend.redirect.entryPoint` is defined for the redirection (they will not be used in this case). From d88554fa926b8c541dcd43536737af0e8103c6bd Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 9 Jan 2018 12:40:04 +0100 Subject: [PATCH 13/13] fix: list entries parsing. --- autogen/gentemplates/gen.go | 2 +- provider/kv/kv.go | 29 +++++++++++++++++++++++++++++ templates/kv.tmpl | 2 +- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/autogen/gentemplates/gen.go b/autogen/gentemplates/gen.go index e4b479f9a..2eb30a08e 100644 --- a/autogen/gentemplates/gen.go +++ b/autogen/gentemplates/gen.go @@ -572,7 +572,7 @@ var _templatesKvTmpl = []byte(`{{$frontends := List .Prefix "/frontends/" }} [frontends]{{range $frontends}} {{$frontend := Last .}} - {{$entryPoints := SplitGet . "/entrypoints"}} + {{$entryPoints := GetList . "/entrypoints"}} [frontends."{{$frontend}}"] backend = "{{Get "" . "/backend"}}" passHostHeader = {{Get "true" . "/passHostHeader"}} diff --git a/provider/kv/kv.go b/provider/kv/kv.go index 6d5e4d0f9..9c065723a 100644 --- a/provider/kv/kv.go +++ b/provider/kv/kv.go @@ -147,6 +147,7 @@ func (p *Provider) loadConfig() *types.Configuration { "getSticky": p.getSticky, "hasStickinessLabel": p.hasStickinessLabel, "getStickinessCookieName": p.getStickinessCookieName, + "GetList": p.getList, } configuration, err := p.GetConfiguration("templates/kv.tmpl", KvFuncMap, templateObjects) @@ -264,3 +265,31 @@ func (p *Provider) hasStickinessLabel(rootPath string) bool { func (p *Provider) getStickinessCookieName(rootPath string) string { return p.get("", rootPath, "/loadbalancer", "/stickiness", "/cookiename") } + +func (p *Provider) getList(keyParts ...string) []string { + values := p.splitGet(keyParts...) + if len(values) > 0 { + return values + } + + return p.getSlice(keyParts...) +} + +// get sub keys. ex: foo/0, foo/1, foo/2 +func (p *Provider) getSlice(keyParts ...string) []string { + baseKey := strings.Join(keyParts, "") + if !strings.HasSuffix(baseKey, "/") { + baseKey += "/" + } + + listKeys := p.list(baseKey) + + var values []string + for _, entryKey := range listKeys { + val := p.get("", entryKey) + if len(val) > 0 { + values = append(values, val) + } + } + return values +} diff --git a/templates/kv.tmpl b/templates/kv.tmpl index 08d353016..fc4ab782f 100644 --- a/templates/kv.tmpl +++ b/templates/kv.tmpl @@ -50,7 +50,7 @@ [frontends]{{range $frontends}} {{$frontend := Last .}} - {{$entryPoints := SplitGet . "/entrypoints"}} + {{$entryPoints := GetList . "/entrypoints"}} [frontends."{{$frontend}}"] backend = "{{Get "" . "/backend"}}" passHostHeader = {{Get "true" . "/passHostHeader"}}