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

View file

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

View file

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

View file

@ -141,6 +141,7 @@ providers:
allowEmptyServices: true
allowExternalNameServices: true
disableIngressClassLookup: true
nativeLBByDefault: true
kubernetesCRD:
endpoint: foobar
token: foobar
@ -154,6 +155,7 @@ providers:
ingressClass: foobar
throttleDuration: 42s
allowEmptyServices: true
nativeLBByDefault: true
kubernetesGateway:
endpoint: 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"`
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"`
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

View file

@ -55,6 +55,7 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
allowCrossNamespace: p.AllowCrossNamespace,
allowExternalNameServices: p.AllowExternalNameServices,
allowEmptyServices: p.AllowEmptyServices,
NativeLBByDefault: p.NativeLBByDefault,
}
for _, route := range ingressRoute.Spec.Routes {
@ -202,6 +203,7 @@ type configBuilder struct {
allowCrossNamespace bool
allowExternalNameServices bool
allowEmptyServices bool
NativeLBByDefault bool
}
// 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
}
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
if service.Spec.Type == corev1.ServiceTypeExternalName {
if !c.allowExternalNameServices {
@ -409,6 +397,24 @@ func (c configBuilder) loadServers(parentNamespace string, svc traefikv1alpha1.L
}), 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)
if endpointsErr != nil {
return nil, endpointsErr

View file

@ -237,7 +237,17 @@ func (p *Provider) loadTCPServers(client Client, namespace string, svc traefikv1
return nil, err
}
if svc.NativeLB {
var servers []dynamic.TCPServer
if service.Spec.Type == corev1.ServiceTypeExternalName {
servers = append(servers, dynamic.TCPServer{
Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(svcPort.Port))),
})
} 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)
@ -246,12 +256,6 @@ func (p *Provider) loadTCPServers(client Client, namespace string, svc traefikv1
return []dynamic.TCPServer{{Address: address}}, nil
}
var servers []dynamic.TCPServer
if service.Spec.Type == corev1.ServiceTypeExternalName {
servers = append(servers, dynamic.TCPServer{
Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(svcPort.Port))),
})
} else {
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name)
if endpointsErr != nil {
return nil, endpointsErr

View file

@ -7180,3 +7180,339 @@ func (p *extensionBuilderRegistryMock) RegisterBackendFuncs(group, kind string,
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,7 +121,17 @@ func (p *Provider) loadUDPServers(client Client, namespace string, svc traefikv1
return nil, err
}
if svc.NativeLB {
var servers []dynamic.UDPServer
if service.Spec.Type == corev1.ServiceTypeExternalName {
servers = append(servers, dynamic.UDPServer{
Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(svcPort.Port))),
})
} 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)
@ -130,12 +140,6 @@ func (p *Provider) loadUDPServers(client Client, namespace string, svc traefikv1
return []dynamic.UDPServer{{Address: address}}, nil
}
var servers []dynamic.UDPServer
if service.Spec.Type == corev1.ServiceTypeExternalName {
servers = append(servers, dynamic.UDPServer{
Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(svcPort.Port))),
})
} else {
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name)
if endpointsErr != nil {
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.
// The Kubernetes Service itself does load-balance to the pods.
// By default, NativeLB is false.
NativeLB bool `json:"nativeLB,omitempty"`
NativeLB *bool `json:"nativeLB,omitempty"`
}
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.
// The Kubernetes Service itself does load-balance to the pods.
// By default, NativeLB is false.
NativeLB bool `json:"nativeLB,omitempty"`
NativeLB *bool `json:"nativeLB,omitempty"`
}
// +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.
// The Kubernetes Service itself does load-balance to the pods.
// By default, NativeLB is false.
NativeLB bool `json:"nativeLB,omitempty"`
NativeLB *bool `json:"nativeLB,omitempty"`
}
// +genclient

View file

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

View file

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

View file

@ -125,7 +125,7 @@ func Test_parseServiceConfig(t *testing.T) {
ServersScheme: "protocol",
ServersTransport: "foobar@file",
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"`
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"`
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
@ -571,6 +572,8 @@ func (p *Provider) loadService(client Client, namespace string, backend netv1.In
return nil, err
}
nativeLB := p.NativeLBByDefault
if svcConfig != nil && svcConfig.Service != nil {
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
}
if svcConfig.Service.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
if svcConfig.Service.NativeLB != nil {
nativeLB = *svcConfig.Service.NativeLB
}
}
@ -609,6 +601,20 @@ func (p *Provider) loadService(client Client, namespace string, backend netv1.In
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)
if endpointsErr != nil {
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)
})
}
}