Add ability for marathon provider to set maxconn values
Initial implementation: Force both to be present to trigger behavior. add ability to see rendered template in debug add support for loadbalancer and circuit breaker specification add documentation for new configuration
This commit is contained in:
parent
4783c7f70a
commit
99ca5d0a03
5 changed files with 298 additions and 15 deletions
|
@ -711,6 +711,10 @@ domain = "marathon.localhost"
|
|||
Labels can be used on containers to override default behaviour:
|
||||
|
||||
- `traefik.backend=foo`: assign the application 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.portIndex=1`: register port by index in the application's ports array. Useful when the application exposes multiple ports.
|
||||
- `traefik.port=80`: register the explicit application port value. Cannot be used alongside `traefik.portIndex`.
|
||||
- `traefik.protocol=https`: override the default `http` protocol
|
||||
|
|
|
@ -8,14 +8,16 @@ import (
|
|||
"strings"
|
||||
"text/template"
|
||||
|
||||
"math"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/emilevauge/backoff"
|
||||
"github.com/gambol99/go-marathon"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Marathon holds configuration of the Marathon provider.
|
||||
|
@ -117,17 +119,24 @@ func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage,
|
|||
|
||||
func (provider *Marathon) loadMarathonConfig() *types.Configuration {
|
||||
var MarathonFuncMap = template.FuncMap{
|
||||
"getBackend": provider.getBackend,
|
||||
"getPort": provider.getPort,
|
||||
"getWeight": provider.getWeight,
|
||||
"getDomain": provider.getDomain,
|
||||
"getProtocol": provider.getProtocol,
|
||||
"getPassHostHeader": provider.getPassHostHeader,
|
||||
"getPriority": provider.getPriority,
|
||||
"getEntryPoints": provider.getEntryPoints,
|
||||
"getFrontendRule": provider.getFrontendRule,
|
||||
"getFrontendBackend": provider.getFrontendBackend,
|
||||
"replace": replace,
|
||||
"getBackend": provider.getBackend,
|
||||
"getPort": provider.getPort,
|
||||
"getWeight": provider.getWeight,
|
||||
"getDomain": provider.getDomain,
|
||||
"getProtocol": provider.getProtocol,
|
||||
"getPassHostHeader": provider.getPassHostHeader,
|
||||
"getPriority": provider.getPriority,
|
||||
"getEntryPoints": provider.getEntryPoints,
|
||||
"getFrontendRule": provider.getFrontendRule,
|
||||
"getFrontendBackend": provider.getFrontendBackend,
|
||||
"replace": replace,
|
||||
"hasCircuitBreakerLabels": provider.hasCircuitBreakerLabels,
|
||||
"hasLoadBalancerLabels": provider.hasLoadBalancerLabels,
|
||||
"hasMaxConnLabels": provider.hasMaxConnLabels,
|
||||
"getMaxConnExtractorFunc": provider.getMaxConnExtractorFunc,
|
||||
"getMaxConnAmount": provider.getMaxConnAmount,
|
||||
"getLoadBalancerMethod": provider.getLoadBalancerMethod,
|
||||
"getCircuitBreakerExpression": provider.getCircuitBreakerExpression,
|
||||
}
|
||||
|
||||
applications, err := provider.marathonClient.Applications(nil)
|
||||
|
@ -375,3 +384,60 @@ func (provider *Marathon) getSubDomain(name string) string {
|
|||
}
|
||||
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
|
||||
}
|
||||
|
||||
func (provider *Marathon) hasCircuitBreakerLabels(application marathon.Application) bool {
|
||||
if _, err := provider.getLabel(application, "traefik.backend.circuitbreaker.expression"); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (provider *Marathon) hasLoadBalancerLabels(application marathon.Application) bool {
|
||||
if _, err := provider.getLabel(application, "traefik.backend.loadbalancer.method"); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (provider *Marathon) hasMaxConnLabels(application marathon.Application) bool {
|
||||
if _, err := provider.getLabel(application, "traefik.backend.maxconn.amount"); err != nil {
|
||||
return false
|
||||
}
|
||||
if _, err := provider.getLabel(application, "traefik.backend.maxconn.extractorfunc"); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (provider *Marathon) getMaxConnAmount(application marathon.Application) int64 {
|
||||
if label, err := provider.getLabel(application, "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 *Marathon) getMaxConnExtractorFunc(application marathon.Application) string {
|
||||
if label, err := provider.getLabel(application, "traefik.backend.maxconn.extractorfunc"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "request.host"
|
||||
}
|
||||
|
||||
func (provider *Marathon) getLoadBalancerMethod(application marathon.Application) string {
|
||||
if label, err := provider.getLabel(application, "traefik.backend.loadbalancer.method"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "wrr"
|
||||
}
|
||||
|
||||
func (provider *Marathon) getCircuitBreakerExpression(application marathon.Application) string {
|
||||
if label, err := provider.getLabel(application, "traefik.backend.circuitbreaker.expression"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "NetworkErrorRatio() > 1"
|
||||
}
|
||||
|
|
|
@ -111,6 +111,200 @@ func TestMarathonLoadConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "/testLoadBalancerAndCircuitBreaker",
|
||||
Ports: []int{80},
|
||||
Labels: &map[string]string{
|
||||
"traefik.backend.loadbalancer.method": "drr",
|
||||
"traefik.backend.circuitbreaker.expression": "NetworkErrorRatio() > 0.5",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tasks: &marathon.Tasks{
|
||||
Tasks: []marathon.Task{
|
||||
{
|
||||
ID: "testLoadBalancerAndCircuitBreaker",
|
||||
AppID: "/testLoadBalancerAndCircuitBreaker",
|
||||
Host: "127.0.0.1",
|
||||
Ports: []int{80},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedFrontends: map[string]*types.Frontend{
|
||||
`frontend-testLoadBalancerAndCircuitBreaker`: {
|
||||
Backend: "backend-testLoadBalancerAndCircuitBreaker",
|
||||
PassHostHeader: true,
|
||||
EntryPoints: []string{},
|
||||
Routes: map[string]types.Route{
|
||||
`route-host-testLoadBalancerAndCircuitBreaker`: {
|
||||
Rule: "Host:testLoadBalancerAndCircuitBreaker.docker.localhost",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedBackends: map[string]*types.Backend{
|
||||
"backend-testLoadBalancerAndCircuitBreaker": {
|
||||
Servers: map[string]types.Server{
|
||||
"server-testLoadBalancerAndCircuitBreaker": {
|
||||
URL: "http://127.0.0.1:80",
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: &types.CircuitBreaker{
|
||||
Expression: "NetworkErrorRatio() > 0.5",
|
||||
},
|
||||
LoadBalancer: &types.LoadBalancer{
|
||||
Method: "drr",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "/testMaxConn",
|
||||
Ports: []int{80},
|
||||
Labels: &map[string]string{
|
||||
"traefik.backend.maxconn.amount": "1000",
|
||||
"traefik.backend.maxconn.extractorfunc": "client.ip",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tasks: &marathon.Tasks{
|
||||
Tasks: []marathon.Task{
|
||||
{
|
||||
ID: "testMaxConn",
|
||||
AppID: "/testMaxConn",
|
||||
Host: "127.0.0.1",
|
||||
Ports: []int{80},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedFrontends: map[string]*types.Frontend{
|
||||
`frontend-testMaxConn`: {
|
||||
Backend: "backend-testMaxConn",
|
||||
PassHostHeader: true,
|
||||
EntryPoints: []string{},
|
||||
Routes: map[string]types.Route{
|
||||
`route-host-testMaxConn`: {
|
||||
Rule: "Host:testMaxConn.docker.localhost",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedBackends: map[string]*types.Backend{
|
||||
"backend-testMaxConn": {
|
||||
Servers: map[string]types.Server{
|
||||
"server-testMaxConn": {
|
||||
URL: "http://127.0.0.1:80",
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
MaxConn: &types.MaxConn{
|
||||
Amount: 1000,
|
||||
ExtractorFunc: "client.ip",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "/testMaxConnOnlySpecifyAmount",
|
||||
Ports: []int{80},
|
||||
Labels: &map[string]string{
|
||||
"traefik.backend.maxconn.amount": "1000",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tasks: &marathon.Tasks{
|
||||
Tasks: []marathon.Task{
|
||||
{
|
||||
ID: "testMaxConnOnlySpecifyAmount",
|
||||
AppID: "/testMaxConnOnlySpecifyAmount",
|
||||
Host: "127.0.0.1",
|
||||
Ports: []int{80},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedFrontends: map[string]*types.Frontend{
|
||||
`frontend-testMaxConnOnlySpecifyAmount`: {
|
||||
Backend: "backend-testMaxConnOnlySpecifyAmount",
|
||||
PassHostHeader: true,
|
||||
EntryPoints: []string{},
|
||||
Routes: map[string]types.Route{
|
||||
`route-host-testMaxConnOnlySpecifyAmount`: {
|
||||
Rule: "Host:testMaxConnOnlySpecifyAmount.docker.localhost",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedBackends: map[string]*types.Backend{
|
||||
"backend-testMaxConnOnlySpecifyAmount": {
|
||||
Servers: map[string]types.Server{
|
||||
"server-testMaxConnOnlySpecifyAmount": {
|
||||
URL: "http://127.0.0.1:80",
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
MaxConn: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "/testMaxConnOnlyExtractorFunc",
|
||||
Ports: []int{80},
|
||||
Labels: &map[string]string{
|
||||
"traefik.backend.maxconn.extractorfunc": "client.ip",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tasks: &marathon.Tasks{
|
||||
Tasks: []marathon.Task{
|
||||
{
|
||||
ID: "testMaxConnOnlyExtractorFunc",
|
||||
AppID: "/testMaxConnOnlyExtractorFunc",
|
||||
Host: "127.0.0.1",
|
||||
Ports: []int{80},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedFrontends: map[string]*types.Frontend{
|
||||
`frontend-testMaxConnOnlyExtractorFunc`: {
|
||||
Backend: "backend-testMaxConnOnlyExtractorFunc",
|
||||
PassHostHeader: true,
|
||||
EntryPoints: []string{},
|
||||
Routes: map[string]types.Route{
|
||||
`route-host-testMaxConnOnlyExtractorFunc`: {
|
||||
Rule: "Host:testMaxConnOnlyExtractorFunc.docker.localhost",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedBackends: map[string]*types.Backend{
|
||||
"backend-testMaxConnOnlyExtractorFunc": {
|
||||
Servers: map[string]types.Server{
|
||||
"server-testMaxConnOnlyExtractorFunc": {
|
||||
URL: "http://127.0.0.1:80",
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
MaxConn: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
|
|
|
@ -10,12 +10,13 @@ import (
|
|||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/autogen"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Provider defines methods of a provider.
|
||||
|
@ -80,7 +81,9 @@ func (p *BaseProvider) getConfiguration(defaultTemplateFile string, funcMap temp
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := toml.Decode(buffer.String(), configuration); err != nil {
|
||||
var renderedTemplate = buffer.String()
|
||||
// log.Debugf("Rendering results of %s:\n%s", defaultTemplateFile, renderedTemplate)
|
||||
if _, err := toml.Decode(renderedTemplate, configuration); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return configuration, nil
|
||||
|
|
|
@ -5,6 +5,22 @@
|
|||
weight = {{getWeight . $apps}}
|
||||
{{end}}
|
||||
|
||||
{{range .Applications}}
|
||||
{{ if hasMaxConnLabels . }}
|
||||
[backends.backend{{getFrontendBackend . }}.maxconn]
|
||||
amount = {{getMaxConnAmount . }}
|
||||
extractorfunc = "{{getMaxConnExtractorFunc . }}"
|
||||
{{end}}
|
||||
{{ if hasLoadBalancerLabels . }}
|
||||
[backends.backend{{getFrontendBackend . }}.loadbalancer]
|
||||
method = "{{getLoadBalancerMethod . }}"
|
||||
{{end}}
|
||||
{{ if hasCircuitBreakerLabels . }}
|
||||
[backends.backend{{getFrontendBackend . }}.circuitbreaker]
|
||||
expression = "{{getCircuitBreakerExpression . }}"
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
[frontends]{{range .Applications}}
|
||||
[frontends.frontend{{.ID | replace "/" "-"}}]
|
||||
backend = "backend{{getFrontendBackend .}}"
|
||||
|
|
Loading…
Reference in a new issue