diff --git a/docs/configuration/backends/kubernetes.md b/docs/configuration/backends/kubernetes.md index 789d8a88b..ad6c15218 100644 --- a/docs/configuration/backends/kubernetes.md +++ b/docs/configuration/backends/kubernetes.md @@ -108,7 +108,7 @@ The endpoint may be specified to override the environment variable values inside When the environment variables are not found, Traefik will try to connect to the Kubernetes API server with an external-cluster client. In this case, the endpoint is required. -Specifically, it may be set to the URL used by `kubectl proxy` to connect to a Kubernetes cluster using the granted autentication and authorization of the associated kubeconfig. +Specifically, it may be set to the URL used by `kubectl proxy` to connect to a Kubernetes cluster using the granted authentication and authorization of the associated kubeconfig. ### `labelselector` diff --git a/docs/configuration/tracing.md b/docs/configuration/tracing.md index db523f072..f49a3a38b 100644 --- a/docs/configuration/tracing.md +++ b/docs/configuration/tracing.md @@ -1,6 +1,6 @@ # Tracing -Tracing system allows developers to visualize call flows in there infrastructures. +The tracing system allows developers to visualize call flows in their infrastructure. We use [OpenTracing](http://opentracing.io). It is an open standard designed for distributed tracing. diff --git a/docs/index.md b/docs/index.md index 833c85f33..2afd6b199 100644 --- a/docs/index.md +++ b/docs/index.md @@ -138,7 +138,7 @@ IP: 172.27.0.3 Run more instances of your `whoami` service with the following command: ```shell -docker-compose up -d --scale whoami=2 +docker-compose scale whoami=2 ``` Go back to your browser ([http://localhost:8080](http://localhost:8080)) and see that Træfik has automatically detected the new instance of the container. diff --git a/docs/user-guide/docker-and-lets-encrypt.md b/docs/user-guide/docker-and-lets-encrypt.md index 8fd443037..af7ebe93a 100644 --- a/docs/user-guide/docker-and-lets-encrypt.md +++ b/docs/user-guide/docker-and-lets-encrypt.md @@ -232,7 +232,7 @@ Finally but not unimportantly, we tell Træfik to route **to** port `9000`, sinc `Service labels` allow managing many routes for the same container. When both `container labels` and `service labels` are defined, `container labels` are just used as default values for missing `service labels` but no frontend/backend are going to be defined only with these labels. -Obviously, labels `traefik.frontend.rule` and `traefik.port` described above, will only be used to complete information set in `service labels` during the container frontends/bakends creation. +Obviously, labels `traefik.frontend.rule` and `traefik.port` described above, will only be used to complete information set in `service labels` during the container frontends/backends creation. In the example, two service names are defined : `basic` and `admin`. They allow creating two frontends and two backends. diff --git a/metrics/prometheus.go b/metrics/prometheus.go index 62ad86dea..c9660082d 100644 --- a/metrics/prometheus.go +++ b/metrics/prometheus.go @@ -7,6 +7,7 @@ import ( "sync" "github.com/containous/mux" + "github.com/containous/traefik/log" "github.com/containous/traefik/safe" "github.com/containous/traefik/types" "github.com/go-kit/kit/metrics" @@ -15,25 +16,31 @@ import ( ) const ( - metricNamePrefix = "traefik_" + // MetricNamePrefix prefix of all metric names + MetricNamePrefix = "traefik_" // server meta information - configReloadsTotalName = metricNamePrefix + "config_reloads_total" - configReloadsFailuresTotalName = metricNamePrefix + "config_reloads_failure_total" - configLastReloadSuccessName = metricNamePrefix + "config_last_reload_success" - configLastReloadFailureName = metricNamePrefix + "config_last_reload_failure" + metricConfigPrefix = MetricNamePrefix + "config_" + configReloadsTotalName = metricConfigPrefix + "reloads_total" + configReloadsFailuresTotalName = metricConfigPrefix + "reloads_failure_total" + configLastReloadSuccessName = metricConfigPrefix + "last_reload_success" + configLastReloadFailureName = metricConfigPrefix + "last_reload_failure" // entrypoint - entrypointReqsTotalName = metricNamePrefix + "entrypoint_requests_total" - entrypointReqDurationName = metricNamePrefix + "entrypoint_request_duration_seconds" - entrypointOpenConnsName = metricNamePrefix + "entrypoint_open_connections" + metricEntryPointPrefix = MetricNamePrefix + "entrypoint_" + entrypointReqsTotalName = metricEntryPointPrefix + "requests_total" + entrypointReqDurationName = metricEntryPointPrefix + "request_duration_seconds" + entrypointOpenConnsName = metricEntryPointPrefix + "open_connections" - // backend level - backendReqsTotalName = metricNamePrefix + "backend_requests_total" - backendReqDurationName = metricNamePrefix + "backend_request_duration_seconds" - backendOpenConnsName = metricNamePrefix + "backend_open_connections" - backendRetriesTotalName = metricNamePrefix + "backend_retries_total" - backendServerUpName = metricNamePrefix + "backend_server_up" + // backend level. + + // MetricBackendPrefix prefix of all backend metric names + MetricBackendPrefix = MetricNamePrefix + "backend_" + backendReqsTotalName = MetricBackendPrefix + "requests_total" + backendReqDurationName = MetricBackendPrefix + "request_duration_seconds" + backendOpenConnsName = MetricBackendPrefix + "open_connections" + backendRetriesTotalName = MetricBackendPrefix + "retries_total" + backendServerUpName = MetricBackendPrefix + "server_up" ) // promState holds all metric state internally and acts as the only Collector we register for Prometheus. @@ -61,6 +68,16 @@ func (h PrometheusHandler) AddRoutes(router *mux.Router) { // RegisterPrometheus registers all Prometheus metrics. // It must be called only once and failing to register the metrics will lead to a panic. func RegisterPrometheus(config *types.Prometheus) Registry { + standardRegistry := initStandardRegistry(config) + + if !registerPromState() { + return nil + } + + return standardRegistry +} + +func initStandardRegistry(config *types.Prometheus) Registry { buckets := []float64{0.1, 0.3, 1.2, 5.0} if config.Buckets != nil { buckets = config.Buckets @@ -137,7 +154,6 @@ func RegisterPrometheus(config *types.Prometheus) Registry { backendRetries.cv.Describe, backendServerUp.gv.Describe, } - stdprometheus.MustRegister(promState) return &standardRegistry{ enabled: true, @@ -156,6 +172,17 @@ func RegisterPrometheus(config *types.Prometheus) Registry { } } +func registerPromState() bool { + if err := stdprometheus.Register(promState); err != nil { + if _, ok := err.(stdprometheus.AlreadyRegisteredError); !ok { + log.Errorf("Unable to register Traefik to Prometheus: %v", err) + return false + } + log.Debug("Prometheus collector already registered.") + } + return true +} + // OnConfigurationUpdate receives the current configuration from Traefik. // It then converts the configuration to the optimized package internal format // and sets it to the promState. diff --git a/metrics/prometheus_test.go b/metrics/prometheus_test.go index af5a6b52f..520795faa 100644 --- a/metrics/prometheus_test.go +++ b/metrics/prometheus_test.go @@ -11,8 +11,82 @@ import ( "github.com/containous/traefik/types" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" + "github.com/stretchr/testify/assert" ) +func TestRegisterPromState(t *testing.T) { + // Reset state of global promState. + defer promState.reset() + + testCases := []struct { + desc string + prometheusSlice []*types.Prometheus + initPromState bool + unregisterPromState bool + expectedNbRegistries int + }{ + { + desc: "Register once", + prometheusSlice: []*types.Prometheus{{}}, + expectedNbRegistries: 1, + initPromState: true, + }, + { + desc: "Register once with no promState init", + prometheusSlice: []*types.Prometheus{{}}, + expectedNbRegistries: 0, + }, + { + desc: "Register twice", + prometheusSlice: []*types.Prometheus{{}, {}}, + expectedNbRegistries: 2, + initPromState: true, + }, + { + desc: "Register twice with no promstate init", + prometheusSlice: []*types.Prometheus{{}, {}}, + expectedNbRegistries: 0, + }, + { + desc: "Register twice with unregister", + prometheusSlice: []*types.Prometheus{{}, {}}, + unregisterPromState: true, + expectedNbRegistries: 2, + initPromState: true, + }, + { + desc: "Register twice with unregister but no promstate init", + prometheusSlice: []*types.Prometheus{{}, {}}, + unregisterPromState: true, + expectedNbRegistries: 0, + }, + } + + for _, test := range testCases { + actualNbRegistries := 0 + for _, prom := range test.prometheusSlice { + if test.initPromState { + initStandardRegistry(prom) + } + + promReg := registerPromState() + if promReg != false { + actualNbRegistries++ + } + + if test.unregisterPromState { + prometheus.Unregister(promState) + } + + promState.reset() + } + + prometheus.Unregister(promState) + + assert.Equal(t, test.expectedNbRegistries, actualNbRegistries) + } +} + // reset is a utility method for unit testing. It should be called after each // test run that changes promState internally in order to avoid dependencies // between unit tests. diff --git a/requirements.txt b/requirements.txt index 01eb8ecaa..be343f58a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -mkdocs>=0.17.3 -pymdown-extensions>=1.4 -mkdocs-bootswatch>=0.4.0 -mkdocs-material>=2.2.6 +mkdocs==0.17.5 +pymdown-extensions==4.12 +mkdocs-bootswatch==0.5.0 +mkdocs-material==2.9.4 diff --git a/server/server.go b/server/server.go index 34c943307..14a9882c1 100644 --- a/server/server.go +++ b/server/server.go @@ -633,8 +633,11 @@ func registerMetricClients(metricsConfig *types.Metrics) metrics.Registry { var registries []metrics.Registry if metricsConfig.Prometheus != nil { - registries = append(registries, metrics.RegisterPrometheus(metricsConfig.Prometheus)) - log.Debug("Configured Prometheus metrics") + prometheusRegister := metrics.RegisterPrometheus(metricsConfig.Prometheus) + if prometheusRegister != nil { + registries = append(registries, prometheusRegister) + log.Debug("Configured Prometheus metrics") + } } if metricsConfig.Datadog != nil { registries = append(registries, metrics.RegisterDatadog(metricsConfig.Datadog))