Native Kubernetes service load-balancing at the provider level

This commit is contained in:
Prajith 2024-04-29 15:50:04 +05:30 committed by GitHub
parent 73e5dbbfe5
commit 8d2a2ff08f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 642 additions and 50 deletions

View file

@ -337,6 +337,30 @@ providers:
--providers.kubernetescrd.allowexternalnameservices=true --providers.kubernetescrd.allowexternalnameservices=true
``` ```
### `nativeLBByDefault`
_Optional, Default: false_
Defines whether to use Native Kubernetes load-balancing mode by default.
For more information, please check out the IngressRoute `nativeLB` option [documentation](../routing/providers/kubernetes-crd.md#load-balancing).
```yaml tab="File (YAML)"
providers:
kubernetesCRD:
nativeLBByDefault: true
# ...
```
```toml tab="File (TOML)"
[providers.kubernetesCRD]
nativeLBByDefault = true
# ...
```
```bash tab="CLI"
--providers.kubernetescrd.nativeLBByDefault=true
```
## Full Example ## Full Example
For additional information, refer to the [full example](../user-guides/crd-acme/index.md) with Let's Encrypt. For additional information, refer to the [full example](../user-guides/crd-acme/index.md) with Let's Encrypt.

View file

@ -467,6 +467,30 @@ providers:
--providers.kubernetesingress.allowexternalnameservices=true --providers.kubernetesingress.allowexternalnameservices=true
``` ```
### `nativeLBByDefault`
_Optional, Default: false_
Defines whether to use Native Kubernetes load-balancing mode by default.
For more information, please check out the `traefik.ingress.kubernetes.io/service.nativelb` [service annotation documentation](../routing/providers/kubernetes-ingress.md#on-service).
```yaml tab="File (YAML)"
providers:
kubernetesIngress:
nativeLBByDefault: true
# ...
```
```toml tab="File (TOML)"
[providers.kubernetesIngress]
nativeLBByDefault = true
# ...
```
```bash tab="CLI"
--providers.kubernetesingress.nativeLBByDefault=true
```
### Further ### Further
To learn more about the various aspects of the Ingress specification that Traefik supports, To learn more about the various aspects of the Ingress specification that Traefik supports,

View file

@ -714,6 +714,9 @@ Kubernetes label selector to use.
`--providers.kubernetescrd.namespaces`: `--providers.kubernetescrd.namespaces`:
Kubernetes namespaces. Kubernetes namespaces.
`--providers.kubernetescrd.nativelbbydefault`:
Defines whether to use Native Kubernetes load-balancing mode by default. (Default: ```false```)
`--providers.kubernetescrd.throttleduration`: `--providers.kubernetescrd.throttleduration`:
Ingress refresh throttle duration (Default: ```0```) Ingress refresh throttle duration (Default: ```0```)
@ -795,6 +798,9 @@ Kubernetes Ingress label selector to use.
`--providers.kubernetesingress.namespaces`: `--providers.kubernetesingress.namespaces`:
Kubernetes namespaces. Kubernetes namespaces.
`--providers.kubernetesingress.nativelbbydefault`:
Defines whether to use Native Kubernetes load-balancing mode by default. (Default: ```false```)
`--providers.kubernetesingress.throttleduration`: `--providers.kubernetesingress.throttleduration`:
Ingress refresh throttle duration (Default: ```0```) Ingress refresh throttle duration (Default: ```0```)

View file

@ -714,6 +714,9 @@ Kubernetes label selector to use.
`TRAEFIK_PROVIDERS_KUBERNETESCRD_NAMESPACES`: `TRAEFIK_PROVIDERS_KUBERNETESCRD_NAMESPACES`:
Kubernetes namespaces. Kubernetes namespaces.
`TRAEFIK_PROVIDERS_KUBERNETESCRD_NATIVELBBYDEFAULT`:
Defines whether to use Native Kubernetes load-balancing mode by default. (Default: ```false```)
`TRAEFIK_PROVIDERS_KUBERNETESCRD_THROTTLEDURATION`: `TRAEFIK_PROVIDERS_KUBERNETESCRD_THROTTLEDURATION`:
Ingress refresh throttle duration (Default: ```0```) Ingress refresh throttle duration (Default: ```0```)
@ -795,6 +798,9 @@ Kubernetes Ingress label selector to use.
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_NAMESPACES`: `TRAEFIK_PROVIDERS_KUBERNETESINGRESS_NAMESPACES`:
Kubernetes namespaces. Kubernetes namespaces.
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_NATIVELBBYDEFAULT`:
Defines whether to use Native Kubernetes load-balancing mode by default. (Default: ```false```)
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_THROTTLEDURATION`: `TRAEFIK_PROVIDERS_KUBERNETESINGRESS_THROTTLEDURATION`:
Ingress refresh throttle duration (Default: ```0```) Ingress refresh throttle duration (Default: ```0```)

View file

@ -124,6 +124,7 @@
allowEmptyServices = true allowEmptyServices = true
allowExternalNameServices = true allowExternalNameServices = true
disableIngressClassLookup = true disableIngressClassLookup = true
nativeLBByDefault = true
[providers.kubernetesIngress.ingressEndpoint] [providers.kubernetesIngress.ingressEndpoint]
ip = "foobar" ip = "foobar"
hostname = "foobar" hostname = "foobar"
@ -139,6 +140,7 @@
ingressClass = "foobar" ingressClass = "foobar"
throttleDuration = "42s" throttleDuration = "42s"
allowEmptyServices = true allowEmptyServices = true
nativeLBByDefault = true
[providers.kubernetesGateway] [providers.kubernetesGateway]
endpoint = "foobar" endpoint = "foobar"
token = "foobar" token = "foobar"

View file

@ -141,6 +141,7 @@ providers:
allowEmptyServices: true allowEmptyServices: true
allowExternalNameServices: true allowExternalNameServices: true
disableIngressClassLookup: true disableIngressClassLookup: true
nativeLBByDefault: true
kubernetesCRD: kubernetesCRD:
endpoint: foobar endpoint: foobar
token: foobar token: foobar
@ -154,6 +155,7 @@ providers:
ingressClass: foobar ingressClass: foobar
throttleDuration: 42s throttleDuration: 42s
allowEmptyServices: true allowEmptyServices: true
nativeLBByDefault: true
kubernetesGateway: kubernetesGateway:
endpoint: foobar endpoint: foobar
token: foobar token: foobar

View file

@ -0,0 +1,15 @@
apiVersion: traefik.io/v1alpha1
kind: IngressRouteTCP
metadata:
name: global-native-lb
namespace: default
spec:
entryPoints:
- foo
routes:
- match: HostSNI(`foo.com`)
services:
- name: native-svc-tcp
port: 8000

View file

@ -0,0 +1,14 @@
apiVersion: traefik.io/v1alpha1
kind: IngressRouteUDP
metadata:
name: global-native-lb
namespace: default
spec:
entryPoints:
- foo
routes:
- services:
- name: native-svc-udp
port: 8000

View file

@ -0,0 +1,16 @@
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: global-native-lb
namespace: default
spec:
entryPoints:
- foo
routes:
- match: Host(`foo.com`)
kind: Rule
services:
- name: native-svc
port: 80

View file

@ -60,6 +60,7 @@ type Provider struct {
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"` IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"`
ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"` ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
AllowEmptyServices bool `description:"Allow the creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"` AllowEmptyServices bool `description:"Allow the creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"`
NativeLBByDefault bool `description:"Defines whether to use Native Kubernetes load-balancing mode by default." json:"nativeLBByDefault,omitempty" toml:"nativeLBByDefault,omitempty" yaml:"nativeLBByDefault,omitempty" export:"true"`
lastConfiguration safe.Safe lastConfiguration safe.Safe

View file

@ -55,6 +55,7 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
allowCrossNamespace: p.AllowCrossNamespace, allowCrossNamespace: p.AllowCrossNamespace,
allowExternalNameServices: p.AllowExternalNameServices, allowExternalNameServices: p.AllowExternalNameServices,
allowEmptyServices: p.AllowEmptyServices, allowEmptyServices: p.AllowEmptyServices,
NativeLBByDefault: p.NativeLBByDefault,
} }
for _, route := range ingressRoute.Spec.Routes { for _, route := range ingressRoute.Spec.Routes {
@ -202,6 +203,7 @@ type configBuilder struct {
allowCrossNamespace bool allowCrossNamespace bool
allowExternalNameServices bool allowExternalNameServices bool
allowEmptyServices bool allowEmptyServices bool
NativeLBByDefault bool
} }
// buildTraefikService creates the configuration for the traefik service defined in tService, // buildTraefikService creates the configuration for the traefik service defined in tService,
@ -377,20 +379,6 @@ func (c configBuilder) loadServers(parentNamespace string, svc traefikv1alpha1.L
return nil, err return nil, err
} }
if svc.NativeLB {
address, err := getNativeServiceAddress(*service, *svcPort)
if err != nil {
return nil, fmt.Errorf("getting native Kubernetes Service address: %w", err)
}
protocol, err := parseServiceProtocol(svc.Scheme, svcPort.Name, svcPort.Port)
if err != nil {
return nil, err
}
return []dynamic.Server{{URL: fmt.Sprintf("%s://%s", protocol, address)}}, nil
}
var servers []dynamic.Server var servers []dynamic.Server
if service.Spec.Type == corev1.ServiceTypeExternalName { if service.Spec.Type == corev1.ServiceTypeExternalName {
if !c.allowExternalNameServices { if !c.allowExternalNameServices {
@ -409,6 +397,24 @@ func (c configBuilder) loadServers(parentNamespace string, svc traefikv1alpha1.L
}), nil }), nil
} }
nativeLB := c.NativeLBByDefault
if svc.NativeLB != nil {
nativeLB = *svc.NativeLB
}
if nativeLB {
address, err := getNativeServiceAddress(*service, *svcPort)
if err != nil {
return nil, fmt.Errorf("getting native Kubernetes Service address: %w", err)
}
protocol, err := parseServiceProtocol(svc.Scheme, svcPort.Name, svcPort.Port)
if err != nil {
return nil, err
}
return []dynamic.Server{{URL: fmt.Sprintf("%s://%s", protocol, address)}}, nil
}
endpoints, endpointsExists, endpointsErr := c.client.GetEndpoints(namespace, sanitizedName) endpoints, endpointsExists, endpointsErr := c.client.GetEndpoints(namespace, sanitizedName)
if endpointsErr != nil { if endpointsErr != nil {
return nil, endpointsErr return nil, endpointsErr

View file

@ -237,21 +237,25 @@ func (p *Provider) loadTCPServers(client Client, namespace string, svc traefikv1
return nil, err return nil, err
} }
if svc.NativeLB {
address, err := getNativeServiceAddress(*service, *svcPort)
if err != nil {
return nil, fmt.Errorf("getting native Kubernetes Service address: %w", err)
}
return []dynamic.TCPServer{{Address: address}}, nil
}
var servers []dynamic.TCPServer var servers []dynamic.TCPServer
if service.Spec.Type == corev1.ServiceTypeExternalName { if service.Spec.Type == corev1.ServiceTypeExternalName {
servers = append(servers, dynamic.TCPServer{ servers = append(servers, dynamic.TCPServer{
Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(svcPort.Port))), Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(svcPort.Port))),
}) })
} else { } else {
nativeLB := p.NativeLBByDefault
if svc.NativeLB != nil {
nativeLB = *svc.NativeLB
}
if nativeLB {
address, err := getNativeServiceAddress(*service, *svcPort)
if err != nil {
return nil, fmt.Errorf("getting native Kubernetes Service address: %w", err)
}
return []dynamic.TCPServer{{Address: address}}, nil
}
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name) endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name)
if endpointsErr != nil { if endpointsErr != nil {
return nil, endpointsErr return nil, endpointsErr

View file

@ -7180,3 +7180,339 @@ func (p *extensionBuilderRegistryMock) RegisterBackendFuncs(group, kind string,
p.groupKindBackendFuncs[group][kind] = builderFunc p.groupKindBackendFuncs[group][kind] = builderFunc
} }
func TestGlobalNativeLB(t *testing.T) {
testCases := []struct {
desc string
ingressClass string
paths []string
NativeLBByDefault bool
expected *dynamic.Configuration
}{
{
desc: "Empty",
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
ServersTransports: map[string]*dynamic.TCPServersTransport{},
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
ServersTransports: map[string]*dynamic.ServersTransport{},
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "HTTP with global native Service LB",
paths: []string{"services.yml", "with_global_native_service_lb.yml"},
NativeLBByDefault: true,
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
ServersTransports: map[string]*dynamic.TCPServersTransport{},
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
ServersTransports: map[string]*dynamic.ServersTransport{},
Routers: map[string]*dynamic.Router{
"default-global-native-lb-6f97418635c7e18853da": {
EntryPoints: []string{"foo"},
Service: "default-global-native-lb-6f97418635c7e18853da",
Rule: "Host(`foo.com`)",
Priority: 0,
},
},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
"default-global-native-lb-6f97418635c7e18853da": {
LoadBalancer: &dynamic.ServersLoadBalancer{
ResponseForwarding: &dynamic.ResponseForwarding{FlushInterval: dynamic.DefaultFlushInterval},
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:80",
},
},
PassHostHeader: Bool(true),
},
},
},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "HTTP with native Service LB in ingressroute",
paths: []string{"services.yml", "with_native_service_lb.yml"},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
ServersTransports: map[string]*dynamic.TCPServersTransport{},
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
ServersTransports: map[string]*dynamic.ServersTransport{},
Routers: map[string]*dynamic.Router{
"default-test-route-6f97418635c7e18853da": {
EntryPoints: []string{"foo"},
Service: "default-test-route-6f97418635c7e18853da",
Rule: "Host(`foo.com`)",
Priority: 0,
},
},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
"default-test-route-6f97418635c7e18853da": {
LoadBalancer: &dynamic.ServersLoadBalancer{
ResponseForwarding: &dynamic.ResponseForwarding{FlushInterval: dynamic.DefaultFlushInterval},
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:80",
},
},
PassHostHeader: Bool(true),
},
},
},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "TCP with global native Service LB",
paths: []string{"tcp/services.yml", "tcp/with_global_native_service_lb.yml"},
NativeLBByDefault: true,
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
ServersTransports: map[string]*dynamic.ServersTransport{},
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
TCP: &dynamic.TCPConfiguration{
ServersTransports: map[string]*dynamic.TCPServersTransport{},
Routers: map[string]*dynamic.TCPRouter{
"default-global-native-lb-fdd3e9338e47a45efefc": {
EntryPoints: []string{"foo"},
Service: "default-global-native-lb-fdd3e9338e47a45efefc",
Rule: "HostSNI(`foo.com`)",
},
},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{
"default-global-native-lb-fdd3e9338e47a45efefc": {
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
},
},
},
},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "TCP with native Service LB in ingressroute",
paths: []string{"tcp/services.yml", "tcp/with_native_service_lb.yml"},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
ServersTransports: map[string]*dynamic.ServersTransport{},
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
TCP: &dynamic.TCPConfiguration{
ServersTransports: map[string]*dynamic.TCPServersTransport{},
Routers: map[string]*dynamic.TCPRouter{
"default-test.route-fdd3e9338e47a45efefc": {
EntryPoints: []string{"foo"},
Service: "default-test.route-fdd3e9338e47a45efefc",
Rule: "HostSNI(`foo.com`)",
},
},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{
"default-test.route-fdd3e9338e47a45efefc": {
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
},
},
},
},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "UDP with native Service LB in ingressroute",
paths: []string{"udp/services.yml", "udp/with_native_service_lb.yml"},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"default-test.route-0": {
EntryPoints: []string{"foo"},
Service: "default-test.route-0",
},
},
Services: map[string]*dynamic.UDPService{
"default-test.route-0": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
},
},
},
},
},
HTTP: &dynamic.HTTPConfiguration{
ServersTransports: map[string]*dynamic.ServersTransport{},
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
TCP: &dynamic.TCPConfiguration{
ServersTransports: map[string]*dynamic.TCPServersTransport{},
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "UDP with global native Service LB",
paths: []string{"udp/services.yml", "udp/with_global_native_service_lb.yml"},
NativeLBByDefault: true,
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"default-global-native-lb-0": {
EntryPoints: []string{"foo"},
Service: "default-global-native-lb-0",
},
},
Services: map[string]*dynamic.UDPService{
"default-global-native-lb-0": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
},
},
},
},
},
HTTP: &dynamic.HTTPConfiguration{
ServersTransports: map[string]*dynamic.ServersTransport{},
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
TCP: &dynamic.TCPConfiguration{
ServersTransports: map[string]*dynamic.TCPServersTransport{},
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
var k8sObjects []runtime.Object
var crdObjects []runtime.Object
for _, path := range test.paths {
yamlContent, err := os.ReadFile(filepath.FromSlash("./fixtures/" + path))
if err != nil {
panic(err)
}
objects := k8s.MustParseYaml(yamlContent)
for _, obj := range objects {
switch o := obj.(type) {
case *corev1.Service, *corev1.Endpoints, *corev1.Secret:
k8sObjects = append(k8sObjects, o)
case *traefikv1alpha1.IngressRoute:
crdObjects = append(crdObjects, o)
case *traefikv1alpha1.IngressRouteTCP:
crdObjects = append(crdObjects, o)
case *traefikv1alpha1.IngressRouteUDP:
crdObjects = append(crdObjects, o)
case *traefikv1alpha1.Middleware:
crdObjects = append(crdObjects, o)
case *traefikv1alpha1.TraefikService:
crdObjects = append(crdObjects, o)
case *traefikv1alpha1.TLSOption:
crdObjects = append(crdObjects, o)
case *traefikv1alpha1.TLSStore:
crdObjects = append(crdObjects, o)
default:
}
}
}
kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
crdClient := traefikcrdfake.NewSimpleClientset(crdObjects...)
client := newClientImpl(kubeClient, crdClient)
stopCh := make(chan struct{})
eventCh, err := client.WatchAll([]string{"default", "cross-ns"}, stopCh)
require.NoError(t, err)
if k8sObjects != nil || crdObjects != nil {
// just wait for the first event
<-eventCh
}
p := Provider{NativeLBByDefault: test.NativeLBByDefault}
conf := p.loadConfigurationFromCRD(context.Background(), client)
assert.Equal(t, test.expected, conf)
})
}
}

View file

@ -121,21 +121,25 @@ func (p *Provider) loadUDPServers(client Client, namespace string, svc traefikv1
return nil, err return nil, err
} }
if svc.NativeLB {
address, err := getNativeServiceAddress(*service, *svcPort)
if err != nil {
return nil, fmt.Errorf("getting native Kubernetes Service address: %w", err)
}
return []dynamic.UDPServer{{Address: address}}, nil
}
var servers []dynamic.UDPServer var servers []dynamic.UDPServer
if service.Spec.Type == corev1.ServiceTypeExternalName { if service.Spec.Type == corev1.ServiceTypeExternalName {
servers = append(servers, dynamic.UDPServer{ servers = append(servers, dynamic.UDPServer{
Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(svcPort.Port))), Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(svcPort.Port))),
}) })
} else { } else {
nativeLB := p.NativeLBByDefault
if svc.NativeLB != nil {
nativeLB = *svc.NativeLB
}
if nativeLB {
address, err := getNativeServiceAddress(*service, *svcPort)
if err != nil {
return nil, fmt.Errorf("getting native Kubernetes Service address: %w", err)
}
return []dynamic.UDPServer{{Address: address}}, nil
}
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name) endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name)
if endpointsErr != nil { if endpointsErr != nil {
return nil, endpointsErr return nil, endpointsErr

View file

@ -125,7 +125,7 @@ type LoadBalancerSpec struct {
// whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. // whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP.
// The Kubernetes Service itself does load-balance to the pods. // The Kubernetes Service itself does load-balance to the pods.
// By default, NativeLB is false. // By default, NativeLB is false.
NativeLB bool `json:"nativeLB,omitempty"` NativeLB *bool `json:"nativeLB,omitempty"`
} }
type ResponseForwarding struct { type ResponseForwarding struct {

View file

@ -92,7 +92,7 @@ type ServiceTCP struct {
// whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. // whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP.
// The Kubernetes Service itself does load-balance to the pods. // The Kubernetes Service itself does load-balance to the pods.
// By default, NativeLB is false. // By default, NativeLB is false.
NativeLB bool `json:"nativeLB,omitempty"` NativeLB *bool `json:"nativeLB,omitempty"`
} }
// +genclient // +genclient

View file

@ -37,7 +37,7 @@ type ServiceUDP struct {
// whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. // whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP.
// The Kubernetes Service itself does load-balance to the pods. // The Kubernetes Service itself does load-balance to the pods.
// By default, NativeLB is false. // By default, NativeLB is false.
NativeLB bool `json:"nativeLB,omitempty"` NativeLB *bool `json:"nativeLB,omitempty"`
} }
// +genclient // +genclient

View file

@ -577,6 +577,11 @@ func (in *LoadBalancerSpec) DeepCopyInto(out *LoadBalancerSpec) {
*out = new(int) *out = new(int)
**out = **in **out = **in
} }
if in.NativeLB != nil {
in, out := &in.NativeLB, &out.NativeLB
*out = new(bool)
**out = **in
}
return return
} }
@ -1333,6 +1338,11 @@ func (in *ServiceTCP) DeepCopyInto(out *ServiceTCP) {
*out = new(dynamic.ProxyProtocol) *out = new(dynamic.ProxyProtocol)
**out = **in **out = **in
} }
if in.NativeLB != nil {
in, out := &in.NativeLB, &out.NativeLB
*out = new(bool)
**out = **in
}
return return
} }
@ -1355,6 +1365,11 @@ func (in *ServiceUDP) DeepCopyInto(out *ServiceUDP) {
*out = new(int) *out = new(int)
**out = **in **out = **in
} }
if in.NativeLB != nil {
in, out := &in.NativeLB, &out.NativeLB
*out = new(bool)
**out = **in
}
return return
} }

View file

@ -45,7 +45,7 @@ type ServiceIng struct {
ServersTransport string `json:"serversTransport,omitempty"` ServersTransport string `json:"serversTransport,omitempty"`
PassHostHeader *bool `json:"passHostHeader"` PassHostHeader *bool `json:"passHostHeader"`
Sticky *dynamic.Sticky `json:"sticky,omitempty" label:"allowEmpty"` Sticky *dynamic.Sticky `json:"sticky,omitempty" label:"allowEmpty"`
NativeLB bool `json:"nativeLB,omitempty"` NativeLB *bool `json:"nativeLB,omitempty"`
} }
// SetDefaults sets the default values. // SetDefaults sets the default values.

View file

@ -125,7 +125,7 @@ func Test_parseServiceConfig(t *testing.T) {
ServersScheme: "protocol", ServersScheme: "protocol",
ServersTransport: "foobar@file", ServersTransport: "foobar@file",
PassHostHeader: Bool(true), PassHostHeader: Bool(true),
NativeLB: true, NativeLB: Bool(true),
}, },
}, },
}, },

View file

@ -0,0 +1,30 @@
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
name: global-native-lb
namespace: default
spec:
rules:
- host: traefik.tchouk
http:
paths:
- path: /bar
backend:
service:
name: service1
port:
number: 8080
pathType: Prefix
---
kind: Service
apiVersion: v1
metadata:
name: service1
namespace: default
spec:
ports:
- port: 8080
clusterIP: 10.0.0.1
type: ClusterIP
externalName: traefik.wtf

View file

@ -52,6 +52,7 @@ type Provider struct {
AllowEmptyServices bool `description:"Allow creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"` AllowEmptyServices bool `description:"Allow creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"`
AllowExternalNameServices bool `description:"Allow ExternalName services." json:"allowExternalNameServices,omitempty" toml:"allowExternalNameServices,omitempty" yaml:"allowExternalNameServices,omitempty" export:"true"` AllowExternalNameServices bool `description:"Allow ExternalName services." json:"allowExternalNameServices,omitempty" toml:"allowExternalNameServices,omitempty" yaml:"allowExternalNameServices,omitempty" export:"true"`
DisableIngressClassLookup bool `description:"Disables the lookup of IngressClasses." json:"disableIngressClassLookup,omitempty" toml:"disableIngressClassLookup,omitempty" yaml:"disableIngressClassLookup,omitempty" export:"true"` DisableIngressClassLookup bool `description:"Disables the lookup of IngressClasses." json:"disableIngressClassLookup,omitempty" toml:"disableIngressClassLookup,omitempty" yaml:"disableIngressClassLookup,omitempty" export:"true"`
NativeLBByDefault bool `description:"Defines whether to use Native Kubernetes load-balancing mode by default." json:"nativeLBByDefault,omitempty" toml:"nativeLBByDefault,omitempty" yaml:"nativeLBByDefault,omitempty" export:"true"`
lastConfiguration safe.Safe lastConfiguration safe.Safe
@ -571,6 +572,8 @@ func (p *Provider) loadService(client Client, namespace string, backend netv1.In
return nil, err return nil, err
} }
nativeLB := p.NativeLBByDefault
if svcConfig != nil && svcConfig.Service != nil { if svcConfig != nil && svcConfig.Service != nil {
svc.LoadBalancer.Sticky = svcConfig.Service.Sticky svc.LoadBalancer.Sticky = svcConfig.Service.Sticky
@ -582,19 +585,8 @@ func (p *Provider) loadService(client Client, namespace string, backend netv1.In
svc.LoadBalancer.ServersTransport = svcConfig.Service.ServersTransport svc.LoadBalancer.ServersTransport = svcConfig.Service.ServersTransport
} }
if svcConfig.Service.NativeLB { if svcConfig.Service.NativeLB != nil {
address, err := getNativeServiceAddress(*service, portSpec) nativeLB = *svcConfig.Service.NativeLB
if err != nil {
return nil, fmt.Errorf("getting native Kubernetes Service address: %w", err)
}
protocol := getProtocol(portSpec, portSpec.Name, svcConfig)
svc.LoadBalancer.Servers = []dynamic.Server{
{URL: fmt.Sprintf("%s://%s", protocol, address)},
}
return svc, nil
} }
} }
@ -609,6 +601,20 @@ func (p *Provider) loadService(client Client, namespace string, backend netv1.In
return svc, nil return svc, nil
} }
if nativeLB {
address, err := getNativeServiceAddress(*service, portSpec)
if err != nil {
return nil, fmt.Errorf("getting native Kubernetes Service address: %w", err)
}
protocol := getProtocol(portSpec, portSpec.Name, svcConfig)
svc.LoadBalancer.Servers = []dynamic.Server{
{URL: fmt.Sprintf("%s://%s", protocol, address)},
}
return svc, nil
}
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, backend.Service.Name) endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, backend.Service.Name)
if endpointsErr != nil { if endpointsErr != nil {
return nil, endpointsErr return nil, endpointsErr

View file

@ -1864,3 +1864,84 @@ func TestGetCertificates(t *testing.T) {
}) })
} }
} }
func TestLoadConfigurationFromIngressesWithNativeLBByDefault(t *testing.T) {
testCases := []struct {
desc string
ingressClass string
expected *dynamic.Configuration
}{
{
desc: "Ingress with native service lb",
expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{
"testing-traefik-tchouk-bar": {
Rule: "Host(`traefik.tchouk`) && PathPrefix(`/bar`)",
Service: "testing-service1-8080",
},
},
Services: map[string]*dynamic.Service{
"testing-service1-8080": {
LoadBalancer: &dynamic.ServersLoadBalancer{
ResponseForwarding: &dynamic.ResponseForwarding{FlushInterval: dynamic.DefaultFlushInterval},
PassHostHeader: Bool(true),
Servers: []dynamic.Server{
{
URL: "http://10.0.0.1:8080",
},
},
},
},
},
},
},
},
{
desc: "Ingress with native lb by default",
expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{
"default-global-native-lb-traefik-tchouk-bar": {
Rule: "Host(`traefik.tchouk`) && PathPrefix(`/bar`)",
Service: "default-service1-8080",
},
},
Services: map[string]*dynamic.Service{
"default-service1-8080": {
LoadBalancer: &dynamic.ServersLoadBalancer{
ResponseForwarding: &dynamic.ResponseForwarding{FlushInterval: dynamic.DefaultFlushInterval},
PassHostHeader: Bool(true),
Servers: []dynamic.Server{
{
URL: "http://10.0.0.1:8080",
},
},
},
},
},
},
},
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
clientMock := newClientMock(generateTestFilename(test.desc))
p := Provider{
IngressClass: test.ingressClass,
NativeLBByDefault: true,
}
conf := p.loadConfigurationFromIngresses(context.Background(), clientMock)
assert.Equal(t, test.expected, conf)
})
}
}