From d89bdfbd279052358889c81e77cef875a1bc1a62 Mon Sep 17 00:00:00 2001 From: Bruce Lee Date: Thu, 25 Aug 2016 00:22:06 -0400 Subject: [PATCH] Add backend features to docker --- docs/toml.md | 4 ++ provider/docker.go | 87 +++++++++++++++++++++++++++++++++++------ provider/docker_test.go | 63 +++++++++++++++++++++++++++++ templates/docker.tmpl | 16 ++++++++ 4 files changed, 159 insertions(+), 11 deletions(-) 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 .}}