Add weighted round robin load balancer on TCP
Co-authored-by: Mathieu Lonjaret <mathieu.lonjaret@gmail.com>
This commit is contained in:
parent
8e18d37b3d
commit
685c6dc00c
33 changed files with 787 additions and 239 deletions
|
@ -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
|
|
@ -13,6 +13,7 @@ spec:
|
|||
services:
|
||||
- name: whoamitcp
|
||||
port: 8000
|
||||
weight: 2
|
||||
- name: whoamitcp2
|
||||
port: 8080
|
||||
|
||||
weight: 3
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
key, e := makeServiceKey(route.Match, ingressName)
|
||||
if e != nil {
|
||||
logger.Error(e)
|
||||
continue
|
||||
}
|
||||
|
||||
serviceName := makeID(ingressRouteTCP.Namespace, key)
|
||||
conf.Routers[serviceName] = &dynamic.TCPRouter{
|
||||
EntryPoints: ingressRouteTCP.Spec.EntryPoints,
|
||||
Rule: route.Match,
|
||||
|
@ -83,37 +102,53 @@ 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 {
|
||||
tlsOptionsName := ingressRouteTCP.Spec.TLS.Options.Name
|
||||
// Is a Kubernetes CRD reference (i.e. not a cross-provider reference)
|
||||
ns := ingressRouteTCP.Spec.TLS.Options.Namespace
|
||||
if !strings.Contains(tlsOptionsName, "@") {
|
||||
if len(ns) == 0 {
|
||||
ns = ingressRouteTCP.Namespace
|
||||
}
|
||||
tlsOptionsName = makeID(ns, tlsOptionsName)
|
||||
} else if len(ns) > 0 {
|
||||
logger.
|
||||
WithField("TLSoptions", ingressRouteTCP.Spec.TLS.Options.Name).
|
||||
Warnf("namespace %q is ignored in cross-provider context", ns)
|
||||
}
|
||||
|
||||
conf.Routers[serviceName].TLS.Options = tlsOptionsName
|
||||
|
||||
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
|
||||
if !strings.Contains(tlsOptionsName, "@") {
|
||||
if len(ns) == 0 {
|
||||
ns = ingressRouteTCP.Namespace
|
||||
}
|
||||
tlsOptionsName = makeID(ns, tlsOptionsName)
|
||||
} else if len(ns) > 0 {
|
||||
logger.
|
||||
WithField("TLSoptions", ingressRouteTCP.Spec.TLS.Options.Name).
|
||||
Warnf("namespace %q is ignored in cross-provider context", ns)
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -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{
|
||||
Servers: []dynamic.TCPServer{
|
||||
Weighted: &dynamic.TCPWeightedRoundRobin{
|
||||
Services: []dynamic.TCPWRRService{
|
||||
{
|
||||
Address: "10.10.0.1:8000",
|
||||
Port: "",
|
||||
Name: "default/test.route-fdd3e9338e47a45efefc-whoamitcp-8000",
|
||||
Weight: func(i int) *int { return &i }(2),
|
||||
},
|
||||
{
|
||||
Address: "10.10.0.2:8000",
|
||||
Port: "",
|
||||
},
|
||||
{
|
||||
Address: "10.10.0.3:8080",
|
||||
Port: "",
|
||||
},
|
||||
{
|
||||
Address: "10.10.0.4:8080",
|
||||
Port: "",
|
||||
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",
|
||||
},
|
||||
{
|
||||
Address: "10.10.0.2:8000",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default/test.route-fdd3e9338e47a45efefc-whoamitcp2-8080": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "10.10.0.3:8080",
|
||||
},
|
||||
{
|
||||
Address: "10.10.0.4:8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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 {
|
||||
|
|
|
@ -44,8 +44,10 @@ type TLSOptionTCPRef struct {
|
|||
|
||||
// ServiceTCP defines an upstream to proxy traffic.
|
||||
type ServiceTCP struct {
|
||||
Name string `json:"name"`
|
||||
Port int32 `json:"port"`
|
||||
Name string `json:"name"`
|
||||
Port int32 `json:"port"`
|
||||
Weight *int `json:"weight,omitempty"`
|
||||
TerminationDelay *int `json:"terminationDelay,omitempty"`
|
||||
}
|
||||
|
||||
// +genclient
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue