Merge pull request #701 from containous/carry-pr-446

Carry PR 446 - Add sticky session support (round two!)
This commit is contained in:
Emile Vauge 2016-09-30 11:25:26 +02:00 committed by GitHub
commit 38b62d4ae3
12 changed files with 116 additions and 48 deletions

View file

@ -222,6 +222,17 @@ For example:
- Another possible value for `extractorfunc` is `client.ip` which will categorize requests based on client source ip.
- Lastly `extractorfunc` can take the value of `request.header.ANY_HEADER` which will categorize requests based on `ANY_HEADER` that you provide.
Sticky sessions are supported with both load balancers. When sticky sessions are enabled, a cookie called `_TRAEFIK_BACKEND` is set on the initial
request. On subsequent requests, the client will be directed to the backend stored in the cookie if it is still healthy. If not, a new backend
will be assigned.
For example:
```toml
[backends]
[backends.backend1]
[backends.backend1.loadbalancer]
sticky = true
```
## Servers
Servers are simply defined using a `URL`. You can also apply a custom `weight` to each server (this will be used by load-balancing).

View file

@ -711,6 +711,7 @@ Labels can be used on containers to override default behaviour:
- `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.loadbalancer.sticky=true`: enable backend sticky sessions
- `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
@ -810,6 +811,7 @@ Labels can be used on containers to override default behaviour:
- `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.loadbalancer.sticky=true`: enable backend sticky sessions
- `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`.

59
glide.lock generated
View file

@ -1,10 +1,10 @@
hash: c0ac205a859d78847e21d3cd63f427ffba985755c6ae84373e4a20364ba39b05
updated: 2016-09-28T16:50:04.352639437+01:00
updated: 2016-09-30T10:57:42.336729457+02:00
imports:
- name: github.com/abbot/go-http-auth
version: cb4372376e1e00e9f6ab9ec142e029302c9e7140
- name: github.com/boltdb/bolt
version: fff57c100f4dea1905678da7e90d92429dff2904
version: 5cc10bbbc5c141029940133bb33c9e969512a698
- name: github.com/BurntSushi/toml
version: 99064174e013895bbd9b025c31100bd1d9b590ca
- name: github.com/BurntSushi/ty
@ -18,7 +18,7 @@ imports:
- name: github.com/codegangsta/cli
version: 1efa31f08b9333f1bd4882d61f9d668a70cd902e
- name: github.com/codegangsta/negroni
version: 3f7ce7b928e14ff890b067e5bbbc80af73690a9c
version: dc6b9d037e8dab60cbfc09c61d6932537829be8b
- name: github.com/containous/flaeg
version: a731c034dda967333efce5f8d276aeff11f8ff87
- name: github.com/containous/mux
@ -32,11 +32,11 @@ imports:
- pkg/pathutil
- pkg/types
- name: github.com/davecgh/go-spew
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
subpackages:
- spew
- name: github.com/docker/distribution
version: 99cb7c0946d2f5a38015443e515dc916295064d7
version: 87917f30529e6a7fca8eaff2932424915fb11225
subpackages:
- context
- digest
@ -116,13 +116,13 @@ imports:
- types/time
- types/versions
- name: github.com/docker/go-connections
version: 988efe982fdecb46f01d53465878ff1f2ff411ce
version: 990a1a1a70b0da4c4cb70e117971a4f0babfbf1a
subpackages:
- nat
- sockets
- tlsconfig
- name: github.com/docker/go-units
version: f2145db703495b2e525c59662db69a7344b00bb8
version: f2d77a61e3c169b43402a0a1e84f06daf29b8190
- name: github.com/docker/libcompose
version: d1876c1d68527a49c0aac22a0b161acc7296b740
subpackages:
@ -141,7 +141,7 @@ imports:
- version
- yaml
- name: github.com/docker/libkv
version: 3fce6a0f26e07da3eac45796a8e255547a47a750
version: 35d3e2084c650109e7bcc7282655b1bc8ba924ff
subpackages:
- store
- store/boltdb
@ -151,13 +151,13 @@ imports:
- name: github.com/donovanhide/eventsource
version: fd1de70867126402be23c306e1ce32828455d85b
- name: github.com/elazarl/go-bindata-assetfs
version: 9a6736ed45b44bf3835afeebb3034b57ed329f3e
version: 57eb5e1fc594ad4b0b1dbea7b286d299e0cb43c2
- name: github.com/gambol99/go-marathon
version: a558128c87724cd7430060ef5aedf39f83937f55
- name: github.com/go-check/check
version: 11d3bc7aa68e238947792f30573146a3231fc0f1
version: 4f90aeace3a26ad7021961c297b22c42160c7b25
- name: github.com/gogo/protobuf
version: 89f1976ff373a3e549675d2f212c10f98b6c6316
version: e33835a643a970c11ac74f6333f5f6866387a101
subpackages:
- proto
- name: github.com/golang/glog
@ -167,17 +167,18 @@ imports:
subpackages:
- query
- name: github.com/gorilla/context
version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42
version: aed02d124ae4a0e94fea4541c8effd05bf0c8296
- name: github.com/hashicorp/consul
version: d5b7530ec593f1ec2a8f8a7c145bcadafa88b572
version: fce7d75609a04eeb9d4bf41c8dc592aac18fc97d
subpackages:
- api
- name: github.com/hashicorp/go-cleanhttp
version: ad28ea4487f05916463e2423a55166280e8254b5
version: 875fb671b3ddc66f8e2f0acc33829c8cb989a38d
- name: github.com/hashicorp/serf
version: b7a120a5fc494f6dd5e858f42fd0fd4022d6320f
version: 6c4672d66fc6312ddde18399262943e21175d831
subpackages:
- coordinate
- serf
- name: github.com/jarcoal/httpmock
version: 145b10d659265440f062c31ea15326166bae56ee
- name: github.com/libkermit/compose
@ -219,7 +220,7 @@ imports:
- name: github.com/miekg/dns
version: 5d001d020961ae1c184f9f8152fdc73810481677
- name: github.com/mitchellh/mapstructure
version: ca63d7c062ee3c9f34db231e352b60012b4fd0c1
version: d2dd0262208475919e1a362f675cfc0e7c10e905
- name: github.com/moul/http2curl
version: b1479103caacaa39319f75e7f57fc545287fca0d
- name: github.com/NYTimes/gziphandler
@ -227,11 +228,11 @@ imports:
- name: github.com/ogier/pflag
version: 45c278ab3607870051a2ea9040bb85fcb8557481
- name: github.com/opencontainers/runc
version: d9fec4c63b089ddfc267194ecb6cda58a13f072c
version: 1a81e9ab1f138c091fe5c86d0883f87716088527
subpackages:
- libcontainer/user
- name: github.com/parnurzeal/gorequest
version: 29ced6f360a5ac3823a3675b4e29fbab336cc186
version: 045012d33ef41ea146c1b675df9296d0dc1a212d
- name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages:
@ -239,22 +240,22 @@ imports:
- name: github.com/ryanuber/go-glob
version: 572520ed46dbddaed19ea3d9541bdd0494163693
- name: github.com/samuel/go-zookeeper
version: 87e1bca4477a3cc767ca71be023ced183d74e538
version: e64db453f3512cade908163702045e0f31137843
subpackages:
- zk
- name: github.com/Sirupsen/logrus
version: 3ec0642a7fb6488f65b06f9040adc67e3990296a
version: a283a10442df8dc09befd873fab202bf8a253d6a
- name: github.com/streamrail/concurrent-map
version: 8bf1e9bacbf65b10c81d0f4314cf2b1ebef728b5
version: 65a174a3a4188c0b7099acbc6cfa0c53628d3287
- name: github.com/stretchr/objx
version: cbeaeb16a013161a98496fad62933b1d21786672
- name: github.com/stretchr/testify
version: 976c720a22c8eb4eb6a0b4348ad85ad12491a506
version: d77da356e56a7428ad25149ca77381849a6a5232
subpackages:
- assert
- mock
- name: github.com/thoas/stats
version: 152b5d051953fdb6e45f14b6826962aadc032324
version: 79b768ff1780f4e5b0ed132e192bfeefe9f85a9c
- name: github.com/tv42/zbase32
version: 03389da7e0bf9844767f82690f4d68fc097a1306
- name: github.com/ugorji/go
@ -262,7 +263,7 @@ imports:
subpackages:
- codec
- name: github.com/unrolled/render
version: 3f4913244021dede105b62caecfb01f0c1eebeda
version: 198ad4d8b8a4612176b804ca10555b222a086b40
- name: github.com/vdemeester/docker-events
version: be74d4929ec1ad118df54349fda4b0cba60f849b
- name: github.com/vdemeester/shakers
@ -284,7 +285,7 @@ imports:
- name: github.com/vulcand/route
version: cb89d787ddbb1c5849a7ac9f79004c1fd12a4a32
- name: github.com/vulcand/vulcand
version: 643ca8acff8386e3b276f6feb8ba9b5893dbc4a2
version: 28a4e5c0892167589737b95ceecbcef00295be50
subpackages:
- conntracker
- plugin
@ -314,11 +315,11 @@ imports:
- name: gopkg.in/fsnotify.v1
version: a8a77c9133d2d6fd8334f3260d06f60e8d80a5fb
- name: gopkg.in/mgo.v2
version: 22287bab4379e1fbf6002fb4eb769888f3fb224c
version: 29cc868a5ca65f401ff318143f9408d02f4799cc
subpackages:
- bson
- name: gopkg.in/square/go-jose.v1
version: aa2e30fdd1fe9dd3394119af66451ae790d50e0d
version: e3f973b66b91445ec816dd7411ad1b6495a5a2fc
subpackages:
- cipher
- json
@ -336,9 +337,9 @@ testImports:
- name: github.com/libkermit/docker-check
version: cbe0ef03b3d23070eac4d00ba8828f2cc7f7e5a3
- name: github.com/spf13/pflag
version: 08b1a584251b5b62f458943640fc8ebd4d50aaa5
version: 5644820622454e71517561946e3d94b9f9db6842
- name: github.com/vbatts/tar-split
version: bd4c5d64c3e9297f410025a3b1bd0c58f659e721
version: 6810cedb21b2c3d0b9bb8f9af12ff2dc7a2f14df
subpackages:
- archive/tar
- tar/asm

View file

@ -252,6 +252,7 @@ func (provider *Docker) loadDockerConfig(containersInspected []dockerData) *type
"hasMaxConnLabels": provider.hasMaxConnLabels,
"getMaxConnAmount": provider.getMaxConnAmount,
"getMaxConnExtractorFunc": provider.getMaxConnExtractorFunc,
"getSticky": provider.getSticky,
"replace": replace,
}
@ -261,18 +262,27 @@ func (provider *Docker) loadDockerConfig(containersInspected []dockerData) *type
}, containersInspected).([]dockerData)
frontends := map[string][]dockerData{}
backends := map[string]dockerData{}
servers := map[string][]dockerData{}
for _, container := range filteredContainers {
frontendName := provider.getFrontendName(container)
frontends[frontendName] = append(frontends[frontendName], container)
backendName := provider.getBackend(container)
backends[backendName] = container
servers[backendName] = append(servers[backendName], container)
}
templateObjects := struct {
Containers []dockerData
Frontends map[string][]dockerData
Backends map[string]dockerData
Servers map[string][]dockerData
Domain string
}{
filteredContainers,
frontends,
backends,
servers,
provider.Domain,
}
@ -291,7 +301,9 @@ func (provider *Docker) hasCircuitBreakerLabel(container dockerData) bool {
}
func (provider *Docker) hasLoadBalancerLabel(container dockerData) bool {
if _, err := getLabel(container, "traefik.backend.loadbalancer.method"); err != nil {
_, errMethod := getLabel(container, "traefik.backend.loadbalancer.method")
_, errSticky := getLabel(container, "traefik.backend.loadbalancer.sticky")
if errMethod != nil && errSticky != nil {
return false
}
return true
@ -439,6 +451,13 @@ func (provider *Docker) getWeight(container dockerData) string {
return "1"
}
func (provider *Docker) getSticky(container dockerData) string {
if _, err := getLabel(container, "traefik.backend.loadbalancer.sticky"); err == nil {
return "true"
}
return "false"
}
func (provider *Docker) getDomain(container dockerData) string {
if label, err := getLabel(container, "traefik.domain"); err == nil {
return label

View file

@ -940,7 +940,6 @@ func TestDockerLoadDockerConfig(t *testing.T) {
},
},
CircuitBreaker: nil,
LoadBalancer: nil,
},
},
},
@ -1027,7 +1026,6 @@ func TestDockerLoadDockerConfig(t *testing.T) {
},
},
CircuitBreaker: nil,
LoadBalancer: nil,
},
},
},

View file

@ -138,6 +138,7 @@ func (provider *Marathon) loadMarathonConfig() *types.Configuration {
"getMaxConnAmount": provider.getMaxConnAmount,
"getLoadBalancerMethod": provider.getLoadBalancerMethod,
"getCircuitBreakerExpression": provider.getCircuitBreakerExpression,
"getSticky": provider.getSticky,
}
applications, err := provider.marathonClient.Applications(nil)
@ -347,6 +348,13 @@ func (provider *Marathon) getProtocol(task marathon.Task, applications []maratho
return "http"
}
func (provider *Marathon) getSticky(application marathon.Application) string {
if sticky, err := provider.getLabel(application, "traefik.backend.loadbalancer.sticky"); err == nil {
return sticky
}
return "false"
}
func (provider *Marathon) getPassHostHeader(application marathon.Application) string {
if passHostHeader, err := provider.getLabel(application, "traefik.frontend.passHostHeader"); err == nil {
return passHostHeader
@ -411,7 +419,9 @@ func (provider *Marathon) hasCircuitBreakerLabels(application marathon.Applicati
}
func (provider *Marathon) hasLoadBalancerLabels(application marathon.Application) bool {
if _, err := provider.getLabel(application, "traefik.backend.loadbalancer.method"); err != nil {
_, errMethod := provider.getLabel(application, "traefik.backend.loadbalancer.method")
_, errSticky := provider.getLabel(application, "traefik.backend.loadbalancer.sticky")
if errMethod != nil && errSticky != nil {
return false
}
return true

View file

@ -107,7 +107,6 @@ func TestMarathonLoadConfig(t *testing.T) {
},
},
CircuitBreaker: nil,
LoadBalancer: nil,
},
},
},

View file

@ -537,16 +537,30 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
log.Errorf("Skipping frontend %s...", frontendName)
continue frontend
}
lbMethod, err := types.NewLoadBalancerMethod(configuration.Backends[frontend.Backend].LoadBalancer)
if err != nil {
log.Errorf("Error loading load balancer method '%+v' for frontend %s: %v", configuration.Backends[frontend.Backend].LoadBalancer, frontendName, err)
log.Errorf("Skipping frontend %s...", frontendName)
continue frontend
}
stickysession := configuration.Backends[frontend.Backend].LoadBalancer.Sticky
cookiename := "_TRAEFIK_BACKEND"
var sticky *roundrobin.StickySession
if stickysession {
sticky = roundrobin.NewStickySession(cookiename)
}
switch lbMethod {
case types.Drr:
log.Debugf("Creating load-balancer drr")
rebalancer, _ := roundrobin.NewRebalancer(rr, roundrobin.RebalancerLogger(oxyLogger))
if stickysession {
log.Debugf("Sticky session with cookie %v", cookiename)
rebalancer, _ = roundrobin.NewRebalancer(rr, roundrobin.RebalancerLogger(oxyLogger), roundrobin.RebalancerStickySession(sticky))
}
lb = rebalancer
for serverName, server := range configuration.Backends[frontend.Backend].Servers {
url, err := url.Parse(server.URL)
@ -565,6 +579,10 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
}
case types.Wrr:
log.Debugf("Creating load-balancer wrr")
if stickysession {
log.Debugf("Sticky session with cookie %v", cookiename)
rr, _ = roundrobin.New(saveBackend, roundrobin.EnableStickySession(sticky))
}
lb = rr
for serverName, server := range configuration.Backends[frontend.Backend].Servers {
url, err := url.Parse(server.URL)

View file

@ -1,23 +1,29 @@
[backends]{{range .Containers}}
{{if hasCircuitBreakerLabel .}}
[backends.backend-{{getBackend .}}.circuitbreaker]
expression = "{{getCircuitBreakerExpression .}}"
{{$backendServers := .Servers}}
[backends]{{range $backendName, $backend := .Backends}}
{{if hasCircuitBreakerLabel $backend}}
[backends.backend-{{$backendName}}.circuitbreaker]
expression = "{{getCircuitBreakerExpression $backend}}"
{{end}}
{{if hasLoadBalancerLabel .}}
[backends.backend-{{getBackend .}}.loadbalancer]
method = "{{getLoadBalancerMethod .}}"
{{if hasLoadBalancerLabel $backend}}
[backends.backend-{{$backendName}}.loadbalancer]
method = "{{getLoadBalancerMethod $backend}}"
sticky = {{getSticky $backend}}
{{end}}
{{if hasMaxConnLabels .}}
[backends.backend-{{getBackend .}}.maxconn]
amount = {{getMaxConnAmount . }}
extractorfunc = "{{getMaxConnExtractorFunc . }}"
{{if hasMaxConnLabels $backend}}
[backends.backend-{{$backendName}}.maxconn]
amount = {{getMaxConnAmount $backend}}
extractorfunc = "{{getMaxConnExtractorFunc $backend}}"
{{end}}
{{$servers := index $backendServers $backendName}}
{{range $serverName, $server := $servers}}
[backends.backend-{{$backendName}}.servers.server-{{$server.Name | replace "/" "" | replace "." "-"}}]
url = "{{getProtocol $server}}://{{getIPAddress $server}}:{{getPort $server}}"
weight = {{getWeight $server}}
{{end}}
[backends.backend-{{getBackend .}}.servers.server-{{.Name | replace "/" "" | replace "." "-"}}]
url = "{{getProtocol .}}://{{getIPAddress .}}:{{getPort .}}"
weight = {{getWeight .}}
{{end}}
[frontends]{{range $frontend, $containers := .Frontends}}

View file

@ -12,9 +12,11 @@
{{end}}
{{$loadBalancer := Get "" . "/loadbalancer/" "method"}}
{{$sticky := Get "false" . "/loadbalancer/" "sticky"}}
{{with $loadBalancer}}
[backends."{{Last $backend}}".loadBalancer]
method = "{{$loadBalancer}}"
sticky = {{$sticky}}
{{end}}
{{$maxConnAmt := Get "" . "/maxconn/" "amount"}}

View file

@ -14,6 +14,7 @@
{{ if hasLoadBalancerLabels . }}
[backends.backend{{getFrontendBackend . }}.loadbalancer]
method = "{{getLoadBalancerMethod . }}"
sticky = {{getSticky .}}
{{end}}
{{ if hasCircuitBreakerLabels . }}
[backends.backend{{getFrontendBackend . }}.circuitbreaker]

View file

@ -24,6 +24,7 @@ type MaxConn struct {
// LoadBalancer holds load balancing configuration.
type LoadBalancer struct {
Method string `json:"method,omitempty"`
Sticky bool `json:"sticky,omitempty"`
}
// CircuitBreaker holds circuit breaker configuration.