diff --git a/docs/toml.md b/docs/toml.md index 50cad270d..86c6e2c4d 100644 --- a/docs/toml.md +++ b/docs/toml.md @@ -612,6 +612,10 @@ exposedbydefault = true Labels can be used on containers to override default behaviour: - `traefik.backend=foo`: assign the container to `foo` backend +- `traefik.backend.maxconn.amount=10`: set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect. +- `traefik.backend.maxconn.extractorfunc=client.ip`: set the function to be used against the request to determine what to limit maximum connections to the backend by. Must be used in conjunction with the above label to take effect. +- `traefik.backend.loadbalancer.method=drr`: override the default `wrr` load balancer algorithm +- `traefik.backend.circuitbreaker.expression=NetworkErrorRatio() > 0.5`: create a [circuit breaker](/basics/#backends) to be used against the backend - `traefik.port=80`: register this port. Useful when the container exposes multiples ports. - `traefik.protocol=https`: override the default `http` protocol - `traefik.weight=10`: assign this weight to the container diff --git a/provider/docker.go b/provider/docker.go index 4e2a696bd..4467a472c 100644 --- a/provider/docker.go +++ b/provider/docker.go @@ -2,6 +2,7 @@ package provider import ( "errors" + "math" "net/http" "strconv" "strings" @@ -150,17 +151,24 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, po func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.ContainerJSON) *types.Configuration { var DockerFuncMap = template.FuncMap{ - "getBackend": provider.getBackend, - "getIPAddress": provider.getIPAddress, - "getPort": provider.getPort, - "getWeight": provider.getWeight, - "getDomain": provider.getDomain, - "getProtocol": provider.getProtocol, - "getPassHostHeader": provider.getPassHostHeader, - "getPriority": provider.getPriority, - "getEntryPoints": provider.getEntryPoints, - "getFrontendRule": provider.getFrontendRule, - "replace": replace, + "getBackend": provider.getBackend, + "getIPAddress": provider.getIPAddress, + "getPort": provider.getPort, + "getWeight": provider.getWeight, + "getDomain": provider.getDomain, + "getProtocol": provider.getProtocol, + "getPassHostHeader": provider.getPassHostHeader, + "getPriority": provider.getPriority, + "getEntryPoints": provider.getEntryPoints, + "getFrontendRule": provider.getFrontendRule, + "hasCircuitBreakerLabel": provider.hasCircuitBreakerLabel, + "getCircuitBreakerExpression": provider.getCircuitBreakerExpression, + "hasLoadBalancerLabel": provider.hasLoadBalancerLabel, + "getLoadBalancerMethod": provider.getLoadBalancerMethod, + "hasMaxConnLabels": provider.hasMaxConnLabels, + "getMaxConnAmount": provider.getMaxConnAmount, + "getMaxConnExtractorFunc": provider.getMaxConnExtractorFunc, + "replace": replace, } // filter containers @@ -191,6 +199,63 @@ func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.Conta return configuration } +func (provider *Docker) hasCircuitBreakerLabel(container dockertypes.ContainerJSON) bool { + if _, err := getLabel(container, "traefik.backend.circuitbreaker.expression"); err != nil { + return false + } + return true +} + +func (provider *Docker) hasLoadBalancerLabel(container dockertypes.ContainerJSON) bool { + if _, err := getLabel(container, "traefik.backend.loadbalancer.method"); err != nil { + return false + } + return true +} + +func (provider *Docker) hasMaxConnLabels(container dockertypes.ContainerJSON) bool { + if _, err := getLabel(container, "traefik.backend.maxconn.amount"); err != nil { + return false + } + if _, err := getLabel(container, "traefik.backend.maxconn.extractorfunc"); err != nil { + return false + } + return true +} + +func (provider *Docker) getCircuitBreakerExpression(container dockertypes.ContainerJSON) string { + if label, err := getLabel(container, "traefik.backend.circuitbreaker.expression"); err == nil { + return label + } + return "NetworkErrorRatio() > 1" +} + +func (provider *Docker) getLoadBalancerMethod(container dockertypes.ContainerJSON) string { + if label, err := getLabel(container, "traefik.backend.loadbalancer.method"); err == nil { + return label + } + return "wrr" +} + +func (provider *Docker) getMaxConnAmount(container dockertypes.ContainerJSON) int64 { + if label, err := getLabel(container, "traefik.backend.maxconn.amount"); err == nil { + i, errConv := strconv.ParseInt(label, 10, 64) + if errConv != nil { + log.Errorf("Unable to parse traefik.backend.maxconn.amount %s", label) + return math.MaxInt64 + } + return i + } + return math.MaxInt64 +} + +func (provider *Docker) getMaxConnExtractorFunc(container dockertypes.ContainerJSON) string { + if label, err := getLabel(container, "traefik.backend.maxconn.extractorfunc"); err == nil { + return label + } + return "request.host" +} + func (provider *Docker) containerFilter(container dockertypes.ContainerJSON, exposedByDefaultFlag bool) bool { _, err := strconv.Atoi(container.Config.Labels["traefik.port"]) if len(container.NetworkSettings.Ports) == 0 && err != nil { diff --git a/provider/docker_test.go b/provider/docker_test.go index 80e9b8375..63d4df7f2 100644 --- a/provider/docker_test.go +++ b/provider/docker_test.go @@ -1016,6 +1016,69 @@ func TestDockerLoadDockerConfig(t *testing.T) { }, }, }, + { + containers: []docker.ContainerJSON{ + { + ContainerJSONBase: &docker.ContainerJSONBase{ + Name: "test1", + }, + Config: &container.Config{ + Labels: map[string]string{ + "traefik.backend": "foobar", + "traefik.frontend.entryPoints": "http,https", + "traefik.backend.maxconn.amount": "1000", + "traefik.backend.maxconn.extractorfunc": "somethingelse", + "traefik.backend.loadbalancer.method": "drr", + "traefik.backend.circuitbreaker.expression": "NetworkErrorRatio() > 0.5", + }, + }, + NetworkSettings: &docker.NetworkSettings{ + NetworkSettingsBase: docker.NetworkSettingsBase{ + Ports: nat.PortMap{ + "80/tcp": {}, + }, + }, + Networks: map[string]*network.EndpointSettings{ + "bridge": { + IPAddress: "127.0.0.1", + }, + }, + }, + }, + }, + expectedFrontends: map[string]*types.Frontend{ + "frontend-Host-test1-docker-localhost": { + Backend: "backend-foobar", + PassHostHeader: true, + EntryPoints: []string{"http", "https"}, + Routes: map[string]types.Route{ + "route-frontend-Host-test1-docker-localhost": { + Rule: "Host:test1.docker.localhost", + }, + }, + }, + }, + expectedBackends: map[string]*types.Backend{ + "backend-foobar": { + Servers: map[string]types.Server{ + "server-test1": { + URL: "http://127.0.0.1:80", + Weight: 1, + }, + }, + CircuitBreaker: &types.CircuitBreaker{ + Expression: "NetworkErrorRatio() > 0.5", + }, + LoadBalancer: &types.LoadBalancer{ + Method: "drr", + }, + MaxConn: &types.MaxConn{ + Amount: 1000, + ExtractorFunc: "somethingelse", + }, + }, + }, + }, } provider := &Docker{ diff --git a/templates/docker.tmpl b/templates/docker.tmpl index a45963d5f..5b9acce33 100644 --- a/templates/docker.tmpl +++ b/templates/docker.tmpl @@ -1,4 +1,20 @@ [backends]{{range .Containers}} + {{if hasCircuitBreakerLabel .}} + [backends.backend-{{getBackend .}}.circuitbreaker] + expression = "{{getCircuitBreakerExpression .}}" + {{end}} + + {{if hasLoadBalancerLabel .}} + [backends.backend-{{getBackend .}}.loadbalancer] + method = "{{getLoadBalancerMethod .}}" + {{end}} + + {{if hasMaxConnLabels .}} + [backends.backend-{{getBackend .}}.maxconn] + amount = {{getMaxConnAmount . }} + extractorfunc = "{{getMaxConnExtractorFunc . }}" + {{end}} + [backends.backend-{{getBackend .}}.servers.server-{{.Name | replace "/" "" | replace "." "-"}}] url = "{{getProtocol .}}://{{getIPAddress .}}:{{getPort .}}" weight = {{getWeight .}}