diff --git a/docs/toml.md b/docs/toml.md index 5b64c9a72..459f16cac 100644 --- a/docs/toml.md +++ b/docs/toml.md @@ -1604,6 +1604,12 @@ domain = "rancher.localhost" # Watch = true +# Polling interval (in seconds) +# +# Optional +# +RefreshSeconds = 15 + # Expose Rancher services by default in traefik # # Optional @@ -1611,6 +1617,13 @@ Watch = true # ExposedByDefault = false +# Filter services with unhealthy states and health states +# +# Optional +# Default: false +# +EnableServiceHealthFilter = false + # Endpoint to use when connecting to Rancher # # Required diff --git a/provider/rancher/rancher.go b/provider/rancher/rancher.go index 356a5b66a..e02b2c7cd 100644 --- a/provider/rancher/rancher.go +++ b/provider/rancher/rancher.go @@ -21,11 +21,6 @@ import ( rancher "github.com/rancher/go-rancher/client" ) -const ( - // RancherDefaultWatchTime is the duration of the interval when polling rancher - RancherDefaultWatchTime = 15 * time.Second -) - var ( withoutPagination *rancher.ListOpts ) @@ -34,12 +29,14 @@ var _ provider.Provider = (*Provider)(nil) // Provider holds configurations of the provider. type Provider struct { - provider.BaseProvider `mapstructure:",squash"` - Endpoint string `description:"Rancher server HTTP(S) endpoint."` - AccessKey string `description:"Rancher server access key."` - SecretKey string `description:"Rancher server Secret Key."` - ExposedByDefault bool `description:"Expose Services by default"` - Domain string `description:"Default domain used"` + provider.BaseProvider `mapstructure:",squash"` + Endpoint string `description:"Rancher server HTTP(S) endpoint."` + AccessKey string `description:"Rancher server access key."` + SecretKey string `description:"Rancher server Secret Key."` + ExposedByDefault bool `description:"Expose Services by default"` + Domain string `description:"Default domain used"` + RefreshSeconds int `description:"Polling interval (in seconds)"` + EnableServiceHealthFilter bool `description:"Filter services with unhealthy states and health states."` } type rancherData struct { @@ -47,6 +44,7 @@ type rancherData struct { Labels map[string]string // List of labels set to container or service Containers []string Health string + State string } func init() { @@ -56,7 +54,7 @@ func init() { } func (r rancherData) String() string { - return fmt.Sprintf("{name:%s, labels:%v, containers: %v, health: %s}", r.Name, r.Labels, r.Containers, r.Health) + return fmt.Sprintf("{name:%s, labels:%v, containers: %v, health: %s, state: %s}", r.Name, r.Labels, r.Containers, r.Health, r.State) } // Frontend Labels @@ -261,7 +259,7 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s if p.Watch { _, cancel := context.WithCancel(ctx) - ticker := time.NewTicker(RancherDefaultWatchTime) + ticker := time.NewTicker(time.Second * time.Duration(p.RefreshSeconds)) pool.Go(func(stop chan bool) { for { select { @@ -384,6 +382,7 @@ func parseRancherData(environments []*rancher.Environment, services []*rancher.S rancherData := rancherData{ Name: environment.Name + "/" + service.Name, Health: service.HealthState, + State: service.State, Labels: make(map[string]string), Containers: []string{}, } @@ -393,11 +392,8 @@ func parseRancherData(environments []*rancher.Environment, services []*rancher.S } for _, container := range containers { - for key, value := range container.Labels { - - if key == "io.rancher.stack_service.name" && value == rancherData.Name { - rancherData.Containers = append(rancherData.Containers, container.PrimaryIpAddress) - } + if container.Labels["io.rancher.stack_service.name"] == rancherData.Name && containerFilter(container) { + rancherData.Containers = append(rancherData.Containers, container.PrimaryIpAddress) } } rancherDataList = append(rancherDataList, rancherData) @@ -463,6 +459,20 @@ func (p *Provider) loadRancherConfig(services []rancherData) *types.Configuratio } +func containerFilter(container *rancher.Container) bool { + if container.HealthState != "" && container.HealthState != "healthy" && container.HealthState != "updating-healthy" { + log.Debugf("Filtering container %s with healthState of %s", container.Name, container.HealthState) + return false + } + + if container.State != "" && container.State != "running" && container.State != "updating-running" { + log.Debugf("Filtering container %s with state of %s", container.Name, container.State) + return false + } + + return true +} + func (p *Provider) serviceFilter(service rancherData) bool { if service.Labels["traefik.port"] == "" { @@ -475,9 +485,18 @@ func (p *Provider) serviceFilter(service rancherData) bool { return false } - if service.Health != "" && service.Health != "healthy" { - log.Debugf("Filtering unhealthy or starting service %s", service.Name) - return false + // Only filter services by Health (HealthState) and State if EnableServiceHealthFilter is true + if p.EnableServiceHealthFilter { + + if service.Health != "" && service.Health != "healthy" && service.Health != "updating-healthy" { + log.Debugf("Filtering service %s with healthState of %s", service.Name, service.Health) + return false + } + + if service.State != "" && service.State != "active" && service.State != "updating-active" && service.State != "upgraded" { + log.Debugf("Filtering service %s with state of %s", service.Name, service.State) + return false + } } return true diff --git a/provider/rancher/rancher_test.go b/provider/rancher/rancher_test.go index 93bc3409d..84662d7eb 100644 --- a/provider/rancher/rancher_test.go +++ b/provider/rancher/rancher_test.go @@ -1,12 +1,148 @@ package rancher import ( - "github.com/containous/traefik/types" "reflect" "strings" "testing" + + "github.com/containous/traefik/types" + rancher "github.com/rancher/go-rancher/client" ) +func TestRancherServiceFilter(t *testing.T) { + provider := &Provider{ + Domain: "rancher.localhost", + EnableServiceHealthFilter: true, + } + + services := []struct { + service rancherData + expected bool + }{ + { + service: rancherData{ + Labels: map[string]string{ + "traefik.enable": "true", + }, + Health: "healthy", + State: "active", + }, + expected: false, + }, + { + service: rancherData{ + Labels: map[string]string{ + "traefik.port": "80", + "traefik.enable": "false", + }, + Health: "healthy", + State: "active", + }, + expected: false, + }, + { + service: rancherData{ + Labels: map[string]string{ + "traefik.port": "80", + "traefik.enable": "true", + }, + Health: "unhealthy", + State: "active", + }, + expected: false, + }, + { + service: rancherData{ + Labels: map[string]string{ + "traefik.port": "80", + "traefik.enable": "true", + }, + Health: "healthy", + State: "inactive", + }, + expected: false, + }, + { + service: rancherData{ + Labels: map[string]string{ + "traefik.port": "80", + "traefik.enable": "true", + }, + Health: "healthy", + State: "active", + }, + expected: true, + }, + { + service: rancherData{ + Labels: map[string]string{ + "traefik.port": "80", + "traefik.enable": "true", + }, + Health: "healthy", + State: "upgraded", + }, + expected: true, + }, + } + + for _, e := range services { + actual := provider.serviceFilter(e.service) + if actual != e.expected { + t.Fatalf("expected %t, got %t", e.expected, actual) + } + } +} + +func TestRancherContainerFilter(t *testing.T) { + containers := []struct { + container *rancher.Container + expected bool + }{ + { + container: &rancher.Container{ + HealthState: "unhealthy", + State: "running", + }, + expected: false, + }, + { + container: &rancher.Container{ + HealthState: "healthy", + State: "stopped", + }, + expected: false, + }, + { + container: &rancher.Container{ + State: "stopped", + }, + expected: false, + }, + { + container: &rancher.Container{ + HealthState: "healthy", + State: "running", + }, + expected: true, + }, + { + container: &rancher.Container{ + HealthState: "updating-healthy", + State: "updating-running", + }, + expected: true, + }, + } + + for _, e := range containers { + actual := containerFilter(e.container) + if actual != e.expected { + t.Fatalf("expected %t, got %t", e.expected, actual) + } + } +} + func TestRancherGetFrontendName(t *testing.T) { provider := &Provider{ Domain: "rancher.localhost", diff --git a/server/configuration.go b/server/configuration.go index e6226b8fb..ed4decc5e 100644 --- a/server/configuration.go +++ b/server/configuration.go @@ -446,6 +446,8 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration { var defaultRancher rancher.Provider defaultRancher.Watch = true defaultRancher.ExposedByDefault = true + defaultRancher.RefreshSeconds = 15 + defaultRancher.EnableServiceHealthFilter = false // default DynamoDB var defaultDynamoDB dynamodb.Provider diff --git a/traefik.sample.toml b/traefik.sample.toml index e1981d40c..6e4ec501b 100644 --- a/traefik.sample.toml +++ b/traefik.sample.toml @@ -1046,6 +1046,12 @@ # # Watch = true +# Polling interval (in seconds) +# +# Optional +# +# RefreshSeconds = 15 + # Expose Rancher services by default in traefik # # Optional @@ -1053,6 +1059,13 @@ # # ExposedByDefault = false +# Filter services with unhealthy states and health states +# +# Optional +# Default: false +# +# EnableServiceHealthFilter = false + # Endpoint to use when connecting to Rancher # # Required