diff --git a/CHANGELOG.md b/CHANGELOG.md index 29773a40b..70a39026e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Change Log +## [v1.4.0-rc3](https://github.com/containous/traefik/tree/v1.4.0-rc3) (2017-09-18) +[All Commits](https://github.com/containous/traefik/compare/v1.4.0-rc2...v1.4.0-rc3) + +**Enhancements:** +- **[acme]** Display Traefik logs in integration tests ([#2114](https://github.com/containous/traefik/pull/2114) by [ldez](https://github.com/ldez)) +- **[authentication]** Manage Headers for the Authentication forwarding. ([#2132](https://github.com/containous/traefik/pull/2132) by [ldez](https://github.com/ldez)) +- Fail fast in IT and fix some flaky tests ([#2126](https://github.com/containous/traefik/pull/2126) by [ldez](https://github.com/ldez)) + +**Bug fixes:** +- **[consul]** Fix Consul Catalog refresh ([#2089](https://github.com/containous/traefik/pull/2089) by [Juliens](https://github.com/Juliens)) +- **[healthcheck]** Fix healthcheck port ([#2131](https://github.com/containous/traefik/pull/2131) by [fredix](https://github.com/fredix)) +- **[logs,middleware]** Enable loss less rotation of log files ([#2062](https://github.com/containous/traefik/pull/2062) by [marco-jantke](https://github.com/marco-jantke)) +- **[rancher]** Add stack name to backend name generation to fix rancher metadata backend ([#2107](https://github.com/containous/traefik/pull/2107) by [SantoDE](https://github.com/SantoDE)) +- **[rancher]** Rancher host IP address ([#2101](https://github.com/containous/traefik/pull/2101) by [matq007](https://github.com/matq007)) +- Fixes entry points configuration. ([#2120](https://github.com/containous/traefik/pull/2120) by [ldez](https://github.com/ldez)) + +**Documentation:** +- **[acme,provider]** Enhance documentation readability. ([#2095](https://github.com/containous/traefik/pull/2095) by [ldez](https://github.com/ldez)) +- **[api]** Add examples of proxying ping ([#2102](https://github.com/containous/traefik/pull/2102) by [deitch](https://github.com/deitch)) +- **[k8s]** Add guide section on production advice, esp. CPU. ([#2113](https://github.com/containous/traefik/pull/2113) by [timoreimann](https://github.com/timoreimann)) +- **[metrics]** Enhance web backend documentation ([#2122](https://github.com/containous/traefik/pull/2122) by [ldez](https://github.com/ldez)) +- Add forward auth documentation. ([#2110](https://github.com/containous/traefik/pull/2110) by [ldez](https://github.com/ldez)) +- User guide gRPC ([#2108](https://github.com/containous/traefik/pull/2108) by [Juliens](https://github.com/Juliens)) +- Document custom error page restrictions. ([#2104](https://github.com/containous/traefik/pull/2104) by [timoreimann](https://github.com/timoreimann)) + ## [v1.4.0-rc2](https://github.com/containous/traefik/tree/v1.4.0-rc2) (2017-09-08) [All Commits](https://github.com/containous/traefik/compare/v1.4.0-rc1...v1.4.0-rc2) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4b6b46acc..6a52a7548 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ ## Building -You need either [Docker](https://github.com/docker/docker) and `make` (Method 1), or `go` (Method 2) in order to build traefik. For changes to its dependencies, the `glide` dependency management tool and `glide-vc` plugin are required. +You need either [Docker](https://github.com/docker/docker) and `make` (Method 1), or `go` (Method 2) in order to build Traefik. For changes to its dependencies, the `glide` dependency management tool and `glide-vc` plugin are required. ### Method 1: Using `Docker` and `Makefile` diff --git a/README.md b/README.md index 2bfae92db..11ab9c1b0 100644 --- a/README.md +++ b/README.md @@ -17,20 +17,20 @@ It supports several backends ([Docker](https://www.docker.com/), [Swarm mode](ht --- -| **[Overview](#overview)** | -**[Features](#features)** | -**[Supported backends](#supported-backends)** | -**[Quickstart](#quickstart)** | -**[Web UI](#web-ui)** | -**[Test it](#test-it)** | -**[Documentation](#documentation)** | -**[Support](#support)** | -**[Release cycle](#release-cycle)** | +. **[Overview](#overview)** . +**[Features](#features)** . +**[Supported backends](#supported-backends)** . +**[Quickstart](#quickstart)** . +**[Web UI](#web-ui)** . +**[Test it](#test-it)** . +**[Documentation](#documentation)** . -| **[Contributing](#contributing)** | -**[Maintainers](#maintainers)** | -**[Plumbing](#plumbing)** | -**[Credits](#credits)** | +. **[Support](#support)** . +**[Release cycle](#release-cycle)** . +**[Contributing](#contributing)** . +**[Maintainers](#maintainers)** . +**[Plumbing](#plumbing)** . +**[Credits](#credits)** . --- @@ -61,7 +61,7 @@ Run it and forget it! - [It's fast](https://docs.traefik.io/benchmarks) - No dependency hell, single binary made with go -- [Tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) official docker image +- [Tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) docker image - Rest API - Hot-reloading of configuration. No need to restart the process - Circuit breakers, retry diff --git a/acme/account.go b/acme/account.go index 7a897cdba..b73c73bc5 100644 --- a/acme/account.go +++ b/acme/account.go @@ -178,7 +178,7 @@ func (dc *DomainsCertificates) renewCertificates(acmeCert *Certificate, domain D return nil } } - return fmt.Errorf("Certificate to renew not found for domain %s", domain.Main) + return fmt.Errorf("certificate to renew not found for domain %s", domain.Main) } func (dc *DomainsCertificates) addCertificateForDomains(acmeCert *Certificate, domain Domain) (*DomainsCertificate, error) { diff --git a/acme/challengeProvider.go b/acme/challengeProvider.go index 2b1f2f37e..09b6aa1eb 100644 --- a/acme/challengeProvider.go +++ b/acme/challengeProvider.go @@ -43,7 +43,7 @@ func (c *challengeProvider) getCertificate(domain string) (cert *tls.Certificate } } } - return fmt.Errorf("Cannot find challenge cert for domain %s", domain) + return fmt.Errorf("cannot find challenge cert for domain %s", domain) } notify := func(err error, time time.Duration) { log.Errorf("Error getting cert: %v, retrying in %s", err, time) diff --git a/auth/forward.go b/auth/forward.go deleted file mode 100644 index 5c4262257..000000000 --- a/auth/forward.go +++ /dev/null @@ -1,60 +0,0 @@ -package auth - -import ( - "io/ioutil" - "net/http" - - "github.com/containous/traefik/log" - "github.com/containous/traefik/types" -) - -// Forward the authentication to a external server -func Forward(forward *types.Forward, w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { - httpClient := http.Client{} - - if forward.TLS != nil { - tlsConfig, err := forward.TLS.CreateTLSConfig() - if err != nil { - log.Debugf("Impossible to configure TLS to call %s. Cause %s", forward.Address, err) - w.WriteHeader(http.StatusInternalServerError) - return - } - httpClient.Transport = &http.Transport{ - TLSClientConfig: tlsConfig, - } - - } - - forwardReq, err := http.NewRequest(http.MethodGet, forward.Address, nil) - if err != nil { - log.Debugf("Error calling %s. Cause %s", forward.Address, err) - w.WriteHeader(http.StatusInternalServerError) - return - } - forwardReq.Header = r.Header - - forwardResponse, forwardErr := httpClient.Do(forwardReq) - if forwardErr != nil { - log.Debugf("Error calling %s. Cause: %s", forward.Address, forwardErr) - w.WriteHeader(http.StatusInternalServerError) - return - } - - body, readError := ioutil.ReadAll(forwardResponse.Body) - if readError != nil { - log.Debugf("Error reading body %s. Cause: %s", forward.Address, readError) - w.WriteHeader(http.StatusInternalServerError) - return - } - defer forwardResponse.Body.Close() - - if forwardResponse.StatusCode < http.StatusOK || forwardResponse.StatusCode >= http.StatusMultipleChoices { - log.Debugf("Remote error %s. StatusCode: %d", forward.Address, forwardResponse.StatusCode) - w.WriteHeader(forwardResponse.StatusCode) - w.Write(body) - return - } - - r.RequestURI = r.URL.RequestURI() - next(w, r) -} diff --git a/cmd/traefik/configuration.go b/cmd/traefik/configuration.go index 5093a2e88..b61c9d50d 100644 --- a/cmd/traefik/configuration.go +++ b/cmd/traefik/configuration.go @@ -12,6 +12,7 @@ import ( "github.com/containous/traefik/provider/dynamodb" "github.com/containous/traefik/provider/ecs" "github.com/containous/traefik/provider/etcd" + "github.com/containous/traefik/provider/eureka" "github.com/containous/traefik/provider/file" "github.com/containous/traefik/provider/kubernetes" "github.com/containous/traefik/provider/marathon" @@ -148,6 +149,10 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration { defaultDynamoDB.TableName = "traefik" defaultDynamoDB.Watch = true + // default Eureka + var defaultEureka eureka.Provider + defaultEureka.Delay = "30s" + // default AccessLog defaultAccessLog := types.AccessLog{ Format: accesslog.CommonFormat, @@ -168,6 +173,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration { Mesos: &defaultMesos, ECS: &defaultECS, Rancher: &defaultRancher, + Eureka: &defaultEureka, DynamoDB: &defaultDynamoDB, Retry: &configuration.Retry{}, HealthCheck: &configuration.HealthCheckConfig{}, diff --git a/configuration/configuration.go b/configuration/configuration.go index a8494436a..2a21ab398 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -193,18 +193,11 @@ func (ep *EntryPoints) String() string { // Set's argument is a string to be parsed to set the flag. // It's a comma-separated list, so we split it. func (ep *EntryPoints) Set(value string) error { - regex := regexp.MustCompile(`(?:Name:(?P\S*))\s*(?:Address:(?P
\S*))?\s*(?:TLS:(?P\S*))?\s*((?PTLS))?\s*(?:CA:(?P\S*))?\s*(?:Redirect.EntryPoint:(?P\S*))?\s*(?:Redirect.Regex:(?P\\S*))?\s*(?:Redirect.Replacement:(?P\S*))?\s*(?:Compress:(?P\S*))?\s*(?:WhiteListSourceRange:(?P\S*))?\s*(?:ProxyProtocol:(?P\S*))?`) - match := regex.FindAllStringSubmatch(value, -1) - if match == nil { - return fmt.Errorf("bad EntryPoints format: %s", value) - } - matchResult := match[0] - result := make(map[string]string) - for i, name := range regex.SubexpNames() { - if i != 0 { - result[name] = matchResult[i] - } + result, err := parseEntryPointsConfiguration(value) + if err != nil { + return err } + var configTLS *TLS if len(result["TLS"]) > 0 { certs := Certificates{} @@ -232,24 +225,13 @@ func (ep *EntryPoints) Set(value string) error { } } - compress := false - if len(result["Compress"]) > 0 { - compress = strings.EqualFold(result["Compress"], "true") || - strings.EqualFold(result["Compress"], "enable") || - strings.EqualFold(result["Compress"], "on") - } - whiteListSourceRange := []string{} if len(result["WhiteListSourceRange"]) > 0 { whiteListSourceRange = strings.Split(result["WhiteListSourceRange"], ",") } - proxyprotocol := false - if len(result["ProxyProtocol"]) > 0 { - proxyprotocol = strings.EqualFold(result["ProxyProtocol"], "true") || - strings.EqualFold(result["ProxyProtocol"], "enable") || - strings.EqualFold(result["ProxyProtocol"], "on") - } + compress := toBool(result, "Compress") + proxyProtocol := toBool(result, "ProxyProtocol") (*ep)[result["Name"]] = &EntryPoint{ Address: result["Address"], @@ -257,12 +239,37 @@ func (ep *EntryPoints) Set(value string) error { Redirect: redirect, Compress: compress, WhitelistSourceRange: whiteListSourceRange, - ProxyProtocol: proxyprotocol, + ProxyProtocol: proxyProtocol, } return nil } +func parseEntryPointsConfiguration(value string) (map[string]string, error) { + regex := regexp.MustCompile(`(?:Name:(?P\S*))\s*(?:Address:(?P
\S*))?\s*(?:TLS:(?P\S*))?\s*(?PTLS)?\s*(?:CA:(?P\S*))?\s*(?:Redirect\.EntryPoint:(?P\S*))?\s*(?:Redirect\.Regex:(?P\S*))?\s*(?:Redirect\.Replacement:(?P\S*))?\s*(?:Compress:(?P\S*))?\s*(?:WhiteListSourceRange:(?P\S*))?\s*(?:ProxyProtocol:(?P\S*))?`) + match := regex.FindAllStringSubmatch(value, -1) + if match == nil { + return nil, fmt.Errorf("bad EntryPoints format: %s", value) + } + matchResult := match[0] + result := make(map[string]string) + for i, name := range regex.SubexpNames() { + if i != 0 && len(matchResult[i]) != 0 { + result[name] = matchResult[i] + } + } + return result, nil +} + +func toBool(conf map[string]string, key string) bool { + if val, ok := conf[key]; ok { + return strings.EqualFold(val, "true") || + strings.EqualFold(val, "enable") || + strings.EqualFold(val, "on") + } + return false +} + // Get return the EntryPoints map func (ep *EntryPoints) Get() interface{} { return EntryPoints(*ep) diff --git a/configuration/configuration_test.go b/configuration/configuration_test.go new file mode 100644 index 000000000..5d476e4df --- /dev/null +++ b/configuration/configuration_test.go @@ -0,0 +1,201 @@ +package configuration + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_parseEntryPointsConfiguration(t *testing.T) { + testCases := []struct { + name string + value string + expectedResult map[string]string + }{ + { + name: "all parameters", + value: "Name:foo Address:bar TLS:goo TLS CA:car Redirect.EntryPoint:RedirectEntryPoint Redirect.Regex:RedirectRegex Redirect.Replacement:RedirectReplacement Compress:true WhiteListSourceRange:WhiteListSourceRange ProxyProtocol:true", + expectedResult: map[string]string{ + "Name": "foo", + "Address": "bar", + "CA": "car", + "TLS": "goo", + "TLSACME": "TLS", + "RedirectEntryPoint": "RedirectEntryPoint", + "RedirectRegex": "RedirectRegex", + "RedirectReplacement": "RedirectReplacement", + "WhiteListSourceRange": "WhiteListSourceRange", + "ProxyProtocol": "true", + "Compress": "true", + }, + }, + { + name: "proxy protocol on", + value: "Name:foo ProxyProtocol:on", + expectedResult: map[string]string{ + "Name": "foo", + "ProxyProtocol": "on", + }, + }, + { + name: "compress on", + value: "Name:foo Compress:on", + expectedResult: map[string]string{ + "Name": "foo", + "Compress": "on", + }, + }, + { + name: "TLS", + value: "Name:foo TLS:goo TLS", + expectedResult: map[string]string{ + "Name": "foo", + "TLS": "goo", + "TLSACME": "TLS", + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + conf, err := parseEntryPointsConfiguration(test.value) + if err != nil { + t.Error(err) + } + + for key, value := range conf { + fmt.Println(key, value) + } + + assert.Len(t, conf, len(test.expectedResult)) + assert.Equal(t, test.expectedResult, conf) + }) + } +} + +func Test_toBool(t *testing.T) { + testCases := []struct { + name string + value string + key string + expectedBool bool + }{ + { + name: "on", + value: "on", + key: "foo", + expectedBool: true, + }, + { + name: "true", + value: "true", + key: "foo", + expectedBool: true, + }, + { + name: "enable", + value: "enable", + key: "foo", + expectedBool: true, + }, + { + name: "arbitrary string", + value: "bar", + key: "foo", + expectedBool: false, + }, + { + name: "no existing entry", + value: "bar", + key: "fii", + expectedBool: false, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + conf := map[string]string{ + "foo": test.value, + } + + result := toBool(conf, test.key) + + assert.Equal(t, test.expectedBool, result) + }) + } +} + +func TestEntryPoints_Set(t *testing.T) { + testCases := []struct { + name string + expression string + expectedEntryPointName string + expectedEntryPoint *EntryPoint + }{ + { + name: "all parameters", + expression: "Name:foo Address:bar TLS:goo,gii TLS CA:car Redirect.EntryPoint:RedirectEntryPoint Redirect.Regex:RedirectRegex Redirect.Replacement:RedirectReplacement Compress:true WhiteListSourceRange:Range ProxyProtocol:true", + expectedEntryPointName: "foo", + expectedEntryPoint: &EntryPoint{ + Address: "bar", + Redirect: &Redirect{ + EntryPoint: "RedirectEntryPoint", + Regex: "RedirectRegex", + Replacement: "RedirectReplacement", + }, + Compress: true, + ProxyProtocol: true, + WhitelistSourceRange: []string{"Range"}, + TLS: &TLS{ + ClientCAFiles: []string{"car"}, + Certificates: Certificates{ + { + CertFile: FileOrContent("goo"), + KeyFile: FileOrContent("gii"), + }, + }, + }, + }, + }, + { + name: "compress on", + expression: "Name:foo Compress:on", + expectedEntryPointName: "foo", + expectedEntryPoint: &EntryPoint{ + Compress: true, + WhitelistSourceRange: []string{}, + }, + }, + { + name: "compress true", + expression: "Name:foo Compress:true", + expectedEntryPointName: "foo", + expectedEntryPoint: &EntryPoint{ + Compress: true, + WhitelistSourceRange: []string{}, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + eps := EntryPoints{} + err := eps.Set(test.expression) + require.NoError(t, err) + + ep := eps[test.expectedEntryPointName] + assert.EqualValues(t, test.expectedEntryPoint, ep) + }) + } +} diff --git a/docs/basics.md b/docs/basics.md index 0f35b0635..490e67b8e 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -2,7 +2,7 @@ ## Concepts -Let's take our example from the [overview](https://docs.traefik.io/#overview) again: +Let's take our example from the [overview](/#overview) again: > Imagine that you have deployed a bunch of microservices on your infrastructure. You probably used a service registry (like etcd or consul) and/or an orchestrator (swarm, Mesos/Marathon) to manage all these services. @@ -185,6 +185,7 @@ In TOML file, you can use multiple routes: ``` Here `frontend3` will forward the traffic to the `backend2` if the rules `Host:test3.localhost` **AND** `Path:/test` are matched. + You can also use the notation using a `;` separator, same result: ```toml @@ -208,7 +209,8 @@ Finally, you can create a rule to bind multiple domains or Path to a frontend, u #### Rules Order -When combining `Modifier` rules with `Matcher` rules, it is important to remember that `Modifier` rules **ALWAYS** apply after the `Matcher` rules. +When combining `Modifier` rules with `Matcher` rules, it is important to remember that `Modifier` rules **ALWAYS** apply after the `Matcher` rules. + The following rules are both `Matchers` and `Modifiers`, so the `Matcher` portion of the rule will apply first, and the `Modifier` will apply later. - `PathStrip` @@ -252,7 +254,8 @@ Here, `frontend1` will be matched before `frontend2` (`10 > 5`). #### Custom headers -Custom headers can be configured through the frontends, to add headers to either requests or responses that match the frontend's rules. This allows for setting headers such as `X-Script-Name` to be added to the request, or custom headers to be added to the response: +Custom headers can be configured through the frontends, to add headers to either requests or responses that match the frontend's rules. +This allows for setting headers such as `X-Script-Name` to be added to the request, or custom headers to be added to the response. ```toml [frontends] @@ -270,7 +273,10 @@ In this example, all matches to the path `/cheese` will have the `X-Script-Name` #### Security headers -Security related headers (HSTS headers, SSL redirection, Browser XSS filter, etc) can be added and configured per frontend in a similar manner to the custom headers above. This functionality allows for some easy security features to quickly be set. An example of some of the security headers: +Security related headers (HSTS headers, SSL redirection, Browser XSS filter, etc) can be added and configured per frontend in a similar manner to the custom headers above. +This functionality allows for some easy security features to quickly be set. + +An example of some of the security headers: ```toml [frontends] @@ -290,7 +296,8 @@ Security related headers (HSTS headers, SSL redirection, Browser XSS filter, etc In this example, traffic routed through the first frontend will have the `X-Frame-Options` header set to `DENY`, and the second will only allow HTTPS request through, otherwise will return a 301 HTTPS redirect. -The detailed documentation for those security headers can be found in [unrolled/secure](https://github.com/unrolled/secure#available-options). +!!! note + The detailed documentation for those security headers can be found in [unrolled/secure](https://github.com/unrolled/secure#available-options). #### Rate limiting @@ -324,10 +331,12 @@ These can "burst" up to 10 and 200 in each period respectively. ### Backends A backend is responsible to load-balance the traffic coming from one or more frontends to a set of http servers. + Various methods of load-balancing are supported: - `wrr`: Weighted Round Robin -- `drr`: Dynamic Round Robin: increases weights on servers that perform better than others. It also rolls back to original weights if the servers have changed. +- `drr`: Dynamic Round Robin: increases weights on servers that perform better than others. + It also rolls back to original weights if the servers have changed. A circuit breaker can also be applied to a backend, preventing high loads on failing servers. Initial state is Standby. CB observes the statistics and does not modify the request. @@ -366,9 +375,10 @@ For example: - Another possible value for `extractorfunc` is `client.ip` which will categorize requests based on client source ip. - Lastly `extractorfunc` can take the value of `request.header.ANY_HEADER` which will categorize requests based on `ANY_HEADER` that you provide. -Sticky sessions are supported with both load balancers. When sticky sessions are enabled, a cookie called `_TRAEFIK_BACKEND` is set on the initial -request. On subsequent requests, the client will be directed to the backend stored in the cookie if it is still healthy. If not, a new backend -will be assigned. +Sticky sessions are supported with both load balancers. +When sticky sessions are enabled, a cookie called `_TRAEFIK_BACKEND` is set on the initial request. +On subsequent requests, the client will be directed to the backend stored in the cookie if it is still healthy. +If not, a new backend will be assigned. For example: ```toml @@ -378,12 +388,9 @@ For example: sticky = true ``` -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. +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 pathappended 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 @@ -466,7 +473,9 @@ Each item takes precedence over the item below it: It means that arguments override configuration file, and key-value store overrides arguments. -Note that the provider-enabling argument parameters (e.g., `--docker`) set all default values for the specific provider. It must not be used if a configuration source with less precedence wants to set a non-default provider value. +!!! note + the provider-enabling argument parameters (e.g., `--docker`) set all default values for the specific provider. + It must not be used if a configuration source with less precedence wants to set a non-default provider value. #### Configuration file @@ -532,18 +541,19 @@ traefik [command] [--flag=flag_argument] 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. +- `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. - `bug`: The easiest way to submit a pre-filled issue. -- `healthcheck`: Calls traefik `/ping` to check health. +- `healthcheck`: Calls Traefik `/ping` to check health. Each command may have related flags. + All those related flags will be displayed with : ```bash traefik [command] --help ``` -Note that each command is described at the beginning of the help section: +Each command is described at the beginning of the help section: ```bash traefik --help @@ -557,16 +567,20 @@ Here is the easiest way to submit a pre-filled issue on [Træfik GitHub](https:/ traefik bug ``` -See https://www.youtube.com/watch?v=Lyz62L8m93I. +Watch [this demo](https://www.youtube.com/watch?v=Lyz62L8m93I). ### Command: healthcheck This command allows to check the health of Traefik. Its exit status is `0` if Traefik is healthy and `1` if it is unhealthy. + This can be used with Docker [HEALTHCHECK](https://docs.docker.com/engine/reference/builder/#healthcheck) instruction or any other health check orchestration mechanism. -Note: the `web` provider must be enabled to allow `/ping` calls by the `healthcheck` command. +!!! note + The [`web` provider](/configuration/backends/web) must be enabled to allow `/ping` calls by the `healthcheck` command. ```bash -$ traefik healthcheck +traefik healthcheck +``` +```bash OK: http://:8082/ping ``` diff --git a/docs/benchmarks.md b/docs/benchmarks.md index d320015c6..032810e93 100644 --- a/docs/benchmarks.md +++ b/docs/benchmarks.md @@ -14,7 +14,7 @@ I used 4 VMs for the tests with the following configuration: ## Setup 1. One VM used to launch the benchmarking tool [wrk](https://github.com/wg/wrk) -2. One VM for traefik (v1.0.0-beta.416) / nginx (v1.4.6) +2. One VM for Traefik (v1.0.0-beta.416) / nginx (v1.4.6) 3. Two VMs for 2 backend servers in go [whoami](https://github.com/emilevauge/whoamI/) Each VM has been tuned using the following limits: @@ -182,7 +182,8 @@ Requests/sec: 33591.67 Transfer/sec: 4.97MB ``` -### traefik: +### Traefik: + ```shell wrk -t20 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-traefik:8000/bench Running 1m test @ http://IP-traefik:8000/bench @@ -209,5 +210,5 @@ Not bad for young project :) ! Some areas of possible improvements: - Use [GO_REUSEPORT](https://github.com/kavu/go_reuseport) listener -- Run a separate server instance per CPU core with `GOMAXPROCS=1` (it appears during benchmarks that there is a lot more context switches with traefik than with nginx) +- Run a separate server instance per CPU core with `GOMAXPROCS=1` (it appears during benchmarks that there is a lot more context switches with Traefik than with nginx) diff --git a/docs/configuration/acme.md b/docs/configuration/acme.md index 5feade558..1e259c460 100644 --- a/docs/configuration/acme.md +++ b/docs/configuration/acme.md @@ -1,33 +1,31 @@ -## ACME (Let's Encrypt) configuration +# ACME (Let's Encrypt) configuration + +See also [Let's Encrypt examples](/user-guide/examples/#lets-encrypt-support) and [Docker & Let's Encrypt user guide](/user-guide/docker-and-lets-encrypt). + +## Configuration ```toml -# Sample entrypoint configuration when using ACME +# Sample entrypoint configuration when using ACME. [entryPoints] [entryPoints.https] address = ":443" [entryPoints.https.tls] -# Enable ACME (Let's Encrypt): automatic SSL +# Enable ACME (Let's Encrypt): automatic SSL. [acme] -# Email address used for registration +# Email address used for registration. # # Required # email = "test@traefik.io" # File or key used for certificates storage. -# WARNING, if you use Traefik in Docker, you have 2 options: -# - create a file on your host and mount it as a volume -# storageFile = "acme.json" -# $ docker run -v "/my/host/acme.json:acme.json" traefik -# - mount the folder containing the file as a volume -# storageFile = "/etc/traefik/acme/acme.json" -# $ docker run -v "/my/host/acme:/etc/traefik/acme" traefik # # Required # -storage = "acme.json" # or "traefik/acme/account" if using KV store +storage = "acme.json" +# or `storage = "traefik/acme/account"` if using KV store. # Entrypoint to proxy acme challenge/apply certificates to. # WARNING, must point to an entrypoint on port 443 @@ -36,71 +34,49 @@ storage = "acme.json" # or "traefik/acme/account" if using KV store # entryPoint = "https" -# Use a DNS based acme challenge rather than external HTTPS access, e.g. for a firewalled server -# Select the provider that matches the DNS domain that will host the challenge TXT record, -# and provide environment variables with access keys to enable setting it: -# - cloudflare: CLOUDFLARE_EMAIL, CLOUDFLARE_API_KEY -# - digitalocean: DO_AUTH_TOKEN -# - dnsimple: DNSIMPLE_EMAIL, DNSIMPLE_OAUTH_TOKEN -# - dnsmadeeasy: DNSMADEEASY_API_KEY, DNSMADEEASY_API_SECRET -# - exoscale: EXOSCALE_API_KEY, EXOSCALE_API_SECRET -# - gandi: GANDI_API_KEY -# - linode: LINODE_API_KEY -# - manual: none, but run traefik interactively & turn on acmeLogging to see instructions & press Enter -# - namecheap: NAMECHEAP_API_USER, NAMECHEAP_API_KEY -# - rfc2136: RFC2136_TSIG_KEY, RFC2136_TSIG_SECRET, RFC2136_TSIG_ALGORITHM, RFC2136_NAMESERVER -# - route53: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, or configured user/instance IAM profile -# - dyn: DYN_CUSTOMER_NAME, DYN_USER_NAME, DYN_PASSWORD -# - vultr: VULTR_API_KEY -# - ovh: OVH_ENDPOINT, OVH_APPLICATION_KEY, OVH_APPLICATION_SECRET, OVH_CONSUMER_KEY -# - pdns: PDNS_API_KEY, PDNS_API_URL +# Use a DNS based acme challenge rather than external HTTPS access +# # # Optional # # dnsProvider = "digitalocean" -# By default, the dnsProvider will verify the TXT DNS challenge record before letting ACME verify +# By default, the dnsProvider will verify the TXT DNS challenge record before letting ACME verify. # If delayDontCheckDNS is greater than zero, avoid this & instead just wait so many seconds. -# Useful if internal networks block external DNS queries +# Useful if internal networks block external DNS queries. # # Optional # # delayDontCheckDNS = 0 -# If true, display debug log messages from the acme client library +# If true, display debug log messages from the acme client library. # # Optional # # acmeLogging = true -# Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate. -# WARNING, TLS handshakes will be slow when requesting a hostname certificate for the first time, this can leads to DoS attacks. -# WARNING, Take note that Let's Encrypt have rate limiting: https://letsencrypt.org/docs/rate-limits +# Enable on demand certificate. # # Optional # # onDemand = true -# Enable certificate generation on frontends Host rules. This will request a certificate from Let's Encrypt for each frontend with a Host rule. -# For example, a rule Host:test1.traefik.io,test2.traefik.io will request a certificate with main domain test1.traefik.io and SAN test2.traefik.io. +# Enable certificate generation on frontends Host rules. # # Optional # -# OnHostRule = true +# onHostRule = true -# CA server to use -# Uncomment the line to run on the staging let's encrypt server -# Leave comment to go to prod +# CA server to use. +# - Uncomment the line to run on the staging let's encrypt server. +# - Leave comment to go to prod. # # Optional # # caServer = "https://acme-staging.api.letsencrypt.org/directory" -# Domains list -# You can provide SANs (alternative domains) to each main domain -# All domains must have A/AAAA records pointing to Traefik -# WARNING, Take note that Let's Encrypt have rate limiting: https://letsencrypt.org/docs/rate-limits -# Each domain & SANs will lead to a certificate request. +# Domains list. +# # [[acme.domains]] # main = "local1.com" # sans = ["test1.local1.com", "test2.local1.com"] @@ -112,3 +88,151 @@ entryPoint = "https" # [[acme.domains]] # main = "local4.com" ``` + +### `storage` + +```toml +[acme] +# ... +storage = "acme.json" +# ... +``` + +File or key used for certificates storage. + +**WARNING** If you use Traefik in Docker, you have 2 options: + +- create a file on your host and mount it as a volume: +```toml +storage = "acme.json" +``` +```bash +docker run -v "/my/host/acme.json:acme.json" traefik +``` + +- mount the folder containing the file as a volume +```toml +storage = "/etc/traefik/acme/acme.json" +``` +```bash +docker run -v "/my/host/acme:/etc/traefik/acme" traefik +``` + +### `dnsProvider` + +```toml +[acme] +# ... +dnsProvider = "digitalocean" +# ... +``` + +Use a DNS based acme challenge rather than external HTTPS access, e.g. for a firewalled server. + +Select the provider that matches the DNS domain that will host the challenge TXT record, and provide environment variables with access keys to enable setting it: + +| Provider | Configuration | +|----------------------------------------------|-----------------------------------------------------------------------------------------------------------| +| [Cloudflare](https://www.cloudflare.com) | `CLOUDFLARE_EMAIL`, `CLOUDFLARE_API_KEY` | +| [DigitalOcean](https://www.digitalocean.com) | `DO_AUTH_TOKEN` | +| [DNSimple](https://dnsimple.com) | `DNSIMPLE_EMAIL`, `DNSIMPLE_OAUTH_TOKEN` | +| [DNS Made Easy](https://dnsmadeeasy.com) | `DNSMADEEASY_API_KEY`, `DNSMADEEASY_API_SECRET` | +| [Exoscale](https://www.exoscale.ch) | `EXOSCALE_API_KEY`, `EXOSCALE_API_SECRET` | +| [Gandi](https://www.gandi.net) | `GANDI_API_KEY` | +| [Linode](https://www.linode.com) | `LINODE_API_KEY` | +| manual | none, but run Traefik interactively & turn on `acmeLogging` to see instructions & press Enter. | +| [Namecheap](https://www.namecheap.com) | `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY` | +| RFC2136 | `RFC2136_TSIG_KEY`, `RFC2136_TSIG_SECRET`, `RFC2136_TSIG_ALGORITHM`, `RFC2136_NAMESERVER` | +| [Route 53](https://aws.amazon.com/route53/) | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, or configured user/instance IAM profile. | +| [dyn](https://dyn.com) | `DYN_CUSTOMER_NAME`, `DYN_USER_NAME`, `DYN_PASSWORD` | +| [VULTR](https://www.vultr.com) | `VULTR_API_KEY` | +| [OVH](https://www.ovh.com) | `OVH_ENDPOINT`, `OVH_APPLICATION_KEY`, `OVH_APPLICATION_SECRET`, `OVH_CONSUMER_KEY` | +| [pdns](https://www.powerdns.com) | `PDNS_API_KEY`, `PDNS_API_URL` | + +### `delayDontCheckDNS` + +```toml +[acme] +# ... +delayDontCheckDNS = 0 +# ... +``` + +By default, the dnsProvider will verify the TXT DNS challenge record before letting ACME verify. +If `delayDontCheckDNS` is greater than zero, avoid this & instead just wait so many seconds. + +Useful if internal networks block external DNS queries. + +### `onDemand` + +```toml +[acme] +# ... +onDemand = true +# ... +``` + +Enable on demand certificate. + +This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate. + +!!! warning + TLS handshakes will be slow when requesting a hostname certificate for the first time, this can leads to DoS attacks. + +!!! warning + Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits) + +### `onHostRule` + +```toml +[acme] +# ... +onHostRule = true +# ... +``` + +Enable certificate generation on frontends Host rules. + +This will request a certificate from Let's Encrypt for each frontend with a Host rule. + +For example, a rule `Host:test1.traefik.io,test2.traefik.io` will request a certificate with main domain `test1.traefik.io` and SAN `test2.traefik.io`. + +### `caServer` + +```toml +[acme] +# ... +caServer = "https://acme-staging.api.letsencrypt.org/directory" +# ... +``` + +CA server to use. + +- Uncomment the line to run on the staging Let's Encrypt server. +- Leave comment to go to prod. + +### `domains` + +```toml +[acme] +# ... +[[acme.domains]] +main = "local1.com" +sans = ["test1.local1.com", "test2.local1.com"] +[[acme.domains]] +main = "local2.com" +sans = ["test1.local2.com", "test2.local2.com"] +[[acme.domains]] +main = "local3.com" +[[acme.domains]] +main = "local4.com" +# ... +``` + +You can provide SANs (alternative domains) to each main domain. +All domains must have A/AAAA records pointing to Traefik. + +!!! warning + Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits). + +Each domain & SANs will lead to a certificate request. diff --git a/docs/configuration/backends/boltdb.md b/docs/configuration/backends/boltdb.md index e9e64d799..05423ef43 100644 --- a/docs/configuration/backends/boltdb.md +++ b/docs/configuration/backends/boltdb.md @@ -1,36 +1,59 @@ # BoltDB Backend -Træfik can be configured to use BoltDB as a backend configuration: +Træfik can be configured to use BoltDB as a backend configuration. ```toml ################################################################ # BoltDB configuration backend ################################################################ -# Enable BoltDB configuration backend +# Enable BoltDB configuration backend. [boltdb] -# BoltDB file +# BoltDB file. # # Required +# Default: "127.0.0.1:4001" # endpoint = "/my.db" -# Enable watch BoltDB changes +# Enable watch BoltDB changes. # # Optional +# Default: true # watch = true # Prefix used for KV store. # # Optional +# Default: "/traefik" # prefix = "/traefik" -# Override default configuration template. For advanced users :) +# Override default configuration template. +# For advanced users :) # # Optional # filename = "boltdb.tmpl" + +# Use BoltDB user/pass authentication. +# +# Optional +# +# username = foo +# password = bar + +# Enable BoltDB TLS connection. +# +# Optional +# +# [boltdb.tls] +# ca = "/etc/ssl/ca.crt" +# cert = "/etc/ssl/boltdb.crt" +# key = "/etc/ssl/boltdb.key" +# insecureskipverify = true ``` + +To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific). diff --git a/docs/configuration/backends/consul.md b/docs/configuration/backends/consul.md index 5b1c7b430..d4d762918 100644 --- a/docs/configuration/backends/consul.md +++ b/docs/configuration/backends/consul.md @@ -2,90 +2,101 @@ ## Consul Key-Value backend -Træfik can be configured to use Consul as a backend configuration: +Træfik can be configured to use Consul as a backend configuration. ```toml ################################################################ # Consul KV configuration backend ################################################################ -# Enable Consul KV configuration backend +# Enable Consul KV configuration backend. [consul] -# Consul server endpoint +# Consul server endpoint. # # Required +# Default: "127.0.0.1:8500" # endpoint = "127.0.0.1:8500" -# Enable watch Consul changes +# Enable watch Consul changes. # # Optional +# Default: true # watch = true # Prefix used for KV store. # # Optional +# Default: traefik # prefix = "traefik" -# Override default configuration template. For advanced users :) +# Override default configuration template. +# For advanced users :) # # Optional # # filename = "consul.tmpl" -# Enable consul TLS connection +# Use Consul user/pass authentication. # # Optional # -# [consul.tls] -# ca = "/etc/ssl/ca.crt" -# cert = "/etc/ssl/consul.crt" -# key = "/etc/ssl/consul.key" -# insecureskipverify = true +# username = foo +# password = bar + +# Enable Consul TLS connection. +# +# Optional +# +# [consul.tls] +# ca = "/etc/ssl/ca.crt" +# cert = "/etc/ssl/consul.crt" +# key = "/etc/ssl/consul.key" +# insecureskipverify = true ``` -Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on traefik KV structure. +To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific). -## Consul catalog backend +Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on Traefik KV structure. -Træfik can be configured to use service discovery catalog of Consul as a backend configuration: + +## 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 +# Enable Consul Catalog configuration backend. [consulCatalog] -# Consul server endpoint +# Consul server endpoint. # # Required +# Default: "127.0.0.1:8500" # endpoint = "127.0.0.1:8500" -# Default domain used. -# -# Optional -# -domain = "consul.localhost" - -# Expose Consul catalog services by default in traefik +# Expose Consul catalog services by default in Traefik. # # Optional +# Default: true # exposedByDefault = false -# Prefix for Consul catalog tags +# Prefix for Consul catalog tags. # # Optional +# Default: "traefik" # prefix = "traefik" -# Default frontEnd Rule for Consul services +# Default frontEnd Rule for Consul services. # # The format is a Go Template with: # - ".ServiceName", ".Domain" and ".Attributes" available @@ -93,13 +104,18 @@ prefix = "traefik" # - "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. +This backend will create routes matching on hostname based on the service name used in Consul. -Additional settings can be defined using Consul Catalog tags: +To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific). + +### Tags + +Additional settings can be defined using Consul Catalog tags. | Tag | Description | |---------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| diff --git a/docs/configuration/backends/docker.md b/docs/configuration/backends/docker.md index 12de434a2..f10db04a3 100644 --- a/docs/configuration/backends/docker.md +++ b/docs/configuration/backends/docker.md @@ -9,7 +9,7 @@ Træfik can be configured to use Docker as a backend configuration. # Docker configuration backend ################################################################ -# Enable Docker configuration backend +# Enable Docker configuration backend. [docker] # Docker server endpoint. Can be a tcp or a unix socket endpoint. @@ -25,41 +25,43 @@ endpoint = "unix:///var/run/docker.sock" # domain = "docker.localhost" -# Enable watch docker changes +# Enable watch docker changes. # # Optional # watch = true -# Override default configuration template. For advanced users :) +# Override default configuration template. +# For advanced users :) # # Optional # # filename = "docker.tmpl" -# Expose containers by default in traefik -# If set to false, containers that don't have `traefik.enable=true` will be ignored +# Expose containers by default in Traefik. +# If set to false, containers that don't have `traefik.enable=true` will be ignored. # # Optional # Default: true # exposedbydefault = true -# Use the IP address from the binded port instead of the inner network one. For specific use-case :) +# Use the IP address from the binded port instead of the inner network one. +# For specific use-case :) # # Optional # Default: false # usebindportip = true -# Use Swarm Mode services as data provider +# Use Swarm Mode services as data provider. # # Optional # Default: false # swarmmode = false -# Enable docker TLS connection +# Enable docker TLS connection. # # Optional # @@ -70,6 +72,9 @@ swarmmode = false # insecureskipverify = true ``` +To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific). + + ## Docker Swarm Mode ```toml @@ -77,10 +82,11 @@ swarmmode = false # Docker Swarmmode configuration backend ################################################################ -# Enable Docker configuration backend +# Enable Docker configuration backend. [docker] -# Docker server endpoint. Can be a tcp or a unix socket endpoint. +# Docker server endpoint. +# Can be a tcp or a unix socket endpoint. # # Required # Default: "unix:///var/run/docker.sock" @@ -95,40 +101,52 @@ endpoint = "tcp://127.0.0.1:2375" # domain = "docker.localhost" -# Enable watch docker changes +# Enable watch docker changes. # # Optional +# Default: true # watch = true -# Use Docker Swarm Mode as data provider +# Use Docker Swarm Mode as data provider. +# +# Optional +# Default: false +# swarmmode = true -# Override default configuration template. For advanced users :) +# Override default configuration template. +# For advanced users :) # # Optional # # filename = "docker.tmpl" -# Expose services by default in traefik +# Expose services by default in Traefik. # # Optional # Default: true # exposedbydefault = false -# Enable docker TLS connection +# Enable docker TLS connection. # # Optional # -# [swarm.tls] +# [docker.tls] # ca = "/etc/ssl/ca.crt" # cert = "/etc/ssl/docker.crt" # key = "/etc/ssl/docker.key" # insecureskipverify = true ``` -## Labels can be used on containers to override default behaviour +To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific). + +## Labels: overriding default behaviour + +### On Containers + +Labels can be used on containers to override default behaviour. | Label | Description | |---------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| @@ -149,9 +167,11 @@ exposedbydefault = false | `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.frontend.whitelistSourceRange:RANGE` | List of IP-Ranges which are allowed to access. An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. | -| `traefik.docker.network` | Set the docker network to use for connections to this container. If a container is linked to several networks, be sure to set the proper network name (you can check with docker inspect ) otherwise it will randomly pick one (depending on how docker is returning them). For instance when deploying docker `stack` from compose files, the compose defined networks will be prefixed with the `stack` name. | +| `traefik.docker.network` | Set the docker network to use for connections to this container. If a container is linked to several networks, be sure to set the proper network name (you can check with `docker inspect `) otherwise it will randomly pick one (depending on how docker is returning them). For instance when deploying docker `stack` from compose files, the compose defined networks will be prefixed with the `stack` name. | -### Services labels can be used for overriding default behaviour +### On Service + +Services labels can be used for overriding default behaviour | Label | Description | |---------------------------------------------------|--------------------------------------------------------------------------------------------------| diff --git a/docs/configuration/backends/dynamodb.md b/docs/configuration/backends/dynamodb.md index da4eb62e2..23224f719 100644 --- a/docs/configuration/backends/dynamodb.md +++ b/docs/configuration/backends/dynamodb.md @@ -1,63 +1,71 @@ # DynamoDB Backend -Træfik can be configured to use Amazon DynamoDB as a backend configuration: +Træfik can be configured to use Amazon DynamoDB as a backend configuration. + +## Configuration ```toml ################################################################ # DynamoDB configuration backend ################################################################ -# Enable DynamoDB configuration backend +# Enable DynamoDB configuration backend. [dynamodb] -# DyanmoDB Table Name -# -# Optional -# -TableName = "traefik" - -# Enable watch DynamoDB changes -# -# Optional -# -Watch = true - -# Polling interval (in seconds) -# -# Optional -# -RefreshSeconds = 15 - -# Region to use when connecting to AWS +# Region to use when connecting to AWS. # # Required # -Region = "us-west-1" +region = "us-west-1" -# AccessKeyID to use when connecting to AWS +# DyanmoDB Table Name. +# +# Optional +# Default: "traefik" +# +tableName = "traefik" + +# Enable watch DynamoDB changes. +# +# Optional +# Default: true +# +watch = true + +# Polling interval (in seconds). +# +# Optional +# Default: 15 +# +refreshSeconds = 15 + +# AccessKeyID to use when connecting to AWS. # # Optional # -AccessKeyID = "abc" +accessKeyID = "abc" -# SecretAccessKey to use when connecting to AWS +# SecretAccessKey to use when connecting to AWS. # # Optional # -SecretAccessKey = "123" +secretAccessKey = "123" -# Endpoint of local dynamodb instance for testing +# Endpoint of local dynamodb instance for testing? # # Optional # -Endpoint = "http://localhost:8080" +endpoint = "http://localhost:8080" ``` +## Table Items + Items in the `dynamodb` table must have three attributes: - `id` (string): The id is the primary key. - `name`(string): The name is used as the name of the frontend or backend. -- `frontend` or `backend` (map): This attribute's structure matches exactly the structure of a Frontend or Backend type in traefik. +- `frontend` or `backend` (map): This attribute's structure matches exactly the structure of a Frontend or Backend type in Traefik. See `types/types.go` for details. The presence or absence of this attribute determines its type. So an item should never have both a `frontend` and a `backend` attribute. + diff --git a/docs/configuration/backends/ecs.md b/docs/configuration/backends/ecs.md index 04a707a4b..6b7a148cb 100644 --- a/docs/configuration/backends/ecs.md +++ b/docs/configuration/backends/ecs.md @@ -1,102 +1,99 @@ # ECS Backend -Træfik can be configured to use Amazon ECS as a backend configuration: +Træfik can be configured to use Amazon ECS as a backend configuration. + +## Configuration ```toml ################################################################ # ECS configuration backend ################################################################ -# Enable ECS configuration backend +# Enable ECS configuration backend. [ecs] -# ECS Cluster Name +# ECS Cluster Name. # -# DEPRECATED - Please use Clusters +# DEPRECATED - Please use `clusters`. # -Cluster = "default" +cluster = "default" -# ECS Clusters Name +# ECS Clusters Name. # # Optional # Default: ["default"] # -Clusters = ["default"] +clusters = ["default"] -# Enable watch ECS changes +# Enable watch ECS changes. # # Optional # Default: true # -Watch = true +watch = true -# Enable auto discover ECS clusters +# Default domain used. +# +# Optional +# Default: "" +# +domain = "ecs.localhost" + +# Enable auto discover ECS clusters. # # Optional # Default: false # -AutoDiscoverClusters = false +autoDiscoverClusters = false -# Polling interval (in seconds) +# Polling interval (in seconds). # # Optional # Default: 15 # -RefreshSeconds = 15 +refreshSeconds = 15 -# Expose ECS services by default in traefik +# Expose ECS services by default in Traefik. # # Optional # Default: true # -ExposedByDefault = false +exposedByDefault = false -# Region to use when connecting to AWS +# Region to use when connecting to AWS. # # Optional # -Region = "us-east-1" +region = "us-east-1" -# AccessKeyID to use when connecting to AWS +# AccessKeyID to use when connecting to AWS. # # Optional # -AccessKeyID = "abc" +accessKeyID = "abc" -# SecretAccessKey to use when connecting to AWS +# SecretAccessKey to use when connecting to AWS. # # Optional # -SecretAccessKey = "123" +secretAccessKey = "123" -# Override default configuration template. For advanced users :) +# Override default configuration template. +# For advanced users :) # # Optional # # filename = "ecs.tmpl" ``` -Labels can be used on task containers to override default behaviour: - -| Label | Description | -|---------------------------------------------------|------------------------------------------------------------------------------------------| -| `traefik.protocol=https` | override the default `http` protocol | -| `traefik.weight=10` | assign this weight to the container | -| `traefik.enable=false` | disable this container in Træfik | -| `traefik.backend.loadbalancer.method=drr` | override the default `wrr` load balancer algorithm | -| `traefik.backend.loadbalancer.sticky=true` | enable backend sticky sessions | -| `traefik.frontend.rule=Host:test.traefik.io` | override the default frontend rule (Default: `Host:{containerName}.{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` | - If `AccessKeyID`/`SecretAccessKey` is not given credentials will be resolved in the following order: - From environment variables; `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_SESSION_TOKEN`. - Shared credentials, determined by `AWS_PROFILE` and `AWS_SHARED_CREDENTIALS_FILE`, defaults to `default` and `~/.aws/credentials`. - EC2 instance role or ECS task role +## Policy + Træfik needs the following policy to read ECS information: ```json @@ -122,3 +119,20 @@ Træfik needs the following policy to read ECS information: ] } ``` + +## Labels: overriding default behaviour + +Labels can be used on task containers to override default behaviour: + +| Label | Description | +|---------------------------------------------------|------------------------------------------------------------------------------------------| +| `traefik.protocol=https` | override the default `http` protocol | +| `traefik.weight=10` | assign this weight to the container | +| `traefik.enable=false` | disable this container in Træfik | +| `traefik.backend.loadbalancer.method=drr` | override the default `wrr` load balancer algorithm | +| `traefik.backend.loadbalancer.sticky=true` | enable backend sticky sessions | +| `traefik.frontend.rule=Host:test.traefik.io` | override the default frontend rule (Default: `Host:{containerName}.{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` | diff --git a/docs/configuration/backends/etcd.md b/docs/configuration/backends/etcd.md index 568bcfb94..03c871109 100644 --- a/docs/configuration/backends/etcd.md +++ b/docs/configuration/backends/etcd.md @@ -1,55 +1,61 @@ # Etcd Backend -Træfik can be configured to use Etcd as a backend configuration: +Træfik can be configured to use Etcd as a backend configuration. ```toml ################################################################ # Etcd configuration backend ################################################################ -# Enable Etcd configuration backend +# Enable Etcd configuration backend. [etcd] -# Etcd server endpoint +# Etcd server endpoint. # # Required +# Default: "127.0.0.1:2379" # endpoint = "127.0.0.1:2379" -# Enable watch Etcd changes +# Enable watch Etcd changes. # # Optional +# Default: true # watch = true # Prefix used for KV store. # # Optional +# Default: "/traefik" # prefix = "/traefik" -# Override default configuration template. For advanced users :) +# Override default configuration template. +# For advanced users :) # # Optional # # filename = "etcd.tmpl" -# Use etcd user/pass authentication +# Use etcd user/pass authentication. # # Optional # # username = foo # password = bar -# Enable etcd TLS connection +# Enable etcd TLS connection. # # Optional # -# [etcd.tls] -# ca = "/etc/ssl/ca.crt" -# cert = "/etc/ssl/etcd.crt" -# key = "/etc/ssl/etcd.key" -# insecureskipverify = true +# [etcd.tls] +# ca = "/etc/ssl/ca.crt" +# cert = "/etc/ssl/etcd.crt" +# key = "/etc/ssl/etcd.key" +# insecureskipverify = true ``` -Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on traefik KV structure. +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. diff --git a/docs/configuration/backends/eureka.md b/docs/configuration/backends/eureka.md index 7881167ef..2e7ab8173 100644 --- a/docs/configuration/backends/eureka.md +++ b/docs/configuration/backends/eureka.md @@ -1,29 +1,30 @@ # Eureka Backend -Træfik can be configured to use Eureka as a backend configuration: +Træfik can be configured to use Eureka as a backend configuration. ```toml ################################################################ # Eureka configuration backend ################################################################ -# Enable Eureka configuration backend +# Enable Eureka configuration backend. [eureka] # Eureka server endpoint. -# endpoint := "http://my.eureka.server/eureka" # # Required # endpoint = "http://my.eureka.server/eureka" -# Override default configuration time between refresh +# Override default configuration time between refresh. # # Optional -# default 30s +# Default: 30s +# delay = "1m" -# Override default configuration template. For advanced users :) +# Override default configuration template. +# For advanced users :) # # Optional # diff --git a/docs/configuration/backends/file.md b/docs/configuration/backends/file.md index 08347757d..16f83d528 100644 --- a/docs/configuration/backends/file.md +++ b/docs/configuration/backends/file.md @@ -1,6 +1,12 @@ # File Backends -Like any other reverse proxy, Træfik can be configured with a file. You have three choices: +Like any other reverse proxy, Træfik can be configured with a file. + +You have three choices: + +- [Simple](/configuration/backends/file/#simple) +- [Rules in a Separate File](/configuration/backends/file/#rules-in-a-separate-file) +- [Multiple `.toml` Files](/configuration/backends/file/#multiple-toml-files) ## Simple @@ -145,7 +151,7 @@ filename = "rules.toml" rule = "Path:/test" ``` -## Multiple .toml Files +## Multiple `.toml` Files You could have multiple `.toml` files in a directory: diff --git a/docs/configuration/backends/kubernetes.md b/docs/configuration/backends/kubernetes.md index 822d14f4a..9b549403d 100644 --- a/docs/configuration/backends/kubernetes.md +++ b/docs/configuration/backends/kubernetes.md @@ -1,30 +1,23 @@ # Kubernetes Ingress Backend -Træfik can be configured to use Kubernetes Ingress as a backend configuration: +Træfik can be configured to use Kubernetes Ingress as a backend configuration. + +See also [Kubernetes user guide](/user-guide/kubernetes). + + +## Configuration ```toml ################################################################ # Kubernetes Ingress configuration backend ################################################################ -# Enable Kubernetes Ingress configuration backend + +# Enable Kubernetes Ingress configuration backend. [kubernetes] -# Kubernetes server endpoint +# Kubernetes server endpoint. # -# When deployed as a replication controller in Kubernetes, Traefik will use -# the environment variables KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT -# to construct the endpoint. -# Secure token will be found in /var/run/secrets/kubernetes.io/serviceaccount/token -# and SSL CA cert in /var/run/secrets/kubernetes.io/serviceaccount/ca.crt -# -# The endpoint may be given to override the environment variable values. -# -# When the environment variables are not found, Traefik will try to connect to -# the Kubernetes API server with an external-cluster client. In this case, the -# endpoint is required. Specifically, it may be set to the URL used by -# `kubectl proxy` to connect to a Kubernetes cluster from localhost. -# -# Optional for in-cluster configuration, required otherwise +# Optional for in-cluster configuration, required otherwise. # Default: empty # # endpoint = "http://localhost:8080" @@ -36,8 +29,8 @@ Træfik can be configured to use Kubernetes Ingress as a backend configuration: # # token = "my token" -# Path to the certificate authority file used for the Kubernetes client -# configuration. +# Path to the certificate authority file. +# Used for the Kubernetes client configuration. # # Optional # Default: empty @@ -52,35 +45,70 @@ Træfik can be configured to use Kubernetes Ingress as a backend configuration: # namespaces = ["default", "production"] # Ingress label selector to identify Ingress objects that should be processed. -# See https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors for details. # # Optional # Default: empty (process all Ingresses) # # labelselector = "A and not B" + +# Disable PassHost Headers. +# +# Optional +# Default: false +# +# disablePassHostHeaders = true ``` +### `endpoint` + +The Kubernetes server endpoint. + +When deployed as a replication controller in Kubernetes, Traefik will use the environment variables `KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT` to construct the endpoint. + +Secure token will be found in `/var/run/secrets/kubernetes.io/serviceaccount/token` and SSL CA cert in `/var/run/secrets/kubernetes.io/serviceaccount/ca.crt` + +The endpoint may be given to override the environment variable values. + +When the environment variables are not found, Traefik will try to connect to the Kubernetes API server with an external-cluster client. +In this case, the endpoint is required. +Specifically, it may be set to the URL used by `kubectl proxy` to connect to a Kubernetes cluster from localhost. + +### `labelselector` + +Ingress label selector to identify Ingress objects that should be processed. + +See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. + + +## Annotations + Annotations can be used on containers to override default behaviour for the whole Ingress resource: -- `traefik.frontend.rule.type: PathPrefixStrip`: override the default frontend rule type (Default: `PathPrefix`). -- `traefik.frontend.priority: 3`: override the default frontend rule priority (Default: `len(Path)`). +- `traefik.frontend.rule.type: PathPrefixStrip` + Override the default frontend rule type. Default: `PathPrefix`. +- `traefik.frontend.priority: 3` + Override the default frontend rule priority. Annotations can be used on the Kubernetes service to override default behaviour: -- `traefik.backend.loadbalancer.method=drr`: override the default `wrr` load balancer algorithm -- `traefik.backend.loadbalancer.sticky=true`: enable backend sticky sessions +- `traefik.backend.loadbalancer.method=drr` + Override the default `wrr` load balancer algorithm +- `traefik.backend.loadbalancer.sticky=true` + Enable backend sticky sessions You can find here an example [ingress](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheese-ingress.yaml) and [replication controller](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/traefik.yaml). -Additionally, an annotation can be used on Kubernetes services to set the [circuit breaker expression](https://docs.traefik.io/basics/#backends) for a backend. +Additionally, an annotation can be used on Kubernetes services to set the [circuit breaker expression](/basics/#backends) for a backend. -- `traefik.backend.circuitbreaker: `: set the circuit breaker expression for the backend (Default: nil). +- `traefik.backend.circuitbreaker: ` + Set the circuit breaker expression for the backend. Default: `nil`. -As known from nginx when used as Kubernetes Ingress Controller, a List of IP-Ranges which are allowed to access can be configured by using an ingress annotation: +As known from nginx when used as Kubernetes Ingress Controller, a list of IP-Ranges which are allowed to access can be configured by using an ingress annotation: - `ingress.kubernetes.io/whitelist-source-range: "1.2.3.0/24, fe80::/16"` -An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. +An unset or empty list allows all Source-IPs to access. +If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. ### Authentication @@ -89,7 +117,8 @@ Is possible to add additional authentication annotations in the Ingress rule. The source of the authentication is a secret that contains usernames and passwords inside the the key auth. - `ingress.kubernetes.io/auth-type`: `basic` -- `ingress.kubernetes.io/auth-secret`: contains the usernames and passwords with access to the paths defined in the Ingress Rule. +- `ingress.kubernetes.io/auth-secret` + Contains the usernames and passwords with access to the paths defined in the Ingress Rule. The secret must be created in the same namespace as the Ingress rule. diff --git a/docs/configuration/backends/marathon.md b/docs/configuration/backends/marathon.md index ce688ce5a..d67513c7a 100644 --- a/docs/configuration/backends/marathon.md +++ b/docs/configuration/backends/marathon.md @@ -1,26 +1,33 @@ # Marathon Backend -Træfik can be configured to use Marathon as a backend configuration: +Træfik can be configured to use Marathon as a backend configuration. + +See also [Marathon user guide](/user-guide/marathon). + + +## Configuration ```toml ################################################################ # Mesos/Marathon configuration backend ################################################################ -# Enable Marathon configuration backend +# Enable Marathon configuration backend. [marathon] # Marathon server endpoint. # You can also specify multiple endpoint for Marathon: -# endpoint := "http://10.241.1.71:8080,10.241.1.72:8080,10.241.1.73:8080" +# endpoint = "http://10.241.1.71:8080,10.241.1.72:8080,10.241.1.73:8080" # # Required +# Default: "http://127.0.0.1:8080" # endpoint = "http://127.0.0.1:8080" -# Enable watch Marathon changes +# Enable watch Marathon changes. # # Optional +# Default: true # watch = true @@ -31,20 +38,21 @@ watch = true # domain = "marathon.localhost" -# Override default configuration template. For advanced users :) +# Override default configuration template. +# For advanced users :) # # Optional # # filename = "marathon.tmpl" -# Expose Marathon apps by default in traefik +# Expose Marathon apps by default in Traefik. # # Optional # Default: true # -# exposedByDefault = true +# exposedByDefault = false -# Convert Marathon groups to subdomains +# Convert Marathon groups to subdomains. # Default behavior: /foo/bar/myapp => foo-bar-myapp.{defaultDomain} # with groupsAsSubDomains enabled: /foo/bar/myapp => myapp.bar.foo.{defaultDomain} # @@ -53,52 +61,54 @@ domain = "marathon.localhost" # # groupsAsSubDomains = true -# Enable compatibility with marathon-lb labels +# Enable compatibility with marathon-lb labels. # # Optional # Default: false # # marathonLBCompatibility = true -# Enable Marathon basic authentication +# Enable Marathon basic authentication. # # Optional # -# [marathon.basic] -# httpBasicAuthUser = "foo" -# httpBasicPassword = "bar" +# [marathon.basic] +# httpBasicAuthUser = "foo" +# httpBasicPassword = "bar" # TLS client configuration. https://golang.org/pkg/crypto/tls/#Config # # Optional # -# [marathon.TLS] -# CA = "/etc/ssl/ca.crt" -# Cert = "/etc/ssl/marathon.cert" -# Key = "/etc/ssl/marathon.key" -# InsecureSkipVerify = true +# [marathon.TLS] +# CA = "/etc/ssl/ca.crt" +# Cert = "/etc/ssl/marathon.cert" +# Key = "/etc/ssl/marathon.key" +# InsecureSkipVerify = true -# DCOSToken for DCOS environment, This will override the Authorization header +# DCOSToken for DCOS environment. +# This will override the Authorization header. # # Optional # # dcosToken = "xxxxxx" -# Override DialerTimeout +# Override DialerTimeout. # Amount of time to allow the Marathon provider to wait to open a TCP connection # to a Marathon master. # Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw -# values (digits). If no units are provided, the value is parsed assuming -# seconds. +# values (digits). +# If no units are provided, the value is parsed assuming seconds. # # Optional # Default: "60s" +# # dialerTimeout = "60s" # Set the TCP Keep Alive interval for the Marathon HTTP Client. # Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw -# values (digits). If no units are provided, the value is parsed assuming -# seconds. +# values (digits). +# If no units are provided, the value is parsed assuming seconds. # # Optional # Default: "10s" @@ -113,21 +123,28 @@ domain = "marathon.localhost" # Optional # Default: false # -# forceTaskHostname = false +# forceTaskHostname = true # Applications may define readiness checks which are probed by Marathon during -# deployments periodically and the results exposed via the API. Enabling the -# following parameter causes Traefik to filter out tasks whose readiness checks -# have not succeeded. -# Note that the checks are only valid at deployment times. See the Marathon -# guide for details. +# deployments periodically and the results exposed via the API. +# Enabling the following parameter causes Traefik to filter out tasks +# whose readiness checks have not succeeded. +# Note that the checks are only valid at deployment times. +# See the Marathon guide for details. # # Optional # Default: false # -# respectReadinessChecks = false +# respectReadinessChecks = true ``` +To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific). + + +## Labels: overriding default behaviour + +### On Containers + Labels can be used on containers to override default behaviour: | Label | Description | @@ -151,6 +168,8 @@ Labels can be used on containers to override default behaviour: | `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`. | +### On Services + If several ports need to be exposed from a container, the services labels can be used: | Label | Description | diff --git a/docs/configuration/backends/mesos.md b/docs/configuration/backends/mesos.md index 86484b80d..8f983d232 100644 --- a/docs/configuration/backends/mesos.md +++ b/docs/configuration/backends/mesos.md @@ -1,13 +1,13 @@ # Mesos Generic Backend -Træfik can be configured to use Mesos as a backend configuration: +Træfik can be configured to use Mesos as a backend configuration. ```toml ################################################################ # Mesos configuration backend ################################################################ -# Enable Mesos configuration backend +# Enable Mesos configuration backend. [mesos] # Mesos server endpoint. @@ -16,12 +16,14 @@ Træfik can be configured to use Mesos as a backend configuration: # endpoint = "zk://192.168.35.20:2181,192.168.35.21:2181,192.168.35.22:2181/mesos" # # Required +# Default: "http://127.0.0.1:5050" # endpoint = "http://127.0.0.1:8080" -# Enable watch Mesos changes +# Enable watch Mesos changes. # # Optional +# Default: true # watch = true @@ -32,18 +34,19 @@ watch = true # domain = "mesos.localhost" -# Override default configuration template. For advanced users :) +# Override default configuration template. +# For advanced users :) # # Optional # # filename = "mesos.tmpl" -# Expose Mesos apps by default in traefik +# Expose Mesos apps by default in Traefik. # # Optional -# Default: false +# Default: true # -# ExposedByDefault = true +# ExposedByDefault = false # TLS client configuration. https://golang.org/pkg/crypto/tls/#Config # @@ -52,30 +55,39 @@ domain = "mesos.localhost" # [mesos.TLS] # InsecureSkipVerify = true -# Zookeeper timeout (in seconds) +# Zookeeper timeout (in seconds). # # Optional # Default: 30 # # ZkDetectionTimeout = 30 -# Polling interval (in seconds) +# Polling interval (in seconds). # # Optional # Default: 30 # # RefreshSeconds = 30 -# IP sources (e.g. host, docker, mesos, rkt) +# IP sources (e.g. host, docker, mesos, rkt). # # Optional # # IPSources = "host" -# HTTP Timeout (in seconds) +# HTTP Timeout (in seconds). # # Optional # Default: 30 # # StateTimeoutSecond = "30" + +# Convert groups to subdomains. +# Default behavior: /foo/bar/myapp => foo-bar-myapp.{defaultDomain} +# with groupsAsSubDomains enabled: /foo/bar/myapp => myapp.bar.foo.{defaultDomain} +# +# Optional +# Default: false +# +# groupsAsSubDomains = true ``` diff --git a/docs/configuration/backends/rancher.md b/docs/configuration/backends/rancher.md index 784bc51a2..ffea34d83 100644 --- a/docs/configuration/backends/rancher.md +++ b/docs/configuration/backends/rancher.md @@ -1,13 +1,15 @@ # Rancher Backend -Træfik can be configured to use Rancher as a backend configuration: +Træfik can be configured to use Rancher as a backend configuration. + +## Global Configuration ```toml ################################################################ # Rancher configuration backend ################################################################ -# Enable Rancher configuration backend +# Enable Rancher configuration backend. [rancher] # Default domain used. @@ -17,27 +19,28 @@ Træfik can be configured to use Rancher as a backend configuration: # domain = "rancher.localhost" -# Enable watch Rancher changes +# Enable watch Rancher changes. # # Optional # Default: true # watch = true -# Polling interval (in seconds) +# Polling interval (in seconds). # # Optional +# Default: 15 # refreshSeconds = 15 -# Expose Rancher services by default in traefik +# Expose Rancher services by default in Traefik. # # Optional # Default: true # exposedByDefault = false -# Filter services with unhealthy states and inactive states +# Filter services with unhealthy states and inactive states. # # Optional # Default: false @@ -45,18 +48,20 @@ exposedByDefault = false enableServiceHealthFilter = true ``` +To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific). + ## Rancher Metadata Service ```toml # Enable Rancher metadata service configuration backend instead of the API -# configuration backend +# configuration backend. # # Optional # Default: false # [rancher.metadata] -# Poll the Rancher metadata service for changes every `rancher.RefreshSeconds` +# Poll the Rancher metadata service for changes every `rancher.RefreshSeconds`. # NOTE: this is less accurate than the default long polling technique which # will provide near instantaneous updates to Traefik # @@ -65,7 +70,7 @@ enableServiceHealthFilter = true # intervalPoll = true -# Prefix used for accessing the Rancher metadata service +# Prefix used for accessing the Rancher metadata service. # # Optional # Default: "/latest" @@ -76,24 +81,24 @@ prefix = "/2016-07-29" ## Rancher API ```toml -# Enable Rancher API configuration backend +# Enable Rancher API configuration backend. # # Optional # Default: true # [rancher.api] -# Endpoint to use when connecting to the Rancher API +# Endpoint to use when connecting to the Rancher API. # # Required endpoint = "http://rancherserver.example.com/v1" -# AccessKey to use when connecting to the Rancher API +# AccessKey to use when connecting to the Rancher API. # # Required accessKey = "XXXXXXXXXXXXXXXXXXXX" -# SecretKey to use when connecting to the Rancher API +# SecretKey to use when connecting to the Rancher API. # # Required secretKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -102,10 +107,10 @@ secretKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" !!! note If Traefik needs access to the Rancher API, you need to set the `endpoint`, `accesskey` and `secretkey` parameters. - To enable traefik to fetch information about the Environment it's deployed in only, you need to create an `Environment API Key`. + To enable Traefik to fetch information about the Environment it's deployed in only, you need to create an `Environment API Key`. This can be found within the API Key advanced options. -## Labels +## Labels: overriding default behaviour Labels can be used on task containers to override default behaviour: diff --git a/docs/configuration/backends/web.md b/docs/configuration/backends/web.md index b435294b8..2df072a8c 100644 --- a/docs/configuration/backends/web.md +++ b/docs/configuration/backends/web.md @@ -3,35 +3,33 @@ Træfik can be configured: - using a RESTful api. -- to use a metric system (like Prometheus, DataDog or StatD, ...). +- to use a monitoring system (like Prometheus, DataDog or StatD, ...). - to expose a Web Dashboard. ## Configuration ```toml +# Enable web backend. [web] -# Web administration port +# Web administration port. # # Required +# Default: ":8080" # address = ":8080" -# SSL certificate and key used +# SSL certificate and key used. # # Optional # -# CertFile = "traefik.crt" -# KeyFile = "traefik.key" +# certFile = "traefik.crt" +# keyFile = "traefik.key" -# Set REST API to read-only mode +# Set REST API to read-only mode. # # Optional -# ReadOnly = false - -# Enable more detailed statistics -# [web.statistics] -# RecentErrors = 10 +# readOnly = false ``` ## Web UI @@ -42,7 +40,10 @@ address = ":8080" ### Authentication -- Basic Authentication +!!! note + The `/ping` path of the api is excluded from authentication (since 1.4). + +#### Basic Authentication Passwords can be encoded in MD5, SHA1 and BCrypt: you can use `htpasswd` to generate those ones. @@ -50,13 +51,18 @@ Users can be specified directly in the toml file, or indirectly by referencing a if both are provided, the two are merged, with external file contents having precedence. ```toml +[web] +# ... + # To enable basic auth on the webui with 2 user/pass: test:test and test2:test2 - [web.auth.basic] - users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"] - usersFile = "/path/to/.htpasswd" +[web.auth.basic] +users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"] +usersFile = "/path/to/.htpasswd" + +# ... ``` -- Digest Authentication +#### Digest Authentication You can use `htdigest` to generate those ones. @@ -64,10 +70,15 @@ Users can be specified directly in the toml file, or indirectly by referencing a if both are provided, the two are merged, with external file contents having precedence ```toml +[web] +# ... + # To enable digest auth on the webui with 2 user/realm/pass: test:traefik:test and test2:traefik:test2 - [web.auth.digest] - users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"] - usersFile = "/path/to/.htdigest" +[web.auth.digest] +users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"] +usersFile = "/path/to/.htdigest" + +# ... ``` @@ -75,32 +86,96 @@ Users can be specified directly in the toml file, or indirectly by referencing a You can enable Traefik to export internal metrics to different monitoring systems. -- Prometheus +### Prometheus ```toml +[web] +# ... + # To enable Traefik to export internal metrics to Prometheus [web.metrics.prometheus] - Buckets=[0.1,0.3,1.2,5.0] + +# Buckets for latency metrics +# +# Optional +# Default: [0.1, 0.3, 1.2, 5] +buckets=[0.1,0.3,1.2,5.0] + +# ... ``` -- DataDog +### DataDog ```toml +[web] +# ... + # DataDog metrics exporter type [web.metrics.datadog] - Address = "localhost:8125" - Pushinterval = "10s" + +# DataDog's address. +# +# Required +# Default: "localhost:8125" +# +address = "localhost:8125" + +# DataDog push interval +# +# Optional +# Default: "10s" +# +pushinterval = "10s" + +# ... ``` -- StatsD +### StatsD ```toml +[web] +# ... + # StatsD metrics exporter type [web.metrics.statsd] - Address = "localhost:8125" - Pushinterval = "10s" + +# StatD's address. +# +# Required +# Default: "localhost:8125" +# +address = "localhost:8125" + +# StatD push interval +# +# Optional +# Default: "10s" +# +pushinterval = "10s" + +# ... ``` + +## Statistics + +```toml +[web] +# ... + +# Enable more detailed statistics. +[web.statistics] + +# Number of recent errors logged. +# +# Default: 10 +# +recentErrors = 10 + +# ... +``` + + ## API | Path | Method | Description | @@ -126,7 +201,7 @@ You can enable Traefik to export internal metrics to different monitoring system #### Ping ```shell -$ curl -sv "http://localhost:8080/ping" +curl -sv "http://localhost:8080/ping" ``` ```shell * Trying ::1... @@ -148,7 +223,7 @@ OK #### Health ```shell -$ curl -s "http://localhost:8080/health" | jq . +curl -s "http://localhost:8080/health" | jq . ``` ```json { @@ -209,7 +284,7 @@ $ curl -s "http://localhost:8080/health" | jq . #### Provider configurations ```shell -$ curl -s "http://localhost:8080/api" | jq . +curl -s "http://localhost:8080/api" | jq . ``` ```json { diff --git a/docs/configuration/backends/zookeeper.md b/docs/configuration/backends/zookeeper.md index 4d834497d..55b6cb88a 100644 --- a/docs/configuration/backends/zookeeper.md +++ b/docs/configuration/backends/zookeeper.md @@ -1,38 +1,61 @@ # Zookeeper Backend -Træfik can be configured to use Zookeeper as a backend configuration: +Træfik can be configured to use Zookeeper as a backend configuration. ```toml ################################################################ # Zookeeper configuration backend ################################################################ -# Enable Zookeeperconfiguration backend +# Enable Zookeeperconfiguration backend. [zookeeper] -# Zookeeper server endpoint +# Zookeeper server endpoint. # # Required +# Default: "127.0.0.1:2181" # endpoint = "127.0.0.1:2181" -# Enable watch Zookeeper changes +# Enable watch Zookeeper changes. # # Optional +# Default: true # watch = true # Prefix used for KV store. # # Optional +# Default: "/traefik" # -prefix = "traefik" +prefix = "/traefik" -# Override default configuration template. For advanced users :) +# Override default configuration template. +# For advanced users :) # # Optional # # filename = "zookeeper.tmpl" + +# Use Zookeeper user/pass authentication. +# +# Optional +# +# username = foo +# password = bar + +# Enable Zookeeper TLS connection. +# +# Optional +# +# [zookeeper.tls] +# ca = "/etc/ssl/ca.crt" +# cert = "/etc/ssl/zookeeper.crt" +# key = "/etc/ssl/zookeeper.key" +# insecureskipverify = true ``` -Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on traefik KV structure. +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. diff --git a/docs/configuration/commons.md b/docs/configuration/commons.md index af3fa76be..f46118210 100644 --- a/docs/configuration/commons.md +++ b/docs/configuration/commons.md @@ -4,45 +4,34 @@ ```toml # Duration to give active requests a chance to finish before Traefik stops. -# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits). -# If no units are provided, the value is parsed assuming seconds. -# Note: in this time frame no new requests are accepted. # # Optional # Default: "10s" # # graceTimeOut = "10s" -# Enable debug mode +# Enable debug mode. # # Optional # Default: false # # debug = true -# Periodically check if a new version has been released +# Periodically check if a new version has been released. # # Optional # Default: true # # checkNewVersion = false -# Backends throttle duration: minimum duration in seconds between 2 events from providers -# before applying a new configuration. It avoids unnecessary reloads if multiples events -# are sent in a short amount of time. -# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw -# values (digits). If no units are provided, the value is parsed assuming -# seconds. +# Backends throttle duration. # # Optional # Default: "2s" # # ProvidersThrottleDuration = "2s" -# Controls the maximum idle (keep-alive) connections to keep per-host. If zero, DefaultMaxIdleConnsPerHost -# from the Go standard library net/http module is used. -# If you encounter 'too many open files' errors, you can either increase this -# value or change the `ulimit`. +# Controls the maximum idle (keep-alive) connections to keep per-host. # # Optional # Default: 200 @@ -50,15 +39,15 @@ # MaxIdleConnsPerHost = 200 # If set to true invalid SSL certificates are accepted for backends. -# Note: This disables detection of man-in-the-middle attacks so should only be used on secure backend networks. +# This disables detection of man-in-the-middle attacks so should only be used on secure backend networks. # # Optional # Default: false # # InsecureSkipVerify = true -# Register Certificates in the RootCA. This certificates will be use for backends calls. -# Note: You can use file path or cert content directly +# Register Certificates in the RootCA. +# # Optional # Default: [] # @@ -73,6 +62,29 @@ # defaultEntryPoints = ["http", "https"] ``` +- `graceTimeOut`: Duration to give active requests a chance to finish before Traefik stops. +Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits). +If no units are provided, the value is parsed assuming seconds. +**Note:** in this time frame no new requests are accepted. + +- `ProvidersThrottleDuration`: Backends throttle duration: minimum duration in seconds between 2 events from providers before applying a new configuration. +It avoids unnecessary reloads if multiples events are sent in a short amount of time. +Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits). +If no units are provided, the value is parsed assuming seconds. + +- `MaxIdleConnsPerHost`: Controls the maximum idle (keep-alive) connections to keep per-host. +If zero, `DefaultMaxIdleConnsPerHost` from the Go standard library net/http module is used. +If you encounter 'too many open files' errors, you can either increase this value or change the `ulimit`. + +- `InsecureSkipVerify` : If set to true invalid SSL certificates are accepted for backends. +**Note:** This disables detection of man-in-the-middle attacks so should only be used on secure backend networks. + +- `RootCAs`: Register Certificates in the RootCA. This certificates will be use for backends calls. +**Note** You can use file path or cert content directly + +- `defaultEntryPoints`: Entrypoints to be used by frontends that do not specify any entrypoint. +Each frontend can specify its own entrypoints. + ## Constraints @@ -80,18 +92,6 @@ In a micro-service architecture, with a central service discovery, setting const Træfik filters services according to service attributes/tags set in your configuration backends. -Supported backends: - -- Docker -- Consul K/V -- BoltDB -- Zookeeper -- Etcd -- Consul Catalog -- Rancher -- Marathon -- Kubernetes (using a provider-specific mechanism based on label selectors) - Supported filters: - `tag` @@ -120,14 +120,27 @@ constraints = ["tag!=us-*", "tag!=asia-*"] ### Backend-specific +Supported backends: + +- Docker +- Consul K/V +- BoltDB +- Zookeeper +- Etcd +- Consul Catalog +- Rancher +- Marathon +- Kubernetes (using a provider-specific mechanism based on label selectors) + ```toml # Backend-specific constraint [consulCatalog] -endpoint = "127.0.0.1:8500" +# ... constraints = ["tag==api"] +# Backend-specific constraint [marathon] -endpoint = "127.0.0.1:8800" +# ... constraints = ["tag==api", "tag!=v*-beta"] ``` @@ -190,14 +203,17 @@ Traefik will close and reopen its log files, assuming they're configured, on rec This allows the logs to be rotated and processed by an external program, such as `logrotate`. !!! note - that this does not work on Windows due to the lack of USR signals. + This does not work on Windows due to the lack of USR signals. ## Custom Error pages Custom error pages can be returned, in lieu of the default, according to frontend-configured ranges of HTTP Status codes. + In the example below, if a 503 status is returned from the frontend "website", the custom error page at http://2.3.4.5/503.html is returned with the actual status code set in the HTTP header. -Note, the `503.html` page itself is not hosted on traefik, but some other infrastructure. + +!!! note + The `503.html` page itself is not hosted on Traefik, but some other infrastructure. ```toml [frontends] @@ -226,6 +242,9 @@ Instead, the query parameter can also be set to some generic error page like so: Now the `500s.html` error page is returned for the configured code range. The configured status code ranges are inclusive; that is, in the above example, the `500s.html` page will be returned for status codes `500` through, and including, `599`. +Custom error pages are easiest to implement using the file provider. +For dynamic providers, the corresponding template file needs to be customized accordingly and referenced in the Traefik configuration. + ## Retry Configuration @@ -248,12 +267,7 @@ The configured status code ranges are inclusive; that is, in the above example, # Enable custom health check options. [healthcheck] -# Set the default health check interval. Will only be effective if health check -# paths are defined. Given provider-specific support, the value may be -# overridden on a per-backend basis. -# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw -# values (digits). If no units are provided, the value is parsed assuming -# seconds. +# Set the default health check interval. # # Optional # Default: "30s" @@ -261,6 +275,11 @@ The configured status code ranges are inclusive; that is, in the above example, # interval = "30s" ``` +- `interval` set the default health check interval. +Will only be effective if health check paths are defined. +Given provider-specific support, the value may be overridden on a per-backend basis. +Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits). +If no units are provided, the value is parsed assuming seconds. ## Timeouts @@ -272,20 +291,13 @@ The configured status code ranges are inclusive; that is, in the above example, [respondingTimeouts] # readTimeout is the maximum duration for reading the entire request, including the body. -# If zero, no timeout exists. -# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw -# values (digits). If no units are provided, the value is parsed assuming seconds. # # Optional # Default: "0s" # # readTimeout = "5s" -# writeTimeout is the maximum duration before timing out writes of the response. It covers the time from the end of -# the request header read to the end of the response write. -# If zero, no timeout exists. -# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw -# values (digits). If no units are provided, the value is parsed assuming seconds. +# writeTimeout is the maximum duration before timing out writes of the response. # # Optional # Default: "0s" @@ -293,9 +305,6 @@ The configured status code ranges are inclusive; that is, in the above example, # writeTimeout = "5s" # idleTimeout is the maximum duration an idle (keep-alive) connection will remain idle before closing itself. -# If zero, no timeout exists. -# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw -# values (digits). If no units are provided, the value is parsed assuming seconds. # # Optional # Default: "180s" @@ -303,6 +312,22 @@ The configured status code ranges are inclusive; that is, in the above example, # idleTimeout = "360s" ``` +- `readTimeout` is the maximum duration for reading the entire request, including the body. +If zero, no timeout exists. +Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits). +If no units are provided, the value is parsed assuming seconds. + +- `writeTimeout` is the maximum duration before timing out writes of the response. +It covers the time from the end of the request header read to the end of the response write. +If zero, no timeout exists. +Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits). +If no units are provided, the value is parsed assuming seconds. + +- `idleTimeout` is the maximum duration an idle (keep-alive) connection will remain idle before closing itself. +If zero, no timeout exists. +Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits). +If no units are provided, the value is parsed assuming seconds. + ### Forwarding Timeouts `forwardingTimeouts` are timeouts for requests forwarded to the backend servers. @@ -311,9 +336,6 @@ The configured status code ranges are inclusive; that is, in the above example, [forwardingTimeouts] # dialTimeout is the amount of time to wait until a connection to a backend server can be established. -# If zero, no timeout exists. -# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw -# values (digits). If no units are provided, the value is parsed assuming seconds. # # Optional # Default: "30s" @@ -321,9 +343,6 @@ The configured status code ranges are inclusive; that is, in the above example, # dialTimeout = "30s" # responseHeaderTimeout is the amount of time to wait for a server's response headers after fully writing the request (including its body, if any). -# If zero, no timeout exists. -# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw -# values (digits). If no units are provided, the value is parsed assuming seconds. # # Optional # Default: "0s" @@ -331,6 +350,17 @@ The configured status code ranges are inclusive; that is, in the above example, # responseHeaderTimeout = "0s" ``` +- `dialTimeout` is the amount of time to wait until a connection to a backend server can be established. +If zero, no timeout exists. +Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits). +If no units are provided, the value is parsed assuming seconds. + +- `responseHeaderTimeout` is the amount of time to wait for a server's response headers after fully writing the request (including its body, if any). +If zero, no timeout exists. +Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits). +If no units are provided, the value is parsed assuming seconds. + + ### Idle Timeout (deprecated) Use [respondingTimeouts](/configuration/commons/#responding-timeouts) instead of `IdleTimeout`. diff --git a/docs/configuration/entrypoints.md b/docs/configuration/entrypoints.md index dc19b6ef4..ed8a08ede 100644 --- a/docs/configuration/entrypoints.md +++ b/docs/configuration/entrypoints.md @@ -109,23 +109,55 @@ Users can be specified directly in the toml file, or indirectly by referencing a usersFile = "/path/to/.htdigest" ``` +### Forward Authentication + +This configuration will first forward the request to `http://authserver.com/auth`. + +If the response code is 2XX, access is granted and the original request is performed. +Otherwise, the response from the auth server is returned. + +```toml +[entryPoints] + [entrypoints.http] + # ... + # To enable forward auth on an entrypoint + [entrypoints.http.auth.forward] + address = "https://authserver.com/auth" + + # Trust existing X-Forwarded-* headers. + # Useful with another reverse proxy in front of Traefik. + # + # Optional + # Default: false + # + trustForwardHeader = true + + # Enable forward auth TLS connection. + # + # Optional + # + [entrypoints.http.auth.forward.tls] + cert = "authserver.crt" + key = "authserver.key" +``` + ## Specify Minimum TLS Version -To specify an https entrypoint with a minimum TLS version, and specifying an array of cipher suites (from crypto/tls). +To specify an https entry point with a minimum TLS version, and specifying an array of cipher suites (from crypto/tls). ```toml [entryPoints] [entryPoints.https] address = ":443" [entryPoints.https.tls] - MinVersion = "VersionTLS12" - CipherSuites = ["TLS_RSA_WITH_AES_256_GCM_SHA384"] + minVersion = "VersionTLS12" + cipherSuites = ["TLS_RSA_WITH_AES_256_GCM_SHA384"] [[entryPoints.https.tls.certificates]] - CertFile = "integration/fixtures/https/snitest.com.cert" - KeyFile = "integration/fixtures/https/snitest.com.key" + certFile = "integration/fixtures/https/snitest.com.cert" + keyFile = "integration/fixtures/https/snitest.com.key" [[entryPoints.https.tls.certificates]] - CertFile = "integration/fixtures/https/snitest.org.cert" - KeyFile = "integration/fixtures/https/snitest.org.key" + certFile = "integration/fixtures/https/snitest.org.cert" + keyFile = "integration/fixtures/https/snitest.org.key" ``` ## Compression diff --git a/docs/img/grpc.svg b/docs/img/grpc.svg new file mode 100644 index 000000000..3f1d53c76 --- /dev/null +++ b/docs/img/grpc.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/index.md b/docs/index.md index 0be52991c..4900aa90e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,7 +4,7 @@ [![Build Status SemaphoreCI](https://semaphoreci.com/api/v1/containous/traefik/branches/master/shields_badge.svg)](https://semaphoreci.com/containous/traefik) [![Docs](https://img.shields.io/badge/docs-current-brightgreen.svg)](https://docs.traefik.io) -[![Go Report Card](https://goreportcard.com/badge/kubernetes/helm)](https://goreportcard.com/report/containous/traefik) +[![Go Report Card](https://goreportcard.com/badge/github.com/containous/traefik)](https://goreportcard.com/report/github.com/containous/traefik) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/containous/traefik/blob/master/LICENSE.md) [![Join the chat at https://traefik.herokuapp.com](https://img.shields.io/badge/style-register-green.svg?style=social&label=Slack)](https://traefik.herokuapp.com) [![Twitter](https://img.shields.io/twitter/follow/traefikproxy.svg?style=social)](https://twitter.com/intent/follow?screen_name=traefikproxy) @@ -37,9 +37,9 @@ Run it and forget it! ## Features -- [It's fast](https://docs.traefik.io/benchmarks) +- [It's fast](/benchmarks) - No dependency hell, single binary made with go -- [Tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) official docker image +- [Tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) docker image - Rest API - Hot-reloading of configuration. No need to restart the process - Circuit breakers, retry diff --git a/docs/theme/styles/extra.css b/docs/theme/styles/extra.css index 397c97eb0..9694531e2 100644 --- a/docs/theme/styles/extra.css +++ b/docs/theme/styles/extra.css @@ -9,7 +9,12 @@ .md-typeset__table td code { word-break: unset; } + .md-typeset__table tr :nth-child(1) { word-wrap: break-word; max-width: 30em; } + +p { + text-align: justify; +} diff --git a/docs/user-guide/cluster.md b/docs/user-guide/cluster.md index cf06a9702..370add8f8 100644 --- a/docs/user-guide/cluster.md +++ b/docs/user-guide/cluster.md @@ -1,21 +1,25 @@ # Clustering / High Availability (beta) This guide explains how to use Træfik in high availability mode. + In order to deploy and configure multiple Træfik instances, without copying the same configuration file on each instance, we will use a distributed Key-Value store. ## Prerequisites You will need a working KV store cluster. +_(Currently, we recommend [Consul](https://consul.io) .)_ ## File configuration to KV store migration We created a special Træfik command to help configuring your Key Value store from a Træfik TOML configuration file. + Please refer to [this section](/user-guide/kv-config/#store-configuration-in-key-value-store) to get more details. ## Deploy a Træfik cluster Once your Træfik configuration is uploaded on your KV store, you can start each Træfik instance. + A Træfik cluster is based on a manager/worker model. + When starting, Træfik will elect a manager. If this instance fails, another manager will be automatically elected. - diff --git a/docs/user-guide/docker-and-lets-encrypt.md b/docs/user-guide/docker-and-lets-encrypt.md index 3582a18a6..1f803cb4b 100644 --- a/docs/user-guide/docker-and-lets-encrypt.md +++ b/docs/user-guide/docker-and-lets-encrypt.md @@ -1,19 +1,24 @@ # Docker & Traefik In this use case, we want to use Traefik as a _layer-7_ load balancer with SSL termination for a set of micro-services used to run a web application. + We also want to automatically _discover any services_ on the Docker host and let Traefik reconfigure itself automatically when containers get created (or shut down) so HTTP traffic can be routed accordingly. + In addition, we want to use Let's Encrypt to automatically generate and renew SSL certificates per hostname. ## Setting Up In order for this to work, you'll need a server with a public IP address, with Docker installed on it. + In this example, we're using the fictitious domain _my-awesome-app.org_. + In real-life, you'll want to use your own domain and have the DNS configured accordingly so the hostname records you'll want to use point to the aforementioned public IP address. ## Networking Docker containers can only communicate with each other over TCP when they share at least one network. This makes sense from a topological point of view in the context of networking, since Docker under the hood creates IPTable rules so containers can't reach other containers _unless you'd want to_. + In this example, we're going to use a single network called `web` where all containers that are handling HTTP traffic (including Traefik) will reside in. On the Docker host, run the following command: @@ -37,6 +42,7 @@ touch /opt/traefik/traefik.toml ``` The `docker-compose.yml` file will provide us with a simple, consistent and more importantly, a deterministic way to create Traefik. + The contents of the file is as follows: ```yaml @@ -62,10 +68,10 @@ networks: external: true ``` -As you can see, we're mounting the `traefik.toml` file as well as the (empty) `acme.json` file in the container. -Also, we're mounting the `/var/run/docker.sock` Docker socket in the container as well, so Traefik can listen to Docker events and reconfigure it's own internal configuration when containers are created (or shut down). +As you can see, we're mounting the `traefik.toml` file as well as the (empty) `acme.json` file in the container. +Also, we're mounting the `/var/run/docker.sock` Docker socket in the container as well, so Traefik can listen to Docker events and reconfigure it's own internal configuration when containers are created (or shut down). Also, we're making sure the container is automatically restarted by the Docker engine in case of problems (or: if the server is rebooted). -We're publishing the default HTTP ports `80` and `443` on the host, and making sure the container is placed within the `web` network we've created earlier on. +We're publishing the default HTTP ports `80` and `443` on the host, and making sure the container is placed within the `web` network we've created earlier on. Finally, we're giving this container a static name called `traefik`. Let's take a look at a simply `traefik.toml` configuration as well before we'll create the Traefik container: @@ -106,7 +112,8 @@ This is the minimum configuration required to do the following: - Check for new versions of Traefik periodically - Create two entry points, namely an `HTTP` endpoint on port `80`, and an `HTTPS` endpoint on port `443` where all incoming traffic on port `80` will immediately get redirected to `HTTPS`. - Enable the Docker configuration backend and listen for container events on the Docker unix socket we've mounted earlier. However, **new containers will not be exposed by Traefik by default, we'll get into this in a bit!** -- Enable automatic request and configuration of SSL certificates using Let's Encrypt. These certificates will be stored in the `acme.json` file, which you can back-up yourself and store off-premises. +- Enable automatic request and configuration of SSL certificates using Let's Encrypt. + These certificates will be stored in the `acme.json` file, which you can back-up yourself and store off-premises. Alright, let's boot the container. From the `/opt/traefik` directory, run `docker-compose up -d` which will create and start the Traefik container. @@ -114,7 +121,9 @@ Alright, let's boot the container. From the `/opt/traefik` directory, run `docke Now that we've fully configured and started Traefik, it's time to get our applications running! -Let's take a simple example of a micro-service project consisting of various services, where some will be exposed to the outside world and some will not. The `docker-compose.yml` of our project looks like this: +Let's take a simple example of a micro-service project consisting of various services, where some will be exposed to the outside world and some will not. + +The `docker-compose.yml` of our project looks like this: ```yaml version: "2.1" @@ -173,16 +182,19 @@ networks: external: true ``` -Here, we can see a set of services with two applications that we're actually exposing to the outside world. -Notice how there isn't a single container that has any published ports to the host -- everything is routed through Docker networks. +Here, we can see a set of services with two applications that we're actually exposing to the outside world. +Notice how there isn't a single container that has any published ports to the host -- everything is routed through Docker networks. Also, only the containers that we want traffic to get routed to are attached to the `web` network we created at the start of this document. + Since the `traefik` container we've created and started earlier is also attached to this network, HTTP requests can now get routed to these containers. ### Labels As mentioned earlier, we don't want containers exposed automatically by Traefik. + The reason behind this is simple: we want to have control over this process ourselves. Thanks to Docker labels, we can tell Traefik how to create it's internal routing configuration. + Let's take a look at the labels themselves for the `app` service, which is a HTTP webservice listing on port 9000: ```yaml @@ -194,14 +206,17 @@ Let's take a look at the labels themselves for the `app` service, which is a HTT ``` First, we specify the `backend` name which corresponds to the actual service we're routing **to**. -We also tell Traefik to use the `web` network to route HTTP traffic to this container. With the `frontend.rule` label, we tell Traefik that we want to route to this container if the incoming HTTP request contains the `Host` `app.my-awesome-app.org`. -Essentially, this is the actual rule used for Layer-7 load balancing. + +We also tell Traefik to use the `web` network to route HTTP traffic to this container. +With the `frontend.rule` label, we tell Traefik that we want to route to this container if the incoming HTTP request contains the `Host` `app.my-awesome-app.org`. +Essentially, this is the actual rule used for Layer-7 load balancing. With the `traefik.enable` label, we tell Traefik to include this container in it's internal configuration. + Finally but not unimportantly, we tell Traefik to route **to** port `9000`, since that is the actual TCP/IP port the container actually listens on. #### Gotchas and tips -- Always specify the correct port where the container expects HTTP traffic using `traefik.port` label. +- Always specify the correct port where the container expects HTTP traffic using `traefik.port` label. If a container exposes multiple ports, Traefik may forward traffic to the wrong port. Even if a container only exposes one port, you should always write configuration defensively and explicitly. - Should you choose to enable the `exposedbydefault` flag in the `traefik.toml` configuration, be aware that all containers that are placed in the same network as Traefik will automatically be reachable from the outside world, for everyone and everyone to see. @@ -213,5 +228,6 @@ Finally but not unimportantly, we tell Traefik to route **to** port `9000`, sinc ### Final thoughts -Using Traefik as a Layer-7 load balancer in combination with both Docker and Let's Encrypt provides you with an extremely flexible, performant and self-configuring solution for your projects. +Using Traefik as a Layer-7 load balancer in combination with both Docker and Let's Encrypt provides you with an extremely flexible, powerful and self-configuring solution for your projects. + With Let's Encrypt, your endpoints are automatically secured with production-ready SSL certificates that are renewed automatically as well. diff --git a/docs/user-guide/examples.md b/docs/user-guide/examples.md index f15a43cdd..07128cdc8 100644 --- a/docs/user-guide/examples.md +++ b/docs/user-guide/examples.md @@ -22,11 +22,11 @@ defaultEntryPoints = ["http", "https"] address = ":443" [entryPoints.https.tls] [[entryPoints.https.tls.certificates]] - CertFile = "integration/fixtures/https/snitest.com.cert" - KeyFile = "integration/fixtures/https/snitest.com.key" + certFile = "integration/fixtures/https/snitest.com.cert" + keyFile = "integration/fixtures/https/snitest.com.key" [[entryPoints.https.tls.certificates]] - CertFile = "integration/fixtures/https/snitest.org.cert" - KeyFile = "integration/fixtures/https/snitest.org.key" + certFile = "integration/fixtures/https/snitest.org.cert" + keyFile = "integration/fixtures/https/snitest.org.key" ``` Note that we can either give path to certificate file or directly the file content itself ([like in this TOML example](/user-guide/kv-config/#upload-the-configuration-in-the-key-value-store)). @@ -43,8 +43,8 @@ defaultEntryPoints = ["http", "https"] address = ":443" [entryPoints.https.tls] [[entryPoints.https.tls.certificates]] - CertFile = "examples/traefik.crt" - KeyFile = "examples/traefik.key" + certFile = "examples/traefik.crt" + keyFile = "examples/traefik.key" ``` ## Let's Encrypt support @@ -76,6 +76,7 @@ entryPoint = "https" ``` This configuration allows generating Let's Encrypt certificates for the four domains `local[1-4].com` with described SANs. + Traefik generates these certificates when it starts and it needs to be restart if new domains are added. ### OnHostRule option @@ -106,6 +107,7 @@ entryPoint = "https" ``` This configuration allows generating Let's Encrypt certificates for the four domains `local[1-4].com`. + Traefik generates these certificates when it starts. If a backend is added with a `onHost` rule, Traefik will automatically generate the Let's Encrypt certificate for the new domain. @@ -121,10 +123,9 @@ If a backend is added with a `onHost` rule, Traefik will automatically generate [acme] email = "test@traefik.io" storage = "acme.json" -OnDemand = true +onDemand = true caServer = "http://172.18.0.1:4000/directory" entryPoint = "https" - ``` This configuration allows generating a Let's Encrypt certificate during the first HTTPS request on a new domain. @@ -166,8 +167,10 @@ entryPoint = "https" main = "local4.com" ``` -DNS challenge needs environment variables to be executed. This variables have to be set on the machine/container which host Traefik. -These variables has described [in this section](toml/#acme-lets-encrypt-configuration). +DNS challenge needs environment variables to be executed. +This variables have to be set on the machine/container which host Traefik. + +These variables has described [in this section](/configuration/acme/#dnsprovider). ### OnHostRule option and provided certificates @@ -177,8 +180,8 @@ These variables has described [in this section](toml/#acme-lets-encrypt-configur address = ":443" [entryPoints.https.tls] [[entryPoints.https.tls.certificates]] - CertFile = "examples/traefik.crt" - KeyFile = "examples/traefik.key" + certFile = "examples/traefik.crt" + keyFile = "examples/traefik.key" [acme] email = "test@traefik.io" @@ -226,7 +229,6 @@ entryPoint = "https" endpoint = "127.0.0.1:8500" watch = true prefix = "traefik" - ``` This configuration allows to use the key `traefik/acme/account` to get/set Let's Encrypt certificates content. @@ -277,7 +279,7 @@ defaultEntryPoints = ["http"] ## Pass Authenticated user to application via headers Providing an authentication method as described above, it is possible to pass the user to the application -via a configurable header value +via a configurable header value. ```toml defaultEntryPoints = ["http"] @@ -293,6 +295,91 @@ defaultEntryPoints = ["http"] ## Override the Traefik HTTP server IdleTimeout and/or throttle configurations from re-loading too quickly ```toml -IdleTimeout = "360s" -ProvidersThrottleDuration = "5s" +providersThrottleDuration = "5s" + +[respondingTimeouts] +idleTimeout = "360s" ``` + +## Securing Ping Health Check + +The `/ping` health-check URL is enabled together with the web admin panel, enabled with the command-line `--web` or config file option `[web]`. +Thus, if you have a regular path for `/foo` and an entrypoint on `:80`, you would access them as follows: + +* Regular path: `http://hostname:80/foo` +* Admin panel: `http://hostname:8080/` +* Ping URL: `http://hostname:8080/ping` + +However, for security reasons, you may want to be able to expose the `/ping` health-check URL to outside health-checkers, e.g. an Internet service or cloud load-balancer, _without_ exposing your admin panel's port. +In many environments, the security staff may not _allow_ you to expose it. + +You have two options: + +* Enable `/ping` on a regular entrypoint +* Enable `/ping` on a dedicated port + +### Enable ping health check on a regular entrypoint + +To proxy `/ping` from a regular entrypoint to the admin one without exposing the panel, do the following: + +```toml +[backends] + [backends.traefik] + [backends.traefik.servers.server1] + url = "http://localhost:8080" + weight = 10 + +[frontends] + [frontends.traefikadmin] + backend = "traefik" + [frontends.traefikadmin.routes.ping] + rule = "Path:/ping" +``` + +The above creates a new backend called `traefik`, listening on `http://localhost:8080`, i.e. the local admin port. +We only expose the admin panel via the `frontend` named `traefikadmin`, and only expose the `/ping` Path. +Be careful with the `traefikadmin` frontend. If you do _not_ specify a `Path:` rule, you would expose the entire dashboard. + +### Enable ping health check on dedicated port + +If you do not want to or cannot expose the health-check on a regular entrypoint - e.g. your security rules do not allow it, or you have a conflicting path - then you can enable health-check on its own entrypoint. +Use the following config: + +```toml +defaultEntryPoints = ["http"] + +[entryPoints] + [entryPoints.http] + address = ":80" + [entryPoints.ping] + address = ":8082" + +[backends] + [backends.traefik] + [backends.traefik.servers.server1] + url = "http://localhost:8080" + weight = 10 + +[frontends] + [frontends.traefikadmin] + backend = "traefik" + entrypoints = ["ping"] + [frontends.traefikadmin.routes.ping] + rule = "Path:/ping" +``` + +The above is similar to the previous example, but instead of enabling `/ping` on the _default_ entrypoint, we enable it on a _dedicated_ entrypoint. + +In the above example, you would access a regular path, admin panel and health-check as follows: + +* Regular path: `http://hostname:80/foo` +* Admin panel: `http://hostname:8080/` +* Ping URL: `http://hostname:8082/ping` + +Note the dedicated port `:8082` for `/ping`. + +In the above example, it is _very_ important to create a named dedicated entrypoint, and do **not** include it in `defaultEntryPoints`. +Otherwise, you are likely to expose _all_ services via that entrypoint. + +In the above example, we have two entrypoints, `http` and `ping`, but we only included `http` in `defaultEntryPoints`, while explicitly tying `frontend.traefikadmin` to the `ping` entrypoint. +This ensures that all the "normal" frontends will be exposed via entrypoint `http` and _not_ via entrypoint `ping`. diff --git a/docs/user-guide/grpc.md b/docs/user-guide/grpc.md new file mode 100644 index 000000000..a5412b5c1 --- /dev/null +++ b/docs/user-guide/grpc.md @@ -0,0 +1,148 @@ +# gRPC example + +This section explains how to use Traefik as reverse proxy for gRPC application with self-signed certificates. + +!!! warning + As gRPC needs HTTP2, we need valid HTTPS certificates on both gRPC Server and Træfik. + +

+gRPC architecture +

+ +## gRPC Server certificate + +In order to secure the gRPC server, we generate a self-signed certificate for backend url: + +```bash +openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./backend.key -out ./backend.crt +``` + +That will prompt for information, the important answer is: + +``` +Common Name (e.g. server FQDN or YOUR name) []: backend.local +``` + +## gRPC Client certificate + +Generate your self-signed certificate for frontend url: + +```bash +openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./frontend.key -out ./frontend.crt +``` + +with + +``` +Common Name (e.g. server FQDN or YOUR name) []: frontend.local +``` + +## Træfik configuration + +At last, we configure our Træfik instance to use both self-signed certificates. + +```toml +defaultEntryPoints = ["https"] + +# For secure connection on backend.local +RootCAs = [ "./backend.cert" ] + +[entryPoints] + [entryPoints.https] + address = ":4443" + [entryPoints.https.tls] + # For secure connection on frontend.local + [[entryPoints.https.tls.certificates]] + certFile = "./frontend.cert" + keyFile = "./frontend.key" + + +[web] + address = ":8080" + +[file] + +[backends] + [backends.backend1] + [backends.backend1.servers.server1] + # Access on backend with HTTPS + url = "https://backend.local:8080" + + +[frontends] + [frontends.frontend1] + backend = "backend1" + [frontends.frontend1.routes.test_1] + rule = "Host:frontend.local" +``` + +## Conclusion + +We don't need specific configuration to use gRPC in Træfik, we just need to be careful that all the exchanges (between client and Træfik, and between Træfik and backend) are valid HTTPS communications (without `InsecureSkipVerify` enabled) because gRPC use HTTP2. + +## A gRPC example in go + +We will use the gRPC greeter example in [grpc-go](https://github.com/grpc/grpc-go/tree/master/examples/helloworld) + +!!! warning + In order to use this gRPC example, we need to modify it to use HTTPS + +So we modify the "gRPC server example" to use our own self-signed certificate: + +```go +// ... + +// Read cert and key file +BackendCert := ioutil.ReadFile("./backend.cert") +BackendKey := ioutil.ReadFile("./backend.key") + +// Generate Certificate struct +cert, err := tls.X509KeyPair(BackendCert, BackendKey) +if err != nil { + return err +} + +// Create credentials +creds := credentials.NewServerTLSFromCert(&cert) + +// Use Credentials in gRPC server options +serverOption := grpc.Creds(creds) +var s *grpc.Server = grpc.NewServer(serverOption) +defer s.Stop() + +helloworld.RegisterGreeterServer(s, &myserver{}) +err := s.Serve(lis) + +// ... +``` + +Next we will modify gRPC Client to use our Træfik self-signed certificate: + +```go +// ... + +// Read cert file +FrontendCert := ioutil.ReadFile("./frontend.cert") + +// Create CertPool +roots := x509.NewCertPool() +roots.AppendCertsFromPEM(FrontendCert) + +// Create credentials +credsClient := credentials.NewClientTLSFromCert(roots, "") + +// Dial with specific Transport (with credentials) +conn, err := grpc.Dial("https://frontend:4443", grpc.WithTransportCredentials(credsClient)) +if err != nil { + return err +} + +defer conn.Close() +client := helloworld.NewGreeterClient(conn) + +name := "World" +r, err := client.SayHello(context.Background(), &helloworld.HelloRequest{Name: name}) + +// ... +``` + diff --git a/docs/user-guide/kubernetes.md b/docs/user-guide/kubernetes.md index 111a9f014..274135af2 100644 --- a/docs/user-guide/kubernetes.md +++ b/docs/user-guide/kubernetes.md @@ -1,6 +1,7 @@ # Kubernetes Ingress Controller This guide explains how to use Træfik as an Ingress controller in a Kubernetes cluster. + If you are not familiar with Ingresses in Kubernetes you might want to read the [Kubernetes user guide](https://kubernetes.io/docs/concepts/services-networking/ingress/) The config files used in this guide can be found in the [examples directory](https://github.com/containous/traefik/tree/master/examples/k8s) @@ -72,9 +73,10 @@ kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/exa It is possible to use Træfik with a [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) or a [DaemonSet](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/) object, whereas both options have their own pros and cons: - The scalability is much better when using a Deployment, because you will have a Single-Pod-per-Node model when using the DeaemonSet. -It is possible to exclusively run a Service on a dedicated set of machines using taints and tolerations with a DaemonSet. -On the other hand the DaemonSet allows you to access any Node directly on Port 80 and 443, where you have to setup a [Service](https://kubernetes.io/docs/concepts/services-networking/service/) object with a Deployment. + +- The scalability is much better when using a Deployment, because you will have a Single-Pod-per-Node model when using the DeaemonSet. +- It is possible to exclusively run a Service on a dedicated set of machines using taints and tolerations with a DaemonSet. +- On the other hand the DaemonSet allows you to access any Node directly on Port 80 and 443, where you have to setup a [Service](https://kubernetes.io/docs/concepts/services-networking/service/) object with a Deployment. The Deployment objects looks like this: @@ -131,7 +133,8 @@ spec: ``` [examples/k8s/traefik-deployment.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/traefik-deployment.yaml) -> The Service will expose two NodePorts which allow access to the ingress and the web interface. +!!! note + The Service will expose two NodePorts which allow access to the ingress and the web interface. The DaemonSet objects looks not much different: @@ -198,20 +201,20 @@ spec: To deploy Træfik to your cluster start by submitting one of the YAML files to the cluster with `kubectl`: ```shell -$ kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/traefik-deployment.yaml +kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/traefik-deployment.yaml ``` ```shell -$ kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/traefik-ds.yaml +kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/traefik-ds.yaml ``` -There are some significant differences between using Deployments and DaemonSets. -The Deployment has easier up and down scaling possibilities. It can implement full pod lifecycle and supports rolling updates from Kubernetes 1.2. -At least one Pod is needed to run the Deployment. -The DaemonSet automatically scales to all nodes that meets a specific selector and guarantees to fill nodes one at a time. -Rolling updates are fully supported from Kubernetes 1.7 for DaemonSets as well. - +There are some significant differences between using Deployments and DaemonSets: +- The Deployment has easier up and down scaling possibilities. + It can implement full pod lifecycle and supports rolling updates from Kubernetes 1.2. + At least one Pod is needed to run the Deployment. +- The DaemonSet automatically scales to all nodes that meets a specific selector and guarantees to fill nodes one at a time. + Rolling updates are fully supported from Kubernetes 1.7 for DaemonSets as well. ### Check the Pods @@ -220,8 +223,10 @@ Now lets check if our command was successful. Start by listing the pods in the `kube-system` namespace: ```shell -$ kubectl --namespace=kube-system get pods +kubectl --namespace=kube-system get pods +``` +``` NAME READY STATUS RESTARTS AGE kube-addon-manager-minikubevm 1/1 Running 0 4h kubernetes-dashboard-s8krj 1/1 Running 0 4h @@ -231,14 +236,17 @@ traefik-ingress-controller-678226159-eqseo 1/1 Running 0 7m You should see that after submitting the Deployment or DaemonSet to Kubernetes it has launched a Pod, and it is now running. _It might take a few moments for kubernetes to pull the Træfik image and start the container._ -> You could also check the deployment with the Kubernetes dashboard, run -> `minikube dashboard` to open it in your browser, then choose the `kube-system` -> namespace from the menu at the top right of the screen. +!!! note + You could also check the deployment with the Kubernetes dashboard, run + `minikube dashboard` to open it in your browser, then choose the `kube-system` + namespace from the menu at the top right of the screen. You should now be able to access Træfik on port 80 of your Minikube instance when using the DaemonSet: ```sh curl $(minikube ip) +``` +``` 404 page not found ``` @@ -246,20 +254,24 @@ If you decided to use the deployment, then you need to target the correct NodePo ```sh curl $(minikube ip): +``` +``` 404 page not found ``` -> We expect to see a 404 response here as we haven't yet given Træfik any configuration. +!!! note + We expect to see a 404 response here as we haven't yet given Træfik any configuration. ## Deploy Træfik using Helm Chart Instead of installing Træfik via an own object, you can also use the Træfik Helm chart. + This allows more complex configuration via Kubernetes [ConfigMap](https://kubernetes.io/docs/tasks/configure-pod-container/configmap/) and enabled TLS certificates. Install Træfik chart by: ```shell -$ helm install stable/traefik +helm install stable/traefik ``` For more information, check out [the doc](https://github.com/kubernetes/charts/tree/master/stable/traefik). @@ -305,9 +317,8 @@ kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/exa Now lets setup an entry in our /etc/hosts file to route `traefik-ui.minikube` to our cluster. -> In production you would want to set up real dns entries. - -> You can get the ip address of your minikube instance by running `minikube ip` +In production you would want to set up real dns entries. +You can get the ip address of your minikube instance by running `minikube ip` ```shell echo "$(minikube ip) traefik-ui.minikube" | sudo tee -a /etc/hosts @@ -346,13 +357,6 @@ spec: containers: - name: cheese image: errm/cheese:stilton - resources: - requests: - cpu: 100m - memory: 50Mi - limits: - cpu: 100m - memory: 50Mi ports: - containerPort: 80 --- @@ -379,13 +383,6 @@ spec: containers: - name: cheese image: errm/cheese:cheddar - resources: - requests: - cpu: 100m - memory: 50Mi - limits: - cpu: 100m - memory: 50Mi ports: - containerPort: 80 --- @@ -412,13 +409,6 @@ spec: containers: - name: cheese image: errm/cheese:wensleydale - resources: - requests: - cpu: 100m - memory: 50Mi - limits: - cpu: 100m - memory: 50Mi ports: - containerPort: 80 ``` @@ -474,8 +464,8 @@ spec: task: wensleydale ``` -> Notice that we also set a [circuit breaker expression](https://docs.traefik.io/basics/#backends) for one of the backends -> by setting the `traefik.backend.circuitbreaker` annotation on the service. +!!! note + We also set a [circuit breaker expression](/basics/#backends) for one of the backends by setting the `traefik.backend.circuitbreaker` annotation on the service. [examples/k8s/cheese-services.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-services.yaml) @@ -519,13 +509,15 @@ spec: ``` [examples/k8s/cheese-ingress.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-ingress.yaml) -> Notice that we list each hostname, and add a backend service. +!!! note + we list each hostname, and add a backend service. ```shell kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheese-ingress.yaml ``` -Now visit the [Træfik dashboard](http://traefik-ui.minikube/) and you should see a frontend for each host. Along with a backend listing for each service with a Server set up for each pod. +Now visit the [Træfik dashboard](http://traefik-ui.minikube/) and you should see a frontend for each host. +Along with a backend listing for each service with a Server set up for each pod. If you edit your `/etc/hosts` again you should be able to access the cheese websites in your browser. @@ -543,7 +535,6 @@ Now lets suppose that our fictional client has decided that while they are super No problem, we say, why don't we reconfigure the sites to host all 3 under one domain. - ```yaml apiVersion: extensions/v1beta1 kind: Ingress @@ -572,9 +563,8 @@ spec: ``` [examples/k8s/cheeses-ingress.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheeses-ingress.yaml) -> Notice that we are configuring Træfik to strip the prefix from the url path -> with the `traefik.frontend.rule.type` annotation so that we can use -> the containers from the previous example without modification. +!!! note + we are configuring Træfik to strip the prefix from the url path with the `traefik.frontend.rule.type` annotation so that we can use the containers from the previous example without modification. ```shell kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheeses-ingress.yaml @@ -632,18 +622,20 @@ spec: ## Forwarding to ExternalNames When specifying an [ExternalName](https://kubernetes.io/docs/concepts/services-networking/service/#services-without-selectors), -Træfik will forward requests to the given host accordingly and use HTTPS when the Service port matches 443. +Træfik will forward requests to the given host accordingly and use HTTPS when the Service port matches 443. This still requires setting up a proper port mapping on the Service from the Ingress port to the (external) Service port. ## Disable passing the Host header By default Træfik will pass the incoming Host header on to the upstream resource. + There are times however where you may not want this to be the case. For example if your service is of the ExternalName type. ### Disable entirely Add the following to your toml config: + ```toml disablePassHostHeaders = true ``` @@ -653,6 +645,7 @@ disablePassHostHeaders = true To disable passing the Host header per ingress resource set the `traefik.frontend.passHostHeader` annotation on your ingress to `false`. Here is an example ingress definition: + ```yaml apiVersion: extensions/v1beta1 kind: Ingress @@ -673,6 +666,7 @@ spec: ``` And an example service definition: + ```yaml apiVersion: v1 kind: Service @@ -696,8 +690,20 @@ If you were to visit `example.com/static` the request would then be passed onto ## Excluding an ingress from Træfik You can control which ingress Træfik cares about by using the `kubernetes.io/ingress.class` annotation. + By default if the annotation is not set at all Træfik will include the ingress. If the annotation is set to anything other than traefik or a blank string Træfik will ignore it. -![](https://i.giphy.com/ujUdrdpX7Ok5W.gif) +## Production advice + +### Resource limitations + +The examples shown deliberately do not specify any [resource limitations](https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/) as there is no one size fits all. + +In a production environment, however, it is important to set proper bounds, especially with regards to CPU: + +- too strict and Traefik will be throttled while serving requests (as Kubernetes imposes hard quotas) +- too loose and Traefik may waste resources not available for other containers + +When in doubt, you should measure your resource needs, and adjust requests and limits accordingly. diff --git a/docs/user-guide/kv-config.md b/docs/user-guide/kv-config.md index 012f757d1..ef24ca907 100644 --- a/docs/user-guide/kv-config.md +++ b/docs/user-guide/kv-config.md @@ -14,13 +14,16 @@ Træfik supports several Key-value stores: ## Static configuration in Key-value store We will see the steps to set it up with an easy example. -Note that we could do the same with any other Key-value Store. -## docker-compose file for Consul +!!! note + We could do the same with any other Key-value Store. + +### docker-compose file for Consul The Træfik global configuration will be getted from a [Consul](https://consul.io) store. First we have to launch Consul in a container. + The [docker-compose file](https://docs.docker.com/compose/compose-file/) allows us to launch Consul and four instances of the trivial app [emilevauge/whoamI](https://github.com/emilevauge/whoamI) : ```yaml @@ -51,12 +54,12 @@ whoami4: image: emilevauge/whoami ``` -## Upload the configuration in the Key-value store +### Upload the configuration in the Key-value store -We should now fill the store with the Træfik global configuration, as we do with a [TOML file configuration](/toml). +We should now fill the store with the Træfik global configuration, as we do with a [TOML file configuration](/toml). To do that, we can send the Key-value pairs via [curl commands](https://www.consul.io/intro/getting-started/kv.html) or via the [Web UI](https://www.consul.io/intro/getting-started/ui.html). -Fortunately, Træfik allows automation of this process using the `storeconfig` subcommand. +Fortunately, Træfik allows automation of this process using the `storeconfig` subcommand. Please refer to the [store Træfik configuration](/user-guide/kv-config/#store-configuration-in-key-value-store) section to get documentation on it. Here is the toml configuration we would like to store in the Key-value Store : @@ -83,7 +86,6 @@ defaultEntryPoints = ["http", "https"] -----END CERTIFICATE-----""" - [consul] endpoint = "127.0.0.1:8500" watch = true @@ -118,9 +120,10 @@ In case you are setting key values manually: Note that we can either give path to certificate file or directly the file content itself. -## Launch Træfik +### Launch Træfik We will now launch Træfik in a container. + We use CLI flags to setup the connection between Træfik and Consul. All the rest of the global configuration is stored in Consul. @@ -138,21 +141,23 @@ traefik: !!! warning Be careful to give the correct IP address and port in the flag `--consul.endpoint`. -## Consul ACL Token support +### Consul ACL Token support -To specify a Consul ACL token for Traefik, we have to set a System Environment variable named `CONSUL_HTTP_TOKEN` prior to starting traefik. This variable must be initialized with the ACL token value. +To specify a Consul ACL token for Traefik, we have to set a System Environment variable named `CONSUL_HTTP_TOKEN` prior to starting Traefik. +This variable must be initialized with the ACL token value. If Traefik is launched into a Docker container, the variable `CONSUL_HTTP_TOKEN` can be initialized with the `-e` Docker option : `-e "CONSUL_HTTP_TOKEN=[consul-acl-token-value]"` -## TLS support +### TLS support To connect to a Consul endpoint using SSL, simply specify `https://` in the `consul.endpoint` property - `--consul.endpoint=https://[consul-host]:[consul-ssl-port]` -## TLS support with client certificates +### TLS support with client certificates So far, only [Consul](https://consul.io) and [etcd](https://coreos.com/etcd/) support TLS connections with client certificates. + To set it up, we should enable [consul security](https://www.consul.io/docs/internals/security.html) (or [etcd security](https://coreos.com/etcd/docs/latest/security.html)). Then, we have to provide CA, Cert and Key to Træfik using `consul` flags : @@ -169,18 +174,20 @@ Or etcd flags : - `--etcd.tls.cert=path/to/the/file` - `--etcd.tls.key=path/to/the/file` -Note that we can either give directly directly the file content itself (instead of the path to certificate) in a TOML file configuration. +!! note + We can either give directly directly the file content itself (instead of the path to certificate) in a TOML file configuration. Remember the command `traefik --help` to display the updated list of flags. -# Dynamic configuration in Key-value store +## Dynamic configuration in Key-value store Following our example, we will provide backends/frontends rules to Træfik. -Note that this section is independent of the way Træfik got its static configuration. -It means that the static configuration can either come from the same Key-value store or from any other sources. +!!! note + This section is independent of the way Træfik got its static configuration. + It means that the static configuration can either come from the same Key-value store or from any other sources. -## Key-value storage structure +### Key-value storage structure Here is the toml configuration we would like to store in the store : @@ -272,14 +279,15 @@ And there, the same dynamic configuration in a KV Store (using `prefix = "traefi | `/traefik/frontends/frontend2/entrypoints` | `http,https` | | `/traefik/frontends/frontend2/routes/test_2/rule` | `PathPrefix:/test` | -## Atomic configuration changes +### Atomic configuration changes Træfik can watch the backends/frontends configuration changes and generate its configuration automatically. -Note that only backends/frontends rules are dynamic, the rest of the Træfik configuration stay static. +!!! note + Only backends/frontends rules are dynamic, the rest of the Træfik configuration stay static. -The [Etcd](https://github.com/coreos/etcd/issues/860) and [Consul](https://github.com/hashicorp/consul/issues/886) backends do not support updating multiple keys atomically. -As a result, it may be possible for Træfik to read an intermediate configuration state despite judicious use of the `--providersThrottleDuration` flag. +The [Etcd](https://github.com/coreos/etcd/issues/860) and [Consul](https://github.com/hashicorp/consul/issues/886) backends do not support updating multiple keys atomically. +As a result, it may be possible for Træfik to read an intermediate configuration state despite judicious use of the `--providersThrottleDuration` flag. To solve this problem, Træfik supports a special key called `/traefik/alias`. If set, Træfik use the value as an alternative key prefix. @@ -292,6 +300,7 @@ Given the key structure below, Træfik will use the `http://172.17.0.2:80` as it | `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` | When an atomic configuration change is required, you may write a new configuration at an alternative prefix. + Here, although the `/traefik_configurations/2/...` keys have been set, the old configuration is still active because the `/traefik/alias` key still points to `/traefik_configurations/1`: | Key | Value | @@ -305,6 +314,7 @@ Here, although the `/traefik_configurations/2/...` keys have been set, the old c | `/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. + Here, we have a 50% balance between the `http://172.17.0.3:80` and the `http://172.17.0.4:80` hosts while no traffic is sent to the `172.17.0.2:80` host: | Key | Value | @@ -317,22 +327,25 @@ Here, we have a 50% balance between the `http://172.17.0.3:80` and the `http://1 | `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.4:80` | | `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` | -Note that Træfik *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik/alias`. -Further, if the `/traefik/alias` key is set, all other configuration with `/traefik/backends` or `/traefik/frontends` prefix are ignored. +!!! note + Træfik *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik/alias`. + Further, if the `/traefik/alias` key is set, all other configuration with `/traefik/backends` or `/traefik/frontends` prefix are ignored. -# Store configuration in Key-value store +## Store configuration in Key-value store + +!!! note + Don't forget to [setup the connection between Træfik and Key-value store](/user-guide/kv-config/#launch-trfk). -Don't forget to [setup the connection between Træfik and Key-value store](/user-guide/kv-config/#launch-trfk). The static Træfik configuration in a key-value store can be automatically created and updated, using the [`storeconfig` subcommand](/basics/#commands). ```bash traefik storeconfig [flags] ... ``` This command is here only to automate the [process which upload the configuration into the Key-value store](/user-guide/kv-config/#upload-the-configuration-in-the-key-value-store). -Træfik will not start but the [static configuration](/basics/#static-trfk-configuration) will be uploaded into the Key-value store. +Træfik will not start but the [static configuration](/basics/#static-trfk-configuration) will be uploaded into the Key-value store. If you configured ACME (Let's Encrypt), your registration account and your certificates will also be uploaded. -To upload your ACME certificates to the KV store, get your traefik TOML file and add the new `storage` option in the `acme` section: +To upload your ACME certificates to the KV store, get your Traefik TOML file and add the new `storage` option in the `acme` section: ```toml [acme] diff --git a/docs/user-guide/marathon.md b/docs/user-guide/marathon.md index 37253f053..8c13ebfab 100644 --- a/docs/user-guide/marathon.md +++ b/docs/user-guide/marathon.md @@ -2,7 +2,7 @@ This guide explains how to integrate Marathon and operate the cluster in a reliable way from Traefik's standpoint. -# Host detection +## Host detection Marathon offers multiple ways to run (Docker-containerized) applications, the most popular ones being @@ -14,9 +14,11 @@ Traefik tries to detect the configured mode and route traffic to the right IP ad Given the complexity of the subject, it is possible that the heuristic fails. Apart from filing an issue and waiting for the feature request / bug report to get addressed, one workaround for such situations is to customize the Marathon template file to the individual needs. -(Note that this does _not_ require rebuilding Traefik but only to point the `filename` configuration parameter to a customized version of the `marathon.tmpl` file on Traefik startup.) -# Port detection +!!! note + This does _not_ require rebuilding Traefik but only to point the `filename` configuration parameter to a customized version of the `marathon.tmpl` file on Traefik startup. + +## Port detection Traefik also attempts to determine the right port (which is a [non-trivial matter in Marathon](https://mesosphere.github.io/marathon/docs/ports.html)). Following is the order by which Traefik tries to identify the port (the first one that yields a positive result will be used): @@ -26,9 +28,9 @@ Following is the order by which Traefik tries to identify the port (the first on 1. The port from the application's `portDefinitions` field (possibly indexed through the `traefik.portIndex` label, otherwise the first one). 1. The port from the application's `ipAddressPerTask` field (possibly indexed through the `traefik.portIndex` label, otherwise the first one). -# Achieving high availability +## Achieving high availability -## Scenarios +### Scenarios There are three scenarios where the availability of a Marathon application could be impaired along with the risk of losing or failing requests: @@ -36,27 +38,29 @@ There are three scenarios where the availability of a Marathon application could - During the shutdown phase when Traefik still routes requests to the backend while the backend is already terminating. - During a failure of the application when Traefik has not yet identified the backend as being erroneous. -The first two scenarios are common with every rolling upgrade of an application (i.e., a new version release or configuration update). +The first two scenarios are common with every rolling upgrade of an application (i.e. a new version release or configuration update). The following sub-sections describe how to resolve or mitigate each scenario. -### Startup +#### Startup It is possible to define [readiness checks](https://mesosphere.github.io/marathon/docs/readiness-checks.html) (available since Marathon version 1.1) per application and have Marathon take these into account during the startup phase. -The idea is that each application provides an HTTP endpoint that Marathon queries periodically during an ongoing deployment in order to mark the associated readiness check result as successful if and only if the endpoint returns a response within the configured HTTP code range. + +The idea is that each application provides an HTTP endpoint that Marathon queries periodically during an ongoing deployment in order to mark the associated readiness check result as successful if and only if the endpoint returns a response within the configured HTTP code range. As long as the check keeps failing, Marathon will not proceed with the deployment (within the configured upgrade strategy bounds). Beginning with version 1.4, Traefik respects readiness check results if the Traefik option is set and checks are configured on the applications accordingly. -Note that due to the way readiness check results are currently exposed by the Marathon API, ready tasks may be taken into rotation with a small delay. -It is on the order of one readiness check timeout interval (as configured on the application specifiation) and guarantees that non-ready tasks do not receive traffic prematurely. + +!!! note + Due to the way readiness check results are currently exposed by the Marathon API, ready tasks may be taken into rotation with a small delay. + It is on the order of one readiness check timeout interval (as configured on the application specifiation) and guarantees that non-ready tasks do not receive traffic prematurely. If readiness checks are not possible, a current mitigation strategy is to enable [retries](/configuration/commons#retry-configuration) and make sure that a sufficient number of healthy application tasks exist so that one retry will likely hit one of those. Apart from its probabilistic nature, the workaround comes at the price of increased latency. -### Shutdown +#### Shutdown -It is possible to install a [termination handler](https://mesosphere.github.io/marathon/docs/health-checks.html) (available since Marathon version 1.3) with each application whose responsibility it is to delay the shutdown process long enough until the backend has been taken out of load-balancing rotation with reasonable confidence -(i.e., Traefik has received an update from the Marathon event bus, recomputes the available Marathon backends, and applies the new configuration). +It is possible to install a [termination handler](https://mesosphere.github.io/marathon/docs/health-checks.html) (available since Marathon version 1.3) with each application whose responsibility it is to delay the shutdown process long enough until the backend has been taken out of load-balancing rotation with reasonable confidence (i.e., Traefik has received an update from the Marathon event bus, recomputes the available Marathon backends, and applies the new configuration). Specifically, each termination handler should install a signal handler listening for a SIGTERM signal and implement the following steps on signal reception: 1. Disable Keep-Alive HTTP connections. @@ -70,12 +74,13 @@ Traefik already ignores Marathon tasks whose state does not match `TASK_RUNNING` How long HTTP requests should continue to be accepted in step 2 depends on how long Traefik needs to receive and process the Marathon configuration update. Under regular operational conditions, it should be on the order of seconds, with 10 seconds possibly being a good default value. -Again, configuring Traefik to do retries (as discussed in the previous section) can serve as a decent workaround strategy. +Again, configuring Traefik to do retries (as discussed in the previous section) can serve as a decent workaround strategy. Paired with termination handlers, they would cover for those cases where either the termination sequence or Traefik cannot complete their part of the orchestration process in time. -### Failure +#### Failure A failing application always happens unexpectedly, and hence, it is very difficult or even impossible to rule out the adversal effects categorically. + Failure reasons vary broadly and could stretch from unacceptable slowness, a task crash, or a network split. There are two mitigaton efforts: @@ -85,19 +90,22 @@ There are two mitigaton efforts: The Marathon health check makes sure that applications once deemed dysfunctional are being rescheduled to different slaves. However, they might take a while to get triggered and the follow-up processes to complete. + For that reason, the Treafik health check provides an additional check that responds more rapidly and does not require a configuration reload to happen. Additionally, it protects from cases that the Marathon health check may not be able to cover, such as a network split. -## (Non-)Alternatives +### (Non-)Alternatives -There are a few alternatives of varying quality that are frequently asked for. The remaining section is going to explore them along with a benefit/cost trade-off. +There are a few alternatives of varying quality that are frequently asked for. -### Reusing Marathon health checks +The remaining section is going to explore them along with a benefit/cost trade-off. + +#### Reusing Marathon health checks It may seem obvious to reuse the Marathon health checks as a signal to Traefik whether an application should be taken into load-balancing rotation or not. Apart from the increased latency a failing health check may have, a major problem with this is is that Marathon does not persist the health check results. -Consequently, if a master re-election occurs in the Marathon clusters, all health check results will revert to the _unknown_ state, effectively causing all applications inside the cluster to become unavailable and leading to a complete cluster failure. +Consequently, if a master re-election occurs in the Marathon clusters, all health check results will revert to the _unknown_ state, effectively causing all applications inside the cluster to become unavailable and leading to a complete cluster failure. Re-elections do not only happen during regular maintenance work (often requiring rolling upgrades of the Marathon nodes) but also when the Marathon leader fails spontaneously. As such, there is no way to handle this situation deterministically. @@ -106,11 +114,14 @@ Finally, Marathon health checks are not mandatory (the default is to use the tas Traefik used to use the health check results as a strict requirement but moved away from it as [users reported the dramatic consequences](https://github.com/containous/traefik/issues/653). If health check results are known to exist, however, they will be used to signal task availability. -### Draining +#### Draining -Another common approach is to let a proxy drain backends that are supposed to shut down. That is, once a backend is supposed to shut down, Traefik would stop forwarding requests. +Another common approach is to let a proxy drain backends that are supposed to shut down. +That is, once a backend is supposed to shut down, Traefik would stop forwarding requests. + +On the plus side, this would not require any modifications to the application in question. +However, implementing this fully within Traefik seems like a non-trivial undertaking. -On the plus side, this would not require any modifications to the application in question. However, implementing this fully within Traefik seems like a non-trivial undertaking. Additionally, the approach is less flexible compared to a custom termination handler since only the latter allows for the implementation of custom termination sequences that go beyond simple request draining (e.g., persisting a snapshot state to disk prior to terminating). The feature is currently not implemented; a request for draining in general is at [issue 41](https://github.com/containous/traefik/issues/41). diff --git a/docs/user-guide/swarm-mode.md b/docs/user-guide/swarm-mode.md index 55cf754e3..5f419d0a6 100644 --- a/docs/user-guide/swarm-mode.md +++ b/docs/user-guide/swarm-mode.md @@ -17,8 +17,8 @@ The cluster consists of: ## Cluster provisioning -First, let's create all the required nodes. It's a shorter version of -the [swarm tutorial](https://docs.docker.com/engine/swarm/swarm-tutorial/). +First, let's create all the required nodes. +It's a shorter version of the [swarm tutorial](https://docs.docker.com/engine/swarm/swarm-tutorial/). ```shell docker-machine create -d virtualbox manager @@ -29,8 +29,8 @@ docker-machine create -d virtualbox worker2 Then, let's setup the cluster, in order : 1. initialize the cluster -2. get the token for other host to join -3. on both workers, join the cluster with the token +1. get the token for other host to join +1. on both workers, join the cluster with the token ```shell docker-machine ssh manager "docker swarm init \ @@ -94,17 +94,19 @@ docker-machine ssh manager "docker service create \ Let's explain this command: -- `--publish 80:80 --publish 8080:8080`: we publish port `80` and `8080` on the cluster. -- `--constraint=node.role==manager`: we ask docker to schedule Træfik on a manager node. -- `--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock`: - we bind mount the docker socket where Træfik is scheduled to be able to speak to the daemon. -- `--network traefik-net`: we attach the Træfik service (and thus the underlying container) to the `traefik-net` network. -- `--docker`: enable docker backend, and `--docker.swarmmode` to enable the swarm mode on Træfik. -- `--web`: activate the webUI on port 8080 +| Option | Description | +|-----------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| +| `--publish 80:80 --publish 8080:8080` | we publish port `80` and `8080` on the cluster. | +| `--constraint=node.role==manager` | we ask docker to schedule Træfik on a manager node. | +| `--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock` | we bind mount the docker socket where Træfik is scheduled to be able to speak to the daemon. | +| `--network traefik-net` | we attach the Træfik service (and thus the underlying container) to the `traefik-net` network. | +| `--docker` | enable docker backend, and `--docker.swarmmode` to enable the swarm mode on Træfik. | +| `--web` | activate the webUI on port 8080 | ## Deploy your apps -We can now deploy our app on the cluster, here [whoami](https://github.com/emilevauge/whoami), a simple web server in Go. We start 2 services, on the `traefik-net` network. +We can now deploy our app on the cluster, here [whoami](https://github.com/emilevauge/whoami), a simple web server in Go. +We start 2 services, on the `traefik-net` network. ```shell docker-machine ssh manager "docker service create \ @@ -121,9 +123,12 @@ docker-machine ssh manager "docker service create \ emilevauge/whoami" ``` -Note that we set whoami1 to use sticky sessions (`--label traefik.backend.loadbalancer.sticky=true`). We'll demonstrate that later. +!!! note + We set whoami1 to use sticky sessions (`--label traefik.backend.loadbalancer.sticky=true`). + We'll demonstrate that later. -**Note**: If using `docker stack deploy`, there is [a specific way that the labels must be defined in the docker-compose file](https://github.com/containous/traefik/issues/994#issuecomment-269095109). +!!! note + If using `docker stack deploy`, there is [a specific way that the labels must be defined in the docker-compose file](https://github.com/containous/traefik/issues/994#issuecomment-269095109). Check that everything is scheduled and started: @@ -182,7 +187,8 @@ X-Forwarded-Proto: http X-Forwarded-Server: 8fbc39271b4c ``` -Note that as Træfik is published, you can access it from any machine and not only the manager. +!!! note + As Træfik is published, you can access it from any machine and not only the manager. ```shell curl -H Host:whoami0.traefik http://$(docker-machine ip worker1) @@ -231,11 +237,9 @@ X-Forwarded-Server: 8fbc39271b4c ```shell docker-machine ssh manager "docker service scale whoami0=5" - docker-machine ssh manager "docker service scale whoami1=5" ``` - Check that we now have 5 replicas of each `whoami` service: ```shell @@ -298,7 +302,9 @@ X-Forwarded-Host: 10.0.9.4:80 X-Forwarded-Proto: http X-Forwarded-Server: 8fbc39271b4c ``` -Wait, I thought we added the sticky flag to `whoami1`? Traefik relies on a cookie to maintain stickyness so you'll need to test this with a browser. + +Wait, I thought we added the sticky flag to `whoami1`? +Traefik relies on a cookie to maintain stickyness so you'll need to test this with a browser. First you need to add `whoami1.traefik` to your hosts file: diff --git a/docs/user-guide/swarm.md b/docs/user-guide/swarm.md index 57e85e220..d63cd7ae2 100644 --- a/docs/user-guide/swarm.md +++ b/docs/user-guide/swarm.md @@ -1,6 +1,7 @@ # Swarm cluster This section explains how to create a multi-host [swarm](https://docs.docker.com/swarm) cluster using [docker-machine](https://docs.docker.com/machine/) and how to deploy Træfik on it. + The cluster consists of: - 2 servers @@ -97,14 +98,17 @@ docker $(docker-machine config mhs-demo0) run \ Let's explain this command: -- `-p 80:80 -p 8080:8080`: we bind ports 80 and 8080 -- `--net=my-net`: run the container on the network my-net -- `-v /var/lib/boot2docker/:/ssl`: mount the ssl keys generated by docker-machine -- `-c /dev/null`: empty config file -- `--docker`: enable docker backend -- `--docker.endpoint=tcp://172.18.0.1:3376`: connect to the swarm master using the docker_gwbridge network -- `--docker.tls`: enable TLS using the docker-machine keys -- `--web`: activate the webUI on port 8080 +| Option | Description | +|-------------------------------------------|---------------------------------------------------------------| +| `-p 80:80 -p 8080:8080` | we bind ports 80 and 8080 | +| `--net=my-net` | run the container on the network my-net | +| `-v /var/lib/boot2docker/:/ssl` | mount the ssl keys generated by docker-machine | +| `-c /dev/null` | empty config file | +| `--docker` | enable docker backend | +| `--docker.endpoint=tcp://172.18.0.1:3376` | connect to the swarm master using the docker_gwbridge network | +| `--docker.tls` | enable TLS using the docker-machine keys | +| `--web` | activate the webUI on port 8080 | + ## Deploy your apps diff --git a/examples/accessLog/traefik.ab.toml b/examples/accessLog/traefik.ab.toml index 28fa15bbb..23f8a0c05 100644 --- a/examples/accessLog/traefik.ab.toml +++ b/examples/accessLog/traefik.ab.toml @@ -19,19 +19,19 @@ address = ":7888" ################################################################ # rules ################################################################ - [backends] - [backends.backend] - [backends.backend.LoadBalancer] - method = "drr" - [backends.backend.servers.server1] - url = "http://127.0.0.1:8081" - [backends.backend.servers.server2] - url = "http://127.0.0.1:8082" - [backends.backend.servers.server3] - url = "http://127.0.0.1:8083" - [frontends] - [frontends.frontend] - backend = "backend" - passHostHeader = true - [frontends.frontend.routes.test] - rule = "Path: /test" + [backends] + [backends.backend] + [backends.backend.LoadBalancer] + method = "drr" + [backends.backend.servers.server1] + url = "http://127.0.0.1:8081" + [backends.backend.servers.server2] + url = "http://127.0.0.1:8082" + [backends.backend.servers.server3] + url = "http://127.0.0.1:8083" + [frontends] + [frontends.frontend] + backend = "backend" + passHostHeader = true + [frontends.frontend.routes.test] + rule = "Path: /test" diff --git a/examples/accessLog/traefik.example.toml b/examples/accessLog/traefik.example.toml index 0e425210d..ca3a44369 100644 --- a/examples/accessLog/traefik.example.toml +++ b/examples/accessLog/traefik.example.toml @@ -19,24 +19,24 @@ address = ":7888" ################################################################ # rules ################################################################ - [backends] - [backends.backend1] - [backends.backend1.servers.server1] - url = "http://127.0.0.1:8081" - [backends.backend2] - [backends.backend2.LoadBalancer] - method = "drr" - [backends.backend2.servers.server1] - url = "http://127.0.0.1:8082" - [backends.backend2.servers.server2] - url = "http://127.0.0.1:8083" + [backends] + [backends.backend1] + [backends.backend1.servers.server1] + url = "http://127.0.0.1:8081" + [backends.backend2] + [backends.backend2.LoadBalancer] + method = "drr" + [backends.backend2.servers.server1] + url = "http://127.0.0.1:8082" + [backends.backend2.servers.server2] + url = "http://127.0.0.1:8083" [frontends] - [frontends.frontend1] - backend = "backend1" - [frontends.frontend1.routes.test_1] - rule = "Path: /test1" - [frontends.frontend2] - backend = "backend2" - passHostHeader = true - [frontends.frontend2.routes.test_2] - rule = "Path: /test2" \ No newline at end of file + [frontends.frontend1] + backend = "backend1" + [frontends.frontend1.routes.test_1] + rule = "Path: /test1" + [frontends.frontend2] + backend = "backend2" + passHostHeader = true + [frontends.frontend2.routes.test_2] + rule = "Path: /test2" \ No newline at end of file diff --git a/examples/acme/acme.toml b/examples/acme/acme.toml index 78d6eadf8..c6f6348ba 100644 --- a/examples/acme/acme.toml +++ b/examples/acme/acme.toml @@ -6,7 +6,7 @@ defaultEntryPoints = ["http", "https"] [entryPoints.http] address = ":80" [entryPoints.http.redirect] - entryPoint = "https" + entryPoint = "https" [entryPoints.https] address = ":443" [entryPoints.https.tls] @@ -22,7 +22,7 @@ caServer = "http://traefik.localhost.com:4000/directory" [web] - address = ":8080" +address = ":8080" [docker] endpoint = "unix:///var/run/docker.sock" diff --git a/glide.lock b/glide.lock index db552fdae..28c14efb5 100644 --- a/glide.lock +++ b/glide.lock @@ -1,4 +1,4 @@ -hash: 857714f15cc10657ae2f15f3ab6592734a1ebc524d32b5353948258ce3043b6a +hash: f5dd83cd0bcf9f38bf6916bc028e108c59aee57ea440e914bc68f2b90da227d3 updated: 2017-09-09T11:52:16.848940186+02:00 imports: - name: cloud.google.com/go @@ -383,9 +383,7 @@ imports: repo: https://github.com/ijc25/Gotty.git vcs: git - name: github.com/NYTimes/gziphandler - version: 824b33f2a7457025697878c865c323f801118043 - repo: https://github.com/containous/gziphandler.git - vcs: git + version: 97ae7fbaf81620fe97840685304a78a306a39c64 - name: github.com/ogier/pflag version: 45c278ab3607870051a2ea9040bb85fcb8557481 - name: github.com/opencontainers/go-digest @@ -784,7 +782,9 @@ testImports: - name: github.com/flynn/go-shlex version: 3f9db97f856818214da2e1057f8ad84803971cff - name: github.com/go-check/check - version: 11d3bc7aa68e238947792f30573146a3231fc0f1 + version: ca0bf163426aa183d03fd4949101785c0347f273 + repo: https://github.com/containous/check.git + vcs: git - name: github.com/gorilla/mux version: e444e69cbd2e2e3e0749a2f3c717cec491552bbf - name: github.com/libkermit/compose diff --git a/glide.yaml b/glide.yaml index 09275755a..96610abfa 100644 --- a/glide.yaml +++ b/glide.yaml @@ -80,9 +80,6 @@ import: vcs: git - package: github.com/abbot/go-http-auth - package: github.com/NYTimes/gziphandler - version: ^v1002.0.0 - repo: https://github.com/containous/gziphandler.git - vcs: git - package: github.com/docker/leadership - package: github.com/satori/go.uuid version: ^1.1.0 @@ -209,7 +206,9 @@ testImport: - package: github.com/docker/libcompose version: 1b708aac26a4fc6f9bff31728a8e3a252ef57dbd - package: github.com/go-check/check - version: 11d3bc7aa68e238947792f30573146a3231fc0f1 + version: fork-containous + repo: https://github.com/containous/check.git + vcs: git - package: github.com/libkermit/compose version: 4a33a16f1446ba205c4da7b09105d5bdc293b432 subpackages: diff --git a/healthcheck/healthcheck.go b/healthcheck/healthcheck.go index ed70fd9a2..d578b98d0 100644 --- a/healthcheck/healthcheck.go +++ b/healthcheck/healthcheck.go @@ -35,7 +35,7 @@ type Options struct { } func (opt Options) String() string { - return fmt.Sprintf("[Path: %s Interval: %s]", opt.Path, opt.Interval) + return fmt.Sprintf("[Path: %s Port: %d Interval: %s]", opt.Path, opt.Port, opt.Interval) } // BackendHealthCheck HealthCheck configuration for a backend @@ -131,14 +131,14 @@ func checkBackend(currentBackend *BackendHealthCheck) { } func (backend *BackendHealthCheck) newRequest(serverURL *url.URL) (*http.Request, error) { - if backend.Options.Port == 0 { + if backend.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.Host = net.JoinHostPort(u.Hostname(), strconv.Itoa(backend.Port)) u.Path = u.Path + backend.Path return http.NewRequest("GET", u.String(), nil) diff --git a/integration/access_log_test.go b/integration/access_log_test.go index f049cccc9..175edeace 100644 --- a/integration/access_log_test.go +++ b/integration/access_log_test.go @@ -30,7 +30,8 @@ func (s *AccessLogSuite) TestAccessLog(c *check.C) { os.Remove(traefikTestLogFile) // Start Traefik - cmd, _ := s.cmdTraefik(withConfigFile("fixtures/access_log_config.toml")) + cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -96,7 +97,7 @@ func CheckAccessLogFormat(c *check.C, line string, i int) { tokens, err := shellwords.Parse(line) c.Assert(err, checker.IsNil) c.Assert(tokens, checker.HasLen, 14) - c.Assert(tokens[6], checker.Matches, `^\d{3}$`) + c.Assert(tokens[6], checker.Matches, `^(-|\d{3})$`) c.Assert(tokens[10], checker.Equals, fmt.Sprintf("%d", i+1)) c.Assert(tokens[11], checker.HasPrefix, "frontend") c.Assert(tokens[12], checker.HasPrefix, "http://127.0.0.1:808") diff --git a/integration/acme_test.go b/integration/acme_test.go index 1d89b8c7f..61e246500 100644 --- a/integration/acme_test.go +++ b/integration/acme_test.go @@ -4,6 +4,7 @@ import ( "crypto/tls" "fmt" "net/http" + "os" "time" "github.com/containous/traefik/integration/try" @@ -101,8 +102,10 @@ func (s *AcmeSuite) retrieveAcmeCertificate(c *check.C, testCase AcmeTestCase) { OnDemand: testCase.onDemand, OnHostRule: !testCase.onDemand, }) + defer os.Remove(file) - cmd, output := s.cmdTraefik(withConfigFile(file)) + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -120,8 +123,6 @@ func (s *AcmeSuite) retrieveAcmeCertificate(c *check.C, testCase AcmeTestCase) { _, err := client.Get("https://127.0.0.1:5001") return err }) - // TODO: waiting a refactor of integration tests - s.displayTraefikLog(c, output) c.Assert(err, checker.IsNil) tr = &http.Transport{ diff --git a/integration/constraint_test.go b/integration/constraint_test.go index ed48ec6b2..dd2968584 100644 --- a/integration/constraint_test.go +++ b/integration/constraint_test.go @@ -80,12 +80,13 @@ func (s *ConstraintSuite) deregisterService(name string, address string) error { } func (s *ConstraintSuite) TestMatchConstraintGlobal(c *check.C) { - cmd, _ := s.cmdTraefik( + cmd, display := s.traefikCmd( withConfigFile("fixtures/consul_catalog/simple.toml"), "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--constraints=tag==api") + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -105,12 +106,13 @@ func (s *ConstraintSuite) TestMatchConstraintGlobal(c *check.C) { } func (s *ConstraintSuite) TestDoesNotMatchConstraintGlobal(c *check.C) { - cmd, _ := s.cmdTraefik( + cmd, display := s.traefikCmd( withConfigFile("fixtures/consul_catalog/simple.toml"), "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--constraints=tag==api") + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -130,12 +132,13 @@ func (s *ConstraintSuite) TestDoesNotMatchConstraintGlobal(c *check.C) { } func (s *ConstraintSuite) TestMatchConstraintProvider(c *check.C) { - cmd, _ := s.cmdTraefik( + cmd, display := s.traefikCmd( withConfigFile("fixtures/consul_catalog/simple.toml"), "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--consulCatalog.constraints=tag==api") + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -155,12 +158,13 @@ func (s *ConstraintSuite) TestMatchConstraintProvider(c *check.C) { } func (s *ConstraintSuite) TestDoesNotMatchConstraintProvider(c *check.C) { - cmd, _ := s.cmdTraefik( + cmd, display := s.traefikCmd( withConfigFile("fixtures/consul_catalog/simple.toml"), "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--consulCatalog.constraints=tag==api") + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -180,13 +184,14 @@ func (s *ConstraintSuite) TestDoesNotMatchConstraintProvider(c *check.C) { } func (s *ConstraintSuite) TestMatchMultipleConstraint(c *check.C) { - cmd, _ := s.cmdTraefik( + cmd, display := s.traefikCmd( withConfigFile("fixtures/consul_catalog/simple.toml"), "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--consulCatalog.constraints=tag==api", "--constraints=tag!=us-*") + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -206,13 +211,14 @@ func (s *ConstraintSuite) TestMatchMultipleConstraint(c *check.C) { } func (s *ConstraintSuite) TestDoesNotMatchMultipleConstraint(c *check.C) { - cmd, _ := s.cmdTraefik( + cmd, display := s.traefikCmd( withConfigFile("fixtures/consul_catalog/simple.toml"), "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--consulCatalog.constraints=tag==api", "--constraints=tag!=us-*") + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() diff --git a/integration/consul_catalog_test.go b/integration/consul_catalog_test.go index e0aa011c6..9cb0c5590 100644 --- a/integration/consul_catalog_test.go +++ b/integration/consul_catalog_test.go @@ -104,10 +104,11 @@ func (s *ConsulCatalogSuite) deregisterService(name string, address string) erro } func (s *ConsulCatalogSuite) TestSimpleConfiguration(c *check.C) { - cmd, _ := s.cmdTraefik( + cmd, display := s.traefikCmd( withConfigFile("fixtures/consul_catalog/simple.toml"), "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500") + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -119,15 +120,20 @@ func (s *ConsulCatalogSuite) TestSimpleConfiguration(c *check.C) { } func (s *ConsulCatalogSuite) TestSingleService(c *check.C) { - cmd, _ := s.cmdTraefik( + cmd, display := s.traefikCmd( withConfigFile("fixtures/consul_catalog/simple.toml"), "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost") + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() + // Wait for Traefik to turn ready. + err = try.GetRequest("http://127.0.0.1:8000/", 2*time.Second, try.StatusCodeIs(http.StatusNotFound)) + c.Assert(err, checker.IsNil) + nginx := s.composeProject.Container(c, "nginx1") err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{}) @@ -143,12 +149,13 @@ func (s *ConsulCatalogSuite) TestSingleService(c *check.C) { } func (s *ConsulCatalogSuite) TestExposedByDefaultFalseSingleService(c *check.C) { - cmd, _ := s.cmdTraefik( + cmd, display := s.traefikCmd( withConfigFile("fixtures/consul_catalog/simple.toml"), "--consulCatalog", "--consulCatalog.exposedByDefault=false", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost") + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -168,12 +175,13 @@ func (s *ConsulCatalogSuite) TestExposedByDefaultFalseSingleService(c *check.C) } func (s *ConsulCatalogSuite) TestExposedByDefaultFalseSimpleServiceMultipleNode(c *check.C) { - cmd, _ := s.cmdTraefik( + cmd, display := s.traefikCmd( withConfigFile("fixtures/consul_catalog/simple.toml"), "--consulCatalog", "--consulCatalog.exposedByDefault=false", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost") + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -198,12 +206,13 @@ func (s *ConsulCatalogSuite) TestExposedByDefaultFalseSimpleServiceMultipleNode( } func (s *ConsulCatalogSuite) TestExposedByDefaultTrueSimpleServiceMultipleNode(c *check.C) { - cmd, _ := s.cmdTraefik( + cmd, display := s.traefikCmd( withConfigFile("fixtures/consul_catalog/simple.toml"), "--consulCatalog", "--consulCatalog.exposedByDefault=true", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost") + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -232,12 +241,13 @@ func (s *ConsulCatalogSuite) TestExposedByDefaultTrueSimpleServiceMultipleNode(c } func (s *ConsulCatalogSuite) TestRefreshConfigWithMultipleNodeWithoutHealthCheck(c *check.C) { - cmd, _ := s.cmdTraefik( + cmd, display := s.traefikCmd( withConfigFile("fixtures/consul_catalog/simple.toml"), "--consulCatalog", "--consulCatalog.exposedByDefault=true", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost") + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -283,20 +293,17 @@ func (s *ConsulCatalogSuite) TestRefreshConfigWithMultipleNodeWithoutHealthCheck } func (s *ConsulCatalogSuite) TestBasicAuthSimpleService(c *check.C) { - cmd, output := s.cmdTraefik( + cmd, display := s.traefikCmd( withConfigFile("fixtures/consul_catalog/simple.toml"), "--consulCatalog", "--consulCatalog.exposedByDefault=true", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost") + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() - defer func() { - s.displayTraefikLog(c, output) - }() - nginx := s.composeProject.Container(c, "nginx1") err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{ diff --git a/integration/consul_test.go b/integration/consul_test.go index e58af2c80..39ecc4e86 100644 --- a/integration/consul_test.go +++ b/integration/consul_test.go @@ -95,7 +95,8 @@ func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) { file := s.adaptFile(c, "fixtures/consul/simple.toml", struct{ ConsulHost string }{consulHost}) defer os.Remove(file) - cmd, _ := s.cmdTraefik(withConfigFile(file)) + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -111,7 +112,8 @@ func (s *ConsulSuite) TestNominalConfiguration(c *check.C) { file := s.adaptFile(c, "fixtures/consul/simple.toml", struct{ ConsulHost string }{consulHost}) defer os.Remove(file) - cmd, _ := s.cmdTraefik(withConfigFile(file)) + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -210,10 +212,11 @@ func (s *ConsulSuite) TestGlobalConfiguration(c *check.C) { c.Assert(err, checker.IsNil) // start traefik - cmd, _ := s.cmdTraefik( + cmd, display := s.traefikCmd( withConfigFile("fixtures/simple_web.toml"), "--consul", "--consul.endpoint="+consulHost+":8500") + defer display(c) err = cmd.Start() c.Assert(err, checker.IsNil) @@ -297,7 +300,7 @@ func (s *ConsulSuite) skipTestGlobalConfigurationWithClientTLS(c *check.C) { c.Assert(err, checker.IsNil) // start traefik - cmd, _ := s.cmdTraefik( + cmd, display := s.traefikCmd( withConfigFile("fixtures/simple_web.toml"), "--consul", "--consul.endpoint="+consulHost+":8585", @@ -305,6 +308,7 @@ func (s *ConsulSuite) skipTestGlobalConfigurationWithClientTLS(c *check.C) { "--consul.tls.cert=resources/tls/consul.cert", "--consul.tls.key=resources/tls/consul.key", "--consul.tls.insecureskipverify") + defer display(c) err = cmd.Start() c.Assert(err, checker.IsNil) @@ -319,10 +323,11 @@ func (s *ConsulSuite) TestCommandStoreConfig(c *check.C) { s.setupConsul(c) consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress - cmd, _ := s.cmdTraefik( + cmd, display := s.traefikCmd( "storeconfig", withConfigFile("fixtures/simple_web.toml"), "--consul.endpoint="+consulHost+":8500") + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) diff --git a/integration/docker_test.go b/integration/docker_test.go index 9f415e4f5..e292c247a 100644 --- a/integration/docker_test.go +++ b/integration/docker_test.go @@ -79,7 +79,8 @@ func (s *DockerSuite) TestSimpleConfiguration(c *check.C) { file := s.adaptFileForHost(c, "fixtures/docker/simple.toml") defer os.Remove(file) - cmd, _ := s.cmdTraefik(withConfigFile(file)) + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -96,7 +97,8 @@ func (s *DockerSuite) TestDefaultDockerContainers(c *check.C) { name := s.startContainer(c, "swarm:1.0.0", "manage", "token://blablabla") // Start traefik - cmd, _ := s.cmdTraefik(withConfigFile(file)) + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -128,7 +130,8 @@ func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) { s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla") // Start traefik - cmd, _ := s.cmdTraefik(withConfigFile(file)) + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -160,7 +163,8 @@ func (s *DockerSuite) TestDockerContainersWithOneMissingLabels(c *check.C) { s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla") // Start traefik - cmd, _ := s.cmdTraefik(withConfigFile(file)) + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() diff --git a/integration/dynamodb_test.go b/integration/dynamodb_test.go index 4d60a75cd..8497908d9 100644 --- a/integration/dynamodb_test.go +++ b/integration/dynamodb_test.go @@ -147,7 +147,8 @@ func (s *DynamoDBSuite) TestSimpleConfiguration(c *check.C) { dynamoURL := "http://" + s.composeProject.Container(c, "dynamo").NetworkSettings.IPAddress + ":8000" file := s.adaptFile(c, "fixtures/dynamodb/simple.toml", struct{ DynamoURL string }{dynamoURL}) defer os.Remove(file) - cmd, _ := s.cmdTraefik(withConfigFile(file)) + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() diff --git a/integration/error_pages_test.go b/integration/error_pages_test.go index 3ba9a308e..ac73047e3 100644 --- a/integration/error_pages_test.go +++ b/integration/error_pages_test.go @@ -33,7 +33,8 @@ func (s *ErrorPagesSuite) TestSimpleConfiguration(c *check.C) { }{s.BackendIP, s.ErrorPageIP}) defer os.Remove(file) - cmd, _ := s.cmdTraefik(withConfigFile(file)) + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -55,7 +56,8 @@ func (s *ErrorPagesSuite) TestErrorPage(c *check.C) { }{s.BackendIP, s.ErrorPageIP}) defer os.Remove(file) - cmd, _ := s.cmdTraefik(withConfigFile(file)) + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() diff --git a/integration/etcd_test.go b/integration/etcd_test.go index d51330332..7a6c9ee93 100644 --- a/integration/etcd_test.go +++ b/integration/etcd_test.go @@ -64,7 +64,8 @@ func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) { file := s.adaptFile(c, "fixtures/etcd/simple.toml", struct{ EtcdHost string }{etcdHost}) defer os.Remove(file) - cmd, _ := s.cmdTraefik(withConfigFile(file)) + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -81,7 +82,8 @@ func (s *EtcdSuite) TestNominalConfiguration(c *check.C) { file := s.adaptFile(c, "fixtures/etcd/simple.toml", struct{ EtcdHost string }{etcdHost}) defer os.Remove(file) - cmd, _ := s.cmdTraefik(withConfigFile(file)) + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -200,10 +202,11 @@ func (s *EtcdSuite) TestGlobalConfiguration(c *check.C) { c.Assert(err, checker.IsNil) // start traefik - cmd, _ := s.cmdTraefik( + cmd, display := s.traefikCmd( withConfigFile("fixtures/simple_web.toml"), "--etcd", "--etcd.endpoint="+etcdHost+":4001") + defer display(c) err = cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -279,10 +282,11 @@ func (s *EtcdSuite) TestGlobalConfiguration(c *check.C) { func (s *EtcdSuite) TestCertificatesContentstWithSNIConfigHandshake(c *check.C) { etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress // start traefik - cmd, _ := s.cmdTraefik( + cmd, display := s.traefikCmd( withConfigFile("fixtures/simple_web.toml"), "--etcd", "--etcd.endpoint="+etcdHost+":4001") + defer display(c) whoami1IP := s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress whoami2IP := s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress @@ -387,10 +391,11 @@ func (s *EtcdSuite) TestCertificatesContentstWithSNIConfigHandshake(c *check.C) func (s *EtcdSuite) TestCommandStoreConfig(c *check.C) { etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress - cmd, _ := s.cmdTraefik( + cmd, display := s.traefikCmd( "storeconfig", withConfigFile("fixtures/simple_web.toml"), "--etcd.endpoint="+etcdHost+":4001") + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) diff --git a/integration/eureka_test.go b/integration/eureka_test.go index 8bcda85e4..eb1be4994 100644 --- a/integration/eureka_test.go +++ b/integration/eureka_test.go @@ -40,7 +40,8 @@ func (s *EurekaSuite) TestSimpleConfiguration(c *check.C) { file := s.adaptFile(c, "fixtures/eureka/simple.toml", struct{ EurekaHost string }{s.eurekaIP}) defer os.Remove(file) - cmd, _ := s.cmdTraefik(withConfigFile(file)) + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() diff --git a/integration/file_test.go b/integration/file_test.go index 2b3cf2344..df43b1b61 100644 --- a/integration/file_test.go +++ b/integration/file_test.go @@ -19,7 +19,8 @@ func (s *FileSuite) SetUpSuite(c *check.C) { } func (s *FileSuite) TestSimpleConfiguration(c *check.C) { - cmd, _ := s.cmdTraefik(withConfigFile("fixtures/file/simple.toml")) + cmd, display := s.traefikCmd(withConfigFile("fixtures/file/simple.toml")) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -31,7 +32,8 @@ func (s *FileSuite) TestSimpleConfiguration(c *check.C) { // #56 regression test, make sure it does not fail func (s *FileSuite) TestSimpleConfigurationNoPanic(c *check.C) { - cmd, _ := s.cmdTraefik(withConfigFile("fixtures/file/56-simple-panic.toml")) + cmd, display := s.traefikCmd(withConfigFile("fixtures/file/56-simple-panic.toml")) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -42,8 +44,8 @@ func (s *FileSuite) TestSimpleConfigurationNoPanic(c *check.C) { } func (s *FileSuite) TestDirectoryConfiguration(c *check.C) { - cmd, _ := s.cmdTraefik(withConfigFile("fixtures/file/directory.toml")) - + cmd, display := s.traefikCmd(withConfigFile("fixtures/file/directory.toml")) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() diff --git a/integration/fixtures/healthcheck/port_overload.toml b/integration/fixtures/healthcheck/port_overload.toml new file mode 100644 index 000000000..b3c76db6c --- /dev/null +++ b/integration/fixtures/healthcheck/port_overload.toml @@ -0,0 +1,26 @@ +defaultEntryPoints = ["http"] + +logLevel = "DEBUG" + +[entryPoints] + [entryPoints.http] + address = ":8000" + +[web] + address = ":8080" + +[file] +[backends] + [backends.backend1] + [backends.backend1.healthcheck] + path = "/health" + port = 80 + interval = "1s" + [backends.backend1.servers.server1] + url = "http://{{.Server1}}:81" + +[frontends] + [frontends.frontend1] + backend = "backend1" + [frontends.frontend1.routes.test_1] + rule = "Host:test.localhost" diff --git a/integration/grpc_test.go b/integration/grpc_test.go index 2db8406e8..f9ec89b7b 100644 --- a/integration/grpc_test.go +++ b/integration/grpc_test.go @@ -24,7 +24,7 @@ type GRPCSuite struct{ BaseSuite } type myserver struct{} -func (suite *GRPCSuite) SetUpSuite(c *check.C) { +func (s *GRPCSuite) SetUpSuite(c *check.C) { var err error LocalhostCert, err = ioutil.ReadFile("./resources/tls/local.cert") c.Assert(err, check.IsNil) @@ -72,7 +72,7 @@ func callHelloClientGRPC() (string, error) { return r.Message, nil } -func (suite *GRPCSuite) TestGRPC(c *check.C) { +func (s *GRPCSuite) TestGRPC(c *check.C) { lis, err := net.Listen("tcp", ":0") _, port, err := net.SplitHostPort(lis.Addr().String()) c.Assert(err, check.IsNil) @@ -83,7 +83,7 @@ func (suite *GRPCSuite) TestGRPC(c *check.C) { c.Assert(err, check.IsNil) }() - file := suite.adaptFile(c, "fixtures/grpc/config.toml", struct { + file := s.adaptFile(c, "fixtures/grpc/config.toml", struct { CertContent string KeyContent string GRPCServerPort string @@ -94,7 +94,8 @@ func (suite *GRPCSuite) TestGRPC(c *check.C) { }) defer os.Remove(file) - cmd, _ := suite.cmdTraefik(withConfigFile(file)) + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) err = cmd.Start() c.Assert(err, check.IsNil) diff --git a/integration/healthcheck_test.go b/integration/healthcheck_test.go index 37205ce78..c1126eb45 100644 --- a/integration/healthcheck_test.go +++ b/integration/healthcheck_test.go @@ -34,7 +34,8 @@ func (s *HealthCheckSuite) TestSimpleConfiguration(c *check.C) { }{s.whoami1IP, s.whoami2IP}) defer os.Remove(file) - cmd, _ := s.cmdTraefik(withConfigFile(file)) + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -112,7 +113,8 @@ func (s *HealthCheckSuite) doTestMultipleEntrypoints(c *check.C, fixture string) }{s.whoami1IP, s.whoami2IP}) defer os.Remove(file) - cmd, _ := s.cmdTraefik(withConfigFile(file)) + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -163,3 +165,50 @@ func (s *HealthCheckSuite) doTestMultipleEntrypoints(c *check.C, fixture string) err = try.Request(frontend1Req, 2*time.Second, try.BodyContains(s.whoami1IP)) c.Assert(err, checker.Not(checker.IsNil)) } + +func (s *HealthCheckSuite) TestPortOverload(c *check.C) { + + // Set one whoami health to 200 + client := &http.Client{} + statusInternalServerErrorReq, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBuffer([]byte("200"))) + c.Assert(err, checker.IsNil) + _, err = client.Do(statusInternalServerErrorReq) + c.Assert(err, checker.IsNil) + + file := s.adaptFile(c, "fixtures/healthcheck/port_overload.toml", struct { + Server1 string + }{s.whoami1IP}) + defer os.Remove(file) + + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) + err = cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + // wait for traefik + err = try.GetRequest("http://127.0.0.1:8080/api/providers", 10*time.Second, try.BodyContains("Host:test.localhost")) + c.Assert(err, checker.IsNil) + + frontendHealthReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/health", nil) + c.Assert(err, checker.IsNil) + frontendHealthReq.Host = "test.localhost" + + //We test bad gateway because we use an invalid port for the backend + err = try.Request(frontendHealthReq, 500*time.Millisecond, try.StatusCodeIs(http.StatusBadGateway)) + c.Assert(err, checker.IsNil) + + // Set one whoami health to 500 + statusInternalServerErrorReq, err = http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBuffer([]byte("500"))) + c.Assert(err, checker.IsNil) + _, err = client.Do(statusInternalServerErrorReq) + c.Assert(err, checker.IsNil) + + // Waiting for Traefik healthcheck + try.Sleep(2 * time.Second) + + // Verify no backend service is available due to failing health checks + err = try.Request(frontendHealthReq, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable)) + c.Assert(err, checker.IsNil) + +} diff --git a/integration/https_test.go b/integration/https_test.go index 6118fcbdb..3d277ec94 100644 --- a/integration/https_test.go +++ b/integration/https_test.go @@ -20,7 +20,8 @@ type HTTPSSuite struct{ BaseSuite } // "snitest.com", which happens to match the CN of 'snitest.com.crt'. The test // verifies that traefik presents the correct certificate. func (s *HTTPSSuite) TestWithSNIConfigHandshake(c *check.C) { - cmd, _ := s.cmdTraefik(withConfigFile("fixtures/https/https_sni.toml")) + cmd, display := s.traefikCmd(withConfigFile("fixtures/https/https_sni.toml")) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -53,7 +54,8 @@ func (s *HTTPSSuite) TestWithSNIConfigHandshake(c *check.C) { // SNI hostnames of "snitest.org" and "snitest.com". The test verifies // that traefik routes the requests to the expected backends. func (s *HTTPSSuite) TestWithSNIConfigRoute(c *check.C) { - cmd, _ := s.cmdTraefik(withConfigFile("fixtures/https/https_sni.toml")) + cmd, display := s.traefikCmd(withConfigFile("fixtures/https/https_sni.toml")) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -111,7 +113,8 @@ func (s *HTTPSSuite) TestWithSNIConfigRoute(c *check.C) { // TestWithClientCertificateAuthentication // The client has to send a certificate signed by a CA trusted by the server func (s *HTTPSSuite) TestWithClientCertificateAuthentication(c *check.C) { - cmd, _ := s.cmdTraefik(withConfigFile("fixtures/https/clientca/https_1ca1config.toml")) + cmd, display := s.traefikCmd(withConfigFile("fixtures/https/clientca/https_1ca1config.toml")) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -157,7 +160,8 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthentication(c *check.C) { // TestWithClientCertificateAuthentication // Use two CA:s and test that clients with client signed by either of them can connect func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipeCAs(c *check.C) { - cmd, _ := s.cmdTraefik(withConfigFile("fixtures/https/clientca/https_2ca1config.toml")) + cmd, display := s.traefikCmd(withConfigFile("fixtures/https/clientca/https_2ca1config.toml")) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -217,7 +221,8 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipeCAs(c *check. // TestWithClientCertificateAuthentication // Use two CA:s in two different files and test that clients with client signed by either of them can connect func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipeCAsMultipleFiles(c *check.C) { - cmd, _ := s.cmdTraefik(withConfigFile("fixtures/https/clientca/https_2ca2config.toml")) + cmd, display := s.traefikCmd(withConfigFile("fixtures/https/clientca/https_2ca2config.toml")) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -281,7 +286,8 @@ func (s *HTTPSSuite) TestWithRootCAsContentForHTTPSOnBackend(c *check.C) { file := s.adaptFile(c, "fixtures/https/rootcas/https.toml", struct{ BackendHost string }{backend.URL}) defer os.Remove(file) - cmd, _ := s.cmdTraefik(withConfigFile(file)) + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -302,7 +308,8 @@ func (s *HTTPSSuite) TestWithRootCAsFileForHTTPSOnBackend(c *check.C) { file := s.adaptFile(c, "fixtures/https/rootcas/https_with_file.toml", struct{ BackendHost string }{backend.URL}) defer os.Remove(file) - cmd, _ := s.cmdTraefik(withConfigFile(file)) + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() diff --git a/integration/integration_test.go b/integration/integration_test.go index 1c0850ff8..fe91ef9ea 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -12,6 +12,7 @@ import ( "testing" "text/template" + "github.com/containous/traefik/log" "github.com/go-check/check" compose "github.com/libkermit/compose/check" checker "github.com/vdemeester/shakers" @@ -88,12 +89,21 @@ func (s *BaseSuite) cmdTraefik(args ...string) (*exec.Cmd, *bytes.Buffer) { return cmd, &out } +func (s *BaseSuite) traefikCmd(args ...string) (*exec.Cmd, func(*check.C)) { + cmd, out := s.cmdTraefik(args...) + return cmd, func(c *check.C) { + if c.Failed() { + s.displayTraefikLog(c, out) + } + } +} + func (s *BaseSuite) displayTraefikLog(c *check.C, output *bytes.Buffer) { if output == nil || output.Len() == 0 { - fmt.Printf("%s: No Traefik logs present.", c.TestName()) + log.Printf("%s: No Traefik logs.", c.TestName()) } else { - fmt.Printf("%s: Traefik logs: ", c.TestName()) - fmt.Println(output.String()) + log.Printf("%s: Traefik logs: ", c.TestName()) + log.Println(output.String()) } } diff --git a/integration/log_rotation_test.go b/integration/log_rotation_test.go index 5bf2b89c4..631757809 100644 --- a/integration/log_rotation_test.go +++ b/integration/log_rotation_test.go @@ -20,7 +20,8 @@ type LogRotationSuite struct{ BaseSuite } func (s *LogRotationSuite) TestAccessLogRotation(c *check.C) { // Start Traefik - cmd, _ := s.cmdTraefik(withConfigFile("fixtures/access_log_config.toml")) + cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -68,7 +69,8 @@ func (s *LogRotationSuite) TestAccessLogRotation(c *check.C) { func (s *LogRotationSuite) TestTraefikLogRotation(c *check.C) { // Start Traefik - cmd, _ := s.cmdTraefik(withConfigFile("fixtures/traefik_log_config.toml")) + cmd, display := s.traefikCmd(withConfigFile("fixtures/traefik_log_config.toml")) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() diff --git a/integration/marathon_test.go b/integration/marathon_test.go index 3e8cadba7..cb18c6e7a 100644 --- a/integration/marathon_test.go +++ b/integration/marathon_test.go @@ -88,7 +88,8 @@ func (s *MarathonSuite) TestConfigurationUpdate(c *check.C) { MarathonURL string }{s.marathonURL}) defer os.Remove(file) - cmd, output := s.cmdTraefik(withConfigFile(file)) + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -103,16 +104,6 @@ func (s *MarathonSuite) TestConfigurationUpdate(c *check.C) { client, err := marathon.NewClient(config) c.Assert(err, checker.IsNil) - // Show the Traefik log if any assertion fails. If the entire test runs - // to a successful completion, we flip the flag at the very end and don't - // display anything. - showTraefikLog := true - defer func() { - if showTraefikLog { - s.displayTraefikLog(c, output) - } - }() - // Create test application to be deployed. app := marathon.NewDockerApplication(). Name("/whoami"). @@ -146,6 +137,4 @@ func (s *MarathonSuite) TestConfigurationUpdate(c *check.C) { // Query application via Traefik. err = try.GetRequest("http://127.0.0.1:8000/app", 30*time.Second, try.StatusCodeIs(http.StatusOK)) c.Assert(err, checker.IsNil) - - showTraefikLog = false } diff --git a/integration/mesos_test.go b/integration/mesos_test.go index 23f248133..12f5eefb1 100644 --- a/integration/mesos_test.go +++ b/integration/mesos_test.go @@ -17,7 +17,8 @@ func (s *MesosSuite) SetUpSuite(c *check.C) { } func (s *MesosSuite) TestSimpleConfiguration(c *check.C) { - cmd, _ := s.cmdTraefik(withConfigFile("fixtures/mesos/simple.toml")) + cmd, display := s.traefikCmd(withConfigFile("fixtures/mesos/simple.toml")) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() diff --git a/integration/timeout_test.go b/integration/timeout_test.go index af13db1d8..78482d122 100644 --- a/integration/timeout_test.go +++ b/integration/timeout_test.go @@ -24,7 +24,8 @@ func (s *TimeoutSuite) TestForwardingTimeouts(c *check.C) { }{httpTimeoutEndpoint}) defer os.Remove(file) - cmd, _ := s.cmdTraefik(withConfigFile(file)) + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() diff --git a/integration/websocket_test.go b/integration/websocket_test.go index d2a4dd988..220562eb8 100644 --- a/integration/websocket_test.go +++ b/integration/websocket_test.go @@ -20,7 +20,7 @@ import ( // WebsocketSuite type WebsocketSuite struct{ BaseSuite } -func (suite *WebsocketSuite) TestBase(c *check.C) { +func (s *WebsocketSuite) TestBase(c *check.C) { var upgrader = gorillawebsocket.Upgrader{} // use default options srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -41,14 +41,15 @@ func (suite *WebsocketSuite) TestBase(c *check.C) { } })) - file := suite.adaptFile(c, "fixtures/websocket/config.toml", struct { + file := s.adaptFile(c, "fixtures/websocket/config.toml", struct { WebsocketServer string }{ WebsocketServer: srv.URL, }) defer os.Remove(file) - cmd, _ := suite.cmdTraefik(withConfigFile(file), "--debug") + cmd, display := s.traefikCmd(withConfigFile(file), "--debug") + defer display(c) err := cmd.Start() c.Assert(err, check.IsNil) @@ -69,7 +70,7 @@ func (suite *WebsocketSuite) TestBase(c *check.C) { c.Assert(string(msg), checker.Equals, "OK") } -func (suite *WebsocketSuite) TestWrongOrigin(c *check.C) { +func (s *WebsocketSuite) TestWrongOrigin(c *check.C) { var upgrader = gorillawebsocket.Upgrader{} // use default options srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -90,14 +91,15 @@ func (suite *WebsocketSuite) TestWrongOrigin(c *check.C) { } })) - file := suite.adaptFile(c, "fixtures/websocket/config.toml", struct { + file := s.adaptFile(c, "fixtures/websocket/config.toml", struct { WebsocketServer string }{ WebsocketServer: srv.URL, }) defer os.Remove(file) - cmd, _ := suite.cmdTraefik(withConfigFile(file), "--debug") + cmd, display := s.traefikCmd(withConfigFile(file), "--debug") + defer display(c) err := cmd.Start() c.Assert(err, check.IsNil) @@ -117,7 +119,7 @@ func (suite *WebsocketSuite) TestWrongOrigin(c *check.C) { c.Assert(err, checker.ErrorMatches, "bad status") } -func (suite *WebsocketSuite) TestOrigin(c *check.C) { +func (s *WebsocketSuite) TestOrigin(c *check.C) { // use default options var upgrader = gorillawebsocket.Upgrader{} @@ -139,14 +141,15 @@ func (suite *WebsocketSuite) TestOrigin(c *check.C) { } })) - file := suite.adaptFile(c, "fixtures/websocket/config.toml", struct { + file := s.adaptFile(c, "fixtures/websocket/config.toml", struct { WebsocketServer string }{ WebsocketServer: srv.URL, }) defer os.Remove(file) - cmd, _ := suite.cmdTraefik(withConfigFile(file), "--debug") + cmd, display := s.traefikCmd(withConfigFile(file), "--debug") + defer display(c) err := cmd.Start() c.Assert(err, check.IsNil) @@ -176,7 +179,7 @@ func (suite *WebsocketSuite) TestOrigin(c *check.C) { } -func (suite *WebsocketSuite) TestWrongOriginIgnoredByServer(c *check.C) { +func (s *WebsocketSuite) TestWrongOriginIgnoredByServer(c *check.C) { var upgrader = gorillawebsocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }} @@ -199,14 +202,15 @@ func (suite *WebsocketSuite) TestWrongOriginIgnoredByServer(c *check.C) { } })) - file := suite.adaptFile(c, "fixtures/websocket/config.toml", struct { + file := s.adaptFile(c, "fixtures/websocket/config.toml", struct { WebsocketServer string }{ WebsocketServer: srv.URL, }) defer os.Remove(file) - cmd, _ := suite.cmdTraefik(withConfigFile(file), "--debug") + cmd, display := s.traefikCmd(withConfigFile(file), "--debug") + defer display(c) err := cmd.Start() c.Assert(err, check.IsNil) @@ -236,7 +240,7 @@ func (suite *WebsocketSuite) TestWrongOriginIgnoredByServer(c *check.C) { } -func (suite *WebsocketSuite) TestSSLTermination(c *check.C) { +func (s *WebsocketSuite) TestSSLTermination(c *check.C) { var upgrader = gorillawebsocket.Upgrader{} // use default options srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -256,14 +260,15 @@ func (suite *WebsocketSuite) TestSSLTermination(c *check.C) { } } })) - file := suite.adaptFile(c, "fixtures/websocket/config_https.toml", struct { + file := s.adaptFile(c, "fixtures/websocket/config_https.toml", struct { WebsocketServer string }{ WebsocketServer: srv.URL, }) defer os.Remove(file) - cmd, _ := suite.cmdTraefik(withConfigFile(file), "--debug") + cmd, display := s.traefikCmd(withConfigFile(file), "--debug") + defer display(c) err := cmd.Start() c.Assert(err, check.IsNil) diff --git a/log/logger.go b/log/logger.go index c60b08acb..9ded03bff 100644 --- a/log/logger.go +++ b/log/logger.go @@ -227,8 +227,10 @@ func RotateFile() error { return nil } - if err := CloseFile(); err != nil { - return fmt.Errorf("error closing log file: %s", err) + if logFile != nil { + defer func(f *os.File) { + f.Close() + }(logFile) } if err := OpenFile(logFilePath); err != nil { diff --git a/log/logger_test.go b/log/logger_test.go new file mode 100644 index 000000000..c91202167 --- /dev/null +++ b/log/logger_test.go @@ -0,0 +1,79 @@ +package log + +import ( + "io/ioutil" + "os" + "strings" + "testing" + "time" +) + +func TestLogRotation(t *testing.T) { + tempDir, err := ioutil.TempDir("", "traefik_") + if err != nil { + t.Fatalf("Error setting up temporary directory: %s", err) + } + + fileName := tempDir + "traefik.log" + if err := OpenFile(fileName); err != nil { + t.Fatalf("Error opening temporary file %s: %s", fileName, err) + } + defer CloseFile() + + rotatedFileName := fileName + ".rotated" + + iterations := 20 + halfDone := make(chan bool) + writeDone := make(chan bool) + go func() { + for i := 0; i < iterations; i++ { + Println("Test log line") + if i == iterations/2 { + halfDone <- true + } + } + writeDone <- true + }() + + <-halfDone + err = os.Rename(fileName, rotatedFileName) + if err != nil { + t.Fatalf("Error renaming file: %s", err) + } + + err = RotateFile() + if err != nil { + t.Fatalf("Error rotating file: %s", err) + } + + select { + case <-writeDone: + gotLineCount := lineCount(t, fileName) + lineCount(t, rotatedFileName) + if iterations != gotLineCount { + t.Errorf("Wanted %d written log lines, got %d", iterations, gotLineCount) + } + case <-time.After(500 * time.Millisecond): + t.Fatalf("test timed out") + } + + close(halfDone) + close(writeDone) +} + +func lineCount(t *testing.T, fileName string) int { + t.Helper() + fileContents, err := ioutil.ReadFile(fileName) + if err != nil { + t.Fatalf("Error reading from file %s: %s", fileName, err) + } + + count := 0 + for _, line := range strings.Split(string(fileContents), "\n") { + if strings.TrimSpace(line) == "" { + continue + } + count++ + } + + return count +} diff --git a/middlewares/accesslog/logger.go b/middlewares/accesslog/logger.go index fc5d52adf..8d6cdbbe1 100644 --- a/middlewares/accesslog/logger.go +++ b/middlewares/accesslog/logger.go @@ -8,6 +8,7 @@ import ( "net/url" "os" "path/filepath" + "sync" "sync/atomic" "time" @@ -34,6 +35,7 @@ type LogHandler struct { logger *logrus.Logger file *os.File filePath string + mu sync.Mutex } // NewLogHandler creates a new LogHandler @@ -148,14 +150,19 @@ func (l *LogHandler) Close() error { // by an external source. func (l *LogHandler) Rotate() error { var err error - if err = l.Close(); err != nil { - return err + + if l.file != nil { + defer func(f *os.File) { + f.Close() + }(l.file) } l.file, err = os.OpenFile(l.filePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0664) if err != nil { return err } + l.mu.Lock() + defer l.mu.Unlock() l.logger.Out = l.file return nil } @@ -226,6 +233,8 @@ func (l *LogHandler) logTheRoundTrip(logDataTable *LogData, crr *captureRequestR fields["downstream_"+k] = logDataTable.DownstreamResponse.Get(k) } + l.mu.Lock() + defer l.mu.Unlock() l.logger.WithFields(fields).Println() } diff --git a/middlewares/accesslog/logger_test.go b/middlewares/accesslog/logger_test.go index e6854a539..61aa16343 100644 --- a/middlewares/accesslog/logger_test.go +++ b/middlewares/accesslog/logger_test.go @@ -10,7 +10,9 @@ import ( "os" "path/filepath" "regexp" + "strings" "testing" + "time" "github.com/containous/traefik/types" shellwords "github.com/mattn/go-shellwords" @@ -36,6 +38,84 @@ var ( testRetryAttempts = 2 ) +func TestLogRotation(t *testing.T) { + tempDir, err := ioutil.TempDir("", "traefik_") + if err != nil { + t.Fatalf("Error setting up temporary directory: %s", err) + } + + fileName := tempDir + "traefik.log" + rotatedFileName := fileName + ".rotated" + + config := &types.AccessLog{FilePath: fileName, Format: CommonFormat} + logHandler, err := NewLogHandler(config) + if err != nil { + t.Fatalf("Error creating new log handler: %s", err) + } + defer logHandler.Close() + + recorder := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "http://localhost", nil) + next := func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusOK) + } + + iterations := 20 + halfDone := make(chan bool) + writeDone := make(chan bool) + go func() { + for i := 0; i < iterations; i++ { + logHandler.ServeHTTP(recorder, req, next) + if i == iterations/2 { + halfDone <- true + } + } + writeDone <- true + }() + + <-halfDone + err = os.Rename(fileName, rotatedFileName) + if err != nil { + t.Fatalf("Error renaming file: %s", err) + } + + err = logHandler.Rotate() + if err != nil { + t.Fatalf("Error rotating file: %s", err) + } + + select { + case <-writeDone: + gotLineCount := lineCount(t, fileName) + lineCount(t, rotatedFileName) + if iterations != gotLineCount { + t.Errorf("Wanted %d written log lines, got %d", iterations, gotLineCount) + } + case <-time.After(500 * time.Millisecond): + t.Fatalf("test timed out") + } + + close(halfDone) + close(writeDone) +} + +func lineCount(t *testing.T, fileName string) int { + t.Helper() + fileContents, err := ioutil.ReadFile(fileName) + if err != nil { + t.Fatalf("Error reading from file %s: %s", fileName, err) + } + + count := 0 + for _, line := range strings.Split(string(fileContents), "\n") { + if strings.TrimSpace(line) == "" { + continue + } + count++ + } + + return count +} + func TestLoggerCLF(t *testing.T) { tmpDir := createTempDir(t, CommonFormat) defer os.RemoveAll(tmpDir) diff --git a/middlewares/authenticator.go b/middlewares/auth/authenticator.go similarity index 71% rename from middlewares/authenticator.go rename to middlewares/auth/authenticator.go index cdd4203f5..5482948d7 100644 --- a/middlewares/authenticator.go +++ b/middlewares/auth/authenticator.go @@ -1,4 +1,4 @@ -package middlewares +package auth import ( "fmt" @@ -7,7 +7,6 @@ import ( "strings" goauth "github.com/abbot/go-http-auth" - "github.com/containous/traefik/auth" "github.com/containous/traefik/log" "github.com/containous/traefik/types" "github.com/urfave/negroni" @@ -64,52 +63,12 @@ func NewAuthenticator(authConfig *types.Auth) (*Authenticator, error) { }) } else if authConfig.Forward != nil { authenticator.handler = negroni.HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { - auth.Forward(authConfig.Forward, w, r, next) + Forward(authConfig.Forward, w, r, next) }) } return &authenticator, nil } -func parserBasicUsers(basic *types.Basic) (map[string]string, error) { - var userStrs []string - if basic.UsersFile != "" { - var err error - if userStrs, err = getLinesFromFile(basic.UsersFile); err != nil { - return nil, err - } - } - userStrs = append(basic.Users, userStrs...) - userMap := make(map[string]string) - for _, user := range userStrs { - split := strings.Split(user, ":") - if len(split) != 2 { - return nil, fmt.Errorf("Error parsing Authenticator user: %v", user) - } - userMap[split[0]] = split[1] - } - return userMap, nil -} - -func parserDigestUsers(digest *types.Digest) (map[string]string, error) { - var userStrs []string - if digest.UsersFile != "" { - var err error - if userStrs, err = getLinesFromFile(digest.UsersFile); err != nil { - return nil, err - } - } - userStrs = append(digest.Users, userStrs...) - userMap := make(map[string]string) - for _, user := range userStrs { - split := strings.Split(user, ":") - if len(split) != 3 { - return nil, fmt.Errorf("Error parsing Authenticator user: %v", user) - } - userMap[split[0]+":"+split[1]] = split[2] - } - return userMap, nil -} - func getLinesFromFile(filename string) ([]string, error) { dat, err := ioutil.ReadFile(filename) if err != nil { diff --git a/middlewares/authenticator_test.go b/middlewares/auth/authenticator_test.go similarity index 72% rename from middlewares/authenticator_test.go rename to middlewares/auth/authenticator_test.go index d27e5d1f3..e27834a6c 100644 --- a/middlewares/authenticator_test.go +++ b/middlewares/auth/authenticator_test.go @@ -1,4 +1,4 @@ -package middlewares +package auth import ( "fmt" @@ -157,7 +157,7 @@ func TestDigestAuthFail(t *testing.T) { } func TestBasicAuthUserHeader(t *testing.T) { - authMiddleware, err := NewAuthenticator(&types.Auth{ + middleware, err := NewAuthenticator(&types.Auth{ Basic: &types.Basic{ Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"}, }, @@ -169,7 +169,7 @@ func TestBasicAuthUserHeader(t *testing.T) { assert.Equal(t, "test", r.Header["X-Webauth-User"][0], "auth user should be set") fmt.Fprintln(w, "traefik") }) - n := negroni.New(authMiddleware) + n := negroni.New(middleware) n.UseHandler(handler) ts := httptest.NewServer(n) defer ts.Close() @@ -186,67 +186,3 @@ func TestBasicAuthUserHeader(t *testing.T) { assert.NoError(t, err, "there should be no error") assert.Equal(t, "traefik\n", string(body), "they should be equal") } - -func TestForwardAuthFail(t *testing.T) { - authTs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, "Forbidden", http.StatusForbidden) - })) - defer authTs.Close() - - authMiddleware, err := NewAuthenticator(&types.Auth{ - Forward: &types.Forward{ - Address: authTs.URL, - }, - }) - assert.NoError(t, err, "there should be no error") - - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "traefik") - }) - n := negroni.New(authMiddleware) - n.UseHandler(handler) - ts := httptest.NewServer(n) - defer ts.Close() - - client := &http.Client{} - req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil) - res, err := client.Do(req) - assert.NoError(t, err, "there should be no error") - assert.Equal(t, http.StatusForbidden, res.StatusCode, "they should be equal") - - body, err := ioutil.ReadAll(res.Body) - assert.NoError(t, err, "there should be no error") - assert.Equal(t, "Forbidden\n", string(body), "they should be equal") -} - -func TestForwardAuthSuccess(t *testing.T) { - authTs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "Success") - })) - defer authTs.Close() - - authMiddleware, err := NewAuthenticator(&types.Auth{ - Forward: &types.Forward{ - Address: authTs.URL, - }, - }) - assert.NoError(t, err, "there should be no error") - - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "traefik") - }) - n := negroni.New(authMiddleware) - n.UseHandler(handler) - ts := httptest.NewServer(n) - defer ts.Close() - - client := &http.Client{} - req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil) - res, err := client.Do(req) - assert.NoError(t, err, "there should be no error") - assert.Equal(t, http.StatusOK, res.StatusCode, "they should be equal") - - body, err := ioutil.ReadAll(res.Body) - assert.NoError(t, err, "there should be no error") - assert.Equal(t, "traefik\n", string(body), "they should be equal") -} diff --git a/middlewares/auth/forward.go b/middlewares/auth/forward.go new file mode 100644 index 000000000..30bcd6452 --- /dev/null +++ b/middlewares/auth/forward.go @@ -0,0 +1,97 @@ +package auth + +import ( + "io/ioutil" + "net" + "net/http" + "strings" + + "github.com/containous/traefik/log" + "github.com/containous/traefik/types" + "github.com/vulcand/oxy/forward" + "github.com/vulcand/oxy/utils" +) + +// Forward the authentication to a external server +func Forward(config *types.Forward, w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { + httpClient := http.Client{} + + if config.TLS != nil { + tlsConfig, err := config.TLS.CreateTLSConfig() + if err != nil { + log.Debugf("Impossible to configure TLS to call %s. Cause %s", config.Address, err) + w.WriteHeader(http.StatusInternalServerError) + return + } + httpClient.Transport = &http.Transport{ + TLSClientConfig: tlsConfig, + } + } + + forwardReq, err := http.NewRequest(http.MethodGet, config.Address, nil) + if err != nil { + log.Debugf("Error calling %s. Cause %s", config.Address, err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + writeHeader(r, forwardReq, config.TrustForwardHeader) + + forwardResponse, forwardErr := httpClient.Do(forwardReq) + if forwardErr != nil { + log.Debugf("Error calling %s. Cause: %s", config.Address, forwardErr) + w.WriteHeader(http.StatusInternalServerError) + return + } + + body, readError := ioutil.ReadAll(forwardResponse.Body) + if readError != nil { + log.Debugf("Error reading body %s. Cause: %s", config.Address, readError) + w.WriteHeader(http.StatusInternalServerError) + return + } + defer forwardResponse.Body.Close() + + if forwardResponse.StatusCode < http.StatusOK || forwardResponse.StatusCode >= http.StatusMultipleChoices { + log.Debugf("Remote error %s. StatusCode: %d", config.Address, forwardResponse.StatusCode) + w.WriteHeader(forwardResponse.StatusCode) + w.Write(body) + return + } + + r.RequestURI = r.URL.RequestURI() + next(w, r) +} + +func writeHeader(req *http.Request, forwardReq *http.Request, trustForwardHeader bool) { + utils.CopyHeaders(forwardReq.Header, req.Header) + + if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil { + if trustForwardHeader { + if prior, ok := req.Header[forward.XForwardedFor]; ok { + clientIP = strings.Join(prior, ", ") + ", " + clientIP + } + } + forwardReq.Header.Set(forward.XForwardedFor, clientIP) + } + + if xfp := req.Header.Get(forward.XForwardedProto); xfp != "" && trustForwardHeader { + forwardReq.Header.Set(forward.XForwardedProto, xfp) + } else if req.TLS != nil { + forwardReq.Header.Set(forward.XForwardedProto, "https") + } else { + forwardReq.Header.Set(forward.XForwardedProto, "http") + } + + if xfp := req.Header.Get(forward.XForwardedPort); xfp != "" && trustForwardHeader { + forwardReq.Header.Set(forward.XForwardedPort, xfp) + } + + if xfh := req.Header.Get(forward.XForwardedHost); xfh != "" && trustForwardHeader { + forwardReq.Header.Set(forward.XForwardedHost, xfh) + } else if req.Host != "" { + forwardReq.Header.Set(forward.XForwardedHost, req.Host) + } else { + forwardReq.Header.Del(forward.XForwardedHost) + } +} diff --git a/middlewares/auth/forward_test.go b/middlewares/auth/forward_test.go new file mode 100644 index 000000000..44b619689 --- /dev/null +++ b/middlewares/auth/forward_test.go @@ -0,0 +1,162 @@ +package auth + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/containous/traefik/testhelpers" + "github.com/containous/traefik/types" + "github.com/stretchr/testify/assert" + "github.com/urfave/negroni" +) + +func TestForwardAuthFail(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "Forbidden", http.StatusForbidden) + })) + defer server.Close() + + middleware, err := NewAuthenticator(&types.Auth{ + Forward: &types.Forward{ + Address: server.URL, + }, + }) + assert.NoError(t, err, "there should be no error") + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "traefik") + }) + n := negroni.New(middleware) + n.UseHandler(handler) + ts := httptest.NewServer(n) + defer ts.Close() + + client := &http.Client{} + req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil) + res, err := client.Do(req) + assert.NoError(t, err, "there should be no error") + assert.Equal(t, http.StatusForbidden, res.StatusCode, "they should be equal") + + body, err := ioutil.ReadAll(res.Body) + assert.NoError(t, err, "there should be no error") + assert.Equal(t, "Forbidden\n", string(body), "they should be equal") +} + +func TestForwardAuthSuccess(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "Success") + })) + defer server.Close() + + middleware, err := NewAuthenticator(&types.Auth{ + Forward: &types.Forward{ + Address: server.URL, + }, + }) + assert.NoError(t, err, "there should be no error") + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "traefik") + }) + n := negroni.New(middleware) + n.UseHandler(handler) + ts := httptest.NewServer(n) + defer ts.Close() + + client := &http.Client{} + req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil) + res, err := client.Do(req) + assert.NoError(t, err, "there should be no error") + assert.Equal(t, http.StatusOK, res.StatusCode, "they should be equal") + + body, err := ioutil.ReadAll(res.Body) + assert.NoError(t, err, "there should be no error") + assert.Equal(t, "traefik\n", string(body), "they should be equal") +} + +func Test_writeHeader(t *testing.T) { + + testCases := []struct { + name string + headers map[string]string + trustForwardHeader bool + emptyHost bool + expectedHeaders map[string]string + }{ + { + name: "trust Forward Header", + headers: map[string]string{ + "Accept": "application/json", + "X-Forwarded-Host": "fii.bir", + }, + trustForwardHeader: true, + expectedHeaders: map[string]string{ + "Accept": "application/json", + "X-Forwarded-Host": "fii.bir", + }, + }, + { + name: "not trust Forward Header", + headers: map[string]string{ + "Accept": "application/json", + "X-Forwarded-Host": "fii.bir", + }, + trustForwardHeader: false, + expectedHeaders: map[string]string{ + "Accept": "application/json", + "X-Forwarded-Host": "foo.bar", + }, + }, + { + name: "trust Forward Header with empty Host", + headers: map[string]string{ + "Accept": "application/json", + "X-Forwarded-Host": "fii.bir", + }, + trustForwardHeader: true, + emptyHost: true, + expectedHeaders: map[string]string{ + "Accept": "application/json", + "X-Forwarded-Host": "fii.bir", + }, + }, + { + name: "not trust Forward Header with empty Host", + headers: map[string]string{ + "Accept": "application/json", + "X-Forwarded-Host": "fii.bir", + }, + trustForwardHeader: false, + emptyHost: true, + expectedHeaders: map[string]string{ + "Accept": "application/json", + "X-Forwarded-Host": "", + }, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + + req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar", nil) + for key, value := range test.headers { + req.Header.Set(key, value) + } + + if test.emptyHost { + req.Host = "" + } + + forwardReq := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar", nil) + + writeHeader(req, forwardReq, test.trustForwardHeader) + + for key, value := range test.expectedHeaders { + assert.Equal(t, value, forwardReq.Header.Get(key)) + } + }) + } +} diff --git a/middlewares/auth/parser.go b/middlewares/auth/parser.go new file mode 100644 index 000000000..9bf82e4a7 --- /dev/null +++ b/middlewares/auth/parser.go @@ -0,0 +1,48 @@ +package auth + +import ( + "fmt" + "strings" + + "github.com/containous/traefik/types" +) + +func parserBasicUsers(basic *types.Basic) (map[string]string, error) { + var userStrs []string + if basic.UsersFile != "" { + var err error + if userStrs, err = getLinesFromFile(basic.UsersFile); err != nil { + return nil, err + } + } + userStrs = append(basic.Users, userStrs...) + userMap := make(map[string]string) + for _, user := range userStrs { + split := strings.Split(user, ":") + if len(split) != 2 { + return nil, fmt.Errorf("Error parsing Authenticator user: %v", user) + } + userMap[split[0]] = split[1] + } + return userMap, nil +} + +func parserDigestUsers(digest *types.Digest) (map[string]string, error) { + var userStrs []string + if digest.UsersFile != "" { + var err error + if userStrs, err = getLinesFromFile(digest.UsersFile); err != nil { + return nil, err + } + } + userStrs = append(digest.Users, userStrs...) + userMap := make(map[string]string) + for _, user := range userStrs { + split := strings.Split(user, ":") + if len(split) != 3 { + return nil, fmt.Errorf("Error parsing Authenticator user: %v", user) + } + userMap[split[0]+":"+split[1]] = split[2] + } + return userMap, nil +} diff --git a/middlewares/compress.go b/middlewares/compress.go index 69ad7c0ad..810f58904 100644 --- a/middlewares/compress.go +++ b/middlewares/compress.go @@ -18,7 +18,6 @@ func (c *Compress) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http. func gzipHandler(h http.Handler) http.Handler { wrapper, err := gziphandler.GzipHandlerWithOpts( - &gziphandler.GzipResponseWriterWrapper{}, gziphandler.CompressionLevel(gzip.DefaultCompression), gziphandler.MinSize(gziphandler.DefaultMinSize)) if err != nil { diff --git a/mkdocs.yml b/mkdocs.yml index 04df35378..cd2a14d8c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -6,18 +6,22 @@ site_url: https://docs.traefik.io repo_name: 'GitHub' repo_url: 'https://github.com/containous/traefik' -# Documentation and theme +# Documentation docs_dir: 'docs' + #theme: united -# theme: readthedocs +#theme: readthedocs theme: 'material' -# theme: bootstrap +#theme: bootstrap site_favicon: 'img/traefik.icon.png' -# Copyright copyright: "Copyright © 2016-2017 Containous SAS" +google_analytics: + - 'UA-51880359-3' + - 'docs.traefik.io' + # Options extra: logo: img/traefik.logo.png @@ -46,8 +50,8 @@ extra: theme_dir: docs/theme/ extra_css: - - theme/styles/extra.css - - theme/styles/atom-one-light.css + - theme/styles/extra.css + - theme/styles/atom-one-light.css extra_javascript: - theme/js/hljs/highlight.pack.js @@ -82,11 +86,12 @@ pages: - 'Backend: Zookeeper': 'configuration/backends/zookeeper.md' - User Guides: - 'Configuration Examples': 'user-guide/examples.md' - - 'Swarm Cluster': 'user-guide/swarm.md' - 'Swarm Mode Cluster': 'user-guide/swarm-mode.md' + - 'Swarm Cluster': 'user-guide/swarm.md' + - 'Let''s Encrypt & Docker': 'user-guide/docker-and-lets-encrypt.md' - 'Kubernetes': 'user-guide/kubernetes.md' - 'Marathon': 'user-guide/marathon.md' - 'Key-value Store Configuration': 'user-guide/kv-config.md' - 'Clustering/HA': 'user-guide/cluster.md' - - 'Let''s Encrypt & Docker': 'user-guide/docker-and-lets-encrypt.md' + - 'gRPC Example': 'user-guide/grpc.md' - Benchmarks: benchmarks.md diff --git a/provider/rancher/api.go b/provider/rancher/api.go index 30d447064..d00e13ac7 100644 --- a/provider/rancher/api.go +++ b/provider/rancher/api.go @@ -10,10 +10,14 @@ import ( "github.com/containous/traefik/log" "github.com/containous/traefik/safe" "github.com/containous/traefik/types" + "github.com/mitchellh/mapstructure" rancher "github.com/rancher/go-rancher/client" ) -const labelRancheStackServiceName = "io.rancher.stack_service.name" +const ( + labelRancherStackServiceName = "io.rancher.stack_service.name" + hostNetwork = "host" +) var withoutPagination *rancher.ListOpts @@ -221,9 +225,24 @@ func parseAPISourcedRancherData(environments []*rancher.Environment, services [] } for _, container := range containers { - if container.Labels[labelRancheStackServiceName] == rancherData.Name && + if container.Labels[labelRancherStackServiceName] == rancherData.Name && containerFilter(container.Name, container.HealthState, container.State) { - rancherData.Containers = append(rancherData.Containers, container.PrimaryIpAddress) + + if container.NetworkMode == hostNetwork { + var endpoints []*rancher.PublicEndpoint + err := mapstructure.Decode(service.PublicEndpoints, &endpoints) + + if err != nil { + log.Errorf("Failed to decode PublicEndpoint: %v", err) + continue + } + + if len(endpoints) > 0 { + rancherData.Containers = append(rancherData.Containers, endpoints[0].IpAddress) + } + } else { + rancherData.Containers = append(rancherData.Containers, container.PrimaryIpAddress) + } } } rancherDataList = append(rancherDataList, rancherData) diff --git a/provider/rancher/metadata.go b/provider/rancher/metadata.go index 2a88abee0..24c9c585b 100644 --- a/provider/rancher/metadata.go +++ b/provider/rancher/metadata.go @@ -38,13 +38,13 @@ func (p *Provider) metadataProvide(configurationChan chan<- types.ConfigMessage, updateConfiguration := func(version string) { log.WithField("metadata_version", version).Debugln("Refreshing configuration from Rancher metadata service") - services, err := client.GetServices() + stacks, err := client.GetStacks() if err != nil { log.Errorf("Failed to query Rancher metadata service: %s", err) return } - rancherData := parseMetadataSourcedRancherData(services) + rancherData := parseMetadataSourcedRancherData(stacks) configuration := p.loadRancherConfig(rancherData) configurationChan <- types.ConfigMessage{ ProviderName: "rancher", @@ -118,21 +118,23 @@ func (p *Provider) longPoll(client rancher.Client, updateConfiguration func(stri <-stop } -func parseMetadataSourcedRancherData(services []rancher.Service) (rancherDataList []rancherData) { - for _, service := range services { - var containerIPAddresses []string - for _, container := range service.Containers { - if containerFilter(container.Name, container.HealthState, container.State) { - containerIPAddresses = append(containerIPAddresses, container.PrimaryIp) +func parseMetadataSourcedRancherData(stacks []rancher.Stack) (rancherDataList []rancherData) { + for _, stack := range stacks { + for _, service := range stack.Services { + var containerIPAddresses []string + for _, container := range service.Containers { + if containerFilter(container.Name, container.HealthState, container.State) { + containerIPAddresses = append(containerIPAddresses, container.PrimaryIp) + } } - } - rancherDataList = append(rancherDataList, rancherData{ - Name: service.Name, - State: service.State, - Labels: service.Labels, - Containers: containerIPAddresses, - }) + rancherDataList = append(rancherDataList, rancherData{ + Name: stack.Name + "/" + service.Name, + State: service.State, + Labels: service.Labels, + Containers: containerIPAddresses, + }) + } } return rancherDataList } diff --git a/provider/web/web.go b/provider/web/web.go index 035052d6f..0e2d655e1 100644 --- a/provider/web/web.go +++ b/provider/web/web.go @@ -12,6 +12,7 @@ import ( "github.com/containous/traefik/autogen" "github.com/containous/traefik/log" "github.com/containous/traefik/middlewares" + mauth "github.com/containous/traefik/middlewares/auth" "github.com/containous/traefik/safe" "github.com/containous/traefik/types" "github.com/containous/traefik/version" @@ -135,7 +136,7 @@ func (provider *Provider) Provide(configurationChan chan<- types.ConfigMessage, var err error var negroniInstance = negroni.New() if provider.Auth != nil { - authMiddleware, err := middlewares.NewAuthenticator(provider.Auth) + authMiddleware, err := mauth.NewAuthenticator(provider.Auth) if err != nil { log.Fatal("Error creating Auth: ", err) } diff --git a/server/server.go b/server/server.go index e573de671..84dcfe56b 100644 --- a/server/server.go +++ b/server/server.go @@ -27,6 +27,7 @@ import ( "github.com/containous/traefik/metrics" "github.com/containous/traefik/middlewares" "github.com/containous/traefik/middlewares/accesslog" + mauth "github.com/containous/traefik/middlewares/auth" "github.com/containous/traefik/provider" "github.com/containous/traefik/safe" "github.com/containous/traefik/types" @@ -283,7 +284,7 @@ func (server *Server) setupServerEntryPoint(newServerEntryPointName string, newS } } if server.globalConfiguration.EntryPoints[newServerEntryPointName].Auth != nil { - authMiddleware, err := middlewares.NewAuthenticator(server.globalConfiguration.EntryPoints[newServerEntryPointName].Auth) + authMiddleware, err := mauth.NewAuthenticator(server.globalConfiguration.EntryPoints[newServerEntryPointName].Auth) if err != nil { log.Fatal("Error starting server: ", err) } @@ -945,7 +946,7 @@ func (server *Server) loadConfig(configurations types.Configurations, globalConf auth.Basic = &types.Basic{ Users: users, } - authMiddleware, err := middlewares.NewAuthenticator(auth) + authMiddleware, err := mauth.NewAuthenticator(auth) if err != nil { log.Errorf("Error creating Auth: %s", err) } else { @@ -1122,6 +1123,7 @@ func parseHealthCheckOptions(lb healthcheck.LoadBalancer, backend string, hc *ty return &healthcheck.Options{ Path: hc.Path, + Port: hc.Port, Interval: interval, LB: lb, } diff --git a/server/server_signals.go b/server/server_signals.go index 443fb6f76..7de108b67 100644 --- a/server/server_signals.go +++ b/server/server_signals.go @@ -27,7 +27,7 @@ func (server *Server) listenSignals() { } if err := log.RotateFile(); err != nil { - log.Errorf("Error rotating error log: %s", err) + log.Errorf("Error rotating traefik log: %s", err) } default: log.Infof("I have to go... %+v", sig) diff --git a/types/types.go b/types/types.go index b9f0cfd1c..e47e6e79f 100644 --- a/types/types.go +++ b/types/types.go @@ -1,16 +1,15 @@ package types import ( + "crypto/tls" + "crypto/x509" "encoding" "errors" "fmt" - "strconv" - "strings" - - "crypto/tls" - "crypto/x509" "io/ioutil" "os" + "strconv" + "strings" "github.com/containous/flaeg" "github.com/containous/traefik/log" @@ -47,6 +46,7 @@ type CircuitBreaker struct { // HealthCheck holds HealthCheck configuration type HealthCheck struct { Path string `json:"path,omitempty"` + Port int `json:"port,omitempty"` Interval string `json:"interval,omitempty"` } @@ -341,8 +341,9 @@ type Digest struct { // Forward authentication type Forward struct { - Address string `description:"Authentication server address"` - TLS *ClientTLS `description:"Enable TLS support"` + Address string `description:"Authentication server address"` + TLS *ClientTLS `description:"Enable TLS support"` + TrustForwardHeader bool `description:"Trust X-Forwarded-* headers"` } // CanonicalDomain returns a lower case domain with trim space diff --git a/vendor/github.com/NYTimes/gziphandler/LICENSE b/vendor/github.com/NYTimes/gziphandler/LICENSE new file mode 100644 index 000000000..df6192d36 --- /dev/null +++ b/vendor/github.com/NYTimes/gziphandler/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016-2017 The New York Times Company + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/NYTimes/gziphandler/LICENSE.md b/vendor/github.com/NYTimes/gziphandler/LICENSE.md deleted file mode 100644 index b7e2ecb63..000000000 --- a/vendor/github.com/NYTimes/gziphandler/LICENSE.md +++ /dev/null @@ -1,13 +0,0 @@ -Copyright (c) 2015 The New York Times Company - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this library except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/vendor/github.com/NYTimes/gziphandler/gzip.go b/vendor/github.com/NYTimes/gziphandler/gzip.go index 8d8c7ce62..b6af9115a 100644 --- a/vendor/github.com/NYTimes/gziphandler/gzip.go +++ b/vendor/github.com/NYTimes/gziphandler/gzip.go @@ -3,7 +3,6 @@ package gziphandler import ( "bufio" "compress/gzip" - "errors" "fmt" "io" "net" @@ -106,7 +105,7 @@ func (w *GzipResponseWriter) Write(b []byte) (int, error) { // If the global writes are bigger than the minSize and we're about to write // a response containing a content type we want to handle, enable // compression. - if len(w.buf) >= w.minSize && handleContentType(w.contentTypes, w) { + if len(w.buf) >= w.minSize && handleContentType(w.contentTypes, w) && w.Header().Get(contentEncoding) == "" { err := w.startGzip() if err != nil { return 0, err @@ -135,7 +134,7 @@ func (w *GzipResponseWriter) startGzip() error { // Initialize the GZIP response. w.init() - // Flush the buffer into the gzip reponse. + // Flush the buffer into the gzip response. n, err := w.gw.Write(w.buf) // This should never happen (per io.Writer docs), but if the write didn't @@ -235,14 +234,10 @@ func NewGzipLevelHandler(level int) (func(http.Handler) http.Handler, error) { // NewGzipLevelAndMinSize behave as NewGzipLevelHandler except it let the caller // specify the minimum size before compression. func NewGzipLevelAndMinSize(level, minSize int) (func(http.Handler) http.Handler, error) { - return GzipHandlerWithOpts(&GzipResponseWriter{}, CompressionLevel(level), MinSize(minSize)) + return GzipHandlerWithOpts(CompressionLevel(level), MinSize(minSize)) } -func GzipHandlerWithOpts(gw GzipWriter, opts ...option) (func(http.Handler) http.Handler, error) { - if gw == nil { - return nil, errors.New("the GzipWriter must be defined") - } - +func GzipHandlerWithOpts(opts ...option) (func(http.Handler) http.Handler, error) { c := &config{ level: gzip.DefaultCompression, minSize: DefaultMinSize, @@ -263,10 +258,12 @@ func GzipHandlerWithOpts(gw GzipWriter, opts ...option) (func(http.Handler) http w.Header().Add(vary, acceptEncoding) if acceptsGzip(r) { - gw.SetResponseWriter(w) - gw.setIndex(index) - gw.setMinSize(c.minSize) - gw.setContentTypes(c.contentTypes) + gw := &GzipResponseWriter{ + ResponseWriter: w, + index: index, + minSize: c.minSize, + contentTypes: c.contentTypes, + } defer gw.Close() h.ServeHTTP(gw, r) diff --git a/vendor/github.com/NYTimes/gziphandler/wrapper.go b/vendor/github.com/NYTimes/gziphandler/wrapper.go deleted file mode 100644 index de2d98f56..000000000 --- a/vendor/github.com/NYTimes/gziphandler/wrapper.go +++ /dev/null @@ -1,66 +0,0 @@ -package gziphandler - -import ( - "bufio" - "net" - "net/http" -) - -const ( - contentEncodingHeader = "Content-Encoding" -) - -// ---------- - -// http.ResponseWriter -// http.Hijacker -type GzipWriter interface { - Header() http.Header - Write([]byte) (int, error) - WriteHeader(int) - Hijack() (net.Conn, *bufio.ReadWriter, error) - Close() error - SetResponseWriter(http.ResponseWriter) - setIndex(int) - setMinSize(int) - setContentTypes([]string) -} - -func (w *GzipResponseWriter) SetResponseWriter(rw http.ResponseWriter) { - w.ResponseWriter = rw -} - -func (w *GzipResponseWriter) setIndex(index int) { - w.index = index -} - -func (w *GzipResponseWriter) setMinSize(minSize int) { - w.minSize = minSize -} - -func (w *GzipResponseWriter) setContentTypes(contentTypes []string) { - w.contentTypes = contentTypes -} - -// -------- - -type GzipResponseWriterWrapper struct { - GzipResponseWriter -} - -func (g *GzipResponseWriterWrapper) Write(b []byte) (int, error) { - if g.gw == nil && isEncoded(g.Header()) { - if g.code != 0 { - g.ResponseWriter.WriteHeader(g.code) - } - return g.ResponseWriter.Write(b) - } - return g.GzipResponseWriter.Write(b) -} - -func isEncoded(headers http.Header) bool { - header := headers.Get(contentEncodingHeader) - // According to https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding, - // content is not encoded if the header 'Content-Encoding' is empty or equals to 'identity'. - return header != "" && header != "identity" -} diff --git a/vendor/github.com/go-check/check/check.go b/vendor/github.com/go-check/check/check.go index c99392a25..e8f23f9d1 100644 --- a/vendor/github.com/go-check/check/check.go +++ b/vendor/github.com/go-check/check/check.go @@ -156,7 +156,7 @@ func (td *tempDir) newPath() string { } } result := filepath.Join(td.path, strconv.Itoa(td.counter)) - td.counter += 1 + td.counter++ return result } @@ -274,7 +274,7 @@ func (c *C) logString(issue string) { func (c *C) logCaller(skip int) { // This is a bit heavier than it ought to be. - skip += 1 // Our own frame. + skip++ // Our own frame. pc, callerFile, callerLine, ok := runtime.Caller(skip) if !ok { return @@ -284,7 +284,7 @@ func (c *C) logCaller(skip int) { testFunc := runtime.FuncForPC(c.method.PC()) if runtime.FuncForPC(pc) != testFunc { for { - skip += 1 + skip++ if pc, file, line, ok := runtime.Caller(skip); ok { // Note that the test line may be different on // distinct calls for the same test. Showing @@ -460,10 +460,10 @@ func (tracker *resultTracker) _loopRoutine() { // Calls still running. Can't stop. select { // XXX Reindent this (not now to make diff clear) - case c = <-tracker._expectChan: - tracker._waiting += 1 + case <-tracker._expectChan: + tracker._waiting++ case c = <-tracker._doneChan: - tracker._waiting -= 1 + tracker._waiting-- switch c.status() { case succeededSt: if c.kind == testKd { @@ -498,9 +498,9 @@ func (tracker *resultTracker) _loopRoutine() { select { case tracker._stopChan <- true: return - case c = <-tracker._expectChan: - tracker._waiting += 1 - case c = <-tracker._doneChan: + case <-tracker._expectChan: + tracker._waiting++ + case <-tracker._doneChan: panic("Tracker got an unexpected done call.") } } @@ -522,6 +522,7 @@ type suiteRunner struct { reportedProblemLast bool benchTime time.Duration benchMem bool + abort bool } type RunConf struct { @@ -533,6 +534,7 @@ type RunConf struct { BenchmarkTime time.Duration // Defaults to 1 second BenchmarkMem bool KeepWorkDir bool + Abort bool } // Create a new suiteRunner able to run all methods in the given suite. @@ -561,6 +563,7 @@ func newSuiteRunner(suite interface{}, runConf *RunConf) *suiteRunner { tempDir: &tempDir{}, keepDir: conf.KeepWorkDir, tests: make([]*methodType, 0, suiteNumMethods), + abort: conf.Abort, } if runner.benchTime == 0 { runner.benchTime = 1 * time.Second @@ -568,13 +571,13 @@ func newSuiteRunner(suite interface{}, runConf *RunConf) *suiteRunner { var filterRegexp *regexp.Regexp if conf.Filter != "" { - if regexp, err := regexp.Compile(conf.Filter); err != nil { + regexp, err := regexp.Compile(conf.Filter) + if err != nil { msg := "Bad filter expression: " + err.Error() runner.tracker.result.RunError = errors.New(msg) return runner - } else { - filterRegexp = regexp } + filterRegexp = regexp } for i := 0; i != suiteNumMethods; i++ { @@ -613,7 +616,9 @@ func (runner *suiteRunner) run() *Result { if c == nil || c.status() == succeededSt { for i := 0; i != len(runner.tests); i++ { c := runner.runTest(runner.tests[i]) - if c.status() == fixturePanickedSt { + status := c.status() + if status == fixturePanickedSt || runner.abort && + (status == failedSt || status == panickedSt) { runner.skipTests(missedSt, runner.tests[i+1:]) break } @@ -637,6 +642,16 @@ func (runner *suiteRunner) run() *Result { return &runner.tracker.result } +// Skip all methods in the given suite. +func (runner *suiteRunner) skip() *Result { + if runner.tracker.result.RunError == nil && len(runner.tests) > 0 { + runner.tracker.start() + runner.skipTests(missedSt, runner.tests) + runner.tracker.waitAndStop() + } + return &runner.tracker.result +} + // Create a call object with the given suite method, and fork a // goroutine with the provided dispatcher for running it. func (runner *suiteRunner) forkCall(method *methodType, kind funcKind, testName string, logb *logger, dispatcher func(c *C)) *C { @@ -871,84 +886,3 @@ func (runner *suiteRunner) reportCallDone(c *C) { runner.output.WriteCallSuccess("MISS", c) } } - -// ----------------------------------------------------------------------- -// Output writer manages atomic output writing according to settings. - -type outputWriter struct { - m sync.Mutex - writer io.Writer - wroteCallProblemLast bool - Stream bool - Verbose bool -} - -func newOutputWriter(writer io.Writer, stream, verbose bool) *outputWriter { - return &outputWriter{writer: writer, Stream: stream, Verbose: verbose} -} - -func (ow *outputWriter) Write(content []byte) (n int, err error) { - ow.m.Lock() - n, err = ow.writer.Write(content) - ow.m.Unlock() - return -} - -func (ow *outputWriter) WriteCallStarted(label string, c *C) { - if ow.Stream { - header := renderCallHeader(label, c, "", "\n") - ow.m.Lock() - ow.writer.Write([]byte(header)) - ow.m.Unlock() - } -} - -func (ow *outputWriter) WriteCallProblem(label string, c *C) { - var prefix string - if !ow.Stream { - prefix = "\n-----------------------------------" + - "-----------------------------------\n" - } - header := renderCallHeader(label, c, prefix, "\n\n") - ow.m.Lock() - ow.wroteCallProblemLast = true - ow.writer.Write([]byte(header)) - if !ow.Stream { - c.logb.WriteTo(ow.writer) - } - ow.m.Unlock() -} - -func (ow *outputWriter) WriteCallSuccess(label string, c *C) { - if ow.Stream || (ow.Verbose && c.kind == testKd) { - // TODO Use a buffer here. - var suffix string - if c.reason != "" { - suffix = " (" + c.reason + ")" - } - if c.status() == succeededSt { - suffix += "\t" + c.timerString() - } - suffix += "\n" - if ow.Stream { - suffix += "\n" - } - header := renderCallHeader(label, c, "", suffix) - ow.m.Lock() - // Resist temptation of using line as prefix above due to race. - if !ow.Stream && ow.wroteCallProblemLast { - header = "\n-----------------------------------" + - "-----------------------------------\n" + - header - } - ow.wroteCallProblemLast = false - ow.writer.Write([]byte(header)) - ow.m.Unlock() - } -} - -func renderCallHeader(label string, c *C, prefix, suffix string) string { - pc := c.method.PC() - return fmt.Sprintf("%s%s: %s: %s%s", prefix, label, niceFuncPath(pc), - niceFuncName(pc), suffix) -} diff --git a/vendor/github.com/go-check/check/checkers.go b/vendor/github.com/go-check/check/checkers.go index bac338729..06608adf6 100644 --- a/vendor/github.com/go-check/check/checkers.go +++ b/vendor/github.com/go-check/check/checkers.go @@ -88,7 +88,7 @@ func (checker *notChecker) Info() *CheckerInfo { } func (checker *notChecker) Check(params []interface{}, names []string) (result bool, error string) { - result, error = checker.sub.Check(params, names) + result, _ = checker.sub.Check(params, names) result = !result return } @@ -212,7 +212,7 @@ type hasLenChecker struct { // The HasLen checker verifies that the obtained value has the // provided length. In many cases this is superior to using Equals -// in conjuction with the len function because in case the check +// in conjunction with the len function because in case the check // fails the value itself will be printed, instead of its length, // providing more details for figuring the problem. // @@ -235,7 +235,10 @@ func (checker *hasLenChecker) Check(params []interface{}, names []string) (resul default: return false, "obtained value type has no length" } - return value.Len() == n, "" + if value.Len() == n { + return true, "" + } + return false, fmt.Sprintf("obtained length = %d", value.Len()) } // ----------------------------------------------------------------------- diff --git a/vendor/github.com/go-check/check/helpers.go b/vendor/github.com/go-check/check/helpers.go index 58a733b50..52dc0823a 100644 --- a/vendor/github.com/go-check/check/helpers.go +++ b/vendor/github.com/go-check/check/helpers.go @@ -212,7 +212,7 @@ func (c *C) internalCheck(funcName string, obtained interface{}, checker Checker // Do the actual check. result, error := checker.Check(params, names) - if !result || error != "" { + if !result { c.logCaller(2) for i := 0; i != len(params); i++ { c.logValue(names[i], params[i]) diff --git a/vendor/github.com/go-check/check/reporter.go b/vendor/github.com/go-check/check/reporter.go new file mode 100644 index 000000000..fb04f76f6 --- /dev/null +++ b/vendor/github.com/go-check/check/reporter.go @@ -0,0 +1,88 @@ +package check + +import ( + "fmt" + "io" + "sync" +) + +// ----------------------------------------------------------------------- +// Output writer manages atomic output writing according to settings. + +type outputWriter struct { + m sync.Mutex + writer io.Writer + wroteCallProblemLast bool + Stream bool + Verbose bool +} + +func newOutputWriter(writer io.Writer, stream, verbose bool) *outputWriter { + return &outputWriter{writer: writer, Stream: stream, Verbose: verbose} +} + +func (ow *outputWriter) Write(content []byte) (n int, err error) { + ow.m.Lock() + n, err = ow.writer.Write(content) + ow.m.Unlock() + return +} + +func (ow *outputWriter) WriteCallStarted(label string, c *C) { + if ow.Stream { + header := renderCallHeader(label, c, "", "\n") + ow.m.Lock() + ow.writer.Write([]byte(header)) + ow.m.Unlock() + } +} + +func (ow *outputWriter) WriteCallProblem(label string, c *C) { + var prefix string + if !ow.Stream { + prefix = "\n-----------------------------------" + + "-----------------------------------\n" + } + header := renderCallHeader(label, c, prefix, "\n\n") + ow.m.Lock() + ow.wroteCallProblemLast = true + ow.writer.Write([]byte(header)) + if !ow.Stream { + c.logb.WriteTo(ow.writer) + } + ow.m.Unlock() +} + +func (ow *outputWriter) WriteCallSuccess(label string, c *C) { + if ow.Stream || (ow.Verbose && c.kind == testKd) { + // TODO Use a buffer here. + var suffix string + if c.reason != "" { + suffix = " (" + c.reason + ")" + } + if c.status() == succeededSt { + suffix += "\t" + c.timerString() + } + suffix += "\n" + if ow.Stream { + suffix += "\n" + } + header := renderCallHeader(label, c, "", suffix) + ow.m.Lock() + // Resist temptation of using line as prefix above due to race. + if !ow.Stream && ow.wroteCallProblemLast { + header = "\n-----------------------------------" + + "-----------------------------------\n" + + header + } + ow.wroteCallProblemLast = false + ow.writer.Write([]byte(header)) + ow.m.Unlock() + } +} + +func renderCallHeader(label string, c *C, prefix, suffix string) string { + pc := c.method.PC() + return fmt.Sprintf("%s%s: %s: %s%s", prefix, label, niceFuncPath(pc), + niceFuncName(pc), suffix) +} diff --git a/vendor/github.com/go-check/check/run.go b/vendor/github.com/go-check/check/run.go index da8fd7987..2923e9490 100644 --- a/vendor/github.com/go-check/check/run.go +++ b/vendor/github.com/go-check/check/run.go @@ -42,6 +42,7 @@ var ( newBenchMem = flag.Bool("check.bmem", false, "Report memory benchmarks") newListFlag = flag.Bool("check.list", false, "List the names of all tests that will be run") newWorkFlag = flag.Bool("check.work", false, "Display and do not remove the test working directory") + abort = flag.Bool("check.abort", true, "Stop testing the suite if a test has failed") ) // TestingT runs all test suites registered with the Suite function, @@ -60,6 +61,7 @@ func TestingT(testingT *testing.T) { BenchmarkTime: benchTime, BenchmarkMem: *newBenchMem, KeepWorkDir: *oldWorkFlag || *newWorkFlag, + Abort: *abort, } if *oldListFlag || *newListFlag { w := bufio.NewWriter(os.Stdout) @@ -80,8 +82,21 @@ func TestingT(testingT *testing.T) { // provided run configuration. func RunAll(runConf *RunConf) *Result { result := Result{} + skipTests := false for _, suite := range allSuites { - result.Add(Run(suite, runConf)) + var res *Result + if skipTests { + // Count missed tests. + res = skipSuite(suite, runConf) + } else { + res = Run(suite, runConf) + } + + result.Add(res) + if runConf.Abort && (res.Failed > 0 || res.Panicked > 0) { + skipTests = true + continue + } } return &result } @@ -92,6 +107,11 @@ func Run(suite interface{}, runConf *RunConf) *Result { return runner.run() } +func skipSuite(suite interface{}, runConf *RunConf) *Result { + runner := newSuiteRunner(suite, runConf) + return runner.skip() +} + // ListAll returns the names of all the test functions registered with the // Suite function that will be run with the provided run configuration. func ListAll(runConf *RunConf) []string {