diff --git a/CHANGELOG.md b/CHANGELOG.md index 5afa0ca5d..ba47dce71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,42 @@ +## [v2.1.0-rc3](https://github.com/containous/traefik/tree/v2.1.0-rc3) (2019-12-02) +[All Commits](https://github.com/containous/traefik/compare/v2.1.0-rc2...v2.1.0-rc3) + +**Bug fixes:** +- **[cli]** fix: sub command help ([#5887](https://github.com/containous/traefik/pull/5887) by [ldez](https://github.com/ldez)) +- **[consulcatalog]** fix: consul catalog constraints. ([#5913](https://github.com/containous/traefik/pull/5913) by [ldez](https://github.com/ldez)) +- **[consulcatalog]** Service registered with same id on Consul Catalog ([#5900](https://github.com/containous/traefik/pull/5900) by [mmatur](https://github.com/mmatur)) +- **[webui]** Web UI: Avoid polling on /api/entrypoints ([#5863](https://github.com/containous/traefik/pull/5863) by [matthieuh](https://github.com/matthieuh)) +- **[webui]** Web UI: Sync toolbar table state with url query params ([#5861](https://github.com/containous/traefik/pull/5861) by [matthieuh](https://github.com/matthieuh)) + +**Misc:** +- **[cli]** Add custom help function to command ([#5923](https://github.com/containous/traefik/pull/5923) by [Ullaakut](https://github.com/Ullaakut)) + +## [v2.0.6](https://github.com/containous/traefik/tree/v2.0.6) (2019-12-02) +[All Commits](https://github.com/containous/traefik/compare/v2.0.5...v2.0.6) + +**Bug fixes:** +- **[acme]** Update go-acme/lego to 3.2.0 ([#5839](https://github.com/containous/traefik/pull/5839) by [kolaente](https://github.com/kolaente)) +- **[cli,healthcheck]** Uses, if it exists, the ping entry point provided in the static configuration ([#5867](https://github.com/containous/traefik/pull/5867) by [jbdoumenjou](https://github.com/jbdoumenjou)) +- **[healthcheck]** Healthcheck managed for all related services ([#5860](https://github.com/containous/traefik/pull/5860) by [jbdoumenjou](https://github.com/jbdoumenjou)) +- **[logs,middleware]** Do not give responsewriter or its headers to asynchronous logging goroutine ([#5840](https://github.com/containous/traefik/pull/5840) by [mpl](https://github.com/mpl)) +- **[middleware]** X-Forwarded-Proto must not skip the redirection. ([#5836](https://github.com/containous/traefik/pull/5836) by [ldez](https://github.com/ldez)) +- **[middleware]** fix: location header rewrite. ([#5835](https://github.com/containous/traefik/pull/5835) by [ldez](https://github.com/ldez)) +- **[middleware]** Remove Request Headers CORS Preflight Requirement ([#5903](https://github.com/containous/traefik/pull/5903) by [dtomcej](https://github.com/dtomcej)) +- **[rancher]** Change service name in rancher provider to make webui service details view work ([#5895](https://github.com/containous/traefik/pull/5895) by [SantoDE](https://github.com/SantoDE)) +- **[tracing]** Fix extraction for zipkin tracing ([#5920](https://github.com/containous/traefik/pull/5920) by [jcchavezs](https://github.com/jcchavezs)) +- **[webui]** Web UI: Avoid unnecessary duplicated api calls ([#5884](https://github.com/containous/traefik/pull/5884) by [matthieuh](https://github.com/matthieuh)) +- **[webui]** Web UI: Avoid some router properties to overflow their container ([#5872](https://github.com/containous/traefik/pull/5872) by [matthieuh](https://github.com/matthieuh)) +- **[webui]** Web UI: Fix displayed tcp service details ([#5868](https://github.com/containous/traefik/pull/5868) by [matthieuh](https://github.com/matthieuh)) + +**Documentation:** +- **[acme]** doc: fix wrong acme information ([#5837](https://github.com/containous/traefik/pull/5837) by [ldez](https://github.com/ldez)) +- **[docker,docker/swarm]** Add Swarm section to the Docker Provider Documentation ([#5874](https://github.com/containous/traefik/pull/5874) by [dduportal](https://github.com/dduportal)) +- **[docker]** Update router entrypoint example ([#5766](https://github.com/containous/traefik/pull/5766) by [woto](https://github.com/woto)) +- **[k8s/helm]** Mention the experimental Helm Chart in the installation section of documentation ([#5879](https://github.com/containous/traefik/pull/5879) by [dduportal](https://github.com/dduportal)) +- doc: remove double quotes on CLI flags. ([#5862](https://github.com/containous/traefik/pull/5862) by [ldez](https://github.com/ldez)) +- Fixed spelling error ([#5834](https://github.com/containous/traefik/pull/5834) by [blakebuthod](https://github.com/blakebuthod)) +- Add back the security section from v1 ([#5832](https://github.com/containous/traefik/pull/5832) by [pascalandy](https://github.com/pascalandy)) + ## [v2.1.0-rc2](https://github.com/containous/traefik/tree/v2.0.4) (2019-11-15) [All Commits](https://github.com/containous/traefik/compare/v2.0.0-rc1...v2.1.0-rc2) diff --git a/cmd/healthcheck/healthcheck.go b/cmd/healthcheck/healthcheck.go index bf1c2762f..0a922b2a0 100644 --- a/cmd/healthcheck/healthcheck.go +++ b/cmd/healthcheck/healthcheck.go @@ -51,9 +51,14 @@ func Do(staticConfiguration static.Configuration) (*http.Response, error) { return nil, errors.New("please enable `ping` to use health check") } - pingEntryPoint, ok := staticConfiguration.EntryPoints["traefik"] + ep := staticConfiguration.Ping.EntryPoint + if ep == "" { + ep = "traefik" + } + + pingEntryPoint, ok := staticConfiguration.EntryPoints[ep] if !ok { - return nil, errors.New("missing `ping` entrypoint") + return nil, fmt.Errorf("ping: missing %s entry point", ep) } client := &http.Client{Timeout: 5 * time.Second} diff --git a/docs/content/getting-started/install-traefik.md b/docs/content/getting-started/install-traefik.md index 96cf721b1..2cda421f0 100644 --- a/docs/content/getting-started/install-traefik.md +++ b/docs/content/getting-started/install-traefik.md @@ -3,6 +3,7 @@ You can install Traefik with the following flavors: * [Use the official Docker image](./#use-the-official-docker-image) +* [(Experimental) Use the Helm Chart](./#use-the-helm-chart) * [Use the binary distribution](./#use-the-binary-distribution) * [Compile your binary from the sources](./#compile-your-binary-from-the-sources) @@ -24,6 +25,70 @@ For more details, go to the [Docker provider documentation](../providers/docker. * Docker images are based from the [Alpine Linux Official image](https://hub.docker.com/_/alpine). * All the orchestrator using docker images could fetch the official Traefik docker image. +## Use the Helm Chart + +!!! warning "Experimental Helm Chart" + + Please note that the Helm Chart for Traefik v2 is still experimental. + + The Traefik Stable Chart from + [Helm's default charts repository](https://github.com/helm/charts/tree/master/stable/traefik) is still using [Traefik v1.7](https://docs.traefik.io/v1.7). + +Traefik can be installed in Kubernetes using the v2.0 Helm chart from . + +Ensure that the following requirements are met: + +* Kubernetes 1.14+ +* Helm version 2.x is [installed](https://v2.helm.sh/docs/using_helm/) and initialized with Tiller + +Retrieve the latest chart version from the repository: + +```bash +# Retrieve Chart from the repository +git clone https://github.com/containous/traefik-helm-chart +``` + +And install it with the `helm` command line: + +```bash +helm install ./traefik-helm-chart +``` + +!!! tip "Helm Features" + + All [Helm features](https://v2.helm.sh/docs/using_helm/#using-helm) are supported. + For instance, installing the chart in a dedicated namespace: + + ```bash tab="Install in a Dedicated Namespace" + # Install in the namespace "traefik-v2" + helm install --namespace=traefik-v2 \ + ./traefik-helm-chart + ``` + +??? example "Installing with Custom Values" + + You can customize the installation by specifying custom values, + as with [any helm chart](https://v2.helm.sh/docs/using_helm/#customizing-the-chart-before-installing). + {: #helm-custom-values } + + The values are not (yet) documented, but are self-explanatory: + you can look at the [default `values.yaml`](https://github.com/containous/traefik-helm-chart/blob/master/values.yaml) file to explore possibilities. + + Example of installation with logging set to `DEBUG`: + + ```bash tab="Using Helm CLI" + helm install --namespace=traefik-v2 \ + --set="logs.loglevel=DEBUG" \ + ./traefik-helm-chart + ``` + + ```yml tab="With a custom values file" + # File custom-values.yml + ## Install with "helm install --values=./custom-values.yml ./traefik-helm-chart + logs: + loglevel: DEBUG + ``` + ## Use the Binary Distribution Grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page. diff --git a/docs/content/https/acme.md b/docs/content/https/acme.md index 80e48d1d6..d23cf4317 100644 --- a/docs/content/https/acme.md +++ b/docs/content/https/acme.md @@ -47,11 +47,11 @@ You can configure Traefik to use an ACME provider (like Let's Encrypt) for autom ``` ```bash tab="CLI" - --entryPoints.web.address=":80" - --entryPoints.websecure.address=":443" + --entryPoints.web.address=:80 + --entryPoints.websecure.address=:443 # ... - --certificatesResolvers.sample.acme.email="your-email@your-domain.org" - --certificatesResolvers.sample.acme.storage="acme.json" + --certificatesResolvers.sample.acme.email=your-email@your-domain.org + --certificatesResolvers.sample.acme.storage=acme.json # used during the challenge --certificatesResolvers.sample.acme.httpChallenge.entryPoint=web ``` @@ -156,8 +156,8 @@ when using the `HTTP-01` challenge, `certificatesResolvers.sample.acme.httpChall ``` ```bash tab="CLI" - --entryPoints.web.address=":80" - --entryPoints.websecure.address=":443" + --entryPoints.web.address=:80 + --entryPoints.websecure.address=:443 # ... --certificatesResolvers.sample.acme.httpChallenge.entryPoint=web ``` @@ -312,7 +312,7 @@ certificatesResolvers: ```bash tab="CLI" # ... ---certificatesResolvers.sample.acme.dnsChallenge.resolvers:="1.1.1.1:53,8.8.8.8:53" +--certificatesResolvers.sample.acme.dnsChallenge.resolvers:=1.1.1.1:53,8.8.8.8:53 ``` #### Wildcard Domains @@ -342,7 +342,7 @@ As described in [Let's Encrypt's post](https://community.letsencrypt.org/t/stagi ```bash tab="CLI" # ... - --certificatesResolvers.sample.acme.caServer="https://acme-staging-v02.api.letsencrypt.org/directory" + --certificatesResolvers.sample.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory # ... ``` diff --git a/docs/content/https/ref-acme.txt b/docs/content/https/ref-acme.txt index d71023048..89431729b 100644 --- a/docs/content/https/ref-acme.txt +++ b/docs/content/https/ref-acme.txt @@ -4,13 +4,13 @@ # # Required # ---certificatesResolvers.sample.acme.email="test@traefik.io" +--certificatesResolvers.sample.acme.email=test@traefik.io # File or key used for certificates storage. # # Required # ---certificatesResolvers.sample.acme.storage="acme.json" +--certificatesResolvers.sample.acme.storage=acme.json # CA server to use. # Uncomment the line to use Let's Encrypt's staging server, @@ -19,7 +19,7 @@ # Optional # Default: "https://acme-v02.api.letsencrypt.org/directory" # ---certificatesResolvers.sample.acme.caServer="https://acme-staging-v02.api.letsencrypt.org/directory" +--certificatesResolvers.sample.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory # KeyType to use. # @@ -75,7 +75,7 @@ # Optional # Default: empty # ---certificatesResolvers.sample.acme.dnsChallenge.resolvers="1.1.1.1:53,8.8.8.8:53" +--certificatesResolvers.sample.acme.dnsChallenge.resolvers=1.1.1.1:53,8.8.8.8:53 # Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready. # diff --git a/docs/content/migration/v1-to-v2.md b/docs/content/migration/v1-to-v2.md index 287981564..07d1c3dad 100644 --- a/docs/content/migration/v1-to-v2.md +++ b/docs/content/migration/v1-to-v2.md @@ -718,11 +718,11 @@ with the path `/admin` stripped, e.g. to `http://:/`. In this case, yo ``` ```bash tab="CLI" - --entryPoints.web.address=":80" - --entryPoints.websecure.address=":443" - --certificatesResolvers.sample.acme.email: your-email@your-domain.org - --certificatesResolvers.sample.acme.storage: acme.json - --certificatesResolvers.sample.acme.httpChallenge.entryPoint: web + --entryPoints.web.address=:80 + --entryPoints.websecure.address=:443 + --certificatesResolvers.sample.acme.email=your-email@your-domain.org + --certificatesResolvers.sample.acme.storage=acme.json + --certificatesResolvers.sample.acme.httpChallenge.entryPoint=web ``` ## Traefik Logs @@ -744,9 +744,9 @@ There is no more log configuration at the root level. ``` ```bash tab="CLI" - --logLevel="DEBUG" - --traefikLog.filePath="/path/to/traefik.log" - --traefikLog.format="json" + --logLevel=DEBUG + --traefikLog.filePath=/path/to/traefik.log + --traefikLog.format=json ``` !!! info "v2" @@ -768,9 +768,9 @@ There is no more log configuration at the root level. ``` ```bash tab="CLI" - --log.level="DEBUG" - --log.filePath="/path/to/traefik.log" - --log.format="json" + --log.level=DEBUG + --log.filePath=/path/to/traefik.log + --log.format=json ``` ## Tracing @@ -794,12 +794,12 @@ Traefik v2 retains OpenTracing support. The `backend` root option from the v1 is ``` ```bash tab="CLI" - --tracing.backend="jaeger" - --tracing.servicename="tracing" - --tracing.jaeger.localagenthostport="12.0.0.1:6831" - --tracing.jaeger.samplingparam="1.0" - --tracing.jaeger.samplingserverurl="http://12.0.0.1:5778/sampling" - --tracing.jaeger.samplingtype="const" + --tracing.backend=jaeger + --tracing.servicename=tracing + --tracing.jaeger.localagenthostport=12.0.0.1:6831 + --tracing.jaeger.samplingparam=1.0 + --tracing.jaeger.samplingserverurl=http://12.0.0.1:5778/sampling + --tracing.jaeger.samplingtype=const ``` !!! info "v2" @@ -827,11 +827,11 @@ Traefik v2 retains OpenTracing support. The `backend` root option from the v1 is ``` ```bash tab="CLI" - --tracing.servicename="tracing" - --tracing.jaeger.localagenthostport="12.0.0.1:6831" - --tracing.jaeger.samplingparam="1.0" - --tracing.jaeger.samplingserverurl="http://12.0.0.1:5778/sampling" - --tracing.jaeger.samplingtype="const" + --tracing.servicename=tracing + --tracing.jaeger.localagenthostport=12.0.0.1:6831 + --tracing.jaeger.samplingparam=1.0 + --tracing.jaeger.samplingserverurl=http://12.0.0.1:5778/sampling + --tracing.jaeger.samplingtype=const ``` ## Metrics @@ -852,7 +852,7 @@ For a basic configuration, the [metrics configuration](../observability/metrics/ ```bash tab="CLI" --metrics.prometheus.buckets=[0.1,0.3,1.2,5.0] - --metrics.prometheus.entrypoint="traefik" + --metrics.prometheus.entrypoint=traefik ``` !!! info "v2" @@ -878,7 +878,7 @@ For a basic configuration, the [metrics configuration](../observability/metrics/ ```bash tab="CLI" --metrics.prometheus.buckets=[0.1,0.3,1.2,5.0] - --metrics.prometheus.entrypoint="metrics" + --metrics.prometheus.entrypoint=metrics ``` ## No More Root Level Key/Values @@ -908,14 +908,14 @@ Each root item has been moved to a related section or removed. ```bash tab="CLI" --checknewversion=false --sendanonymoususage=true - --loglevel="DEBUG" + --loglevel=DEBUG --insecureskipverify=true - --rootcas="/mycert.cert" + --rootcas=/mycert.cert --maxidleconnsperhost=200 - --providersthrottleduration="2s" + --providersthrottleduration=2s --allowminweightzero=true --debug=true - --defaultentrypoints="web","web-secure" + --defaultentrypoints=web,web-secure --keeptrailingslash=true ``` @@ -961,9 +961,9 @@ Each root item has been moved to a related section or removed. ```bash tab="CLI" --global.checknewversion=true --global.sendanonymoususage=true - --log.level="DEBUG" + --log.level=DEBUG --serverstransport.insecureskipverify=true - --serverstransport.rootcas="/mycert.cert" + --serverstransport.rootcas=/mycert.cert --serverstransport.maxidleconnsperhost=42 --providers.providersthrottleduration=42 ``` @@ -1029,7 +1029,7 @@ As the dashboard access is now secured by default you can either: [api] [providers.file] - filename = "/dynamic-conf.toml" + filename = "/dynamic-conf.toml" ##---------------------## diff --git a/docs/content/observability/access-logs.md b/docs/content/observability/access-logs.md index 5a27efc17..234204ed3 100644 --- a/docs/content/observability/access-logs.md +++ b/docs/content/observability/access-logs.md @@ -61,7 +61,7 @@ accessLog: ```bash tab="CLI" # Configuring a buffer of 100 lines --accesslog=true ---accesslog.filepath="/path/to/access.log" +--accesslog.filepath=/path/to/access.log --accesslog.bufferingsize=100 ``` @@ -104,11 +104,11 @@ accessLog: ```bash tab="CLI" # Configuring Multiple Filters --accesslog=true ---accesslog.filepath="/path/to/access.log" ---accesslog.format="json" ---accesslog.filters.statuscodes="200, 300-302" +--accesslog.filepath=/path/to/access.log +--accesslog.format=json +--accesslog.filters.statuscodes=200,300-302 --accesslog.filters.retryattempts ---accesslog.filters.minduration="10ms" +--accesslog.filters.minduration=10ms ``` ### Limiting the Fields @@ -164,14 +164,14 @@ accessLog: ```bash tab="CLI" # Limiting the Logs to Specific Fields --accesslog=true ---accesslog.filepath="/path/to/access.log" ---accesslog.format="json" ---accesslog.fields.defaultmode="keep" ---accesslog.fields.names.ClientUsername="drop" ---accesslog.fields.headers.defaultmode="keep" ---accesslog.fields.headers.names.User-Agent="redact" ---accesslog.fields.headers.names.Authorization="drop" ---accesslog.fields.headers.names.Content-Type="keep" +--accesslog.filepath=/path/to/access.log +--accesslog.format=json +--accesslog.fields.defaultmode=keep +--accesslog.fields.names.ClientUsername=drop +--accesslog.fields.headers.defaultmode=keep +--accesslog.fields.headers.names.User-Agent=redact +--accesslog.fields.headers.names.Authorization=drop +--accesslog.fields.headers.names.Content-Type=keep ``` ??? info "Available Fields" diff --git a/docs/content/observability/logs.md b/docs/content/observability/logs.md index 37e002f4a..3aa0d6c15 100644 --- a/docs/content/observability/logs.md +++ b/docs/content/observability/logs.md @@ -30,7 +30,7 @@ log: ```bash tab="CLI" # Writing Logs to a File ---log.filePath="/path/to/traefik.log" +--log.filePath=/path/to/traefik.log ``` #### `format` @@ -53,8 +53,8 @@ log: ```bash tab="CLI" # Writing Logs to a File, in JSON ---log.filePath="/path/to/traefik.log" ---log.format="json" +--log.filePath=/path/to/traefik.log +--log.format=json ``` #### `level` @@ -72,7 +72,7 @@ log: ``` ```bash tab="CLI" ---log.level="DEBUG" +--log.level=DEBUG ``` ## Log Rotation diff --git a/docs/content/observability/metrics/datadog.md b/docs/content/observability/metrics/datadog.md index cb412a383..b7763ec5c 100644 --- a/docs/content/observability/metrics/datadog.md +++ b/docs/content/observability/metrics/datadog.md @@ -35,7 +35,7 @@ metrics: ``` ```bash tab="CLI" ---metrics.datadog.address="127.0.0.1:8125" +--metrics.datadog.address=127.0.0.1:8125 ``` #### `addEntryPointsLabels` diff --git a/docs/content/observability/metrics/influxdb.md b/docs/content/observability/metrics/influxdb.md index c30fd44b3..6ffa760b3 100644 --- a/docs/content/observability/metrics/influxdb.md +++ b/docs/content/observability/metrics/influxdb.md @@ -35,7 +35,7 @@ metrics: ``` ```bash tab="CLI" ---metrics.influxdb.address="localhost:8089" +--metrics.influxdb.address=localhost:8089 ``` #### `protocol` @@ -57,7 +57,7 @@ metrics: ``` ```bash tab="CLI" ---metrics.influxdb.protocol="udp" +--metrics.influxdb.protocol=udp ``` #### `database` @@ -69,17 +69,17 @@ InfluxDB database used when protocol is http. ```toml tab="File (TOML)" [metrics] [metrics.influxDB] - database = "" + database = "db" ``` ```yaml tab="File (YAML)" metrics: influxDB: - database: "" + database: "db" ``` ```bash tab="CLI" ---metrics.influxdb.database="" +--metrics.influxdb.database=db ``` #### `retentionPolicy` @@ -91,17 +91,17 @@ InfluxDB retention policy used when protocol is http. ```toml tab="File (TOML)" [metrics] [metrics.influxDB] - retentionPolicy = "" + retentionPolicy = "two_hours" ``` ```yaml tab="File (YAML)" metrics: influxDB: - retentionPolicy: "" + retentionPolicy: "two_hours" ``` ```bash tab="CLI" ---metrics.influxdb.retentionPolicy="" +--metrics.influxdb.retentionPolicy=two_hours ``` #### `username` @@ -113,17 +113,17 @@ InfluxDB username (only with http). ```toml tab="File (TOML)" [metrics] [metrics.influxDB] - username = "" + username = "john" ``` ```yaml tab="File (YAML)" metrics: influxDB: - username: "" + username: "john" ``` ```bash tab="CLI" ---metrics.influxdb.username="" +--metrics.influxdb.username=john ``` #### `password` @@ -135,17 +135,17 @@ InfluxDB password (only with http). ```toml tab="File (TOML)" [metrics] [metrics.influxDB] - password = "" + password = "secret" ``` ```yaml tab="File (YAML)" metrics: influxDB: - password: "" + password: "secret" ``` ```bash tab="CLI" ---metrics.influxdb.password="" +--metrics.influxdb.password=secret ``` #### `addEntryPointsLabels` diff --git a/docs/content/observability/metrics/prometheus.md b/docs/content/observability/metrics/prometheus.md index 54e617fc5..3ecf62f92 100644 --- a/docs/content/observability/metrics/prometheus.md +++ b/docs/content/observability/metrics/prometheus.md @@ -113,8 +113,8 @@ metrics: ``` ```bash tab="CLI" ---entryPoints.metrics.address=":8082" ---metrics.prometheus.entryPoint="metrics" +--entryPoints.metrics.address=:8082 +--metrics.prometheus.entryPoint=metrics ``` #### `manualRouting` diff --git a/docs/content/observability/metrics/statsd.md b/docs/content/observability/metrics/statsd.md index d16714968..bbdcc9513 100644 --- a/docs/content/observability/metrics/statsd.md +++ b/docs/content/observability/metrics/statsd.md @@ -35,7 +35,7 @@ metrics: ``` ```bash tab="CLI" ---metrics.statsd.address="localhost:8125" +--metrics.statsd.address=localhost:8125 ``` #### `addEntryPointsLabels` diff --git a/docs/content/observability/tracing/datadog.md b/docs/content/observability/tracing/datadog.md index 844f4fc70..a6ce77d5d 100644 --- a/docs/content/observability/tracing/datadog.md +++ b/docs/content/observability/tracing/datadog.md @@ -35,7 +35,7 @@ tracing: ``` ```bash tab="CLI" ---tracing.datadog.localAgentHostPort="127.0.0.1:8126" +--tracing.datadog.localAgentHostPort=127.0.0.1:8126 ``` #### `debug` @@ -79,7 +79,7 @@ tracing: ``` ```bash tab="CLI" ---tracing.datadog.globalTag="sample" +--tracing.datadog.globalTag=sample ``` #### `prioritySampling` diff --git a/docs/content/observability/tracing/haystack.md b/docs/content/observability/tracing/haystack.md index eb32bea21..ae0676c70 100644 --- a/docs/content/observability/tracing/haystack.md +++ b/docs/content/observability/tracing/haystack.md @@ -35,7 +35,7 @@ tracing: ``` ```bash tab="CLI" ---tracing.haystack.localAgentHost="127.0.0.1" +--tracing.haystack.localAgentHost=127.0.0.1 ``` #### `localAgentPort` @@ -79,7 +79,7 @@ tracing: ``` ```bash tab="CLI" ---tracing.haystack.globalTag="sample:test" +--tracing.haystack.globalTag=sample:test ``` #### `traceIDHeaderName` @@ -101,7 +101,7 @@ tracing: ``` ```bash tab="CLI" ---tracing.haystack.traceIDHeaderName="sample" +--tracing.haystack.traceIDHeaderName=sample ``` #### `parentIDHeaderName` @@ -123,7 +123,7 @@ tracing: ``` ```bash tab="CLI" ---tracing.haystack.parentIDHeaderName="sample" +--tracing.haystack.parentIDHeaderName=sample ``` #### `spanIDHeaderName` @@ -168,5 +168,5 @@ tracing: ```bash tab="CLI" ---tracing.haystack.baggagePrefixHeaderName="sample" +--tracing.haystack.baggagePrefixHeaderName=sample ``` diff --git a/docs/content/observability/tracing/instana.md b/docs/content/observability/tracing/instana.md index b4e5d78e1..bc6b69958 100644 --- a/docs/content/observability/tracing/instana.md +++ b/docs/content/observability/tracing/instana.md @@ -35,7 +35,7 @@ tracing: ``` ```bash tab="CLI" ---tracing.instana.localAgentHost="127.0.0.1" +--tracing.instana.localAgentHost=127.0.0.1 ``` #### `localAgentPort` @@ -86,5 +86,5 @@ tracing: ``` ```bash tab="CLI" ---tracing.instana.logLevel="info" +--tracing.instana.logLevel=info ``` diff --git a/docs/content/observability/tracing/jaeger.md b/docs/content/observability/tracing/jaeger.md index a95975db1..af4397e05 100644 --- a/docs/content/observability/tracing/jaeger.md +++ b/docs/content/observability/tracing/jaeger.md @@ -39,7 +39,7 @@ tracing: ``` ```bash tab="CLI" ---tracing.jaeger.samplingServerURL="http://localhost:5778/sampling" +--tracing.jaeger.samplingServerURL=http://localhost:5778/sampling ``` #### `samplingType` @@ -61,7 +61,7 @@ tracing: ``` ```bash tab="CLI" ---tracing.jaeger.samplingType="const" +--tracing.jaeger.samplingType=const ``` #### `samplingParam` @@ -89,7 +89,7 @@ tracing: ``` ```bash tab="CLI" ---tracing.jaeger.samplingParam="1.0" +--tracing.jaeger.samplingParam=1.0 ``` #### `localAgentHostPort` @@ -111,7 +111,7 @@ tracing: ``` ```bash tab="CLI" ---tracing.jaeger.localAgentHostPort="127.0.0.1:6831" +--tracing.jaeger.localAgentHostPort=127.0.0.1:6831 ``` #### `gen128Bit` @@ -159,7 +159,7 @@ tracing: ``` ```bash tab="CLI" ---tracing.jaeger.propagation="jaeger" +--tracing.jaeger.propagation=jaeger ``` #### `traceContextHeaderName` @@ -182,7 +182,7 @@ tracing: ``` ```bash tab="CLI" ---tracing.jaeger.traceContextHeaderName="uber-trace-id" +--tracing.jaeger.traceContextHeaderName=uber-trace-id ``` ### `collector` @@ -206,7 +206,7 @@ tracing: ``` ```bash tab="CLI" ---tracing.jaeger.collector.endpoint="http://127.0.0.1:14268/api/traces?format=jaeger.thrift" +--tracing.jaeger.collector.endpoint=http://127.0.0.1:14268/api/traces?format=jaeger.thrift ``` #### `user` @@ -229,7 +229,7 @@ tracing: ``` ```bash tab="CLI" ---tracing.jaeger.collector.user="my-user" +--tracing.jaeger.collector.user=my-user ``` #### `password` @@ -252,5 +252,5 @@ tracing: ``` ```bash tab="CLI" ---tracing.jaeger.collector.password="my-password" +--tracing.jaeger.collector.password=my-password ``` diff --git a/docs/content/observability/tracing/overview.md b/docs/content/observability/tracing/overview.md index a6cb8dbd8..8098cd0ea 100644 --- a/docs/content/observability/tracing/overview.md +++ b/docs/content/observability/tracing/overview.md @@ -52,7 +52,7 @@ tracing: ``` ```bash tab="CLI" ---tracing.serviceName="traefik" +--tracing.serviceName=traefik ``` #### `spanNameLimit` diff --git a/docs/content/observability/tracing/zipkin.md b/docs/content/observability/tracing/zipkin.md index 8aa1c0283..dcd76323e 100644 --- a/docs/content/observability/tracing/zipkin.md +++ b/docs/content/observability/tracing/zipkin.md @@ -35,7 +35,7 @@ tracing: ``` ```bash tab="CLI" ---tracing.zipkin.httpEndpoint="http://localhost:9411/api/v2/spans" +--tracing.zipkin.httpEndpoint=http://localhost:9411/api/v2/spans ``` #### `sameSpan` @@ -101,5 +101,5 @@ tracing: ``` ```bash tab="CLI" ---tracing.zipkin.sampleRate="0.2" +--tracing.zipkin.sampleRate=0.2 ``` diff --git a/docs/content/operations/ping.md b/docs/content/operations/ping.md index 3a5785d9e..0e5abd9d4 100644 --- a/docs/content/operations/ping.md +++ b/docs/content/operations/ping.md @@ -55,8 +55,8 @@ ping: ``` ```bash tab="CLI" ---entryPoints.ping.address=":8082" ---ping.entryPoint="ping" +--entryPoints.ping.address=:8082 +--ping.entryPoint=ping ``` #### `manualRouting` diff --git a/docs/content/providers/consul-catalog.md b/docs/content/providers/consul-catalog.md index c5fb286b0..0b549ae47 100644 --- a/docs/content/providers/consul-catalog.md +++ b/docs/content/providers/consul-catalog.md @@ -1,17 +1,17 @@ # Traefik & Consul Catalog -A Story of Labels, Services & Containers +A Story of Tags, Services & Instances {: .subtitle } ![Consul Catalog](../assets/img/providers/consul.png) -Attach labels to your services and let Traefik do the rest! +Attach tags to your services and let Traefik do the rest! ## Configuration Examples ??? example "Configuring Consul Catalog & Deploying / Exposing Services" - Enabling the consulcatalog provider + Enabling the consul catalog provider ```toml tab="File (TOML)" [providers.consulCatalog] @@ -26,11 +26,10 @@ Attach labels to your services and let Traefik do the rest! --providers.consulcatalog=true ``` - Attaching labels to services + Attaching tags to services ```yaml - labels: - - traefik.http.services.my-service.rule=Host(`mydomain.com`) + - traefik.http.services.my-service.rule=Host(`mydomain.com`) ``` ## Routing Configuration @@ -65,27 +64,27 @@ Defines the polling interval. ### `prefix` -_Optional, Default=/latest_ +_required, Default="traefik"_ ```toml tab="File (TOML)" [providers.consulCatalog] - prefix = "/test" + prefix = "test" # ... ``` ```yaml tab="File (YAML)" providers: consulCatalog: - prefix: /test + prefix: test # ... ``` ```bash tab="CLI" ---providers.consulcatalog.prefix=/test +--providers.consulcatalog.prefix=test # ... ``` -Prefix used for accessing the Consul service metadata. +The prefix for Consul Catalog tags defining traefik labels. ### `requireConsistent` @@ -161,7 +160,7 @@ Use local agent caching for catalog reads. ### `endpoint` -Defines Consul server endpoint. +Defines the Consul server endpoint. #### `address` @@ -504,7 +503,7 @@ providers: ``` Expose Consul Catalog services by default in Traefik. -If set to false, services that don't have a `traefik.enable=true` label will be ignored from the resulting routing configuration. +If set to false, services that don't have a `traefik.enable=true` tag will be ignored from the resulting routing configuration. See also [Restrict the Scope of Service Discovery](./overview.md#restrict-the-scope-of-service-discovery). @@ -532,13 +531,13 @@ providers: The default host rule for all services. -For a given container if no routing rule was defined by a label, it is defined by this defaultRule instead. +For a given service if no routing rule was defined by a tag, it is defined by this defaultRule instead. It must be a valid [Go template](https://golang.org/pkg/text/template/), augmented with the [sprig template functions](http://masterminds.github.io/sprig/). The service name can be accessed as the `Name` identifier, -and the template has access to all the labels defined on this container. +and the template has access to all the labels (i.e. tags beginning with the `prefix`) defined on this service. -This option can be overridden on a container basis with the `traefik.http.routers.Router1.rule` label. +The option can be overridden on an instance basis with the `traefik.http.routers.{name-of-your-choice}.rule` tag. ### `constraints` @@ -546,58 +545,59 @@ _Optional, Default=""_ ```toml tab="File (TOML)" [providers.consulCatalog] - constraints = "Label(`a.label.name`, `foo`)" + constraints = "Tag(`a.tag.name`)" # ... ``` ```yaml tab="File (YAML)" providers: consulCatalog: - constraints: "Label(`a.label.name`, `foo`)" + constraints: "Tag(`a.tag.name`)" # ... ``` ```bash tab="CLI" ---providers.consulcatalog.constraints="Label(`a.label.name`, `foo`)" +--providers.consulcatalog.constraints="Tag(`a.tag.name`)" # ... ``` -Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container. -That is to say, if none of the container's labels match the expression, no route for the container is created. -If the expression is empty, all detected containers are included. +Constraints is an expression that Traefik matches against the service's tags to determine whether to create any route for that service. +That is to say, if none of the service's tags match the expression, no route for that service is created. +If the expression is empty, all detected services are included. -The expression syntax is based on the `Label("key", "value")`, and `LabelRegex("key", "value")` functions, as well as the usual boolean logic, as shown in examples below. +The expression syntax is based on the `Tag("tag")`, and `TagRegex("tag")` functions, +as well as the usual boolean logic, as shown in examples below. ??? example "Constraints Expression Examples" ```toml - # Includes only containers having a label with key `a.label.name` and value `foo` - constraints = "Label(`a.label.name`, `foo`)" + # Includes only services having the tag `a.tag.name=foo` + constraints = "Tag(`a.tag.name=foo`)" ``` ```toml - # Excludes containers having any label with key `a.label.name` and value `foo` - constraints = "!Label(`a.label.name`, `value`)" + # Excludes services having any tag `a.tag.name=foo` + constraints = "!Tag(`a.tag.name=foo`)" ``` ```toml # With logical AND. - constraints = "Label(`a.label.name`, `valueA`) && Label(`another.label.name`, `valueB`)" + constraints = "Tag(`a.tag.name`) && Tag(`another.tag.name`)" ``` ```toml # With logical OR. - constraints = "Label(`a.label.name`, `valueA`) || Label(`another.label.name`, `valueB`)" + constraints = "Tag(`a.tag.name`) || Tag(`another.tag.name`)" ``` ```toml # With logical AND and OR, with precedence set by parentheses. - constraints = "Label(`a.label.name`, `valueA`) && (Label(`another.label.name`, `valueB`) || Label(`yet.another.label.name`, `valueC`))" + constraints = "Tag(`a.tag.name`) && (Tag(`another.tag.name`) || Tag(`yet.another.tag.name`))" ``` ```toml - # Includes only containers having a label with key `a.label.name` and a value matching the `a.+` regular expression. - constraints = "LabelRegex(`a.label.name`, `a.+`)" + # Includes only services having a tag matching the `a\.tag\.t.+` regular expression. + constraints = "TagRegex(`a\.tag\.t.+`)" ``` See also [Restrict the Scope of Service Discovery](./overview.md#restrict-the-scope-of-service-discovery). diff --git a/docs/content/providers/docker.md b/docs/content/providers/docker.md index af39fc68b..5310a78e4 100644 --- a/docs/content/providers/docker.md +++ b/docs/content/providers/docker.md @@ -7,6 +7,9 @@ A Story of Labels & Containers Attach labels to your containers and let Traefik do the rest! +Traefik works with both [Docker (standalone) Engine](https://docs.docker.com/engine/) +and [Docker Swarm Mode](https://docs.docker.com/engine/swarm/). + !!! tip "The Quick Start Uses Docker" If you haven't already, maybe you'd like to go through the [quick start](../getting-started/quick-start.md) that uses the docker provider! @@ -64,7 +67,7 @@ Attach labels to your containers and let Traefik do the rest! ``` ```bash tab="CLI" - --providers.docker.endpoint="tcp://127.0.0.1:2375" + --providers.docker.endpoint=tcp://127.0.0.1:2375 --providers.docker.swarmMode=true ``` @@ -80,15 +83,136 @@ Attach labels to your containers and let Traefik do the rest! - traefik.http.services.my-container-service.loadbalancer.server.port=8080 ``` - !!! important "Labels in Docker Swarm Mode" - While in Swarm Mode, Traefik uses labels found on services, not on individual containers. - - Therefore, if you use a compose file with Swarm Mode, labels should be defined in the `deploy` part of your service. - This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file/#labels-1)). - ## Routing Configuration -See the dedicated section in [routing](../routing/providers/docker.md). +When using Docker as a [provider](https://docs.traefik.io/providers/overview/), +Trafik uses [container labels](https://docs.docker.com/engine/reference/commandline/run/#set-metadata-on-container--l---label---label-file) to retrieve its routing configuration. + +See the list of labels in the dedicated [routing](../routing/providers/docker.md) section. + +### Routing Configuration with Labels + +By default, Traefik watches for [container level labels](https://docs.docker.com/config/labels-custom-metadata/) on a standalone Docker Engine. + +When using Docker Compose, labels are specified by the directive +[`labels`](https://docs.docker.com/compose/compose-file/#labels) from the +["services" objects](https://docs.docker.com/compose/compose-file/#service-configuration-reference). + +!!! tip "Not Only Docker" + Please note that any tool like Nomad, Terraform, Ansible, etc. + that is able to define a Docker container with labels can work + with Traefik & the Docker provider. + +### Port Detection + +Traefik retrieves the private IP and port of containers from the Docker API. + +Ports detection works as follows: + +- If a container [exposes](https://docs.docker.com/engine/reference/builder/#expose) only one port, + then Traefik uses this port for private communication. +- If a container [exposes](https://docs.docker.com/engine/reference/builder/#expose) multiple ports, + or does not expose any port, then you must manually specify which port Traefik should use for communication + by using the label `traefik.http.services..loadbalancer.server.port` + (Read more on this label in the dedicated section in [routing](../routing/providers/docker.md#port)). + +### Docker API Access + +Traefik requires access to the docker socket to get its dynamic configuration. + +You can specify which Docker API Endpoint to use with the directive [`endpoint`](#endpoint). + +!!! warning "Security Note" + + Accessing the Docker API without any restriction is a security concern: + If Traefik is attacked, then the attacker might get access to the underlying host. + {: #security-note } + + As explained in the Docker documentation: ([Docker Daemon Attack Surface page](https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface)): + + !!! quote + [...] only **trusted** users should be allowed to control your Docker daemon [...] + + ??? success "Solutions" + + Expose the Docker socket over TCP, instead of the default Unix socket file. + It allows different implementation levels of the [AAA (Authentication, Authorization, Accounting) concepts](https://en.wikipedia.org/wiki/AAA_(computer_security)), depending on your security assessment: + + - Authentication with Client Certificates as described in ["Protect the Docker daemon socket."](https://docs.docker.com/engine/security/https/) + - Authorize and filter requests to restrict possible actions with [the TecnativaDocker Socket Proxy](https://github.com/Tecnativa/docker-socket-proxy). + - Authorization with the [Docker Authorization Plugin Mechanism](https://docs.docker.com/engine/extend/plugins_authorization/) + - Accounting at networking level, by exposing the socket only inside a Docker private network, only available for Traefik. + - Accounting at container level, by exposing the socket on a another container than Traefik's. + With Swarm mode, it allows scheduling of Traefik on worker nodes, with only the "socket exposer" container on the manager nodes. + - Accounting at kernel level, by enforcing kernel calls with mechanisms like [SELinux](https://en.wikipedia.org/wiki/Security-Enhanced_Linux), to only allows an identified set of actions for Traefik's process (or the "socket exposer" process). + + ??? info "More Resources and Examples" + - ["Paranoid about mounting /var/run/docker.sock?"](https://medium.com/@containeroo/traefik-2-0-paranoid-about-mounting-var-run-docker-sock-22da9cb3e78c) + - [Traefik and Docker: A Discussion with Docker Captain, Bret Fisher](https://blog.containo.us/traefik-and-docker-a-discussion-with-docker-captain-bret-fisher-7f0b9a54ff88) + - [KubeCon EU 2018 Keynote, Running with Scissors, from Liz Rice](https://www.youtube.com/watch?v=ltrV-Qmh3oY) + - [Don't expose the Docker socket (not even to a container)](https://www.lvh.io/posts/dont-expose-the-docker-socket-not-even-to-a-container/) + - [A thread on Stack Overflow about sharing the `/var/run/docker.sock` file](https://news.ycombinator.com/item?id=17983623) + - [To DinD or not to DinD](https://blog.loof.fr/2018/01/to-dind-or-not-do-dind.html) + - [Traefik issue GH-4174 about security with Docker socket](https://github.com/containous/traefik/issues/4174) + - [Inspecting Docker Activity with Socat](https://developers.redhat.com/blog/2015/02/25/inspecting-docker-activity-with-socat/) + - [Letting Traefik run on Worker Nodes](https://blog.mikesir87.io/2018/07/letting-traefik-run-on-worker-nodes/) + - [Docker Socket Proxy from Tecnativa](https://github.com/Tecnativa/docker-socket-proxy) + +## Docker Swarm Mode + +To enable Docker Swarm (instead of standalone Docker) as a configuration provider, +set the [`swarmMode`](#swarmmode) directive to `true`. + +### Routing Configuration with Labels + +While in Swarm Mode, Traefik uses labels found on services, not on individual containers. + +Therefore, if you use a compose file with Swarm Mode, labels should be defined in the +[`deploy`](https://docs.docker.com/compose/compose-file/#labels-1) part of your service. + +This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file)). + +### Port Detection + +Docker Swarm does not provide any [port detection](#port-detection) information to Traefik. + +Therefore you **must** specify the port to use for communication by using the label `traefik.http.services..loadbalancer.server.port` +(Check the reference for this label in the [routing section for Docker](../routing/providers/docker.md#port)). + +### Docker API Access + +Docker Swarm Mode follows the same rules as Docker [API Access](#docker-api-access). + +As the Swarm API is only exposed on the [manager nodes](https://docs.docker.com/engine/swarm/how-swarm-mode-works/nodes/#manager-nodes), you should schedule Traefik on the Swarm manager nodes by default, +by deploying Traefik with a [constraint](https://success.docker.com/article/using-contraints-and-labels-to-control-the-placement-of-containers) on the node's "role": + +```shell tab="With Docker CLI" +docker service create \ + --constraint=node.role==manager \ + #... \ +``` + +```yml tab="With Docker Compose" +version: '3' + +services: + traefik: + # ... + deploy: + placement: + constraints: + - node.role == manager +``` + +!!! tip "Scheduling Traefik on Worker Nodes" + + Following the guidelines given in the previous section ["Docker API Access"](#docker-api-access), + if you expose the Docker API through TCP, then Traefik can be scheduled on any node if the TCP + socket is reachable. + + Please consider the security implications by reading the [Security Note](#security-note). + + A good example can be found on [Bret Fisher's repository](https://github.com/BretFisher/dogvscat/blob/master/stack-proxy-global.yml#L124). ## Provider Configuration @@ -108,51 +232,10 @@ providers: ``` ```bash tab="CLI" ---providers.docker.endpoint="unix:///var/run/docker.sock" +--providers.docker.endpoint=unix:///var/run/docker.sock ``` -Traefik requires access to the docker socket to get its dynamic configuration. - -??? warning "Security Notes" - - Depending on your context, accessing the Docker API without any restriction can be a security concern: If Traefik is attacked, then the attacker might get access to the Docker (or Swarm Mode) backend. - - As explained in the Docker documentation: ([Docker Daemon Attack Surface page](https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface)): - - `[...] only **trusted** users should be allowed to control your Docker daemon [...]` - - !!! tip "Improved Security" - - [TraefikEE](https://containo.us/traefikee) solves this problem by separating the control plane (connected to Docker) and the data plane (handling the requests). - - ??? info "Resources about Docker's Security" - - - [KubeCon EU 2018 Keynote, Running with Scissors, from Liz Rice](https://www.youtube.com/watch?v=ltrV-Qmh3oY) - - [Don't expose the Docker socket (not even to a container)](https://www.lvh.io/posts/dont-expose-the-docker-socket-not-even-to-a-container/) - - [A thread on Stack Overflow about sharing the `/var/run/docker.sock` file](https://news.ycombinator.com/item?id=17983623) - - [To DinD or not to DinD](https://blog.loof.fr/2018/01/to-dind-or-not-do-dind.html) - -??? tip "Security Compensation" - - Expose the Docker socket over TCP, instead of the default Unix socket file. - It allows different implementation levels of the [AAA (Authentication, Authorization, Accounting) concepts](https://en.wikipedia.org/wiki/AAA_(computer_security)), depending on your security assessment: - - - Authentication with Client Certificates as described in ["Protect the Docker daemon socket."](https://docs.docker.com/engine/security/https/) - - Authorization with the [Docker Authorization Plugin Mechanism](https://docs.docker.com/engine/extend/plugins_authorization/) - - Accounting at networking level, by exposing the socket only inside a Docker private network, only available for Traefik. - - Accounting at container level, by exposing the socket on a another container than Traefik's. - With Swarm mode, it allows scheduling of Traefik on worker nodes, with only the "socket exposer" container on the manager nodes. - - Accounting at kernel level, by enforcing kernel calls with mechanisms like [SELinux](https://en.wikipedia.org/wiki/Security-Enhanced_Linux), to only allows an identified set of actions for Traefik's process (or the "socket exposer" process). - - ??? info "Additional Resources" - - - [Traefik issue GH-4174 about security with Docker socket](https://github.com/containous/traefik/issues/4174) - - [Inspecting Docker Activity with Socat](https://developers.redhat.com/blog/2015/02/25/inspecting-docker-activity-with-socat/) - - [Letting Traefik run on Worker Nodes](https://blog.mikesir87.io/2018/07/letting-traefik-run-on-worker-nodes/) - - [Docker Socket Proxy from Tecnativa](https://github.com/Tecnativa/docker-socket-proxy) - -!!! info "Traefik & Swarm Mode" - To let Traefik access the Docker Socket of the Swarm manager, it is mandatory to schedule Traefik on the Swarm manager nodes. +See the sections [Docker API Access](#docker-api-access) and [Docker Swarm API Access](#docker-api-access_1) for more information. ??? example "Using the docker.sock" @@ -186,7 +269,7 @@ Traefik requires access to the docker socket to get its dynamic configuration. ``` ```bash tab="CLI" - --providers.docker.endpoint="unix:///var/run/docker.sock" + --providers.docker.endpoint=unix:///var/run/docker.sock # ... ``` @@ -311,7 +394,7 @@ providers: ``` ```bash tab="CLI" ---providers.docker.defaultRule="Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)" +--providers.docker.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`) # ... ``` @@ -343,7 +426,7 @@ providers: # ... ``` -Activates the Swarm Mode. +Activates the Swarm Mode (instead of standalone Docker). ### `swarmModeRefreshSeconds` @@ -375,19 +458,19 @@ _Optional, Default=""_ ```toml tab="File (TOML)" [providers.docker] - constraints = "Label(`a.label.name`, `foo`)" + constraints = "Label(`a.label.name`,`foo`)" # ... ``` ```yaml tab="File (YAML)" providers: docker: - constraints: "Label(`a.label.name`, `foo`)" + constraints: "Label(`a.label.name`,`foo`)" # ... ``` ```bash tab="CLI" ---providers.docker.constraints="Label(`a.label.name`, `foo`)" +--providers.docker.constraints=Label(`a.label.name`,`foo`) # ... ``` diff --git a/docs/content/providers/kubernetes-crd.md b/docs/content/providers/kubernetes-crd.md index 5be3c8cff..c992d3164 100644 --- a/docs/content/providers/kubernetes-crd.md +++ b/docs/content/providers/kubernetes-crd.md @@ -32,7 +32,7 @@ providers: ``` ```bash tab="CLI" ---providers.kubernetescrd.endpoint="http://localhost:8080" +--providers.kubernetescrd.endpoint=http://localhost:8080 ``` The Kubernetes server endpoint as URL. @@ -66,7 +66,7 @@ providers: ``` ```bash tab="CLI" ---providers.kubernetescrd.token="mytoken" +--providers.kubernetescrd.token=mytoken ``` Bearer token used for the Kubernetes client configuration. @@ -89,7 +89,7 @@ providers: ``` ```bash tab="CLI" ---providers.kubernetescrd.certauthfilepath="/my/ca.crt" +--providers.kubernetescrd.certauthfilepath=/my/ca.crt ``` Path to the certificate authority file. @@ -115,7 +115,7 @@ providers: ``` ```bash tab="CLI" ---providers.kubernetescrd.namespaces="default,production" +--providers.kubernetescrd.namespaces=default,production ``` Array of namespaces to watch. @@ -164,7 +164,7 @@ providers: ``` ```bash tab="CLI" ---providers.kubernetescrd.ingressclass="traefik-internal" +--providers.kubernetescrd.ingressclass=traefik-internal ``` Value of `kubernetes.io/ingress.class` annotation that identifies Ingress objects to be processed. @@ -190,7 +190,7 @@ providers: ``` ```bash tab="CLI" ---providers.kubernetescrd.throttleDuration="10s" +--providers.kubernetescrd.throttleDuration=10s ``` ## Further diff --git a/docs/content/providers/kubernetes-ingress.md b/docs/content/providers/kubernetes-ingress.md index 9bf6f34af..95aeeeda3 100644 --- a/docs/content/providers/kubernetes-ingress.md +++ b/docs/content/providers/kubernetes-ingress.md @@ -67,7 +67,7 @@ providers: ``` ```bash tab="CLI" ---providers.kubernetesingress.endpoint="http://localhost:8080" +--providers.kubernetesingress.endpoint=http://localhost:8080 ``` The Kubernetes server endpoint as URL, which is only used when the behavior based on environment variables described below does not apply. @@ -99,7 +99,7 @@ providers: ``` ```bash tab="CLI" ---providers.kubernetesingress.token="mytoken" +--providers.kubernetesingress.token=mytoken ``` Bearer token used for the Kubernetes client configuration. @@ -122,7 +122,7 @@ providers: ``` ```bash tab="CLI" ---providers.kubernetesingress.certauthfilepath="/my/ca.crt" +--providers.kubernetesingress.certauthfilepath=/my/ca.crt ``` Path to the certificate authority file. @@ -171,7 +171,7 @@ providers: ``` ```bash tab="CLI" ---providers.kubernetesingress.namespaces="default,production" +--providers.kubernetesingress.namespaces=default,production ``` Array of namespaces to watch. @@ -220,7 +220,7 @@ providers: ``` ```bash tab="CLI" ---providers.kubernetesingress.ingressclass="traefik-internal" +--providers.kubernetesingress.ingressclass=traefik-internal ``` Value of `kubernetes.io/ingress.class` annotation that identifies Ingress objects to be processed. @@ -249,7 +249,7 @@ providers: ``` ```bash tab="CLI" ---providers.kubernetesingress.ingressendpoint.hostname="foo.com" +--providers.kubernetesingress.ingressendpoint.hostname=foo.com ``` Hostname used for Kubernetes Ingress endpoints. @@ -273,7 +273,7 @@ providers: ``` ```bash tab="CLI" ---providers.kubernetesingress.ingressendpoint.ip="1.2.3.4" +--providers.kubernetesingress.ingressendpoint.ip=1.2.3.4 ``` IP used for Kubernetes Ingress endpoints. @@ -297,7 +297,7 @@ providers: ``` ```bash tab="CLI" ---providers.kubernetesingress.ingressendpoint.publishedservice="foo-service" +--providers.kubernetesingress.ingressendpoint.publishedservice=foo-service ``` Published Kubernetes Service to copy status from. @@ -320,7 +320,7 @@ providers: ``` ```bash tab="CLI" ---providers.kubernetesingress.throttleDuration="10s" +--providers.kubernetesingress.throttleDuration=10s ``` ## Further diff --git a/docs/content/providers/marathon.md b/docs/content/providers/marathon.md index fe288fbd8..7fcf3ac49 100644 --- a/docs/content/providers/marathon.md +++ b/docs/content/providers/marathon.md @@ -74,8 +74,8 @@ providers: ``` ```bash tab="CLI" ---providers.marathon.basic.httpbasicauthuser="foo" ---providers.marathon.basic.httpbasicpassword="bar" +--providers.marathon.basic.httpbasicauthuser=foo +--providers.marathon.basic.httpbasicpassword=bar ``` Enables Marathon basic authentication. @@ -98,7 +98,7 @@ providers: ``` ```bash tab="CLI" ---providers.marathon.dcosToken="xxxxxx" +--providers.marathon.dcosToken=xxxxxx ``` DCOSToken for DCOS environment. @@ -123,7 +123,7 @@ providers: ``` ```bash tab="CLI" ---providers.marathon.defaultRule="Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)" +--providers.marathon.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`) # ... ``` @@ -182,7 +182,7 @@ providers: ``` ```bash tab="CLI" ---providers.marathon.endpoint="http://10.241.1.71:8080,10.241.1.72:8080,10.241.1.73:8080" +--providers.marathon.endpoint=http://10.241.1.71:8080,10.241.1.72:8080,10.241.1.73:8080 ``` Marathon server endpoint. @@ -223,19 +223,19 @@ _Optional, Default=""_ ```toml tab="File (TOML)" [providers.marathon] - constraints = "Label(`a.label.name`, `foo`)" + constraints = "Label(`a.label.name`,`foo`)" # ... ``` ```yaml tab="File (YAML)" providers: marathon: - constraints: "Label(`a.label.name`, `foo`)" + constraints: "Label(`a.label.name`,`foo`)" # ... ``` ```bash tab="CLI" ---providers.marathon.constraints="Label(`a.label.name`, `foo`)" +--providers.marathon.constraints=Label(`a.label.name`,`foo`) # ... ``` @@ -389,7 +389,7 @@ providers: ``` ```bash tab="CLI" ---providers.marathon.responseHeaderTimeout="66s" +--providers.marathon.responseHeaderTimeout=66s # ... ``` @@ -532,7 +532,7 @@ providers: ``` ```bash tab="CLI" ---providers.marathon.responseHeaderTimeout="10s" +--providers.marathon.responseHeaderTimeout=10s # ... ``` diff --git a/docs/content/providers/rancher.md b/docs/content/providers/rancher.md index 89dc4c6fb..d9d5b4106 100644 --- a/docs/content/providers/rancher.md +++ b/docs/content/providers/rancher.md @@ -104,7 +104,7 @@ providers: ``` ```bash tab="CLI" ---providers.rancher.defaultRule="Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)" +--providers.rancher.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`) # ... ``` @@ -209,7 +209,7 @@ providers: ``` ```bash tab="CLI" ---providers.rancher.prefix="/test" +--providers.rancher.prefix=/test # ... ``` @@ -221,19 +221,19 @@ _Optional, Default=""_ ```toml tab="File (TOML)" [providers.rancher] - constraints = "Label(`a.label.name`, `foo`)" + constraints = "Label(`a.label.name`,`foo`)" # ... ``` ```yaml tab="File (YAML)" providers: rancher: - constraints: "Label(`a.label.name`, `foo`)" + constraints: "Label(`a.label.name`,`foo`)" # ... ``` ```bash tab="CLI" ---providers.rancher.constraints="Label(`a.label.name`, `foo`)" +--providers.rancher.constraints=Label(`a.label.name`,`foo`) # ... ``` diff --git a/docs/content/providers/rancher.txt b/docs/content/providers/rancher.txt index be28f4d99..158826cda 100644 --- a/docs/content/providers/rancher.txt +++ b/docs/content/providers/rancher.txt @@ -17,4 +17,4 @@ --providers.rancher.intervalPoll=false # Prefix used for accessing the Rancher metadata service ---providers.rancher.prefix="/latest" +--providers.rancher.prefix=/latest diff --git a/docs/content/providers/rancher.yml b/docs/content/providers/rancher.yml index bc9d57fff..227b352c3 100644 --- a/docs/content/providers/rancher.yml +++ b/docs/content/providers/rancher.yml @@ -18,4 +18,4 @@ providers: intervalPoll: false # Prefix used for accessing the Rancher metadata service - prefix: "/latest" + prefix: /latest diff --git a/docs/content/routing/entrypoints.md b/docs/content/routing/entrypoints.md index 1f2354acb..12283dcd7 100644 --- a/docs/content/routing/entrypoints.md +++ b/docs/content/routing/entrypoints.md @@ -128,9 +128,9 @@ You can define them using a toml file, CLI arguments, or a key-value store. --entryPoints.name.transport.respondingTimeouts.writeTimeout=42 --entryPoints.name.transport.respondingTimeouts.idleTimeout=42 --entryPoints.name.proxyProtocol.insecure=true - --entryPoints.name.proxyProtocol.trustedIPs="127.0.0.1,192.168.0.1" + --entryPoints.name.proxyProtocol.trustedIPs=127.0.0.1,192.168.0.1 --entryPoints.name.forwardedHeaders.insecure=true - --entryPoints.name.forwardedHeaders.trustedIPs="127.0.0.1,192.168.0.1" + --entryPoints.name.forwardedHeaders.trustedIPs=127.0.0.1,192.168.0.1 ``` ### Forwarded Header diff --git a/docs/content/routing/overview.md b/docs/content/routing/overview.md index c18e1ad3a..8808e0a48 100644 --- a/docs/content/routing/overview.md +++ b/docs/content/routing/overview.md @@ -151,7 +151,7 @@ http: ```bash tab="CLI" # Listen on port 8081 for incoming requests - --entryPoints.web.address=":8081" + --entryPoints.web.address=:8081 # Enable the file provider to define routers / middlewares / services in a file --providers.file.filename=dynamic_conf.toml diff --git a/docs/content/routing/providers/consul-catalog.md b/docs/content/routing/providers/consul-catalog.md index bdbbe44cb..22b4978d6 100644 --- a/docs/content/routing/providers/consul-catalog.md +++ b/docs/content/routing/providers/consul-catalog.md @@ -1,61 +1,61 @@ # Traefik & Consul Catalog -A Story of Labels, Services & Containers +A Story of Tags, Services & Instances {: .subtitle } ![Rancher](../../assets/img/providers/consul.png) -Attach labels to your services and let Traefik do the rest! +Attach tags to your services and let Traefik do the rest! ## Routing Configuration -!!! info "Labels" +!!! info "tags" - - Labels are case insensitive. - - The complete list of labels can be found [the reference page](../../reference/dynamic-configuration/consul-catalog.md) + - tags are case insensitive. + - The complete list of tags can be found [the reference page](../../reference/dynamic-configuration/consul-catalog.md) ### General Traefik creates, for each consul Catalog service, a corresponding [service](../services/index.md) and [router](../routers/index.md). -The Service automatically gets a server per container in this consul Catalog service, and the router gets a default rule attached to it, based on the service name. +The Service automatically gets a server per instance in this consul Catalog service, and the router gets a default rule attached to it, based on the service name. ### Routers -To update the configuration of the Router automatically attached to the container, add labels starting with `traefik.routers.{name-of-your-choice}.` and followed by the option you want to change. +To update the configuration of the Router automatically attached to the service, add tags starting with `traefik.routers.{name-of-your-choice}.` and followed by the option you want to change. -For example, to change the rule, you could add the label ```traefik.http.routers.my-container.rule=Host(`mydomain.com`)```. +For example, to change the rule, you could add the tag ```traefik.http.routers.my-service.rule=Host(`mydomain.com`)```. ??? info "`traefik.http.routers..rule`" - See [rule](../routers/index.md#rule) for more information. + See [rule](../routers/index.md#rule) for more information. ```yaml - - "traefik.http.routers.myrouter.rule=Host(`mydomain.com`)" + traefik.http.routers.myrouter.rule=Host(`mydomain.com`) ``` ??? info "`traefik.http.routers..entrypoints`" - See [entry points](../routers/index.md#entrypoints) for more information. + See [entry points](../routers/index.md#entrypoints) for more information. ```yaml - - "traefik.http.routers.myrouter.entrypoints=web,websecure" + traefik.http.routers.myrouter.entrypoints=web,websecure ``` ??? info "`traefik.http.routers..middlewares`" - See [middlewares](../routers/index.md#middlewares) and [middlewares overview](../../middlewares/overview.md) for more information. + See [middlewares](../routers/index.md#middlewares) and [middlewares overview](../../middlewares/overview.md) for more information. ```yaml - - "traefik.http.routers.myrouter.middlewares=auth,prefix,cb" + traefik.http.routers.myrouter.middlewares=auth,prefix,cb ``` ??? info "`traefik.http.routers..service`" - See [rule](../routers/index.md#service) for more information. + See [rule](../routers/index.md#service) for more information. ```yaml - - "traefik.http.routers.myrouter.service=myservice" + traefik.http.routers.myrouter.service=myservice ``` ??? info "`traefik.http.routers..tls`" @@ -63,7 +63,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers See [tls](../routers/index.md#tls) for more information. ```yaml - - "traefik.http.routers.myrouter>.tls=true" + traefik.http.routers.myrouter>.tls=true ``` ??? info "`traefik.http.routers..tls.certresolver`" @@ -71,7 +71,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers See [certResolver](../routers/index.md#certresolver) for more information. ```yaml - - "traefik.http.routers.myrouter.tls.certresolver=myresolver" + traefik.http.routers.myrouter.tls.certresolver=myresolver ``` ??? info "`traefik.http.routers..tls.domains[n].main`" @@ -79,7 +79,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers See [domains](../routers/index.md#domains) for more information. ```yaml - - "traefik.http.routers.myrouter.tls.domains[0].main=foobar.com" + traefik.http.routers.myrouter.tls.domains[0].main=foobar.com ``` ??? info "`traefik.http.routers..tls.domains[n].sans`" @@ -87,7 +87,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers See [domains](../routers/index.md#domains) for more information. ```yaml - - "traefik.http.routers.myrouter.tls.domains[0].sans=test.foobar.com,dev.foobar.com" + traefik.http.routers.myrouter.tls.domains[0].sans=test.foobar.com,dev.foobar.com ``` ??? info "`traefik.http.routers..tls.options`" @@ -95,31 +95,31 @@ For example, to change the rule, you could add the label ```traefik.http.routers See [options](../routers/index.md#options) for more information. ```yaml - - "traefik.http.routers.myrouter.tls.options=foobar" + traefik.http.routers.myrouter.tls.options=foobar ``` ??? info "`traefik.http.routers..priority`" ```yaml - - "traefik.http.routers.myrouter.priority=42" + traefik.http.routers.myrouter.priority=42 ``` ### Services -To update the configuration of the Service automatically attached to the container, -add labels starting with `traefik.http.services.{name-of-your-choice}.`, followed by the option you want to change. +To update the configuration of the Service automatically attached to the service, +add tags starting with `traefik.http.services.{name-of-your-choice}.`, followed by the option you want to change. For example, to change the `passHostHeader` behavior, -you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.passhostheader=false`. +you'd add the tag `traefik.http.services.{name-of-your-choice}.loadbalancer.passhostheader=false`. ??? info "`traefik.http.services..loadbalancer.server.port`" Registers a port. - Useful when the container exposes multiples ports. + Useful when the service exposes multiples ports. ```yaml - - "traefik.http.services.myservice.loadbalancer.server.port=8080" + traefik.http.services.myservice.loadbalancer.server.port=8080 ``` ??? info "`traefik.http.services..loadbalancer.server.scheme`" @@ -127,14 +127,14 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa Overrides the default scheme. ```yaml - - "traefik.http.services.myservice.loadbalancer.server.scheme=http" + traefik.http.services.myservice.loadbalancer.server.scheme=http ``` ??? info "`traefik.http.services..loadbalancer.passhostheader`" ```yaml - - "traefik.http.services.myservice.loadbalancer.passhostheader=true" + traefik.http.services.myservice.loadbalancer.passhostheader=true ``` ??? info "`traefik.http.services..loadbalancer.healthcheck.headers.`" @@ -142,7 +142,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa See [health check](../services/index.md#health-check) for more information. ```yaml - - "traefik.http.services.myservice.loadbalancer.healthcheck.headers.X-Foo=foobar" + traefik.http.services.myservice.loadbalancer.healthcheck.headers.X-Foo=foobar ``` ??? info "`traefik.http.services..loadbalancer.healthcheck.hostname`" @@ -150,7 +150,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa See [health check](../services/index.md#health-check) for more information. ```yaml - - "traefik.http.services.myservice.loadbalancer.healthcheck.hostname=foobar.com" + traefik.http.services.myservice.loadbalancer.healthcheck.hostname=foobar.com ``` ??? info "`traefik.http.services..loadbalancer.healthcheck.interval`" @@ -158,7 +158,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa See [health check](../services/index.md#health-check) for more information. ```yaml - - "traefik.http.services.myservice.loadbalancer.healthcheck.interval=10" + traefik.http.services.myservice.loadbalancer.healthcheck.interval=10 ``` ??? info "`traefik.http.services..loadbalancer.healthcheck.path`" @@ -166,7 +166,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa See [health check](../services/index.md#health-check) for more information. ```yaml - - "traefik.http.services.myservice.loadbalancer.healthcheck.path=/foo" + traefik.http.services.myservice.loadbalancer.healthcheck.path=/foo ``` ??? info "`traefik.http.services..loadbalancer.healthcheck.port`" @@ -174,7 +174,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa See [health check](../services/index.md#health-check) for more information. ```yaml - - "traefik.http.services.myservice.loadbalancer.healthcheck.port=42" + traefik.http.services.myservice.loadbalancer.healthcheck.port=42 ``` ??? info "`traefik.http.services..loadbalancer.healthcheck.scheme`" @@ -182,7 +182,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa See [health check](../services/index.md#health-check) for more information. ```yaml - - "traefik.http.services.myservice.loadbalancer.healthcheck.scheme=http" + traefik.http.services.myservice.loadbalancer.healthcheck.scheme=http ``` ??? info "`traefik.http.services..loadbalancer.healthcheck.timeout`" @@ -190,7 +190,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa See [health check](../services/index.md#health-check) for more information. ```yaml - - "traefik.http.services.myservice.loadbalancer.healthcheck.timeout=10" + traefik.http.services.myservice.loadbalancer.healthcheck.timeout=10 ``` ??? info "`traefik.http.services..loadbalancer.sticky`" @@ -198,7 +198,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa See [sticky sessions](../services/index.md#sticky-sessions) for more information. ```yaml - - "traefik.http.services.myservice.loadbalancer.sticky=true" + traefik.http.services.myservice.loadbalancer.sticky=true ``` ??? info "`traefik.http.services..loadbalancer.sticky.cookie.httponly`" @@ -206,7 +206,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa See [sticky sessions](../services/index.md#sticky-sessions) for more information. ```yaml - - "traefik.http.services.myservice.loadbalancer.sticky.cookie.httponly=true" + traefik.http.services.myservice.loadbalancer.sticky.cookie.httponly=true ``` ??? info "`traefik.http.services..loadbalancer.sticky.cookie.name`" @@ -214,7 +214,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa See [sticky sessions](../services/index.md#sticky-sessions) for more information. ```yaml - - "traefik.http.services.myservice.loadbalancer.sticky.cookie.name=foobar" + traefik.http.services.myservice.loadbalancer.sticky.cookie.name=foobar ``` ??? info "`traefik.http.services..loadbalancer.sticky.cookie.secure`" @@ -222,7 +222,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa See [sticky sessions](../services/index.md#sticky-sessions) for more information. ```yaml - - "traefik.http.services.myservice.loadbalancer.sticky.cookie.secure=true" + traefik.http.services.myservice.loadbalancer.sticky.cookie.secure=true ``` ??? info "`traefik.http.services..loadbalancer.responseforwarding.flushinterval`" @@ -231,12 +231,12 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa FlushInterval specifies the flush interval to flush to the client while copying the response body. ```yaml - - "traefik.http.services.myservice.loadbalancer.responseforwarding.flushinterval=10" + traefik.http.services.myservice.loadbalancer.responseforwarding.flushinterval=10 ``` ### Middleware -You can declare pieces of middleware using labels starting with `traefik.http.middlewares.{name-of-your-choice}.`, followed by the middleware type/options. +You can declare pieces of middleware using tags starting with `traefik.http.middlewares.{name-of-your-choice}.`, followed by the middleware type/options. For example, to declare a middleware [`redirectscheme`](../../middlewares/redirectscheme.md) named `my-redirect`, you'd write `traefik.http.middlewares.my-redirect.redirectscheme.scheme: https`. @@ -246,11 +246,10 @@ More information about available middlewares in the dedicated [middlewares secti ```yaml # ... - labels: - # Declaring a middleware - - traefik.http.middlewares.my-redirect.redirectscheme.scheme=https - # Referencing a middleware - - traefik.http.routers.my-container.middlewares=my-redirect + # Declaring a middleware + traefik.http.middlewares.my-redirect.redirectscheme.scheme=https + # Referencing a middleware + traefik.http.routers.my-service.middlewares=my-redirect ``` !!! warning "Conflicts in Declaration" @@ -259,24 +258,20 @@ More information about available middlewares in the dedicated [middlewares secti ### TCP -You can declare TCP Routers and/or Services using labels. +You can declare TCP Routers and/or Services using tags. ??? example "Declaring TCP Routers and Services" ```yaml - services: - my-container: - # ... - labels: - - "traefik.tcp.routers.my-router.rule=HostSNI(`my-host.com`)" - - "traefik.tcp.routers.my-router.tls=true" - - "traefik.tcp.services.my-service.loadbalancer.server.port=4123" + traefik.tcp.routers.my-router.rule=HostSNI(`my-host.com`) + traefik.tcp.routers.my-router.tls=true + traefik.tcp.services.my-service.loadbalancer.server.port=4123 ``` !!! warning "TCP and HTTP" If you declare a TCP Router/Service, it will prevent Traefik from automatically creating an HTTP Router/Service (like it does by default if no TCP Router/Service is defined). - You can declare both a TCP Router/Service and an HTTP Router/Service for the same container (but you have to do so manually). + You can declare both a TCP Router/Service and an HTTP Router/Service for the same consul service (but you have to do so manually). #### TCP Routers @@ -285,7 +280,7 @@ You can declare TCP Routers and/or Services using labels. See [entry points](../routers/index.md#entrypoints_1) for more information. ```yaml - - "traefik.tcp.routers.mytcprouter.entrypoints=ep1,ep2" + traefik.tcp.routers.mytcprouter.entrypoints=ep1,ep2 ``` ??? info "`traefik.tcp.routers..rule`" @@ -293,7 +288,7 @@ You can declare TCP Routers and/or Services using labels. See [rule](../routers/index.md#rule_1) for more information. ```yaml - - "traefik.tcp.routers.mytcprouter.rule=HostSNI(`myhost.com`)" + traefik.tcp.routers.mytcprouter.rule=HostSNI(`myhost.com`) ``` ??? info "`traefik.tcp.routers..service`" @@ -301,7 +296,7 @@ You can declare TCP Routers and/or Services using labels. See [service](../routers/index.md#services) for more information. ```yaml - - "traefik.tcp.routers.mytcprouter.service=myservice" + traefik.tcp.routers.mytcprouter.service=myservice ``` ??? info "`traefik.tcp.routers..tls`" @@ -309,7 +304,7 @@ You can declare TCP Routers and/or Services using labels. See [TLS](../routers/index.md#tls_1) for more information. ```yaml - - "traefik.tcp.routers.mytcprouter.tls=true" + traefik.tcp.routers.mytcprouter.tls=true ``` ??? info "`traefik.tcp.routers..tls.certresolver`" @@ -317,7 +312,7 @@ You can declare TCP Routers and/or Services using labels. See [certResolver](../routers/index.md#certresolver_1) for more information. ```yaml - - "traefik.tcp.routers.mytcprouter.tls.certresolver=myresolver" + traefik.tcp.routers.mytcprouter.tls.certresolver=myresolver ``` ??? info "`traefik.tcp.routers..tls.domains[n].main`" @@ -325,7 +320,7 @@ You can declare TCP Routers and/or Services using labels. See [domains](../routers/index.md#domains_1) for more information. ```yaml - - "traefik.tcp.routers.mytcprouter.tls.domains[0].main=foobar.com" + traefik.tcp.routers.mytcprouter.tls.domains[0].main=foobar.com ``` ??? info "`traefik.tcp.routers..tls.domains[n].sans`" @@ -333,7 +328,7 @@ You can declare TCP Routers and/or Services using labels. See [domains](../routers/index.md#domains_1) for more information. ```yaml - - "traefik.tcp.routers.mytcprouter.tls.domains[0].sans=test.foobar.com,dev.foobar.com" + traefik.tcp.routers.mytcprouter.tls.domains[0].sans=test.foobar.com,dev.foobar.com ``` ??? info "`traefik.tcp.routers..tls.options`" @@ -341,7 +336,7 @@ You can declare TCP Routers and/or Services using labels. See [options](../routers/index.md#options_1) for more information. ```yaml - - "traefik.tcp.routers.mytcprouter.tls.options=mysoptions" + traefik.tcp.routers.mytcprouter.tls.options=mysoptions ``` ??? info "`traefik.tcp.routers..tls.passthrough`" @@ -349,7 +344,7 @@ You can declare TCP Routers and/or Services using labels. See [TLS](../routers/index.md#tls_1) for more information. ```yaml - - "traefik.tcp.routers.mytcprouter.tls.passthrough=true" + traefik.tcp.routers.mytcprouter.tls.passthrough=true ``` #### TCP Services @@ -359,7 +354,7 @@ You can declare TCP Routers and/or Services using labels. Registers a port of the application. ```yaml - - "traefik.tcp.services.mytcpservice.loadbalancer.server.port=423" + traefik.tcp.services.mytcpservice.loadbalancer.server.port=423 ``` ??? info "`traefik.tcp.services..loadbalancer.terminationdelay`" @@ -367,7 +362,7 @@ You can declare TCP Routers and/or Services using labels. See [termination delay](../services/index.md#termination-delay) for more information. ```yaml - - "traefik.tcp.services.mytcpservice.loadbalancer.terminationdelay=100" + traefik.tcp.services.mytcpservice.loadbalancer.terminationdelay=100 ``` ### Specific Provider Options @@ -375,10 +370,10 @@ You can declare TCP Routers and/or Services using labels. #### `traefik.enable` ```yaml -- "traefik.enable=true" +traefik.enable=true ``` -You can tell Traefik to consider (or not) the container by setting `traefik.enable` to true or false. +You can tell Traefik to consider (or not) the service by setting `traefik.enable` to true or false. This option overrides the value of `exposedByDefault`. diff --git a/docs/content/routing/providers/docker.md b/docs/content/routing/providers/docker.md index 7af257d6b..a111a3d21 100644 --- a/docs/content/routing/providers/docker.md +++ b/docs/content/routing/providers/docker.md @@ -82,7 +82,7 @@ Attach labels to your containers and let Traefik do the rest! ``` ```bash tab="CLI" - --providers.docker.endpoint="tcp://127.0.0.1:2375" + --providers.docker.endpoint=tcp://127.0.0.1:2375 --providers.docker.swarmMode=true ``` @@ -165,7 +165,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers See [entry points](../routers/index.md#entrypoints) for more information. ```yaml - - "traefik.http.routers.myrouter.entrypoints=web,websecure" + - "traefik.http.routers.myrouter.entrypoints=ep1,ep2" ``` ??? info "`traefik.http.routers..middlewares`" @@ -243,11 +243,12 @@ you'd add the label `traefik.http.services..loadbalancer.pa !!! warning "The character `@` is not authorized in the service name ``." ??? info "`traefik.http.services..loadbalancer.server.port`" - + Registers a port. Useful when the container exposes multiples ports. - Mandatory for Docker Swarm. + Mandatory for Docker Swarm (see the section ["Port Detection with Docker Swarm"](../../providers/docker.md#port-detection_1)). + {: #port } ```yaml - "traefik.http.services.myservice.loadbalancer.server.port=8080" diff --git a/docs/content/routing/providers/marathon.md b/docs/content/routing/providers/marathon.md index f8a50ac5f..b272b42a0 100644 --- a/docs/content/routing/providers/marathon.md +++ b/docs/content/routing/providers/marathon.md @@ -67,7 +67,7 @@ For example, to change the routing rule, you could add the label ```"traefik.htt See [entry points](../routers/index.md#entrypoints) for more information. ```json - "traefik.http.routers.myrouter.entrypoints": "web,websecure" + "traefik.http.routers.myrouter.entrypoints": "ep1,ep2" ``` ??? info "`traefik.http.routers..middlewares`" diff --git a/docs/content/routing/providers/rancher.md b/docs/content/routing/providers/rancher.md index f8255b0ac..c0178a910 100644 --- a/docs/content/routing/providers/rancher.md +++ b/docs/content/routing/providers/rancher.md @@ -72,7 +72,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers See [entry points](../routers/index.md#entrypoints) for more information. ```yaml - - "traefik.http.routers.myrouter.entrypoints=web,websecure" + - "traefik.http.routers.myrouter.entrypoints=ep1,ep2" ``` ??? info "`traefik.http.routers..middlewares`" diff --git a/docs/content/routing/routers/index.md b/docs/content/routing/routers/index.md index 773b71d16..523eb97af 100644 --- a/docs/content/routing/routers/index.md +++ b/docs/content/routing/routers/index.md @@ -78,8 +78,8 @@ In the process, routers may use pieces of [middleware](../../middlewares/overvie ```bash tab="CLI" ## Static configuration - --entryPoints.web.address=":80" - --entryPoints.mysql.address=":3306" + --entryPoints.web.address=:80 + --entryPoints.mysql.address=:3306 ``` ## Configuring HTTP Routers @@ -140,9 +140,9 @@ If you want to limit the router scope to a set of entry points, set the `entryPo ```bash tab="CLI" ## Static configuration - --entrypoints.web.address=":80" - --entrypoints.websecure.address=":443" - --entrypoints.other.address=":9090" + --entrypoints.web.address=:80 + --entrypoints.websecure.address=:443 + --entrypoints.other.address=:9090 ``` ??? example "Listens to Specific EntryPoints" @@ -198,9 +198,9 @@ If you want to limit the router scope to a set of entry points, set the `entryPo ```bash tab="CLI" ## Static configuration - --entrypoints.web.address=":80" - --entrypoints.websecure.address=":443" - --entrypoints.other.address=":9090" + --entrypoints.web.address=:80 + --entrypoints.websecure.address=:443 + --entrypoints.other.address=:9090 ``` ### Rule @@ -700,9 +700,9 @@ If you want to limit the router scope to a set of entry points, set the entry po ```bash tab="CLI" ## Static configuration - --entrypoints.web.address=":80" - --entrypoints.websecure.address=":443" - --entrypoints.other.address=":9090" + --entrypoints.web.address=:80 + --entrypoints.websecure.address=:443 + --entrypoints.other.address=:9090 ``` ??? example "Listens to Specific Entry Points" @@ -764,9 +764,9 @@ If you want to limit the router scope to a set of entry points, set the entry po ```bash tab="CLI" ## Static configuration - --entrypoints.web.address=":80" - --entrypoints.websecure.address=":443" - --entrypoints.other.address=":9090" + --entrypoints.web.address=:80 + --entrypoints.websecure.address=:443 + --entrypoints.other.address=:9090 ``` ### Rule diff --git a/docs/content/user-guides/grpc.md b/docs/content/user-guides/grpc.md index 271dc0468..6c8979dec 100644 --- a/docs/content/user-guides/grpc.md +++ b/docs/content/user-guides/grpc.md @@ -32,7 +32,7 @@ api: {} ``` ```yaml tab="CLI" ---entryPoints.web.address=":80" +--entryPoints.web.address=:80 --providers.file.filename=dynamic_conf.toml --api.insecure=true ``` @@ -153,7 +153,7 @@ api: {} ``` ```yaml tab="CLI" ---entryPoints.websecure.address=":4443" +--entryPoints.websecure.address=:4443 # For secure connection on backend.local --serversTransport.rootCAs=./backend.cert --providers.file.filename=dynamic_conf.toml diff --git a/docs/runtime.txt b/docs/runtime.txt index d70c8f8d8..475ba515c 100644 --- a/docs/runtime.txt +++ b/docs/runtime.txt @@ -1 +1 @@ -3.6 +3.7 diff --git a/go.mod b/go.mod index 48b4f425a..6dbed6ffd 100644 --- a/go.mod +++ b/go.mod @@ -67,7 +67,7 @@ require ( github.com/opencontainers/runc v1.0.0-rc8 // indirect github.com/opentracing/basictracer-go v1.0.0 // indirect github.com/opentracing/opentracing-go v1.1.0 - github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.4 + github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5 github.com/openzipkin/zipkin-go v0.2.1 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/philhofer/fwd v1.0.0 // indirect diff --git a/go.sum b/go.sum index 5f8707798..c3856687b 100644 --- a/go.sum +++ b/go.sum @@ -472,8 +472,8 @@ github.com/opentracing/basictracer-go v1.0.0 h1:YyUAhaEfjoWXclZVJ9sGoNct7j4TVk7l github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.4 h1:bzTJRoOZEN7uI1gq594S5HhMYNSud4FKUEwd4aFbsEI= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.4/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5 h1:ZCnq+JUrvXcDVhX/xRolRBZifmabN1HcS1wrPSvxhrU= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1 h1:noL5/5Uf1HpVl3wNsfkZhIKbSWCVi5jgqkONNx8PXcA= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= diff --git a/integration/consul_catalog_test.go b/integration/consul_catalog_test.go index 173b36aa1..70905356b 100644 --- a/integration/consul_catalog_test.go +++ b/integration/consul_catalog_test.go @@ -15,8 +15,10 @@ import ( type ConsulCatalogSuite struct { BaseSuite - consulClient *api.Client - consulAddress string + consulClient *api.Client + consulAgentClient *api.Client + consulAddress string + consulAgentAddress string } func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) { @@ -32,6 +34,13 @@ func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) { // Wait for consul to elect itself leader err = s.waitToElectConsulLeader() c.Assert(err, checker.IsNil) + + s.consulAgentAddress = "http://" + s.composeProject.Container(c, "consul-agent").NetworkSettings.IPAddress + ":8500" + clientAgent, err := api.NewClient(&api.Config{ + Address: s.consulAgentAddress, + }) + c.Check(err, check.IsNil) + s.consulAgentClient = clientAgent } func (s *ConsulCatalogSuite) waitToElectConsulLeader() error { @@ -53,13 +62,17 @@ func (s *ConsulCatalogSuite) TearDownSuite(c *check.C) { } } -func (s *ConsulCatalogSuite) registerService(id, name, address, port string, tags []string) error { +func (s *ConsulCatalogSuite) registerService(id, name, address, port string, tags []string, onAgent bool) error { iPort, err := strconv.Atoi(port) if err != nil { return err } + client := s.consulClient + if onAgent { + client = s.consulAgentClient + } - return s.consulClient.Agent().ServiceRegister(&api.AgentServiceRegistration{ + return client.Agent().ServiceRegister(&api.AgentServiceRegistration{ ID: id, Name: name, Address: address, @@ -68,16 +81,20 @@ func (s *ConsulCatalogSuite) registerService(id, name, address, port string, tag }) } -func (s *ConsulCatalogSuite) deregisterService(id string) error { - return s.consulClient.Agent().ServiceDeregister(id) +func (s *ConsulCatalogSuite) deregisterService(id string, onAgent bool) error { + client := s.consulClient + if onAgent { + client = s.consulAgentClient + } + return client.Agent().ServiceDeregister(id) } func (s *ConsulCatalogSuite) TestWithNotExposedByDefaultAndDefaultsSettings(c *check.C) { - err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}) + err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}, false) c.Assert(err, checker.IsNil) - err = s.registerService("whoami2", "whoami", s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}) + err = s.registerService("whoami2", "whoami", s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}, false) c.Assert(err, checker.IsNil) - err = s.registerService("whoami3", "whoami", s.composeProject.Container(c, "whoami3").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}) + err = s.registerService("whoami3", "whoami", s.composeProject.Container(c, "whoami3").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}, false) c.Assert(err, checker.IsNil) tempObjects := struct { @@ -102,11 +119,11 @@ func (s *ConsulCatalogSuite) TestWithNotExposedByDefaultAndDefaultsSettings(c *c err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2", "Hostname: whoami3")) c.Assert(err, checker.IsNil) - err = s.deregisterService("whoami1") + err = s.deregisterService("whoami1", false) c.Assert(err, checker.IsNil) - err = s.deregisterService("whoami2") + err = s.deregisterService("whoami2", false) c.Assert(err, checker.IsNil) - err = s.deregisterService("whoami3") + err = s.deregisterService("whoami3", false) c.Assert(err, checker.IsNil) } @@ -118,7 +135,7 @@ func (s *ConsulCatalogSuite) TestByLabels(c *check.C) { "traefik.http.services.service1.loadBalancer.server.url=http://" + s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, } - err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels) + err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels, false) c.Assert(err, checker.IsNil) tempObjects := struct { @@ -139,7 +156,7 @@ func (s *ConsulCatalogSuite) TestByLabels(c *check.C) { err = try.GetRequest("http://127.0.0.1:8000/whoami", 2*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2", "Hostname: whoami3")) c.Assert(err, checker.IsNil) - err = s.deregisterService("whoami1") + err = s.deregisterService("whoami1", false) c.Assert(err, checker.IsNil) } @@ -155,7 +172,7 @@ func (s *ConsulCatalogSuite) TestSimpleConfiguration(c *check.C) { file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects) defer os.Remove(file) - err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}) + err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}, false) c.Assert(err, checker.IsNil) cmd, display := s.traefikCmd(withConfigFile(file)) @@ -171,7 +188,7 @@ func (s *ConsulCatalogSuite) TestSimpleConfiguration(c *check.C) { err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1")) c.Assert(err, checker.IsNil) - err = s.deregisterService("whoami1") + err = s.deregisterService("whoami1", false) c.Assert(err, checker.IsNil) } @@ -187,7 +204,7 @@ func (s *ConsulCatalogSuite) TestRegisterServiceWithoutIP(c *check.C) { file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects) defer os.Remove(file) - err := s.registerService("whoami1", "whoami", "", "80", []string{"traefik.enable=true"}) + err := s.registerService("whoami1", "whoami", "", "80", []string{"traefik.enable=true"}, false) c.Assert(err, checker.IsNil) cmd, display := s.traefikCmd(withConfigFile(file)) @@ -202,7 +219,7 @@ func (s *ConsulCatalogSuite) TestRegisterServiceWithoutIP(c *check.C) { err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("whoami@consulcatalog", "\"http://127.0.0.1:80\": \"UP\"")) c.Assert(err, checker.IsNil) - err = s.deregisterService("whoami1") + err = s.deregisterService("whoami1", false) c.Assert(err, checker.IsNil) } @@ -219,7 +236,7 @@ func (s *ConsulCatalogSuite) TestDefaultConsulService(c *check.C) { file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects) defer os.Remove(file) - err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", nil) + err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", nil, false) c.Assert(err, checker.IsNil) // Start traefik @@ -236,7 +253,7 @@ func (s *ConsulCatalogSuite) TestDefaultConsulService(c *check.C) { err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1")) c.Assert(err, checker.IsNil) - err = s.deregisterService("whoami1") + err = s.deregisterService("whoami1", false) c.Assert(err, checker.IsNil) } @@ -259,7 +276,7 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithTCPLabels(c *check.C) { "traefik.tcp.Services.Super.Loadbalancer.server.port=8080", } - err := s.registerService("whoamitcp", "whoamitcp", s.composeProject.Container(c, "whoamitcp").NetworkSettings.IPAddress, "8080", labels) + err := s.registerService("whoamitcp", "whoamitcp", s.composeProject.Container(c, "whoamitcp").NetworkSettings.IPAddress, "8080", labels, false) c.Assert(err, checker.IsNil) // Start traefik @@ -277,7 +294,7 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithTCPLabels(c *check.C) { c.Assert(who, checker.Contains, "whoamitcp") - err = s.deregisterService("whoamitcp") + err = s.deregisterService("whoamitcp", false) c.Assert(err, checker.IsNil) } @@ -297,14 +314,14 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithLabels(c *check.C) { labels := []string{ "traefik.http.Routers.Super.Rule=Host(`my.super.host`)", } - err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels) + err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels, false) c.Assert(err, checker.IsNil) // Start another container by replacing a '.' by a '-' labels = []string{ "traefik.http.Routers.SuperHost.Rule=Host(`my-super.host`)", } - err = s.registerService("whoami2", "whoami", s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress, "80", labels) + err = s.registerService("whoami2", "whoami", s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress, "80", labels, false) c.Assert(err, checker.IsNil) // Start traefik @@ -328,10 +345,63 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithLabels(c *check.C) { err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami2")) c.Assert(err, checker.IsNil) - err = s.deregisterService("whoami1") + err = s.deregisterService("whoami1", false) c.Assert(err, checker.IsNil) - err = s.deregisterService("whoami2") + err = s.deregisterService("whoami2", false) + c.Assert(err, checker.IsNil) +} + +func (s *ConsulCatalogSuite) TestSameServiceIDOnDifferentConsulAgent(c *check.C) { + tempObjects := struct { + ConsulAddress string + DefaultRule string + }{ + ConsulAddress: s.consulAddress, + DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)", + } + + file := s.adaptFile(c, "fixtures/consul_catalog/default_not_exposed.toml", tempObjects) + defer os.Remove(file) + + // Start a container with some labels + labels := []string{ + "traefik.enable=true", + "traefik.http.Routers.Super.service=whoami", + "traefik.http.Routers.Super.Rule=Host(`my.super.host`)", + } + err := s.registerService("whoami", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels, false) + c.Assert(err, checker.IsNil) + + err = s.registerService("whoami", "whoami", s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress, "80", labels, true) + c.Assert(err, checker.IsNil) + + // Start traefik + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) + err = cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) + c.Assert(err, checker.IsNil) + req.Host = "my.super.host" + + err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2")) + c.Assert(err, checker.IsNil) + + req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/api/rawdata", nil) + c.Assert(err, checker.IsNil) + + err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), + try.BodyContainsOr(s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, + s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress)) + c.Assert(err, checker.IsNil) + + err = s.deregisterService("whoami1", false) + c.Assert(err, checker.IsNil) + + err = s.deregisterService("whoami2", true) c.Assert(err, checker.IsNil) } @@ -351,7 +421,7 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithOneMissingLabels(c *check.C) { labels := []string{ "traefik.random.value=my.super.host", } - err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels) + err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels, false) c.Assert(err, checker.IsNil) // Start traefik diff --git a/integration/fixtures/healthcheck/multiple-routers-one-same-service.toml b/integration/fixtures/healthcheck/multiple-routers-one-same-service.toml new file mode 100644 index 000000000..a2d4d5147 --- /dev/null +++ b/integration/fixtures/healthcheck/multiple-routers-one-same-service.toml @@ -0,0 +1,40 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "DEBUG" + +[entryPoints] + [entryPoints.web1] + address = ":8000" + [entryPoints.web2] + address = ":9000" + +[api] + insecure = true + +[providers.file] + filename = "{{ .SelfFilename }}" + +## dynamic configuration ## + +[http.routers] + [http.routers.router1] + entryPoints = ["web1"] + service = "service1" + rule = "Host(`test.localhost`)" + + [http.routers.router2] + entryPoints = ["web2"] + service = "service1" + rule = "Host(`test.localhost`)" + +[http.services] + [http.services.service1.loadBalancer] + [http.services.service1.loadBalancer.healthcheck] + path = "/health" + interval = "1s" + timeout = "0.9s" + [[http.services.service1.loadBalancer.servers]] + url = "http://{{.Server1}}:80" diff --git a/integration/healthcheck_test.go b/integration/healthcheck_test.go index 999c69a9a..16d17a0ba 100644 --- a/integration/healthcheck_test.go +++ b/integration/healthcheck_test.go @@ -205,3 +205,69 @@ func (s *HealthCheckSuite) TestPortOverload(c *check.C) { err = try.Request(frontendHealthReq, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable)) c.Assert(err, checker.IsNil) } + +// Checks if all the loadbalancers created will correctly update the server status +func (s *HealthCheckSuite) TestMultipleRoutersOnSameService(c *check.C) { + file := s.adaptFile(c, "fixtures/healthcheck/multiple-routers-one-same-service.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/rawdata", 60*time.Second, try.BodyContains("Host(`test.localhost`)")) + c.Assert(err, checker.IsNil) + + // Set whoami health to 200 to be sure to start with the wanted status + client := &http.Client{} + statusOkReq, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBuffer([]byte("200"))) + c.Assert(err, checker.IsNil) + _, err = client.Do(statusOkReq) + c.Assert(err, checker.IsNil) + + // check healthcheck on web1 entrypoint + healthReqWeb1, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/health", nil) + c.Assert(err, checker.IsNil) + healthReqWeb1.Host = "test.localhost" + err = try.Request(healthReqWeb1, 1*time.Second, try.StatusCodeIs(http.StatusOK)) + c.Assert(err, checker.IsNil) + + // check healthcheck on web2 entrypoint + healthReqWeb2, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:9000/health", nil) + c.Assert(err, checker.IsNil) + healthReqWeb2.Host = "test.localhost" + + err = try.Request(healthReqWeb2, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) + c.Assert(err, checker.IsNil) + + // Set 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) + + // Verify no backend service is available due to failing health checks + err = try.Request(healthReqWeb1, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable)) + c.Assert(err, checker.IsNil) + + err = try.Request(healthReqWeb2, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable)) + c.Assert(err, checker.IsNil) + + // Change one whoami health to 200 + statusOKReq1, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBuffer([]byte("200"))) + c.Assert(err, checker.IsNil) + _, err = client.Do(statusOKReq1) + c.Assert(err, checker.IsNil) + + // Verify health check + err = try.Request(healthReqWeb1, 3*time.Second, try.StatusCodeIs(http.StatusOK)) + c.Assert(err, checker.IsNil) + + err = try.Request(healthReqWeb2, 3*time.Second, try.StatusCodeIs(http.StatusOK)) + c.Assert(err, checker.IsNil) +} diff --git a/integration/resources/compose/consul_catalog.yml b/integration/resources/compose/consul_catalog.yml index 9299d95f3..852dfd69c 100644 --- a/integration/resources/compose/consul_catalog.yml +++ b/integration/resources/compose/consul_catalog.yml @@ -1,7 +1,15 @@ consul: - image: consul:1.6.1 + image: consul:1.6.2 ports: - 8500:8500 + command: "agent -server -bootstrap -ui -client 0.0.0.0" +consul-agent: + image: consul:1.6.2 + ports: + - 8501:8500 + command: "agent -retry-join consul -client 0.0.0.0" + links: + - consul whoami1: image: containous/whoami:v1.3.0 hostname: whoami1 diff --git a/pkg/cli/commands.go b/pkg/cli/commands.go index 366abd05d..8151f3c26 100644 --- a/pkg/cli/commands.go +++ b/pkg/cli/commands.go @@ -4,18 +4,20 @@ package cli import ( "fmt" + "io" "os" "path/filepath" ) // Command structure contains program/command information (command name and description). type Command struct { - Name string - Description string - Configuration interface{} - Resources []ResourceLoader - Run func([]string) error - Hidden bool + Name string + Description string + Configuration interface{} + Resources []ResourceLoader + Run func([]string) error + CustomHelpFunc func(io.Writer, *Command) error + Hidden bool // AllowArg if not set, disallows any argument that is not a known command or a sub-command. AllowArg bool subCommands []*Command @@ -35,6 +37,15 @@ func (c *Command) AddCommand(cmd *Command) error { return nil } +// PrintHelp calls the custom help function of the command if it's set. +// Otherwise, it calls the default help function. +func (c *Command) PrintHelp(w io.Writer) error { + if c.CustomHelpFunc != nil { + return c.CustomHelpFunc(w, c) + } + return PrintHelp(w, c) +} + // Execute Executes a command. func Execute(cmd *Command) error { return execute(cmd, os.Args, true) @@ -61,10 +72,12 @@ func execute(cmd *Command, args []string, root bool) error { // Calls command by its name. if len(args) >= 2 && cmd.Name == args[1] { - if err := run(cmd, args[2:]); err != nil { - return fmt.Errorf("command %s error: %v", cmd.Name, err) + if len(args) < 3 || !contains(cmd.subCommands, args[2]) { + if err := run(cmd, args[2:]); err != nil { + return fmt.Errorf("command %s error: %v", cmd.Name, err) + } + return nil } - return nil } // No sub-command, calls the current command. @@ -78,6 +91,9 @@ func execute(cmd *Command, args []string, root bool) error { // Trying to find the sub-command. for _, subCmd := range cmd.subCommands { if len(args) >= 2 && subCmd.Name == args[1] { + return execute(subCmd, args, false) + } + if len(args) >= 3 && subCmd.Name == args[2] { return execute(subCmd, args[1:], false) } } @@ -87,16 +103,16 @@ func execute(cmd *Command, args []string, root bool) error { func run(cmd *Command, args []string) error { if len(args) > 0 && !isFlag(args[0]) && !cmd.AllowArg { - _ = PrintHelp(os.Stdout, cmd) + _ = cmd.PrintHelp(os.Stdout) return fmt.Errorf("command not found: %s", args[0]) } if isHelp(args) { - return PrintHelp(os.Stdout, cmd) + return cmd.PrintHelp(os.Stdout) } if cmd.Run == nil { - _ = PrintHelp(os.Stdout, cmd) + _ = cmd.PrintHelp(os.Stdout) return fmt.Errorf("command %s is not runnable", cmd.Name) } diff --git a/pkg/cli/commands_test.go b/pkg/cli/commands_test.go index a74a1d54a..39fa80a34 100644 --- a/pkg/cli/commands_test.go +++ b/pkg/cli/commands_test.go @@ -1,6 +1,10 @@ package cli import ( + "bytes" + "errors" + "fmt" + "io" "io/ioutil" "os" "strings" @@ -55,6 +59,63 @@ func TestCommand_AddCommand(t *testing.T) { } } +func TestCommand_PrintHelp(t *testing.T) { + testCases := []struct { + desc string + command *Command + expectedOutput string + expectedError error + }{ + { + desc: "print default help", + command: &Command{}, + expectedOutput: " \n\nUsage: [command] [flags] [arguments]\n\nUse \" [command] --help\" for help on any command.\n\n", + }, + { + desc: "print custom help", + command: &Command{ + Name: "root", + Description: "Description for root", + Configuration: &struct { + Foo []struct { + Field string + } + }{}, + Run: func(args []string) error { + return nil + }, + CustomHelpFunc: func(w io.Writer, _ *Command) error { + _, _ = fmt.Fprintln(w, "test") + return nil + }, + }, + expectedOutput: "test\n", + }, + { + desc: "error is returned from called help", + command: &Command{ + CustomHelpFunc: func(_ io.Writer, _ *Command) error { + return errors.New("test") + }, + }, + expectedError: errors.New("test"), + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + buffer := &bytes.Buffer{} + err := test.command.PrintHelp(buffer) + + assert.Equal(t, test.expectedError, err) + assert.Equal(t, test.expectedOutput, buffer.String()) + }) + } +} + func Test_execute(t *testing.T) { var called string @@ -559,6 +620,88 @@ func Test_execute(t *testing.T) { }, expected: expected{result: "root---foo=bar--fii=bir"}, }, + { + desc: "sub command help", + args: []string{"", "test", "subtest", "--help"}, + command: func() *Command { + rootCmd := &Command{ + Name: "test", + Resources: []ResourceLoader{&FlagLoader{}}, + } + + subCmd := &Command{ + Name: "subtest", + Resources: []ResourceLoader{&FlagLoader{}}, + } + + err := rootCmd.AddCommand(subCmd) + require.NoError(t, err) + + subSubCmd := &Command{ + Name: "subsubtest", + Resources: []ResourceLoader{&FlagLoader{}}, + } + + err = subCmd.AddCommand(subSubCmd) + require.NoError(t, err) + + subSubSubCmd := &Command{ + Name: "subsubsubtest", + Resources: []ResourceLoader{&FlagLoader{}}, + Run: func([]string) error { + called = "subsubsubtest" + return nil + }, + } + + err = subSubCmd.AddCommand(subSubSubCmd) + require.NoError(t, err) + + return rootCmd + }, + expected: expected{}, + }, + { + desc: "sub sub command help", + args: []string{"", "test", "subtest", "subsubtest", "--help"}, + command: func() *Command { + rootCmd := &Command{ + Name: "test", + Resources: []ResourceLoader{&FlagLoader{}}, + } + + subCmd := &Command{ + Name: "subtest", + Resources: []ResourceLoader{&FlagLoader{}}, + } + + err := rootCmd.AddCommand(subCmd) + require.NoError(t, err) + + subSubCmd := &Command{ + Name: "subsubtest", + Resources: []ResourceLoader{&FlagLoader{}}, + } + + err = subCmd.AddCommand(subSubCmd) + require.NoError(t, err) + + subSubSubCmd := &Command{ + Name: "subsubsubtest", + Resources: []ResourceLoader{&FlagLoader{}}, + Run: func([]string) error { + called = "subsubsubtest" + return nil + }, + } + + err = subSubCmd.AddCommand(subSubSubCmd) + require.NoError(t, err) + + return rootCmd + }, + expected: expected{}, + }, } for _, test := range testCases { @@ -756,3 +899,43 @@ Flags: `, string(out)) } + +func TestName(t *testing.T) { + rootCmd := &Command{ + Name: "test", + Resources: []ResourceLoader{&FlagLoader{}}, + } + + subCmd := &Command{ + Name: "subtest", + Resources: []ResourceLoader{&FlagLoader{}}, + } + + err := rootCmd.AddCommand(subCmd) + require.NoError(t, err) + + subSubCmd := &Command{ + Name: "subsubtest", + Resources: []ResourceLoader{&FlagLoader{}}, + Run: func([]string) error { + return nil + }, + } + + err = subCmd.AddCommand(subSubCmd) + require.NoError(t, err) + + subSubSubCmd := &Command{ + Name: "subsubsubtest", + Resources: []ResourceLoader{&FlagLoader{}}, + Run: func([]string) error { + return nil + }, + } + + err = subSubCmd.AddCommand(subSubSubCmd) + require.NoError(t, err) + + err = execute(rootCmd, []string{"", "test", "subtest", "subsubtest", "subsubsubtest", "--help"}, true) + require.NoError(t, err) +} diff --git a/pkg/cli/loader_file.go b/pkg/cli/loader_file.go index eb0b3f78e..d6ad58b7b 100644 --- a/pkg/cli/loader_file.go +++ b/pkg/cli/loader_file.go @@ -25,7 +25,7 @@ func (f *FileLoader) GetFilename() string { func (f *FileLoader) Load(args []string, cmd *Command) (bool, error) { ref, err := flag.Parse(args, cmd.Configuration) if err != nil { - _ = PrintHelp(os.Stdout, cmd) + _ = cmd.PrintHelp(os.Stdout) return false, err } diff --git a/pkg/healthcheck/healthcheck.go b/pkg/healthcheck/healthcheck.go index c1385c399..95972474d 100644 --- a/pkg/healthcheck/healthcheck.go +++ b/pkg/healthcheck/healthcheck.go @@ -25,14 +25,20 @@ const ( var singleton *HealthCheck var once sync.Once -// BalancerHandler includes functionality for load-balancing management. -type BalancerHandler interface { - ServeHTTP(w http.ResponseWriter, req *http.Request) +// Balancer is the set of operations required to manage the list of servers in a +// load-balancer. +type Balancer interface { Servers() []*url.URL RemoveServer(u *url.URL) error UpsertServer(u *url.URL, options ...roundrobin.ServerOption) error } +// BalancerHandler includes functionality for load-balancing management. +type BalancerHandler interface { + ServeHTTP(w http.ResponseWriter, req *http.Request) + Balancer +} + // metricsRegistry is a local interface in the health check package, exposing only the required metrics // necessary for the health check package. This makes it easier for the tests. type metricsRegistry interface { @@ -49,7 +55,7 @@ type Options struct { Transport http.RoundTripper Interval time.Duration Timeout time.Duration - LB BalancerHandler + LB Balancer } func (opt Options) String() string { @@ -146,18 +152,18 @@ func (hc *HealthCheck) checkBackend(ctx context.Context, backend *BackendConfig) enabledURLs := backend.LB.Servers() var newDisabledURLs []backendURL // FIXME re enable metrics - for _, disableURL := range backend.disabledURLs { + for _, disabledURL := range backend.disabledURLs { // FIXME serverUpMetricValue := float64(0) - if err := checkHealth(disableURL.url, backend); err == nil { + if err := checkHealth(disabledURL.url, backend); err == nil { logger.Warnf("Health check up: Returning to server list. Backend: %q URL: %q Weight: %d", - backend.name, disableURL.url.String(), disableURL.weight) - if err = backend.LB.UpsertServer(disableURL.url, roundrobin.Weight(disableURL.weight)); err != nil { + backend.name, disabledURL.url.String(), disabledURL.weight) + if err = backend.LB.UpsertServer(disabledURL.url, roundrobin.Weight(disabledURL.weight)); err != nil { logger.Error(err) } // FIXME serverUpMetricValue = 1 } else { - logger.Warnf("Health check still failing. Backend: %q URL: %q Reason: %s", backend.name, disableURL.url.String(), err) - newDisabledURLs = append(newDisabledURLs, disableURL) + logger.Warnf("Health check still failing. Backend: %q URL: %q Reason: %s", backend.name, disabledURL.url.String(), err) + newDisabledURLs = append(newDisabledURLs, disabledURL) } // FIXME labelValues := []string{"backend", backend.name, "url", backendurl.url.String()} // FIXME hc.metrics.BackendServerUpGauge().With(labelValues...).Set(serverUpMetricValue) @@ -177,7 +183,7 @@ func (hc *HealthCheck) checkBackend(ctx context.Context, backend *BackendConfig) weight = 1 } } - logger.Warnf("Health check failed: Remove from server list. Backend: %q URL: %q Weight: %d Reason: %s", backend.name, enableURL.String(), weight, err) + logger.Warnf("Health check failed, removing from server list. Backend: %q URL: %q Weight: %d Reason: %s", backend.name, enableURL.String(), weight, err) if err := backend.LB.RemoveServer(enableURL); err != nil { logger.Error(err) } @@ -281,3 +287,38 @@ func (lb *LbStatusUpdater) UpsertServer(u *url.URL, options ...roundrobin.Server } return err } + +// Balancers is a list of Balancers(s) that implements the Balancer interface. +type Balancers []Balancer + +// Servers returns the servers url from all the BalancerHandler +func (b Balancers) Servers() []*url.URL { + var servers []*url.URL + for _, lb := range b { + servers = append(servers, lb.Servers()...) + } + + return servers +} + +// RemoveServer removes the given server from all the BalancerHandler, +// and updates the status of the server to "DOWN". +func (b Balancers) RemoveServer(u *url.URL) error { + for _, lb := range b { + if err := lb.RemoveServer(u); err != nil { + return err + } + } + return nil +} + +// UpsertServer adds the given server to all the BalancerHandler, +// and updates the status of the server to "UP". +func (b Balancers) UpsertServer(u *url.URL, options ...roundrobin.ServerOption) error { + for _, lb := range b { + if err := lb.UpsertServer(u, options...); err != nil { + return err + } + } + return nil +} diff --git a/pkg/middlewares/accesslog/logdata.go b/pkg/middlewares/accesslog/logdata.go index c21e1c2f3..ae39ce9d6 100644 --- a/pkg/middlewares/accesslog/logdata.go +++ b/pkg/middlewares/accesslog/logdata.go @@ -116,7 +116,18 @@ type CoreLogData map[string]interface{} // LogData is the data captured by the middleware so that it can be logged. type LogData struct { Core CoreLogData - Request http.Header + Request request OriginResponse http.Header - DownstreamResponse http.Header + DownstreamResponse downstreamResponse +} + +type downstreamResponse struct { + headers http.Header + status int + size int64 +} + +type request struct { + headers http.Header + count int64 } diff --git a/pkg/middlewares/accesslog/logger.go b/pkg/middlewares/accesslog/logger.go index 53bebac6e..5f9a3c9f9 100644 --- a/pkg/middlewares/accesslog/logger.go +++ b/pkg/middlewares/accesslog/logger.go @@ -47,8 +47,6 @@ func (n noopCloser) Close() error { type handlerParams struct { logDataTable *LogData - crr *captureRequestReader - crw *captureResponseWriter } // Handler will write each request and its response to the access log. @@ -122,7 +120,7 @@ func NewHandler(config *types.AccessLog) (*Handler, error) { go func() { defer logHandler.wg.Done() for handlerParams := range logHandler.logHandlerChan { - logHandler.logTheRoundTrip(handlerParams.logDataTable, handlerParams.crr, handlerParams.crw) + logHandler.logTheRoundTrip(handlerParams.logDataTable) } }() } @@ -162,7 +160,12 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http StartLocal: now.Local(), } - logDataTable := &LogData{Core: core, Request: req.Header} + logDataTable := &LogData{ + Core: core, + Request: request{ + headers: req.Header, + }, + } reqWithDataTable := req.WithContext(context.WithValue(req.Context(), DataTableKey, logDataTable)) @@ -205,16 +208,21 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http core[ClientUsername] = usernameIfPresent(reqWithDataTable.URL) } - logDataTable.DownstreamResponse = crw.Header() + logDataTable.DownstreamResponse = downstreamResponse{ + headers: crw.Header().Clone(), + status: crw.Status(), + size: crw.Size(), + } + if crr != nil { + logDataTable.Request.count = crr.count + } if h.config.BufferingSize > 0 { h.logHandlerChan <- handlerParams{ logDataTable: logDataTable, - crr: crr, - crw: crw, } } else { - h.logTheRoundTrip(logDataTable, crr, crw) + h.logTheRoundTrip(logDataTable) } } @@ -264,7 +272,7 @@ func usernameIfPresent(theURL *url.URL) string { } // Logging handler to log frontend name, backend name, and elapsed time. -func (h *Handler) logTheRoundTrip(logDataTable *LogData, crr *captureRequestReader, crw *captureResponseWriter) { +func (h *Handler) logTheRoundTrip(logDataTable *LogData) { core := logDataTable.Core retryAttempts, ok := core[RetryAttempts].(int) @@ -272,23 +280,22 @@ func (h *Handler) logTheRoundTrip(logDataTable *LogData, crr *captureRequestRead retryAttempts = 0 } core[RetryAttempts] = retryAttempts + core[RequestContentSize] = logDataTable.Request.count - if crr != nil { - core[RequestContentSize] = crr.count - } - - core[DownstreamStatus] = crw.Status() + status := logDataTable.DownstreamResponse.status + core[DownstreamStatus] = status // n.b. take care to perform time arithmetic using UTC to avoid errors at DST boundaries. totalDuration := time.Now().UTC().Sub(core[StartUTC].(time.Time)) core[Duration] = totalDuration - if h.keepAccessLog(crw.Status(), retryAttempts, totalDuration) { - core[DownstreamContentSize] = crw.Size() + if h.keepAccessLog(status, retryAttempts, totalDuration) { + size := logDataTable.DownstreamResponse.size + core[DownstreamContentSize] = size if original, ok := core[OriginContentSize]; ok { o64 := original.(int64) - if crw.Size() != o64 && crw.Size() != 0 { - core[GzipRatio] = float64(o64) / float64(crw.Size()) + if size != o64 && size != 0 { + core[GzipRatio] = float64(o64) / float64(size) } } @@ -305,9 +312,9 @@ func (h *Handler) logTheRoundTrip(logDataTable *LogData, crr *captureRequestRead } } - h.redactHeaders(logDataTable.Request, fields, "request_") + h.redactHeaders(logDataTable.Request.headers, fields, "request_") h.redactHeaders(logDataTable.OriginResponse, fields, "origin_") - h.redactHeaders(logDataTable.DownstreamResponse, fields, "downstream_") + h.redactHeaders(logDataTable.DownstreamResponse.headers, fields, "downstream_") h.mu.Lock() defer h.mu.Unlock() diff --git a/pkg/middlewares/accesslog/logger_test.go b/pkg/middlewares/accesslog/logger_test.go index b93d8fdcd..ad81c1c0b 100644 --- a/pkg/middlewares/accesslog/logger_test.go +++ b/pkg/middlewares/accesslog/logger_test.go @@ -192,6 +192,7 @@ func TestLoggerJSON(t *testing.T) { Format: JSONFormat, }, expected: map[string]func(t *testing.T, value interface{}){ + RequestContentSize: assertFloat64(0), RequestHost: assertString(testHostname), RequestAddr: assertString(testHostname), RequestMethod: assertString(testMethod), diff --git a/pkg/middlewares/headers/headers.go b/pkg/middlewares/headers/headers.go index 149b38219..0fce61796 100644 --- a/pkg/middlewares/headers/headers.go +++ b/pkg/middlewares/headers/headers.go @@ -221,13 +221,11 @@ func (s *Header) processCorsHeaders(rw http.ResponseWriter, req *http.Request) b } reqAcMethod := req.Header.Get("Access-Control-Request-Method") - reqAcHeaders := req.Header.Get("Access-Control-Request-Headers") originHeader := req.Header.Get("Origin") - if reqAcMethod != "" && reqAcHeaders != "" && originHeader != "" && req.Method == http.MethodOptions { + if reqAcMethod != "" && originHeader != "" && req.Method == http.MethodOptions { // If the request is an OPTIONS request with an Access-Control-Request-Method header, - // and Access-Control-Request-Headers headers, and Origin headers, - // then it is a CORS preflight request, + // and Origin headers, then it is a CORS preflight request, // and we need to build a custom response: https://www.w3.org/TR/cors/#preflight-request if s.headers.AccessControlAllowCredentials { rw.Header().Set("Access-Control-Allow-Credentials", "true") diff --git a/pkg/middlewares/headers/headers_test.go b/pkg/middlewares/headers/headers_test.go index 21df2deff..e335399dc 100644 --- a/pkg/middlewares/headers/headers_test.go +++ b/pkg/middlewares/headers/headers_test.go @@ -275,6 +275,25 @@ func TestCORSPreflights(t *testing.T) { "Access-Control-Allow-Headers": {"origin,X-Forwarded-For"}, }, }, + { + desc: "No Request Headers Preflight", + header: NewHeader(emptyHandler, dynamic.Headers{ + AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"}, + AccessControlAllowOrigin: "*", + AccessControlAllowHeaders: []string{"origin", "X-Forwarded-For"}, + AccessControlMaxAge: 600, + }), + requestHeaders: map[string][]string{ + "Access-Control-Request-Method": {"GET", "OPTIONS"}, + "Origin": {"https://foo.bar.org"}, + }, + expected: map[string][]string{ + "Access-Control-Allow-Origin": {"*"}, + "Access-Control-Max-Age": {"600"}, + "Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"}, + "Access-Control-Allow-Headers": {"origin,X-Forwarded-For"}, + }, + }, } for _, test := range testCases { diff --git a/pkg/middlewares/tracing/entrypoint.go b/pkg/middlewares/tracing/entrypoint.go index 195cc994f..007a9c6e3 100644 --- a/pkg/middlewares/tracing/entrypoint.go +++ b/pkg/middlewares/tracing/entrypoint.go @@ -34,7 +34,11 @@ type entryPointMiddleware struct { } func (e *entryPointMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - spanCtx, _ := e.Extract(opentracing.HTTPHeaders, tracing.HTTPHeadersCarrier(req.Header)) + spanCtx, err := e.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header)) + if err != nil { + log.FromContext(middlewares.GetLoggerCtx(req.Context(), "tracing", entryPointTypeName)). + Debugf("Failed to extract the context: %v", err) + } span, req, finish := e.StartSpanf(req, ext.SpanKindRPCServerEnum, "EntryPoint", []string{e.entryPoint, req.Host}, " ", ext.RPCServerOption(spanCtx)) defer finish() diff --git a/pkg/provider/constraints/constraints.go b/pkg/provider/constraints/constraints_labels.go similarity index 70% rename from pkg/provider/constraints/constraints.go rename to pkg/provider/constraints/constraints_labels.go index 5094442b3..cbaa5086b 100644 --- a/pkg/provider/constraints/constraints.go +++ b/pkg/provider/constraints/constraints_labels.go @@ -12,23 +12,23 @@ import ( // It is used in order to create a specific and unique pattern for these labels. const MarathonConstraintPrefix = "Traefik-Marathon-505F9E15-BDC7-45E7-828D-C06C7BAB8091" -type constraintFunc func(map[string]string) bool +type constraintLabelFunc func(map[string]string) bool -// Match reports whether the expression matches with the given labels. +// MatchLabels reports whether the expression matches with the given labels. // The expression must match any logical boolean combination of: // - `Label(labelName, labelValue)` // - `LabelRegex(labelName, regexValue)` // - `MarathonConstraint(field:operator:value)` -func Match(labels map[string]string, expr string) (bool, error) { +func MatchLabels(labels map[string]string, expr string) (bool, error) { if expr == "" { return true, nil } p, err := predicate.NewParser(predicate.Def{ Operators: predicate.Operators{ - AND: andFunc, - NOT: notFunc, - OR: orFunc, + AND: andLabelFunc, + NOT: notLabelFunc, + OR: orLabelFunc, }, Functions: map[string]interface{}{ "Label": labelFn, @@ -45,20 +45,20 @@ func Match(labels map[string]string, expr string) (bool, error) { return false, err } - fn, ok := parse.(constraintFunc) + fn, ok := parse.(constraintLabelFunc) if !ok { - return false, errors.New("not a constraintFunc") + return false, errors.New("not a constraintLabelFunc") } return fn(labels), nil } -func labelFn(name, value string) constraintFunc { +func labelFn(name, value string) constraintLabelFunc { return func(labels map[string]string) bool { return labels[name] == value } } -func labelRegexFn(name, expr string) constraintFunc { +func labelRegexFn(name, expr string) constraintLabelFunc { return func(labels map[string]string) bool { matched, err := regexp.MatchString(expr, labels[name]) if err != nil { @@ -68,7 +68,7 @@ func labelRegexFn(name, expr string) constraintFunc { } } -func marathonFn(value string) constraintFunc { +func marathonFn(value string) constraintLabelFunc { return func(labels map[string]string) bool { for k, v := range labels { if strings.HasPrefix(k, MarathonConstraintPrefix) { @@ -81,19 +81,19 @@ func marathonFn(value string) constraintFunc { } } -func andFunc(a, b constraintFunc) constraintFunc { +func andLabelFunc(a, b constraintLabelFunc) constraintLabelFunc { return func(labels map[string]string) bool { return a(labels) && b(labels) } } -func orFunc(a, b constraintFunc) constraintFunc { +func orLabelFunc(a, b constraintLabelFunc) constraintLabelFunc { return func(labels map[string]string) bool { return a(labels) || b(labels) } } -func notFunc(a constraintFunc) constraintFunc { +func notLabelFunc(a constraintLabelFunc) constraintLabelFunc { return func(labels map[string]string) bool { return !a(labels) } diff --git a/pkg/provider/constraints/constraints_test.go b/pkg/provider/constraints/constraints_labels_test.go similarity index 97% rename from pkg/provider/constraints/constraints_test.go rename to pkg/provider/constraints/constraints_labels_test.go index c6198a329..43fb9e0c6 100644 --- a/pkg/provider/constraints/constraints_test.go +++ b/pkg/provider/constraints/constraints_labels_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestMatch(t *testing.T) { +func TestMatchLabels(t *testing.T) { testCases := []struct { expr string labels map[string]string @@ -192,7 +192,7 @@ func TestMatch(t *testing.T) { t.Run(test.expr, func(t *testing.T) { t.Parallel() - matches, err := Match(test.labels, test.expr) + matches, err := MatchLabels(test.labels, test.expr) if test.expectedErr { require.Error(t, err) } else { diff --git a/pkg/provider/constraints/constraints_tags.go b/pkg/provider/constraints/constraints_tags.go new file mode 100644 index 000000000..2a2fa3ffa --- /dev/null +++ b/pkg/provider/constraints/constraints_tags.go @@ -0,0 +1,92 @@ +package constraints + +import ( + "errors" + "regexp" + + "github.com/vulcand/predicate" +) + +type constraintTagFunc func([]string) bool + +// MatchTags reports whether the expression matches with the given tags. +// The expression must match any logical boolean combination of: +// - `Tag(tagValue)` +// - `TagRegex(regexValue)` +func MatchTags(tags []string, expr string) (bool, error) { + if expr == "" { + return true, nil + } + + p, err := predicate.NewParser(predicate.Def{ + Operators: predicate.Operators{ + AND: andTagFunc, + NOT: notTagFunc, + OR: orTagFunc, + }, + Functions: map[string]interface{}{ + "Tag": tagFn, + "TagRegex": tagRegexFn, + }, + }) + if err != nil { + return false, err + } + + parse, err := p.Parse(expr) + if err != nil { + return false, err + } + + fn, ok := parse.(constraintTagFunc) + if !ok { + return false, errors.New("not a constraintTagFunc") + } + return fn(tags), nil +} + +func tagFn(name string) constraintTagFunc { + return func(tags []string) bool { + for _, tag := range tags { + if tag == name { + return true + } + } + return false + } +} + +func tagRegexFn(expr string) constraintTagFunc { + return func(tags []string) bool { + exp, err := regexp.Compile(expr) + if err != nil { + return false + } + + for _, tag := range tags { + if exp.MatchString(tag) { + return true + } + } + + return false + } +} + +func andTagFunc(a, b constraintTagFunc) constraintTagFunc { + return func(tags []string) bool { + return a(tags) && b(tags) + } +} + +func orTagFunc(a, b constraintTagFunc) constraintTagFunc { + return func(tags []string) bool { + return a(tags) || b(tags) + } +} + +func notTagFunc(a constraintTagFunc) constraintTagFunc { + return func(tags []string) bool { + return !a(tags) + } +} diff --git a/pkg/provider/constraints/constraints_tags_test.go b/pkg/provider/constraints/constraints_tags_test.go new file mode 100644 index 000000000..a563b0b30 --- /dev/null +++ b/pkg/provider/constraints/constraints_tags_test.go @@ -0,0 +1,111 @@ +package constraints + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMatchTags(t *testing.T) { + testCases := []struct { + expr string + tags []string + expected bool + expectedErr bool + }{ + { + expr: `Tag("world")`, + tags: []string{"hello", "world"}, + expected: true, + }, + { + expr: `Tag("worlds")`, + tags: []string{"hello", "world"}, + expected: false, + }, + { + expr: `!Tag("world")`, + tags: []string{"hello", "world"}, + expected: false, + }, + { + expr: `Tag("hello") && Tag("world")`, + tags: []string{"hello", "world"}, + expected: true, + }, + { + expr: `Tag("hello") && Tag("worlds")`, + tags: []string{"hello", "world"}, + expected: false, + }, + { + expr: `Tag("hello") && !Tag("world")`, + tags: []string{"hello", "world"}, + expected: false, + }, + { + expr: `Tag("hello") || Tag( "world")`, + tags: []string{"hello", "world"}, + expected: true, + }, + { + expr: `Tag( "worlds") || Tag("hello")`, + tags: []string{"hello", "world"}, + expected: true, + }, + { + expr: `Tag("hello") || !Tag("world")`, + tags: []string{"hello", "world"}, + expected: true, + }, + { + expr: `Tag()`, + tags: []string{"hello", "world"}, + expectedErr: true, + }, + { + expr: `Foo("hello")`, + tags: []string{"hello", "world"}, + expectedErr: true, + }, + { + expr: `Tag("hello")`, + expected: false, + }, + { + expr: ``, + expected: true, + }, + { + expr: `TagRegex("hel\\w+")`, + tags: []string{"hello", "world"}, + expected: true, + }, + { + expr: `TagRegex("hell\\w+s")`, + tags: []string{"hello", "world"}, + expected: false, + }, + { + expr: `!TagRegex("hel\\w+")`, + tags: []string{"hello", "world"}, + expected: false, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.expr, func(t *testing.T) { + t.Parallel() + + matches, err := MatchTags(test.tags, test.expr) + if test.expectedErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + assert.Equal(t, test.expected, matches) + }) + } +} diff --git a/pkg/provider/consulcatalog/config.go b/pkg/provider/consulcatalog/config.go index 61a360490..1d0407eea 100644 --- a/pkg/provider/consulcatalog/config.go +++ b/pkg/provider/consulcatalog/config.go @@ -18,7 +18,7 @@ func (p *Provider) buildConfiguration(ctx context.Context, items []itemData) *dy configurations := make(map[string]*dynamic.Configuration) for _, item := range items { - svcName := item.Name + "-" + item.ID + svcName := item.Node + "-" + item.Name + "-" + item.ID ctxSvc := log.With(ctx, log.Str("serviceName", svcName)) if !p.keepContainer(ctxSvc, item) { @@ -80,7 +80,7 @@ func (p *Provider) keepContainer(ctx context.Context, item itemData) bool { return false } - matches, err := constraints.Match(item.Labels, p.Constraints) + matches, err := constraints.MatchTags(item.Tags, p.Constraints) if err != nil { logger.Errorf("Error matching constraints expression: %v", err) return false diff --git a/pkg/provider/consulcatalog/config_test.go b/pkg/provider/consulcatalog/config_test.go index a794bd90e..d4259aa58 100644 --- a/pkg/provider/consulcatalog/config_test.go +++ b/pkg/provider/consulcatalog/config_test.go @@ -2,6 +2,7 @@ package consulcatalog import ( "context" + "fmt" "testing" "github.com/containous/traefik/v2/pkg/config/dynamic" @@ -25,6 +26,7 @@ func TestDefaultRule(t *testing.T) { items: []itemData{ { ID: "id", + Node: "Node1", Name: "Test", Address: "127.0.0.1", Port: "80", @@ -66,6 +68,7 @@ func TestDefaultRule(t *testing.T) { items: []itemData{ { ID: "id", + Node: "Node1", Name: "Test", Address: "127.0.0.1", Port: "80", @@ -109,6 +112,7 @@ func TestDefaultRule(t *testing.T) { items: []itemData{ { ID: "Test", + Node: "Node1", Name: "Test", Labels: map[string]string{}, Address: "127.0.0.1", @@ -145,6 +149,7 @@ func TestDefaultRule(t *testing.T) { items: []itemData{ { ID: "Test", + Node: "Node1", Name: "Test", Labels: map[string]string{}, Address: "127.0.0.1", @@ -181,6 +186,7 @@ func TestDefaultRule(t *testing.T) { items: []itemData{ { ID: "Test", + Node: "Node1", Name: "Test", Labels: map[string]string{}, Address: "127.0.0.1", @@ -257,6 +263,7 @@ func Test_buildConfiguration(t *testing.T) { items: []itemData{ { ID: "Test", + Node: "Node1", Name: "Test", Labels: map[string]string{}, Address: "127.0.0.1", @@ -297,6 +304,7 @@ func Test_buildConfiguration(t *testing.T) { items: []itemData{ { ID: "Test", + Node: "Node1", Name: "Test", Labels: map[string]string{}, Address: "127.0.0.1", @@ -305,6 +313,7 @@ func Test_buildConfiguration(t *testing.T) { }, { ID: "Test2", + Node: "Node1", Name: "Test2", Labels: map[string]string{}, Address: "127.0.0.2", @@ -359,6 +368,7 @@ func Test_buildConfiguration(t *testing.T) { items: []itemData{ { ID: "1", + Node: "Node1", Name: "Test", Labels: map[string]string{}, Address: "127.0.0.1", @@ -367,6 +377,110 @@ func Test_buildConfiguration(t *testing.T) { }, { ID: "2", + Node: "Node1", + Name: "Test", + Labels: map[string]string{}, + Address: "127.0.0.2", + Port: "80", + Status: api.HealthPassing, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:80", + }, + { + URL: "http://127.0.0.2:80", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + }, + }, + }, + { + desc: "two containers with same service name & id no label on same node", + items: []itemData{ + { + ID: "1", + Node: "Node1", + Name: "Test", + Labels: map[string]string{}, + Address: "127.0.0.1", + Port: "80", + Status: api.HealthPassing, + }, + { + ID: "1", + Node: "Node1", + Name: "Test", + Labels: map[string]string{}, + Address: "127.0.0.2", + Port: "80", + Status: api.HealthPassing, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.2:80", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + }, + }, + }, + { + desc: "two containers with same service name & id no label on different nodes", + items: []itemData{ + { + ID: "1", + Node: "Node1", + Name: "Test", + Labels: map[string]string{}, + Address: "127.0.0.1", + Port: "80", + Status: api.HealthPassing, + }, + { + ID: "1", + Node: "Node2", Name: "Test", Labels: map[string]string{}, Address: "127.0.0.2", @@ -1320,6 +1434,7 @@ func Test_buildConfiguration(t *testing.T) { items: []itemData{ { ID: "Test", + Node: "Node1", Name: "Test", Labels: map[string]string{}, Address: "127.0.0.2", @@ -1393,6 +1508,7 @@ func Test_buildConfiguration(t *testing.T) { items: []itemData{ { ID: "Test", + Node: "Node1", Name: "Test", Labels: map[string]string{}, Address: "127.0.0.1", @@ -1426,7 +1542,7 @@ func Test_buildConfiguration(t *testing.T) { Status: api.HealthPassing, }, }, - constraints: `Label("traefik.tags", "bar")`, + constraints: `Tag("traefik.tags=bar")`, expected: &dynamic.Configuration{ TCP: &dynamic.TCPConfiguration{ Routers: map[string]*dynamic.TCPRouter{}, @@ -1453,7 +1569,7 @@ func Test_buildConfiguration(t *testing.T) { Status: api.HealthPassing, }, }, - constraints: `Label("traefik.tags", "foo")`, + constraints: `Tag("traefik.tags=foo")`, expected: &dynamic.Configuration{ TCP: &dynamic.TCPConfiguration{ Routers: map[string]*dynamic.TCPRouter{}, @@ -1840,6 +1956,12 @@ func Test_buildConfiguration(t *testing.T) { var err error test.items[i].ExtraConf, err = p.getConfiguration(test.items[i]) require.NoError(t, err) + + var tags []string + for k, v := range test.items[i].Labels { + tags = append(tags, fmt.Sprintf("%s=%s", k, v)) + } + test.items[i].Tags = tags } configuration := p.buildConfiguration(context.Background(), test.items) diff --git a/pkg/provider/consulcatalog/consul_catalog.go b/pkg/provider/consulcatalog/consul_catalog.go index d4b3029d5..4c52eac16 100644 --- a/pkg/provider/consulcatalog/consul_catalog.go +++ b/pkg/provider/consulcatalog/consul_catalog.go @@ -24,11 +24,13 @@ var _ provider.Provider = (*Provider)(nil) type itemData struct { ID string + Node string Name string Address string Port string Status string Labels map[string]string + Tags []string ExtraConf configuration } @@ -156,7 +158,6 @@ func (p *Provider) getConsulServicesData(ctx context.Context) ([]itemData, error } for _, consulService := range consulServices { - labels := tagsToNeutralLabels(consulService.ServiceTags, p.Prefix) address := consulService.ServiceAddress if address == "" { address = consulService.Address @@ -164,10 +165,12 @@ func (p *Provider) getConsulServicesData(ctx context.Context) ([]itemData, error item := itemData{ ID: consulService.ServiceID, + Node: consulService.Node, Name: consulService.ServiceName, Address: address, Port: strconv.Itoa(consulService.ServicePort), - Labels: labels, + Labels: tagsToNeutralLabels(consulService.ServiceTags, p.Prefix), + Tags: consulService.ServiceTags, Status: consulService.Checks.AggregatedStatus(), } diff --git a/pkg/provider/consulcatalog/convert_types_test.go b/pkg/provider/consulcatalog/convert_types_test.go index b0c24b02d..8dece14c2 100644 --- a/pkg/provider/consulcatalog/convert_types_test.go +++ b/pkg/provider/consulcatalog/convert_types_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestTagsToNeutralLabels(t *testing.T) { +func Test_tagsToNeutralLabels(t *testing.T) { testCases := []struct { desc string tags []string diff --git a/pkg/provider/docker/config.go b/pkg/provider/docker/config.go index b2385a7e6..5753053dc 100644 --- a/pkg/provider/docker/config.go +++ b/pkg/provider/docker/config.go @@ -127,7 +127,7 @@ func (p *Provider) keepContainer(ctx context.Context, container dockerData) bool return false } - matches, err := constraints.Match(container.Labels, p.Constraints) + matches, err := constraints.MatchLabels(container.Labels, p.Constraints) if err != nil { logger.Errorf("Error matching constraints expression: %v", err) return false diff --git a/pkg/provider/marathon/config.go b/pkg/provider/marathon/config.go index e2f81c163..9e76640e6 100644 --- a/pkg/provider/marathon/config.go +++ b/pkg/provider/marathon/config.go @@ -185,7 +185,7 @@ func (p *Provider) keepApplication(ctx context.Context, extraConf configuration, } // Filter by constraints. - matches, err := constraints.Match(labels, p.Constraints) + matches, err := constraints.MatchLabels(labels, p.Constraints) if err != nil { logger.Errorf("Error matching constraints expression: %v", err) return false diff --git a/pkg/provider/rancher/config.go b/pkg/provider/rancher/config.go index 6efc00173..c37cef01c 100644 --- a/pkg/provider/rancher/config.go +++ b/pkg/provider/rancher/config.go @@ -121,7 +121,7 @@ func (p *Provider) keepService(ctx context.Context, service rancherData) bool { return false } - matches, err := constraints.Match(service.Labels, p.Constraints) + matches, err := constraints.MatchLabels(service.Labels, p.Constraints) if err != nil { logger.Errorf("Error matching constraints expression: %v", err) return false diff --git a/pkg/provider/rancher/rancher.go b/pkg/provider/rancher/rancher.go index 0cc7e3c7d..1ab9399b9 100644 --- a/pkg/provider/rancher/rancher.go +++ b/pkg/provider/rancher/rancher.go @@ -193,7 +193,7 @@ func (p *Provider) parseMetadataSourcedRancherData(ctx context.Context, stacks [ } service := rancherData{ - Name: service.Name + "/" + stack.Name, + Name: service.Name + "_" + stack.Name, State: service.State, Labels: service.Labels, Port: servicePort, diff --git a/pkg/server/service/service.go b/pkg/server/service/service.go index 519213879..ccc66b783 100644 --- a/pkg/server/service/service.go +++ b/pkg/server/service/service.go @@ -40,7 +40,7 @@ func NewManager(configs map[string]*runtime.ServiceInfo, defaultRoundTripper htt metricsRegistry: metricsRegistry, bufferPool: newBufferPool(), defaultRoundTripper: defaultRoundTripper, - balancers: make(map[string][]healthcheck.BalancerHandler), + balancers: make(map[string]healthcheck.Balancers), configs: configs, } } @@ -51,8 +51,12 @@ type Manager struct { metricsRegistry metrics.Registry bufferPool httputil.BufferPool defaultRoundTripper http.RoundTripper - balancers map[string][]healthcheck.BalancerHandler - configs map[string]*runtime.ServiceInfo + // balancers is the map of all Balancers, keyed by service name. + // There is one Balancer per service handler, and there is one service handler per reference to a service + // (e.g. if 2 routers refer to the same service name, 2 service handlers are created), + // which is why there is not just one Balancer per service name. + balancers map[string]healthcheck.Balancers + configs map[string]*runtime.ServiceInfo } // BuildHTTP Creates a http.Handler for a service configuration. @@ -92,14 +96,14 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string, respons } case conf.Weighted != nil: var err error - lb, err = m.getLoadBalancerWRRServiceHandler(ctx, serviceName, conf.Weighted, responseModifier) + lb, err = m.getWRRServiceHandler(ctx, serviceName, conf.Weighted, responseModifier) if err != nil { conf.AddError(err, true) return nil, err } case conf.Mirroring != nil: var err error - lb, err = m.getLoadBalancerMirrorServiceHandler(ctx, serviceName, conf.Mirroring, responseModifier) + lb, err = m.getMirrorServiceHandler(ctx, conf.Mirroring, responseModifier) if err != nil { conf.AddError(err, true) return nil, err @@ -113,7 +117,7 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string, respons return lb, nil } -func (m *Manager) getLoadBalancerMirrorServiceHandler(ctx context.Context, serviceName string, config *dynamic.Mirroring, responseModifier func(*http.Response) error) (http.Handler, error) { +func (m *Manager) getMirrorServiceHandler(ctx context.Context, config *dynamic.Mirroring, responseModifier func(*http.Response) error) (http.Handler, error) { serviceHandler, err := m.BuildHTTP(ctx, config.Service, responseModifier) if err != nil { return nil, err @@ -134,7 +138,7 @@ func (m *Manager) getLoadBalancerMirrorServiceHandler(ctx context.Context, servi return handler, nil } -func (m *Manager) getLoadBalancerWRRServiceHandler(ctx context.Context, serviceName string, config *dynamic.WeightedRoundRobin, responseModifier func(*http.Response) error) (http.Handler, error) { +func (m *Manager) getWRRServiceHandler(ctx context.Context, serviceName string, config *dynamic.WeightedRoundRobin, responseModifier func(*http.Response) error) (http.Handler, error) { // TODO Handle accesslog and metrics with multiple service name if config.Sticky != nil && config.Sticky.Cookie != nil { config.Sticky.Cookie.Name = cookie.GetName(config.Sticky.Cookie.Name, serviceName) @@ -200,15 +204,12 @@ func (m *Manager) LaunchHealthCheck() { for serviceName, balancers := range m.balancers { ctx := log.With(context.Background(), log.Str(log.ServiceName, serviceName)) - // TODO aggregate - balancer := balancers[0] - // TODO Should all the services handle healthcheck? Handle different types service := m.configs[serviceName].LoadBalancer // Health Check var backendHealthCheck *healthcheck.BackendConfig - if hcOpts := buildHealthCheckOptions(ctx, balancer, serviceName, service.HealthCheck); hcOpts != nil { + if hcOpts := buildHealthCheckOptions(ctx, balancers, serviceName, service.HealthCheck); hcOpts != nil { log.FromContext(ctx).Debugf("Setting up healthcheck for service %s with %s", serviceName, *hcOpts) hcOpts.Transport = m.defaultRoundTripper @@ -224,7 +225,7 @@ func (m *Manager) LaunchHealthCheck() { healthcheck.GetHealthCheck().SetBackendsConfiguration(context.Background(), backendConfigs) } -func buildHealthCheckOptions(ctx context.Context, lb healthcheck.BalancerHandler, backend string, hc *dynamic.HealthCheck) *healthcheck.Options { +func buildHealthCheckOptions(ctx context.Context, lb healthcheck.Balancer, backend string, hc *dynamic.HealthCheck) *healthcheck.Options { if hc == nil || hc.Path == "" { return nil } diff --git a/pkg/tracing/carrier.go b/pkg/tracing/carrier.go deleted file mode 100644 index 57f54865d..000000000 --- a/pkg/tracing/carrier.go +++ /dev/null @@ -1,25 +0,0 @@ -package tracing - -import "net/http" - -// HTTPHeadersCarrier custom implementation to fix duplicated headers -// It has been fixed in https://github.com/opentracing/opentracing-go/pull/191 -type HTTPHeadersCarrier http.Header - -// Set conforms to the TextMapWriter interface. -func (c HTTPHeadersCarrier) Set(key, val string) { - h := http.Header(c) - h.Set(key, val) -} - -// ForeachKey conforms to the TextMapReader interface. -func (c HTTPHeadersCarrier) ForeachKey(handler func(key, val string) error) error { - for k, vals := range c { - for _, v := range vals { - if err := handler(k, v); err != nil { - return err - } - } - } - return nil -} diff --git a/pkg/tracing/tracing.go b/pkg/tracing/tracing.go index 2d19aa7da..9eddc8933 100644 --- a/pkg/tracing/tracing.go +++ b/pkg/tracing/tracing.go @@ -134,7 +134,7 @@ func InjectRequestHeaders(r *http.Request) { err := opentracing.GlobalTracer().Inject( span.Context(), opentracing.HTTPHeaders, - HTTPHeadersCarrier(r.Header)) + opentracing.HTTPHeadersCarrier(r.Header)) if err != nil { log.FromContext(r.Context()).Error(err) } diff --git a/webui/src/_services/HttpService.js b/webui/src/_services/HttpService.js index 19deca0b6..48010c1e0 100644 --- a/webui/src/_services/HttpService.js +++ b/webui/src/_services/HttpService.js @@ -3,15 +3,12 @@ import { APP } from '../_helpers/APP' const apiBase = '/http' function getAllRouters (params) { - return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}`) + return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`) .then(body => { const total = body.data ? body.data.length : 0 - return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`) - .then(body => { - console.log('Success -> HttpService -> getAllRouters', body.data) - // TODO - suggestion: add the total-pages in api response to optimize the query - return { data: body.data || [], total } - }) + console.log('Success -> HttpService -> getAllRouters', body.data) + // TODO - suggestion: add the total-pages in api response to optimize the query + return { data: body.data || [], total } }) } @@ -24,15 +21,12 @@ function getRouterByName (name) { } function getAllServices (params) { - return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}`) + return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`) .then(body => { const total = body.data ? body.data.length : 0 - return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`) - .then(body => { - console.log('Success -> HttpService -> getAllServices', body.data) - // TODO - suggestion: add the total-pages in api response to optimize the query - return { data: body.data || [], total } - }) + console.log('Success -> HttpService -> getAllServices', body.data) + // TODO - suggestion: add the total-pages in api response to optimize the query + return { data: body.data || [], total } }) } @@ -45,15 +39,12 @@ function getServiceByName (name) { } function getAllMiddlewares (params) { - return APP.api.get(`${apiBase}/middlewares?search=${params.query}&status=${params.status}`) + return APP.api.get(`${apiBase}/middlewares?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`) .then(body => { const total = body.data ? body.data.length : 0 - return APP.api.get(`${apiBase}/middlewares?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`) - .then(body => { - console.log('Success -> HttpService -> getAllMiddlewares', body.data) - // TODO - suggestion: add the total-pages in api response to optimize the query - return { data: body.data || [], total } - }) + console.log('Success -> HttpService -> getAllMiddlewares', body.data) + // TODO - suggestion: add the total-pages in api response to optimize the query + return { data: body.data || [], total } }) } diff --git a/webui/src/_services/TcpService.js b/webui/src/_services/TcpService.js index 08f1b6131..694ce099c 100644 --- a/webui/src/_services/TcpService.js +++ b/webui/src/_services/TcpService.js @@ -3,15 +3,12 @@ import { APP } from '../_helpers/APP' const apiBase = '/tcp' function getAllRouters (params) { - return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}`) + return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`) .then(body => { const total = body.data ? body.data.length : 0 - return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`) - .then(body => { - console.log('Success -> HttpService -> getAllRouters', body.data) - // TODO - suggestion: add the total-pages in api response to optimize the query - return { data: body.data || [], total } - }) + console.log('Success -> HttpService -> getAllRouters', body.data) + // TODO - suggestion: add the total-pages in api response to optimize the query + return { data: body.data || [], total } }) } @@ -24,15 +21,12 @@ function getRouterByName (name) { } function getAllServices (params) { - return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}`) + return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`) .then(body => { const total = body.data ? body.data.length : 0 - return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`) - .then(body => { - console.log('Success -> HttpService -> getAllServices', body.data) - // TODO - suggestion: add the total-pages in api response to optimize the query - return { data: body.data || [], total } - }) + console.log('Success -> HttpService -> getAllServices', body.data) + // TODO - suggestion: add the total-pages in api response to optimize the query + return { data: body.data || [], total } }) } diff --git a/webui/src/components/_commons/PanelRouterDetails.vue b/webui/src/components/_commons/PanelRouterDetails.vue index b5c2aec2a..cb53987ce 100644 --- a/webui/src/components/_commons/PanelRouterDetails.vue +++ b/webui/src/components/_commons/PanelRouterDetails.vue @@ -27,7 +27,7 @@
RULE
+ class="app-chip app-chip-wrap app-chip-rule"> {{ data.rule }} @@ -39,7 +39,7 @@
NAME
+ class="app-chip app-chip-wrap app-chip-name"> {{ data.name }} @@ -66,7 +66,7 @@ dense clickable @click.native="$router.push({ path: `/${protocol}/services/${getServiceId()}`})" - class="app-chip app-chip-service"> + class="app-chip app-chip-wrap app-chip-service"> {{ data.service }} diff --git a/webui/src/components/_commons/PanelServiceDetails.vue b/webui/src/components/_commons/PanelServiceDetails.vue index dda6434b9..37e8dfe06 100644 --- a/webui/src/components/_commons/PanelServiceDetails.vue +++ b/webui/src/components/_commons/PanelServiceDetails.vue @@ -45,7 +45,7 @@ - +
Pass Host Header
@@ -54,6 +54,19 @@
+ +
+
+
Termination Delay
+ + {{ data.loadBalancer.terminationDelay }} ms + +
+
+
+ diff --git a/webui/src/components/_commons/ToolBarTable.vue b/webui/src/components/_commons/ToolBarTable.vue index 0d7bb4f16..48febd17a 100644 --- a/webui/src/components/_commons/ToolBarTable.vue +++ b/webui/src/components/_commons/ToolBarTable.vue @@ -26,6 +26,8 @@