Configuration on cicuitbreakers and load balancers
This commit is contained in:
parent
4fb25ca358
commit
0bdd537fb4
9 changed files with 222 additions and 66 deletions
|
@ -1,5 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type GlobalConfiguration struct {
|
||||
Port string
|
||||
GraceTimeOut int64
|
||||
|
@ -27,7 +32,17 @@ func NewGlobalConfiguration() *GlobalConfiguration {
|
|||
}
|
||||
|
||||
type Backend struct {
|
||||
Servers map[string]Server
|
||||
Servers map[string]Server
|
||||
CircuitBreaker *CircuitBreaker
|
||||
LoadBalancer *LoadBalancer
|
||||
}
|
||||
|
||||
type LoadBalancer struct {
|
||||
Method string
|
||||
}
|
||||
|
||||
type CircuitBreaker struct {
|
||||
Expression string
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
|
@ -46,6 +61,34 @@ type Frontend struct {
|
|||
}
|
||||
|
||||
type Configuration struct {
|
||||
Backends map[string]Backend
|
||||
Frontends map[string]Frontend
|
||||
Backends map[string]*Backend
|
||||
Frontends map[string]*Frontend
|
||||
}
|
||||
|
||||
// Load Balancer Method
|
||||
type LoadBalancerMethod uint8
|
||||
|
||||
const (
|
||||
// wrr (default) = Weighted Round Robin
|
||||
wrr LoadBalancerMethod = iota
|
||||
// drr = Dynamic Round Robin
|
||||
drr
|
||||
)
|
||||
|
||||
var loadBalancerMethodNames = []string{
|
||||
"wrr",
|
||||
"drr",
|
||||
}
|
||||
|
||||
func NewLoadBalancerMethod(loadBalancer *LoadBalancer) (LoadBalancerMethod, error) {
|
||||
if loadBalancer != nil {
|
||||
for i, name := range loadBalancerMethodNames {
|
||||
if strings.EqualFold(name, loadBalancer.Method) {
|
||||
return LoadBalancerMethod(i), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return wrr, ErrInvalidLoadBalancerMethod
|
||||
}
|
||||
|
||||
var ErrInvalidLoadBalancerMethod = errors.New("Invalid method, using default")
|
||||
|
|
48
consul.go
48
consul.go
|
@ -48,6 +48,8 @@ var ConsulFuncMap = template.FuncMap{
|
|||
if err != nil {
|
||||
log.Error("Error getting key ", joinedKeys, err)
|
||||
return ""
|
||||
} else if keyPair == nil {
|
||||
return ""
|
||||
}
|
||||
return string(keyPair.Value)
|
||||
},
|
||||
|
@ -75,28 +77,34 @@ func (provider *ConsulProvider) Provide(configurationChan chan<- *Configuration)
|
|||
consulClient, _ := api.NewClient(config)
|
||||
provider.consulClient = consulClient
|
||||
if provider.Watch {
|
||||
var waitIndex uint64
|
||||
keypairs, meta, err := consulClient.KV().Keys("", "", nil)
|
||||
if keypairs == nil && err == nil {
|
||||
log.Error("Key was not found.")
|
||||
if keypairs == nil {
|
||||
log.Error("Key was not found")
|
||||
} else if err != nil {
|
||||
log.Error("Error connecting to consul %s", err)
|
||||
} else {
|
||||
var waitIndex uint64
|
||||
waitIndex = meta.LastIndex
|
||||
go func() {
|
||||
for {
|
||||
opts := api.QueryOptions{
|
||||
WaitIndex: waitIndex,
|
||||
}
|
||||
keypairs, meta, err := consulClient.KV().Keys("", "", &opts)
|
||||
if keypairs == nil {
|
||||
log.Error("Key was not found")
|
||||
} else if err != nil {
|
||||
log.Error("Error connecting to consul %s", err)
|
||||
} else {
|
||||
waitIndex = meta.LastIndex
|
||||
configuration := provider.loadConsulConfig()
|
||||
if configuration != nil {
|
||||
configurationChan <- configuration
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
waitIndex = meta.LastIndex
|
||||
go func() {
|
||||
for {
|
||||
opts := api.QueryOptions{
|
||||
WaitIndex: waitIndex,
|
||||
}
|
||||
keypairs, meta, err := consulClient.KV().Keys("", "", &opts)
|
||||
if keypairs == nil && err == nil {
|
||||
log.Error("Key was not found.")
|
||||
}
|
||||
waitIndex = meta.LastIndex
|
||||
configuration := provider.loadConsulConfig()
|
||||
if configuration != nil {
|
||||
configurationChan <- configuration
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
configuration := provider.loadConsulConfig()
|
||||
configurationChan <- configuration
|
||||
|
|
|
@ -39,12 +39,21 @@ Frontends can be defined using the following rules:
|
|||
### HTTP Backends
|
||||
|
||||
A backend is responsible to load-balance the traffic coming from one or more frontends to a set of http servers.
|
||||
Various types of load-balancing is supported:
|
||||
Various methods of load-balancing is supported:
|
||||
|
||||
* Weighted round robin
|
||||
* Rebalancer: increases weights on servers that perform better than others. It also rolls back to original weights if the servers have changed.
|
||||
* ```wrr```: Weighted Round Robin
|
||||
* ```drr```: Dynamic Round Robin: increases weights on servers that perform better than others. It also rolls back to original weights if the servers have changed.
|
||||
|
||||
A circuit breaker can also be applied to a backend, preventing high loads on failing servers.
|
||||
It can be configured using:
|
||||
|
||||
* Methods: ```LatencyAtQuantileMS```, ```NetworkErrorRatio```, ```ResponseCodeRatio```
|
||||
* Operators: ```AND```, ```OR```, ```EQ```, ```NEQ```, ```LT```, ```LE```, ```GT```, ```GE```
|
||||
|
||||
For example:
|
||||
* ```NetworkErrorRatio() > 0.5```
|
||||
* ```LatencyAtQuantileMS(50.0) > 50```
|
||||
* ```ResponseCodeRatio(500, 600, 0, 600) > 0.5```
|
||||
|
||||
## <a id="global"></a> Global configuration
|
||||
|
||||
|
@ -115,6 +124,8 @@ logLevel = "DEBUG"
|
|||
# rules
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.circuitbreaker]
|
||||
expression = "NetworkErrorRatio() > 0.5"
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://172.17.0.2:80"
|
||||
weight = 10
|
||||
|
@ -122,21 +133,27 @@ logLevel = "DEBUG"
|
|||
url = "http://172.17.0.3:80"
|
||||
weight = 1
|
||||
[backends.backend2]
|
||||
[backends.backend2.LoadBalancer]
|
||||
method = "drr"
|
||||
[backends.backend2.servers.server1]
|
||||
url = "http://172.17.0.4:80"
|
||||
weight = 1
|
||||
[backends.backend2.servers.server2]
|
||||
url = "http://172.17.0.5:80"
|
||||
weight = 2
|
||||
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend2"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "Host"
|
||||
value = "test.localhost"
|
||||
[frontends.frontend2]
|
||||
backend = "backend1"
|
||||
[frontends.frontend2.routes.test_2]
|
||||
rule = "Path"
|
||||
value = "/test"
|
||||
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend2"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "Host"
|
||||
value = "test.localhost"
|
||||
[frontends.frontend2]
|
||||
backend = "backend1"
|
||||
[frontends.frontend2.routes.test_2]
|
||||
rule = "Path"
|
||||
value = "/test"
|
||||
|
||||
```
|
||||
|
||||
|
@ -156,6 +173,8 @@ filename = "rules.toml"
|
|||
# rules.toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.circuitbreaker]
|
||||
expression = "NetworkErrorRatio() > 0.5"
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://172.17.0.2:80"
|
||||
weight = 10
|
||||
|
@ -163,9 +182,14 @@ filename = "rules.toml"
|
|||
url = "http://172.17.0.3:80"
|
||||
weight = 1
|
||||
[backends.backend2]
|
||||
[backends.backend2.LoadBalancer]
|
||||
method = "drr"
|
||||
[backends.backend2.servers.server1]
|
||||
url = "http://172.17.0.4:80"
|
||||
weight = 1
|
||||
[backends.backend2.servers.server2]
|
||||
url = "http://172.17.0.5:80"
|
||||
weight = 2
|
||||
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
|
@ -178,6 +202,7 @@ filename = "rules.toml"
|
|||
[frontends.frontend2.routes.test_2]
|
||||
rule = "Path"
|
||||
value = "/test"
|
||||
|
||||
```
|
||||
|
||||
If you want Træfɪk to watch file changes automatically, just add:
|
||||
|
|
|
@ -13,8 +13,8 @@ type CircuitBreaker struct {
|
|||
circuitBreaker *cbreaker.CircuitBreaker
|
||||
}
|
||||
|
||||
func NewCircuitBreaker(next http.Handler, options ...cbreaker.CircuitBreakerOption) *CircuitBreaker {
|
||||
circuitBreaker, _ := cbreaker.New(next, "NetworkErrorRatio() > 0.5", options...)
|
||||
func NewCircuitBreaker(next http.Handler, expression string, options ...cbreaker.CircuitBreakerOption) *CircuitBreaker {
|
||||
circuitBreaker, _ := cbreaker.New(next, expression, options...)
|
||||
return &CircuitBreaker{circuitBreaker}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,19 @@
|
|||
{{range $backends}}
|
||||
{{$backend := .}}
|
||||
{{$servers := "servers/" | List $backend }}
|
||||
|
||||
{{$circuitBreaker := Get . "circuitbreaker/" "expression"}}
|
||||
{{with $circuitBreaker}}
|
||||
[backends.{{Last $backend}}.circuitBreaker]
|
||||
expression = "{{$circuitBreaker}}"
|
||||
{{end}}
|
||||
|
||||
{{$loadBalancer := Get . "loadbalancer/" "method"}}
|
||||
{{with $loadBalancer}}
|
||||
[backends.{{Last $backend}}.loadBalancer]
|
||||
method = "{{$loadBalancer}}"
|
||||
{{end}}
|
||||
|
||||
{{range $servers}}
|
||||
[backends.{{Last $backend}}.servers.{{Last .}}]
|
||||
url = "{{Get . "/url"}}"
|
||||
|
@ -12,13 +25,13 @@
|
|||
{{end}}
|
||||
|
||||
[frontends]{{range $frontends}}
|
||||
{{$frontend := Last .}}
|
||||
[frontends.{{$frontend}}]
|
||||
backend = "{{Get . "/backend"}}"
|
||||
{{$routes := "routes/" | List .}}
|
||||
{{range $routes}}
|
||||
[frontends.{{$frontend}}.routes.{{Last .}}]
|
||||
rule = "{{Get . "/rule"}}"
|
||||
value = "{{Get . "/value"}}"
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{$frontend := Last .}}
|
||||
[frontends.{{$frontend}}]
|
||||
backend = "{{Get . "/backend"}}"
|
||||
{{$routes := "routes/" | List .}}
|
||||
{{range $routes}}
|
||||
[frontends.{{$frontend}}.routes.{{Last .}}]
|
||||
rule = "{{Get . "/rule"}}"
|
||||
value = "{{Get . "/value"}}"
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
|
|
@ -31,7 +31,6 @@
|
|||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">{{$keyFrontends}}</div>
|
||||
<div class="panel-body">
|
||||
<!--<button type="button" class="btn btn-info">{{$valueFrontends.Backend}}</button>-->
|
||||
<a class="btn btn-info" role="button" data-toggle="collapse" href="#{{$valueFrontends.Backend}}" aria-expanded="false">
|
||||
{{$valueFrontends.Backend}}
|
||||
</a>
|
||||
|
@ -60,6 +59,18 @@
|
|||
{{range $keyBackends, $valueBackends := .Configuration.Backends}}
|
||||
<div class="panel panel-primary" id="{{$keyBackends}}">
|
||||
<div class="panel-heading">{{$keyBackends}}</div>
|
||||
<div class="panel-body">
|
||||
{{with $valueBackends.LoadBalancer}}
|
||||
<a class="btn btn-info" role="button">
|
||||
Load Balancer: {{.Method}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{with $valueBackends.CircuitBreaker}}
|
||||
<a class="btn btn-info" role="button">
|
||||
Circuit Breaker: {{.Expression}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
<table class="table table-striped table-hover">
|
||||
<tr>
|
||||
<td><em>Server</em></td>
|
||||
|
|
25
tests/consul-config.sh
Executable file
25
tests/consul-config.sh
Executable file
|
@ -0,0 +1,25 @@
|
|||
#!/bin/sh
|
||||
|
||||
# backend 1
|
||||
curl -i -H "Accept: application/json" -X PUT -d "NetworkErrorRatio() > 0.5" http://localhost:8500/v1/kv/backends/backend1/circuitbreaker/expression
|
||||
curl -i -H "Accept: application/json" -X PUT -d "http://172.17.0.2:80" http://localhost:8500/v1/kv/backends/backend1/servers/server1/url
|
||||
curl -i -H "Accept: application/json" -X PUT -d "10" http://localhost:8500/v1/kv/backends/backend1/servers/server1/weight
|
||||
curl -i -H "Accept: application/json" -X PUT -d "http://172.17.0.3:80" http://localhost:8500/v1/kv/backends/backend1/servers/server2/url
|
||||
curl -i -H "Accept: application/json" -X PUT -d "1" http://localhost:8500/v1/kv/backends/backend1/servers/server2/weight
|
||||
|
||||
# backend 2
|
||||
curl -i -H "Accept: application/json" -X PUT -d "drr" http://localhost:8500/v1/kv/backends/backend2/loadbalancer/method
|
||||
curl -i -H "Accept: application/json" -X PUT -d "http://172.17.0.4:80" http://localhost:8500/v1/kv/backends/backend2/servers/server1/url
|
||||
curl -i -H "Accept: application/json" -X PUT -d "1" http://localhost:8500/v1/kv/backends/backend2/servers/server1/weight
|
||||
curl -i -H "Accept: application/json" -X PUT -d "http://172.17.0.5:80" http://localhost:8500/v1/kv/backends/backend2/servers/server2/url
|
||||
curl -i -H "Accept: application/json" -X PUT -d "2" http://localhost:8500/v1/kv/backends/backend2/servers/server2/weight
|
||||
|
||||
# frontend 1
|
||||
curl -i -H "Accept: application/json" -X PUT -d "backend2" http://localhost:8500/v1/kv/frontends/frontend1/backend
|
||||
curl -i -H "Accept: application/json" -X PUT -d "Host" http://localhost:8500/v1/kv/frontends/frontend1/routes/test_1/rule
|
||||
curl -i -H "Accept: application/json" -X PUT -d "test.localhost" http://localhost:8500/v1/kv/frontends/frontend1/routes/test_1/value
|
||||
|
||||
# frontend 2
|
||||
curl -i -H "Accept: application/json" -X PUT -d "backend1" http://localhost:8500/v1/kv/frontends/frontend2/backend
|
||||
curl -i -H "Accept: application/json" -X PUT -d "Path" http://localhost:8500/v1/kv/frontends/frontend2/routes/test_2/rule
|
||||
curl -i -H "Accept: application/json" -X PUT -d "/test" http://localhost:8500/v1/kv/frontends/frontend2/routes/test_2/value
|
57
traefik.go
57
traefik.go
|
@ -225,27 +225,54 @@ func LoadConfig(configuration *Configuration, globalConfiguration *GlobalConfigu
|
|||
}
|
||||
if backends[frontend.Backend] == nil {
|
||||
log.Debugf("Creating backend %s", frontend.Backend)
|
||||
lb, _ := roundrobin.New(fwd)
|
||||
rb, _ := roundrobin.NewRebalancer(lb, roundrobin.RebalancerLogger(oxyLogger))
|
||||
for serverName, server := range configuration.Backends[frontend.Backend].Servers {
|
||||
url, err := url.Parse(server.URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("Creating server %s %s", serverName, url.String())
|
||||
rb.UpsertServer(url, roundrobin.Weight(server.Weight))
|
||||
var lb http.Handler
|
||||
rr, _ := roundrobin.New(fwd)
|
||||
lbMethod, err := NewLoadBalancerMethod(configuration.Backends[frontend.Backend].LoadBalancer)
|
||||
if err != nil {
|
||||
configuration.Backends[frontend.Backend].LoadBalancer = &LoadBalancer{Method: "wrr"}
|
||||
}
|
||||
backends[frontend.Backend] = rb
|
||||
switch lbMethod {
|
||||
case drr:
|
||||
log.Debugf("Creating load-balancer drr")
|
||||
rebalancer, _ := roundrobin.NewRebalancer(rr, roundrobin.RebalancerLogger(oxyLogger))
|
||||
lb = rebalancer
|
||||
for serverName, server := range configuration.Backends[frontend.Backend].Servers {
|
||||
url, err := url.Parse(server.URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("Creating server %s %s", serverName, url.String())
|
||||
rebalancer.UpsertServer(url, roundrobin.Weight(server.Weight))
|
||||
}
|
||||
case wrr:
|
||||
log.Debugf("Creating load-balancer wrr")
|
||||
lb = rr
|
||||
for serverName, server := range configuration.Backends[frontend.Backend].Servers {
|
||||
url, err := url.Parse(server.URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("Creating server %s %s", serverName, url.String())
|
||||
rr.UpsertServer(url, roundrobin.Weight(server.Weight))
|
||||
}
|
||||
}
|
||||
var negroni = negroni.New()
|
||||
if configuration.Backends[frontend.Backend].CircuitBreaker != nil {
|
||||
log.Debugf("Creating circuit breaker %s", configuration.Backends[frontend.Backend].CircuitBreaker.Expression)
|
||||
negroni.Use(middlewares.NewCircuitBreaker(lb, configuration.Backends[frontend.Backend].CircuitBreaker.Expression, cbreaker.Logger(oxyLogger)))
|
||||
} else {
|
||||
negroni.UseHandler(lb)
|
||||
}
|
||||
backends[frontend.Backend] = negroni
|
||||
} else {
|
||||
log.Debugf("Reusing backend %s", frontend.Backend)
|
||||
}
|
||||
// stream.New(backends[frontend.Backend], stream.Retry("IsNetworkError() && Attempts() <= " + strconv.Itoa(globalConfiguration.Replay)), stream.Logger(oxyLogger))
|
||||
var negroni = negroni.New()
|
||||
negroni.Use(middlewares.NewCircuitBreaker(backends[frontend.Backend], cbreaker.Logger(oxyLogger)))
|
||||
newRoute.Handler(negroni)
|
||||
// stream.New(backends[frontend.Backend], stream.Retry("IsNetworkError() && Attempts() <= " + strconv.Itoa(globalConfiguration.Replay)), stream.Logger(oxyLogger))
|
||||
|
||||
newRoute.Handler(backends[frontend.Backend])
|
||||
err := newRoute.GetError()
|
||||
if err != nil {
|
||||
log.Error("Error building route ", err)
|
||||
log.Error("Error building route: %s", err)
|
||||
}
|
||||
}
|
||||
return router, nil
|
||||
|
|
|
@ -187,7 +187,7 @@
|
|||
#
|
||||
# Required
|
||||
#
|
||||
# endpoint = "http://127.0.0.1:8500"
|
||||
# endpoint = "127.0.0.1:8500"
|
||||
|
||||
# Enable watch Consul changes
|
||||
#
|
||||
|
@ -214,6 +214,8 @@
|
|||
################################################################
|
||||
# [backends]
|
||||
# [backends.backend1]
|
||||
# [backends.backend1.circuitbreaker]
|
||||
# expression = "NetworkErrorRatio() > 0.5"
|
||||
# [backends.backend1.servers.server1]
|
||||
# url = "http://172.17.0.2:80"
|
||||
# weight = 10
|
||||
|
@ -221,6 +223,8 @@
|
|||
# url = "http://172.17.0.3:80"
|
||||
# weight = 1
|
||||
# [backends.backend2]
|
||||
# [backends.backend2.LoadBalancer]
|
||||
# method = "drr"
|
||||
# [backends.backend2.servers.server1]
|
||||
# url = "http://172.17.0.4:80"
|
||||
# weight = 1
|
||||
|
|
Loading…
Reference in a new issue