Add weighted round robin load balancer on TCP

Co-authored-by: Mathieu Lonjaret <mathieu.lonjaret@gmail.com>
This commit is contained in:
Julien Salleyron 2019-09-13 20:00:06 +02:00 committed by Traefiker Bot
parent 8e18d37b3d
commit 685c6dc00c
33 changed files with 787 additions and 239 deletions

View file

@ -404,13 +404,14 @@ http:
### General
Currently, `LoadBalancer` is the only supported kind of TCP `Service`.
However, since Traefik is an ever evolving project, other kind of TCP Services will be available in the future,
reason why you have to specify it.
Each of the fields of the service section represents a kind of service.
Which means, that for each specified service, one of the fields, and only one,
has to be enabled to define what kind of service is created.
Currently, the two available kinds are `LoadBalancer`, and `Weighted`.
### Load Balancer
### Servers Load Balancer
The load balancers are able to load balance the requests between multiple instances of your programs.
The servers load balancer is in charge of balancing the requests between the servers of the same service.
??? example "Declaring a Service with Two Servers -- Using the [File Provider](../../providers/file.md)"
@ -486,3 +487,54 @@ A negative value means an infinite deadline (i.e. the connection is never fully
loadBalancer:
terminationDelay: 200
```
### Weighted
The Weighted Round Robin (alias `WRR`) load-balancer of services is in charge of balancing the requests between multiple services based on provided weights.
This strategy is only available to load balance between [services](./index.md) and not between [servers](./index.md#servers).
This strategy can only be defined with [File](../../providers/file.md).
```toml tab="TOML"
[tcp.services]
[tcp.services.app]
[[tcp.services.app.weighted.services]]
name = "appv1"
weight = 3
[[tcp.services.app.weighted.services]]
name = "appv2"
weight = 1
[tcp.services.appv1]
[tcp.services.appv1.loadBalancer]
[[tcp.services.appv1.loadBalancer.servers]]
address = "private-ip-server-1/:8080"
[tcp.services.appv2]
[tcp.services.appv2.loadBalancer]
[[tcp.services.appv2.loadBalancer.servers]]
address = "private-ip-server-2/:8080"
```
```yaml tab="YAML"
tcp:
services:
app:
weighted:
services:
- name: appv1
weight: 3
- name: appv2
weight: 1
appv1:
loadBalancer:
servers:
- address: "xxx.xxx.xxx.xxx:8080"
appv2:
loadBalancer:
servers:
- address: "xxx.xxx.xxx.xxx:8080"
```

View file

@ -0,0 +1,42 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[entryPoints]
[entryPoints.tcp]
address = ":8093"
[api]
insecure = true
[providers.file]
filename = "{{ .SelfFilename }}"
## dynamic configuration ##
[tcp]
[tcp.routers]
[tcp.routers.to-whoami-a]
rule = "HostSNI(`whoami-a.test`)"
service = "whoami"
entryPoints = [ "tcp" ]
[tcp.routers.to-whoami-a.tls]
passthrough=true
[[tcp.services.whoami.weighted.services]]
name="whoami-a"
weight=3
[[tcp.services.whoami.weighted.services]]
name="whoami-b"
weight=1
[tcp.services.whoami-a.loadBalancer]
[[tcp.services.whoami-a.loadBalancer.servers]]
address = "localhost:8081"
[tcp.services.whoami-b.loadBalancer]
[[tcp.services.whoami-b.loadBalancer.servers]]
address = "localhost:8082"

View file

@ -79,7 +79,7 @@ func (s *RestSuite) TestSimpleConfigurationInsecure(c *check.C) {
},
Services: map[string]*dynamic.TCPService{
"service1": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress + ":80",
@ -183,7 +183,7 @@ func (s *RestSuite) TestSimpleConfiguration(c *check.C) {
},
Services: map[string]*dynamic.TCPService{
"service1": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress + ":80",

View file

@ -6,6 +6,7 @@ import (
"net/http"
"net/http/httptest"
"os"
"strings"
"time"
"github.com/containous/traefik/v2/integration/try"
@ -266,3 +267,35 @@ func guessWhoTLSMaxVersion(addr, serverName string, tlsCall bool, tlsMaxVersion
return string(out[:n]), nil
}
func (s *TCPSuite) TestWRR(c *check.C) {
file := s.adaptFile(c, "fixtures/tcp/wrr.toml", struct{}{})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI"))
c.Assert(err, checker.IsNil)
call := map[string]int{}
for i := 0; i < 4; i++ {
// Traefik passes through, termination handled by whoami-a
out, err := guessWho("127.0.0.1:8093", "whoami-a.test", true)
c.Assert(err, checker.IsNil)
switch {
case strings.Contains(out, "whoami-a"):
call["whoami-a"]++
case strings.Contains(out, "whoami-b"):
call["whoami-b"]++
default:
call["unknown"]++
}
}
c.Assert(call, checker.DeepEquals, map[string]int{"whoami-a": 3, "whoami-b": 1})
}

View file

@ -136,7 +136,7 @@ func TestHandler_Overview(t *testing.T) {
TCPServices: map[string]*runtime.TCPServiceInfo{
"tcpfoo-service@myprovider": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1",
@ -148,7 +148,7 @@ func TestHandler_Overview(t *testing.T) {
},
"tcpbar-service@myprovider": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.2",
@ -160,7 +160,7 @@ func TestHandler_Overview(t *testing.T) {
},
"tcpfii-service@myprovider": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.2",

View file

@ -255,7 +255,7 @@ func TestHandler_TCP(t *testing.T) {
TCPServices: map[string]*runtime.TCPServiceInfo{
"bar@myprovider": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1:2345",
@ -268,7 +268,7 @@ func TestHandler_TCP(t *testing.T) {
},
"baz@myprovider": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.2:2345",
@ -281,7 +281,7 @@ func TestHandler_TCP(t *testing.T) {
},
"foz@myprovider": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.2:2345",
@ -307,7 +307,7 @@ func TestHandler_TCP(t *testing.T) {
TCPServices: map[string]*runtime.TCPServiceInfo{
"bar@myprovider": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1:2345",
@ -320,7 +320,7 @@ func TestHandler_TCP(t *testing.T) {
},
"baz@myprovider": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.2:2345",
@ -333,7 +333,7 @@ func TestHandler_TCP(t *testing.T) {
},
"foz@myprovider": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.2:2345",
@ -359,7 +359,7 @@ func TestHandler_TCP(t *testing.T) {
TCPServices: map[string]*runtime.TCPServiceInfo{
"bar@myprovider": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1:2345",
@ -372,7 +372,7 @@ func TestHandler_TCP(t *testing.T) {
},
"baz@myprovider": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.2:2345",
@ -385,7 +385,7 @@ func TestHandler_TCP(t *testing.T) {
},
"foz@myprovider": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.2:2345",
@ -411,7 +411,7 @@ func TestHandler_TCP(t *testing.T) {
TCPServices: map[string]*runtime.TCPServiceInfo{
"bar@myprovider": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1:2345",
@ -423,7 +423,7 @@ func TestHandler_TCP(t *testing.T) {
},
"baz@myprovider": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.2:2345",
@ -435,7 +435,7 @@ func TestHandler_TCP(t *testing.T) {
},
"test@myprovider": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.3:2345",
@ -459,7 +459,7 @@ func TestHandler_TCP(t *testing.T) {
TCPServices: map[string]*runtime.TCPServiceInfo{
"bar@myprovider": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1:2345",
@ -483,7 +483,7 @@ func TestHandler_TCP(t *testing.T) {
TCPServices: map[string]*runtime.TCPServiceInfo{
"bar@myprovider": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1:2345",

View file

@ -91,7 +91,7 @@ func TestHandler_RawData(t *testing.T) {
TCPServices: map[string]*runtime.TCPServiceInfo{
"tcpfoo-service@myprovider": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1",

View file

@ -77,7 +77,7 @@ type WRRService struct {
Weight *int `json:"weight,omitempty" toml:"weight,omitempty" yaml:"weight,omitempty"`
}
// SetDefaults Default values for a ServersLoadBalancer.
// SetDefaults Default values for a WRRService.
func (w *WRRService) SetDefaults() {
defaultWeight := 1
w.Weight = &defaultWeight

View file

@ -18,7 +18,29 @@ type TCPConfiguration struct {
// TCPService holds a tcp service configuration (can only be of one type at the same time).
type TCPService struct {
LoadBalancer *TCPLoadBalancerService `json:"loadBalancer,omitempty" toml:"loadBalancer,omitempty" yaml:"loadBalancer,omitempty"`
LoadBalancer *TCPServersLoadBalancer `json:"loadBalancer,omitempty" toml:"loadBalancer,omitempty" yaml:"loadBalancer,omitempty"`
Weighted *TCPWeightedRoundRobin `json:"weighted,omitempty" toml:"weighted,omitempty" yaml:"weighted,omitempty" label:"-"`
}
// +k8s:deepcopy-gen=true
// TCPWeightedRoundRobin is a weighted round robin tcp load-balancer of services.
type TCPWeightedRoundRobin struct {
Services []TCPWRRService `json:"services,omitempty" toml:"services,omitempty" yaml:"services,omitempty"`
}
// +k8s:deepcopy-gen=true
// TCPWRRService is a reference to a tcp service load-balanced with weighted round robin.
type TCPWRRService struct {
Name string `json:"name,omitempty" toml:"name,omitempty" yaml:"name,omitempty"`
Weight *int `json:"weight,omitempty" toml:"weight,omitempty" yaml:"weight,omitempty"`
}
// SetDefaults Default values for a TCPWRRService.
func (w *TCPWRRService) SetDefaults() {
defaultWeight := 1
w.Weight = &defaultWeight
}
// +k8s:deepcopy-gen=true
@ -43,8 +65,8 @@ type RouterTCPTLSConfig struct {
// +k8s:deepcopy-gen=true
// TCPLoadBalancerService holds the LoadBalancerService configuration.
type TCPLoadBalancerService struct {
// TCPServersLoadBalancer holds the LoadBalancerService configuration.
type TCPServersLoadBalancer struct {
// TerminationDelay, corresponds to the deadline that the proxy sets, after one
// of its connected peers indicates it has closed the writing capability of its
// connection, to close the reading capability as well, hence fully terminating the
@ -54,14 +76,14 @@ type TCPLoadBalancerService struct {
Servers []TCPServer `json:"servers,omitempty" toml:"servers,omitempty" yaml:"servers,omitempty" label-slice-as-struct:"server"`
}
// SetDefaults Default values for a TCPLoadBalancerService
func (l *TCPLoadBalancerService) SetDefaults() {
// SetDefaults Default values for a TCPServersLoadBalancer
func (l *TCPServersLoadBalancer) SetDefaults() {
defaultTerminationDelay := 100 // in milliseconds
l.TerminationDelay = &defaultTerminationDelay
}
// Mergeable tells if the given service is mergeable.
func (l *TCPLoadBalancerService) Mergeable(loadBalancer *TCPLoadBalancerService) bool {
func (l *TCPServersLoadBalancer) Mergeable(loadBalancer *TCPServersLoadBalancer) bool {
savedServers := l.Servers
defer func() {
l.Servers = savedServers

View file

@ -1152,32 +1152,6 @@ func (in *TCPConfiguration) DeepCopy() *TCPConfiguration {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TCPLoadBalancerService) DeepCopyInto(out *TCPLoadBalancerService) {
*out = *in
if in.TerminationDelay != nil {
in, out := &in.TerminationDelay, &out.TerminationDelay
*out = new(int)
**out = **in
}
if in.Servers != nil {
in, out := &in.Servers, &out.Servers
*out = make([]TCPServer, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPLoadBalancerService.
func (in *TCPLoadBalancerService) DeepCopy() *TCPLoadBalancerService {
if in == nil {
return nil
}
out := new(TCPLoadBalancerService)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TCPRouter) DeepCopyInto(out *TCPRouter) {
*out = *in
@ -1220,12 +1194,43 @@ func (in *TCPServer) DeepCopy() *TCPServer {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TCPServersLoadBalancer) DeepCopyInto(out *TCPServersLoadBalancer) {
*out = *in
if in.TerminationDelay != nil {
in, out := &in.TerminationDelay, &out.TerminationDelay
*out = new(int)
**out = **in
}
if in.Servers != nil {
in, out := &in.Servers, &out.Servers
*out = make([]TCPServer, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPServersLoadBalancer.
func (in *TCPServersLoadBalancer) DeepCopy() *TCPServersLoadBalancer {
if in == nil {
return nil
}
out := new(TCPServersLoadBalancer)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TCPService) DeepCopyInto(out *TCPService) {
*out = *in
if in.LoadBalancer != nil {
in, out := &in.LoadBalancer, &out.LoadBalancer
*out = new(TCPLoadBalancerService)
*out = new(TCPServersLoadBalancer)
(*in).DeepCopyInto(*out)
}
if in.Weighted != nil {
in, out := &in.Weighted, &out.Weighted
*out = new(TCPWeightedRoundRobin)
(*in).DeepCopyInto(*out)
}
return
@ -1241,6 +1246,50 @@ func (in *TCPService) DeepCopy() *TCPService {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TCPWRRService) DeepCopyInto(out *TCPWRRService) {
*out = *in
if in.Weight != nil {
in, out := &in.Weight, &out.Weight
*out = new(int)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPWRRService.
func (in *TCPWRRService) DeepCopy() *TCPWRRService {
if in == nil {
return nil
}
out := new(TCPWRRService)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TCPWeightedRoundRobin) DeepCopyInto(out *TCPWeightedRoundRobin) {
*out = *in
if in.Services != nil {
in, out := &in.Services, &out.Services
*out = make([]TCPWRRService, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPWeightedRoundRobin.
func (in *TCPWeightedRoundRobin) DeepCopy() *TCPWeightedRoundRobin {
if in == nil {
return nil
}
out := new(TCPWeightedRoundRobin)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TLSCLientCertificateDNInfo) DeepCopyInto(out *TLSCLientCertificateDNInfo) {
*out = *in

View file

@ -208,7 +208,7 @@ func TestDecodeConfiguration(t *testing.T) {
},
Services: map[string]*dynamic.TCPService{
"Service0": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Port: "42",
@ -218,7 +218,7 @@ func TestDecodeConfiguration(t *testing.T) {
},
},
"Service1": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Port: "42",
@ -614,7 +614,7 @@ func TestEncodeConfiguration(t *testing.T) {
},
Services: map[string]*dynamic.TCPService{
"Service0": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Port: "42",
@ -623,7 +623,7 @@ func TestEncodeConfiguration(t *testing.T) {
},
},
"Service1": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Port: "42",

View file

@ -488,7 +488,7 @@ func TestPopulateUsedBy(t *testing.T) {
TCPServices: map[string]*runtime.TCPServiceInfo{
"foo-service@myprovider": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1",
@ -522,7 +522,7 @@ func TestPopulateUsedBy(t *testing.T) {
TCPServices: map[string]*runtime.TCPServiceInfo{
"foo-service@myprovider": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1",
@ -598,7 +598,7 @@ func TestPopulateUsedBy(t *testing.T) {
TCPServices: map[string]*runtime.TCPServiceInfo{
"foo-service@myprovider": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1",
@ -614,7 +614,7 @@ func TestPopulateUsedBy(t *testing.T) {
},
"bar-service@myprovider": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1",

View file

@ -78,7 +78,7 @@ func (p *Provider) buildTCPServiceConfiguration(ctx context.Context, container d
if len(configuration.Services) == 0 {
configuration.Services = make(map[string]*dynamic.TCPService)
lb := &dynamic.TCPLoadBalancerService{}
lb := &dynamic.TCPServersLoadBalancer{}
lb.SetDefaults()
configuration.Services[serviceName] = &dynamic.TCPService{
LoadBalancer: lb,
@ -145,7 +145,7 @@ func (p *Provider) keepContainer(ctx context.Context, container dockerData) bool
return true
}
func (p *Provider) addServerTCP(ctx context.Context, container dockerData, loadBalancer *dynamic.TCPLoadBalancerService) error {
func (p *Provider) addServerTCP(ctx context.Context, container dockerData, loadBalancer *dynamic.TCPServersLoadBalancer) error {
serverPort := ""
if loadBalancer != nil && len(loadBalancer.Servers) > 0 {
serverPort = loadBalancer.Servers[0].Port

View file

@ -2088,7 +2088,7 @@ func Test_buildConfiguration(t *testing.T) {
},
Services: map[string]*dynamic.TCPService{
"Test": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1:80",
@ -2133,7 +2133,7 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{
"Test": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1:80",
@ -2188,7 +2188,7 @@ func Test_buildConfiguration(t *testing.T) {
},
Services: map[string]*dynamic.TCPService{
"foo": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1:8080",
@ -2264,7 +2264,7 @@ func Test_buildConfiguration(t *testing.T) {
},
Services: map[string]*dynamic.TCPService{
"foo": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1:8080",
@ -2331,7 +2331,7 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{
"foo": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1:8080",
@ -2377,7 +2377,7 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{
"foo": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1:8080",

View file

@ -0,0 +1,16 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: HostSNI(`foo.com`)
services:
- name: whoamitcp
port: 8000
terminationDelay: 500

View file

@ -13,6 +13,7 @@ spec:
services:
- name: whoamitcp
port: 8000
weight: 2
- name: whoamitcp2
port: 8080
weight: 3

View file

@ -73,6 +73,8 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
continue
}
// If there is only one service defined, we skip the creation of the load balancer of services,
// i.e. the service on top is directly a load balancer of servers.
if len(route.Services) == 1 {
conf.Services[serviceName] = balancerServerHTTP
break

View file

@ -49,9 +49,16 @@ func (p *Provider) loadIngressRouteTCPConfiguration(ctx context.Context, client
continue
}
var allServers []dynamic.TCPServer
key, err := makeServiceKey(route.Match, ingressName)
if err != nil {
logger.Error(err)
continue
}
serviceName := makeID(ingressRouteTCP.Namespace, key)
for _, service := range route.Services {
servers, err := loadTCPServers(client, ingressRouteTCP.Namespace, service)
balancerServerTCP, err := createLoadBalancerServerTCP(client, ingressRouteTCP.Namespace, service)
if err != nil {
logger.
WithField("serviceName", service.Name).
@ -60,16 +67,28 @@ func (p *Provider) loadIngressRouteTCPConfiguration(ctx context.Context, client
continue
}
allServers = append(allServers, servers...)
// If there is only one service defined, we skip the creation of the load balancer of services,
// i.e. the service on top is directly a load balancer of servers.
if len(route.Services) == 1 {
conf.Services[serviceName] = balancerServerTCP
break
}
key, e := makeServiceKey(route.Match, ingressName)
if e != nil {
logger.Error(e)
continue
serviceKey := fmt.Sprintf("%s-%s-%d", serviceName, service.Name, service.Port)
conf.Services[serviceKey] = balancerServerTCP
srv := dynamic.TCPWRRService{Name: serviceKey}
srv.SetDefaults()
if service.Weight != nil {
srv.Weight = service.Weight
}
if conf.Services[serviceName] == nil {
conf.Services[serviceName] = &dynamic.TCPService{Weighted: &dynamic.TCPWeightedRoundRobin{}}
}
conf.Services[serviceName].Weighted.Services = append(conf.Services[serviceName].Weighted.Services, srv)
}
serviceName := makeID(ingressRouteTCP.Namespace, key)
conf.Routers[serviceName] = &dynamic.TCPRouter{
EntryPoints: ingressRouteTCP.Spec.EntryPoints,
Rule: route.Match,
@ -83,7 +102,10 @@ func (p *Provider) loadIngressRouteTCPConfiguration(ctx context.Context, client
Domains: ingressRouteTCP.Spec.TLS.Domains,
}
if ingressRouteTCP.Spec.TLS.Options != nil && len(ingressRouteTCP.Spec.TLS.Options.Name) > 0 {
if ingressRouteTCP.Spec.TLS.Options == nil || len(ingressRouteTCP.Spec.TLS.Options.Name) == 0 {
continue
}
tlsOptionsName := ingressRouteTCP.Spec.TLS.Options.Name
// Is a Kubernetes CRD reference (i.e. not a cross-provider reference)
ns := ingressRouteTCP.Spec.TLS.Options.Namespace
@ -101,19 +123,32 @@ func (p *Provider) loadIngressRouteTCPConfiguration(ctx context.Context, client
conf.Routers[serviceName].TLS.Options = tlsOptionsName
}
}
conf.Services[serviceName] = &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
Servers: allServers,
},
}
}
}
return conf
}
func createLoadBalancerServerTCP(client Client, namespace string, service v1alpha1.ServiceTCP) (*dynamic.TCPService, error) {
servers, err := loadTCPServers(client, namespace, service)
if err != nil {
return nil, err
}
tcpService := &dynamic.TCPService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: servers,
},
}
if service.TerminationDelay != nil {
tcpService.LoadBalancer.TerminationDelay = service.TerminationDelay
}
return tcpService, nil
}
func loadTCPServers(client Client, namespace string, svc v1alpha1.ServiceTCP) ([]dynamic.TCPServer, error) {
service, exists, err := client.GetService(namespace, svc.Name)
if err != nil {

View file

@ -55,7 +55,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
},
Services: map[string]*dynamic.TCPService{
"default/test.route-fdd3e9338e47a45efefc": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
@ -92,7 +92,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
},
Services: map[string]*dynamic.TCPService{
"default/test.route-fdd3e9338e47a45efefc": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
@ -106,7 +106,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
},
},
"default/test.route-f44ce589164e656d231c": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
@ -130,7 +130,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
},
},
{
desc: "One ingress Route with two different services, their servers will merge",
desc: "One ingress Route with two different services",
paths: []string{"tcp/services.yml", "tcp/with_two_services.yml"},
expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{
@ -143,27 +143,44 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
},
Services: map[string]*dynamic.TCPService{
"default/test.route-fdd3e9338e47a45efefc": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
Weighted: &dynamic.TCPWeightedRoundRobin{
Services: []dynamic.TCPWRRService{
{
Name: "default/test.route-fdd3e9338e47a45efefc-whoamitcp-8000",
Weight: func(i int) *int { return &i }(2),
},
{
Name: "default/test.route-fdd3e9338e47a45efefc-whoamitcp2-8080",
Weight: func(i int) *int { return &i }(3),
},
},
},
},
"default/test.route-fdd3e9338e47a45efefc-whoamitcp-8000": {
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
},
"default/test.route-fdd3e9338e47a45efefc-whoamitcp2-8080": {
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.3:8080",
Port: "",
},
{
Address: "10.10.0.4:8080",
Port: "",
},
},
},
}},
},
},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
@ -247,7 +264,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
},
Services: map[string]*dynamic.TCPService{
"default/test.route-fdd3e9338e47a45efefc": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
@ -286,7 +303,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
},
Services: map[string]*dynamic.TCPService{
"default/test.route-fdd3e9338e47a45efefc": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
@ -345,7 +362,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
},
Services: map[string]*dynamic.TCPService{
"default/test.route-fdd3e9338e47a45efefc": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
@ -403,7 +420,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
},
Services: map[string]*dynamic.TCPService{
"default/test.route-fdd3e9338e47a45efefc": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
@ -460,7 +477,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
},
Services: map[string]*dynamic.TCPService{
"default/test.route-fdd3e9338e47a45efefc": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
@ -506,7 +523,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
},
Services: map[string]*dynamic.TCPService{
"default/test.route-fdd3e9338e47a45efefc": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
@ -552,7 +569,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
},
Services: map[string]*dynamic.TCPService{
"default/test.route-fdd3e9338e47a45efefc": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
@ -589,7 +606,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
},
Services: map[string]*dynamic.TCPService{
"default/test.route-fdd3e9338e47a45efefc": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
@ -612,6 +629,44 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "TCP with terminationDelay",
paths: []string{"tcp/services.yml", "tcp/with_termination_delay.yml"},
expected: &dynamic.Configuration{
TLS: &dynamic.TLSConfiguration{},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{
"default/test.route-fdd3e9338e47a45efefc": {
EntryPoints: []string{"foo"},
Service: "default/test.route-fdd3e9338e47a45efefc",
Rule: "HostSNI(`foo.com`)",
},
},
Services: map[string]*dynamic.TCPService{
"default/test.route-fdd3e9338e47a45efefc": {
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
TerminationDelay: Int(500),
},
},
},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
},
},
}
for _, test := range testCases {

View file

@ -46,6 +46,8 @@ type TLSOptionTCPRef struct {
type ServiceTCP struct {
Name string `json:"name"`
Port int32 `json:"port"`
Weight *int `json:"weight,omitempty"`
TerminationDelay *int `json:"terminationDelay,omitempty"`
}
// +genclient

View file

@ -612,7 +612,9 @@ func (in *RouteTCP) DeepCopyInto(out *RouteTCP) {
if in.Services != nil {
in, out := &in.Services, &out.Services
*out = make([]ServiceTCP, len(*in))
copy(*out, *in)
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
@ -666,6 +668,16 @@ func (in *Service) DeepCopy() *Service {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceTCP) DeepCopyInto(out *ServiceTCP) {
*out = *in
if in.Weight != nil {
in, out := &in.Weight, &out.Weight
*out = new(int)
**out = **in
}
if in.TerminationDelay != nil {
in, out := &in.TerminationDelay, &out.TerminationDelay
*out = new(int)
**out = **in
}
return
}

View file

@ -140,7 +140,7 @@ func (p *Provider) buildTCPServiceConfiguration(ctx context.Context, app maratho
if len(conf.Services) == 0 {
conf.Services = make(map[string]*dynamic.TCPService)
lb := &dynamic.TCPLoadBalancerService{}
lb := &dynamic.TCPServersLoadBalancer{}
lb.SetDefaults()
conf.Services[appName] = &dynamic.TCPService{
LoadBalancer: lb,

View file

@ -1236,7 +1236,7 @@ func TestBuildConfiguration(t *testing.T) {
},
Services: map[string]*dynamic.TCPService{
"app": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "localhost:80",
@ -1268,7 +1268,7 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{
"app": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "localhost:80",
@ -1308,7 +1308,7 @@ func TestBuildConfiguration(t *testing.T) {
},
Services: map[string]*dynamic.TCPService{
"foo": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "localhost:8080",
@ -1349,7 +1349,7 @@ func TestBuildConfiguration(t *testing.T) {
},
Services: map[string]*dynamic.TCPService{
"foo": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "localhost:8080",
@ -1390,7 +1390,7 @@ func TestBuildConfiguration(t *testing.T) {
},
Services: map[string]*dynamic.TCPService{
"foo": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "localhost:8080",

View file

@ -74,7 +74,7 @@ func (p *Provider) buildTCPServiceConfiguration(ctx context.Context, service ran
if len(configuration.Services) == 0 {
configuration.Services = make(map[string]*dynamic.TCPService)
lb := &dynamic.TCPLoadBalancerService{}
lb := &dynamic.TCPServersLoadBalancer{}
lb.SetDefaults()
configuration.Services[serviceName] = &dynamic.TCPService{
LoadBalancer: lb,
@ -146,7 +146,7 @@ func (p *Provider) keepService(ctx context.Context, service rancherData) bool {
return true
}
func (p *Provider) addServerTCP(ctx context.Context, service rancherData, loadBalancer *dynamic.TCPLoadBalancerService) error {
func (p *Provider) addServerTCP(ctx context.Context, service rancherData, loadBalancer *dynamic.TCPServersLoadBalancer) error {
log.FromContext(ctx).Debugf("Trying to add servers for service %s \n", service.Name)
serverPort := ""

View file

@ -508,7 +508,7 @@ func Test_buildConfiguration(t *testing.T) {
},
Services: map[string]*dynamic.TCPService{
"Test": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1:80",
@ -545,7 +545,7 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{
"Test": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1:80",
@ -588,7 +588,7 @@ func Test_buildConfiguration(t *testing.T) {
},
Services: map[string]*dynamic.TCPService{
"foo": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1:8080",
@ -634,7 +634,7 @@ func Test_buildConfiguration(t *testing.T) {
},
Services: map[string]*dynamic.TCPService{
"foo": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1:8080",
@ -693,7 +693,7 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{
"foo": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1:8080",
@ -731,7 +731,7 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{
"foo": {
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1:8080",

View file

@ -23,7 +23,7 @@ func TestRuntimeConfiguration(t *testing.T) {
serviceConfig: map[string]*runtime.TCPServiceInfo{
"foo-service": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Port: "8085",
@ -70,7 +70,7 @@ func TestRuntimeConfiguration(t *testing.T) {
serviceConfig: map[string]*runtime.TCPServiceInfo{
"foo-service": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1:80",
@ -104,7 +104,7 @@ func TestRuntimeConfiguration(t *testing.T) {
serviceConfig: map[string]*runtime.TCPServiceInfo{
"foo-service": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1:80",
@ -137,7 +137,7 @@ func TestRuntimeConfiguration(t *testing.T) {
serviceConfig: map[string]*runtime.TCPServiceInfo{
"foo-service": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1:80",

View file

@ -93,7 +93,9 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string, respons
}
}
if count > 1 {
return nil, errors.New("cannot create service: multi-types service not supported, consider declaring two different pieces of service instead")
err := errors.New("cannot create service: multi-types service not supported, consider declaring two different pieces of service instead")
conf.AddError(err, true)
return nil, err
}
var lb http.Handler

View file

@ -2,6 +2,7 @@ package tcp
import (
"context"
"errors"
"fmt"
"net"
"time"
@ -30,20 +31,21 @@ func (m *Manager) BuildTCP(rootCtx context.Context, serviceName string) (tcp.Han
ctx := internal.AddProviderInContext(rootCtx, serviceQualifiedName)
ctx = log.With(ctx, log.Str(log.ServiceName, serviceName))
// FIXME Check if the service is declared multiple times with different types
conf, ok := m.configs[serviceQualifiedName]
if !ok {
return nil, fmt.Errorf("the service %q does not exist", serviceQualifiedName)
}
if conf.LoadBalancer == nil {
err := fmt.Errorf("the service %q doesn't have any TCP load balancer", serviceQualifiedName)
if conf.LoadBalancer != nil && conf.Weighted != nil {
err := errors.New("cannot create service: multi-types service not supported, consider declaring two different pieces of service instead")
conf.AddError(err, true)
return nil, err
}
logger := log.FromContext(ctx)
loadBalancer := tcp.NewRRLoadBalancer()
switch {
case conf.LoadBalancer != nil:
loadBalancer := tcp.NewWRRLoadBalancer()
if conf.LoadBalancer.TerminationDelay == nil {
defaultTerminationDelay := 100
@ -67,4 +69,21 @@ func (m *Manager) BuildTCP(rootCtx context.Context, serviceName string) (tcp.Han
logger.WithField(log.ServerName, name).Debugf("Creating TCP server %d at %s", name, server.Address)
}
return loadBalancer, nil
case conf.Weighted != nil:
loadBalancer := tcp.NewWRRLoadBalancer()
for _, service := range conf.Weighted.Services {
handler, err := m.BuildTCP(rootCtx, service.Name)
if err != nil {
logger.Errorf("In service %q: %v", serviceQualifiedName, err)
return nil, err
}
loadBalancer.AddWeightServer(handler, service.Weight)
}
return loadBalancer, nil
default:
err := fmt.Errorf("the service %q doesn't have any TCP load balancer", serviceQualifiedName)
conf.AddError(err, true)
return nil, err
}
}

View file

@ -41,7 +41,7 @@ func TestManager_BuildTCP(t *testing.T) {
configs: map[string]*runtime.TCPServiceInfo{
"test": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{Address: "test:31"},
},
@ -56,7 +56,7 @@ func TestManager_BuildTCP(t *testing.T) {
configs: map[string]*runtime.TCPServiceInfo{
"test": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{Address: "foobar"},
},
@ -71,7 +71,7 @@ func TestManager_BuildTCP(t *testing.T) {
configs: map[string]*runtime.TCPServiceInfo{
"serviceName": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{},
LoadBalancer: &dynamic.TCPServersLoadBalancer{},
},
},
},
@ -82,7 +82,7 @@ func TestManager_BuildTCP(t *testing.T) {
configs: map[string]*runtime.TCPServiceInfo{
"serviceName@provider-1": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{},
LoadBalancer: &dynamic.TCPServersLoadBalancer{},
},
},
},
@ -93,7 +93,7 @@ func TestManager_BuildTCP(t *testing.T) {
configs: map[string]*runtime.TCPServiceInfo{
"serviceName@provider-1": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{},
LoadBalancer: &dynamic.TCPServersLoadBalancer{},
},
},
},
@ -105,7 +105,7 @@ func TestManager_BuildTCP(t *testing.T) {
configs: map[string]*runtime.TCPServiceInfo{
"serviceName@provider-1": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "foobar.com:80",
@ -123,7 +123,7 @@ func TestManager_BuildTCP(t *testing.T) {
configs: map[string]*runtime.TCPServiceInfo{
"serviceName@provider-1": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "192.168.0.12:80",
@ -141,7 +141,7 @@ func TestManager_BuildTCP(t *testing.T) {
configs: map[string]*runtime.TCPServiceInfo{
"serviceName@provider-1": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "foobar.com",
@ -159,7 +159,7 @@ func TestManager_BuildTCP(t *testing.T) {
configs: map[string]*runtime.TCPServiceInfo{
"serviceName@provider-1": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPLoadBalancerService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "192.168.0.12",

View file

@ -58,13 +58,14 @@ func (p Proxy) connCopy(dst, src WriteCloser, errCh chan error) {
errClose := dst.CloseWrite()
if errClose != nil {
log.WithoutContext().Errorf("Error while terminating connection: %v", errClose)
log.WithoutContext().Debugf("Error while terminating connection: %v", errClose)
return
}
if p.terminationDelay >= 0 {
err := dst.SetReadDeadline(time.Now().Add(p.terminationDelay))
if err != nil {
log.WithoutContext().Errorf("Error while setting deadline: %v", err)
log.WithoutContext().Debugf("Error while setting deadline: %v", err)
}
}
}

View file

@ -1,48 +0,0 @@
package tcp
import (
"sync"
"github.com/containous/traefik/v2/pkg/log"
)
// RRLoadBalancer is a naive RoundRobin load balancer for TCP services
type RRLoadBalancer struct {
servers []Handler
lock sync.RWMutex
current int
}
// NewRRLoadBalancer creates a new RRLoadBalancer
func NewRRLoadBalancer() *RRLoadBalancer {
return &RRLoadBalancer{}
}
// ServeTCP forwards the connection to the right service
func (r *RRLoadBalancer) ServeTCP(conn WriteCloser) {
if len(r.servers) == 0 {
log.WithoutContext().Error("no available server")
return
}
r.next().ServeTCP(conn)
}
// AddServer appends a server to the existing list
func (r *RRLoadBalancer) AddServer(server Handler) {
r.servers = append(r.servers, server)
}
func (r *RRLoadBalancer) next() Handler {
r.lock.Lock()
defer r.lock.Unlock()
if r.current >= len(r.servers) {
r.current = 0
log.WithoutContext().Debugf("Load balancer: going back to the first available server")
}
handler := r.servers[r.current]
r.current++
return handler
}

View file

@ -0,0 +1,122 @@
package tcp
import (
"fmt"
"sync"
"github.com/containous/traefik/v2/pkg/log"
)
type server struct {
Handler
weight int
}
// WRRLoadBalancer is a naive RoundRobin load balancer for TCP services
type WRRLoadBalancer struct {
servers []server
lock sync.RWMutex
currentWeight int
index int
}
// NewWRRLoadBalancer creates a new WRRLoadBalancer
func NewWRRLoadBalancer() *WRRLoadBalancer {
return &WRRLoadBalancer{
index: -1,
}
}
// ServeTCP forwards the connection to the right service
func (b *WRRLoadBalancer) ServeTCP(conn WriteCloser) {
if len(b.servers) == 0 {
log.WithoutContext().Error("no available server")
return
}
next, err := b.next()
if err != nil {
log.WithoutContext().Errorf("Error during load balancing: %v", err)
conn.Close()
}
next.ServeTCP(conn)
}
// AddServer appends a server to the existing list
func (b *WRRLoadBalancer) AddServer(serverHandler Handler) {
w := 1
b.AddWeightServer(serverHandler, &w)
}
// AddWeightServer appends a server to the existing list with a weight
func (b *WRRLoadBalancer) AddWeightServer(serverHandler Handler, weight *int) {
w := 1
if weight != nil {
w = *weight
}
b.servers = append(b.servers, server{Handler: serverHandler, weight: w})
}
func (b *WRRLoadBalancer) maxWeight() int {
max := -1
for _, s := range b.servers {
if s.weight > max {
max = s.weight
}
}
return max
}
func (b *WRRLoadBalancer) weightGcd() int {
divisor := -1
for _, s := range b.servers {
if divisor == -1 {
divisor = s.weight
} else {
divisor = gcd(divisor, s.weight)
}
}
return divisor
}
func gcd(a, b int) int {
for b != 0 {
a, b = b, a%b
}
return a
}
func (b *WRRLoadBalancer) next() (Handler, error) {
b.lock.Lock()
defer b.lock.Unlock()
if len(b.servers) == 0 {
return nil, fmt.Errorf("no servers in the pool")
}
// The algo below may look messy, but is actually very simple
// it calculates the GCD and subtracts it on every iteration, what interleaves servers
// and allows us not to build an iterator every time we readjust weights
// GCD across all enabled servers
gcd := b.weightGcd()
// Maximum weight across all enabled servers
max := b.maxWeight()
for {
b.index = (b.index + 1) % len(b.servers)
if b.index == 0 {
b.currentWeight -= gcd
if b.currentWeight <= 0 {
b.currentWeight = max
if b.currentWeight == 0 {
return nil, fmt.Errorf("all servers have 0 weight")
}
}
}
srv := b.servers[b.index]
if srv.weight >= b.currentWeight {
return srv, nil
}
}
}

View file

@ -0,0 +1,131 @@
package tcp
import (
"net"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type fakeConn struct {
call map[string]int
}
func (f *fakeConn) Read(b []byte) (n int, err error) {
panic("implement me")
}
func (f *fakeConn) Write(b []byte) (n int, err error) {
f.call[string(b)]++
return len(b), nil
}
func (f *fakeConn) Close() error {
panic("implement me")
}
func (f *fakeConn) LocalAddr() net.Addr {
panic("implement me")
}
func (f *fakeConn) RemoteAddr() net.Addr {
panic("implement me")
}
func (f *fakeConn) SetDeadline(t time.Time) error {
panic("implement me")
}
func (f *fakeConn) SetReadDeadline(t time.Time) error {
panic("implement me")
}
func (f *fakeConn) SetWriteDeadline(t time.Time) error {
panic("implement me")
}
func (f *fakeConn) CloseWrite() error {
panic("implement me")
}
func TestLoadBalancing(t *testing.T) {
testCases := []struct {
desc string
serversWeight map[string]int
totalCall int
expected map[string]int
}{
{
desc: "RoundRobin",
serversWeight: map[string]int{
"h1": 1,
"h2": 1,
},
totalCall: 4,
expected: map[string]int{
"h1": 2,
"h2": 2,
},
},
{
desc: "WeighedRoundRobin",
serversWeight: map[string]int{
"h1": 3,
"h2": 1,
},
totalCall: 4,
expected: map[string]int{
"h1": 3,
"h2": 1,
},
},
{
desc: "WeighedRoundRobin with more call",
serversWeight: map[string]int{
"h1": 3,
"h2": 1,
},
totalCall: 16,
expected: map[string]int{
"h1": 12,
"h2": 4,
},
},
{
desc: "WeighedRoundRobin with 0 weight server",
serversWeight: map[string]int{
"h1": 3,
"h2": 0,
},
totalCall: 16,
expected: map[string]int{
"h1": 16,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
balancer := NewWRRLoadBalancer()
for server, weight := range test.serversWeight {
server := server
balancer.AddWeightServer(HandlerFunc(func(conn WriteCloser) {
_, err := conn.Write([]byte(server))
require.NoError(t, err)
}), &weight)
}
conn := &fakeConn{call: make(map[string]int)}
for i := 0; i < test.totalCall; i++ {
balancer.ServeTCP(conn)
}
assert.Equal(t, test.expected, conn.call)
})
}
}