Migrate to EndpointSlices API

This commit is contained in:
Jesper Noordsij 2024-06-21 14:56:03 +02:00 committed by GitHub
parent 61defcdd66
commit a8a92eb2a5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
88 changed files with 2177 additions and 1555 deletions

View file

@ -35,12 +35,18 @@ rules:
- ""
resources:
- services
- endpoints
- secrets
verbs:
- get
- list
- watch
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- list
- watch
- apiGroups:
- extensions
- networking.k8s.io

View file

@ -0,0 +1,27 @@
---
title: "Traefik Migration Documentation"
description: "Learn the steps needed to migrate to new Traefik Proxy v3 versions. Read the technical documentation."
---
# Migration: Steps needed between the versions
## v3.0 to v3.1
### Kubernetes Provider RBACs
Starting with v3.1, the Kubernetes Providers now use the [EndpointSlices API](https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/) (Kubernetes >=v1.21) to discover service endpoint addresses.
Therefore, in the corresponding RBACs (see [KubernetesIngress](../routing/providers/kubernetes-ingress.md#configuration-example), [KubernetesCRD](../reference/dynamic-configuration/kubernetes-crd.md#rbac), and [KubernetesGateway](../reference/dynamic-configuration/kubernetes-gateway.md#rbac) provider RBACs),
the `endpoints` right has to be removed and the following `endpointslices` right has to be added.
```yaml
...
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- list
- watch
...
```

View file

@ -183,7 +183,7 @@ _Optional, Default: ""_
A label selector can be defined to filter on specific resource objects only,
this applies only to Traefik [Custom Resources](../routing/providers/kubernetes-crd.md#custom-resource-definition-crd)
and has no effect on Kubernetes `Secrets`, `Endpoints` and `Services`.
and has no effect on Kubernetes `Secrets`, `EndpointSlices` and `Services`.
If left empty, Traefik processes all resource objects in the configured namespaces.
See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details.

View file

@ -8,13 +8,19 @@ rules:
- ""
resources:
- services
- endpoints
- secrets
- nodes
verbs:
- get
- list
- watch
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- list
- watch
- apiGroups:
- extensions
- networking.k8s.io

View file

@ -15,12 +15,18 @@ rules:
- ""
resources:
- services
- endpoints
- secrets
verbs:
- get
- list
- watch
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- list
- watch
- apiGroups:
- gateway.networking.k8s.io
resources:

View file

@ -29,12 +29,18 @@ which in turn will create the resulting routers, services, handlers, etc.
- ""
resources:
- services
- endpoints
- secrets
verbs:
- get
- list
- watch
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- list
- watch
- apiGroups:
- extensions
- networking.k8s.io
@ -427,12 +433,19 @@ This way, any Ingress attached to this Entrypoint will have TLS termination by d
- ""
resources:
- services
- endpoints
- secrets
verbs:
- get
- list
- watch
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- get
- list
- watch
- apiGroups:
- extensions
- networking.k8s.io
@ -612,12 +625,19 @@ For more options, please refer to the available [annotations](#on-ingress).
- ""
resources:
- services
- endpoints
- secrets
verbs:
- get
- list
- watch
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- get
- list
- watch
- apiGroups:
- extensions
- networking.k8s.io

View file

@ -172,6 +172,7 @@ nav:
- 'HTTP Challenge': 'user-guides/docker-compose/acme-http/index.md'
- 'DNS Challenge': 'user-guides/docker-compose/acme-dns/index.md'
- 'Migration':
- 'Traefik v3 minor migrations': 'migration/v3.md'
- 'Traefik v2 to v3':
- 'Migration guide': 'migration/v2-to-v3.md'
- 'Configuration changes for v3': 'migration/v2-to-v3-details.md'

View file

@ -15,12 +15,19 @@ rules:
- ""
resources:
- services
- endpoints
- secrets
verbs:
- get
- list
- watch
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- get
- list
- watch
- apiGroups:
- gateway.networking.k8s.io
resources:

View file

@ -1,7 +1,7 @@
version: "3.8"
services:
server:
image: rancher/k3s:v1.20.15-k3s1
image: rancher/k3s:v1.21.14-k3s1
privileged: true
command:
- server
@ -26,7 +26,7 @@ services:
- ./fixtures/k8s:/var/lib/rancher/k3s/server/manifests
node:
image: rancher/k3s:v1.20.15-k3s1
image: rancher/k3s:v1.21.14-k3s1
privileged: true
environment:
K3S_TOKEN: somethingtotallyrandom

View file

@ -17,9 +17,11 @@ import (
"github.com/traefik/traefik/v3/pkg/types"
"github.com/traefik/traefik/v3/pkg/version"
corev1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
kerror "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
kinformers "k8s.io/client-go/informers"
kclientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
@ -46,7 +48,7 @@ type Client interface {
GetTLSStores() []*traefikv1alpha1.TLSStore
GetService(namespace, name string) (*corev1.Service, bool, error)
GetSecret(namespace, name string) (*corev1.Secret, bool, error)
GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error)
GetEndpointSlicesForService(namespace, serviceName string) ([]*discoveryv1.EndpointSlice, error)
GetNodes() ([]*corev1.Node, bool, error)
}
@ -219,7 +221,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
if err != nil {
return nil, err
}
_, err = factoryKube.Core().V1().Endpoints().Informer().AddEventHandler(eventHandler)
_, err = factoryKube.Discovery().V1().EndpointSlices().Informer().AddEventHandler(eventHandler)
if err != nil {
return nil, err
}
@ -444,15 +446,20 @@ func (c *clientWrapper) GetService(namespace, name string) (*corev1.Service, boo
return service, exist, err
}
// GetEndpoints returns the named endpoints from the given namespace.
func (c *clientWrapper) GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) {
// GetEndpointSlicesForService returns the EndpointSlices for the given service name in the given namespace.
func (c *clientWrapper) GetEndpointSlicesForService(namespace, serviceName string) ([]*discoveryv1.EndpointSlice, error) {
if !c.isWatchedNamespace(namespace) {
return nil, false, fmt.Errorf("failed to get endpoints %s/%s: namespace is not within watched namespaces", namespace, name)
return nil, fmt.Errorf("failed to get endpointslices for service %s/%s: namespace is not within watched namespaces", namespace, serviceName)
}
endpoint, err := c.factoriesKube[c.lookupNamespace(namespace)].Core().V1().Endpoints().Lister().Endpoints(namespace).Get(name)
exist, err := translateNotFoundError(err)
return endpoint, exist, err
serviceLabelRequirement, err := labels.NewRequirement(discoveryv1.LabelServiceName, selection.Equals, []string{serviceName})
if err != nil {
return nil, fmt.Errorf("failed to create service label selector requirement: %w", err)
}
serviceSelector := labels.NewSelector()
serviceSelector = serviceSelector.Add(*serviceLabelRequirement)
return c.factoriesKube[c.lookupNamespace(namespace)].Discovery().V1().EndpointSlices().Lister().EndpointSlices(namespace).List(serviceSelector)
}
// GetSecret returns the named secret from the given namespace.

View file

@ -13,19 +13,24 @@ spec:
task: whoami
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami
name: whoami-abc
namespace: default
labels:
kubernetes.io/service-name: whoami
subsets:
addressType: IPv4
ports:
- name: web
port: 80
endpoints:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: web
port: 80
- 10.10.0.1
- 10.10.0.2
conditions:
ready: true
---
apiVersion: v1
@ -43,19 +48,24 @@ spec:
task: whoami2
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami2
name: whoami2-abc
namespace: default
labels:
kubernetes.io/service-name: whoami2
subsets:
addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
- name: web
port: 8080
- 10.10.0.3
- 10.10.0.4
conditions:
ready: true
---
apiVersion: v1
@ -74,19 +84,24 @@ spec:
task: whoami2
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoamitls
name: whoamitls-abc
namespace: default
labels:
kubernetes.io/service-name: whoamitls
subsets:
addressType: IPv4
ports:
- name: websecure
port: 8443
endpoints:
- addresses:
- ip: 10.10.0.5
- ip: 10.10.0.6
ports:
- name: websecure
port: 8443
- 10.10.0.5
- 10.10.0.6
conditions:
ready: true
---
apiVersion: v1
@ -99,25 +114,29 @@ spec:
ports:
- name: websecure2
port: 8443
scheme: https
selector:
app: traefiklabs
task: whoami3
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami3
name: whoami3-abc
namespace: default
labels:
kubernetes.io/service-name: whoami3
subsets:
addressType: IPv4
ports:
- name: websecure2
port: 8443
endpoints:
- addresses:
- ip: 10.10.0.7
- ip: 10.10.0.8
ports:
- name: websecure2
port: 8443
- 10.10.0.7
- 10.10.0.8
conditions:
ready: true
---
apiVersion: v1
@ -135,18 +154,23 @@ spec:
task: whoami-ipv6
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami-ipv6
name: whoami-ipv6-abc
namespace: default
labels:
kubernetes.io/service-name: whoami-ipv6
subsets:
addressType: IPv6
ports:
- name: web
port: 8080
endpoints:
- addresses:
- ip: "2001:db8:85a3:8d3:1319:8a2e:370:7348"
ports:
- name: web
port: 8080
- "2001:db8:85a3:8d3:1319:8a2e:370:7348"
conditions:
ready: true
---
apiVersion: v1
@ -216,25 +240,30 @@ spec:
task: whoami
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami-svc
name: whoami-svc-abc
namespace: cross-ns
labels:
kubernetes.io/service-name: whoami-svc
subsets:
addressType: IPv4
ports:
- name: web
port: 80
endpoints:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: web
port: 80
- 10.10.0.1
- 10.10.0.2
conditions:
ready: true
---
apiVersion: v1
kind: Service
metadata:
name: whoami-without-endpoints-subsets
name: whoami-without-endpointslice-endpoints
namespace: default
spec:
@ -247,11 +276,16 @@ spec:
task: whoami
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami-without-endpoints-subsets
name: whoami-without-endpointslice-endpoints-abc
namespace: default
labels:
kubernetes.io/service-name: whoami-without-endpointslice-endpoints
addressType: IPv4
endpoints: []
---
apiVersion: v1

View file

@ -13,19 +13,24 @@ spec:
task: whoamitcp
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoamitcp
name: whoamitcp-abc
namespace: default
labels:
kubernetes.io/service-name: whoamitcp
subsets:
addressType: IPv4
ports:
- name: myapp
port: 8000
endpoints:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: myapp
port: 8000
- 10.10.0.1
- 10.10.0.2
conditions:
ready: true
---
apiVersion: v1
@ -43,19 +48,24 @@ spec:
task: whoamitcp2
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoamitcp2
name: whoamitcp2-abc
namespace: default
labels:
kubernetes.io/service-name: whoamitcp2
subsets:
addressType: IPv4
ports:
- name: myapp2
port: 8080
endpoints:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
- name: myapp2
port: 8080
- 10.10.0.3
- 10.10.0.4
conditions:
ready: true
---
apiVersion: v1
@ -73,19 +83,24 @@ spec:
task: whoamitcptls2
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoamitcptls
name: whoamitcptls-abc
namespace: default
labels:
kubernetes.io/service-name: whoamitcptls
subsets:
addressType: IPv4
ports:
- name: websecure
port: 443
endpoints:
- addresses:
- ip: 10.10.0.5
- ip: 10.10.0.6
ports:
- name: websecure
port: 443
- 10.10.0.5
- 10.10.0.6
conditions:
ready: true
---
apiVersion: v1
@ -103,34 +118,44 @@ spec:
task: whoamitcp3
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoamitcp3
name: whoamitcp3-abc
namespace: ns3
labels:
kubernetes.io/service-name: whoamitcp3
subsets:
addressType: IPv4
ports:
- name: myapp3
port: 8083
endpoints:
- addresses:
- ip: 10.10.0.7
- ip: 10.10.0.8
ports:
- name: myapp3
port: 8083
- 10.10.0.7
- 10.10.0.8
conditions:
ready: true
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoamitcp3
name: whoamitcp3-abc
namespace: ns4
labels:
kubernetes.io/service-name: whoamitcp3
subsets:
addressType: IPv4
ports:
- name: myapp4
port: 8084
endpoints:
- addresses:
- ip: 10.10.0.9
- ip: 10.10.0.10
ports:
- name: myapp4
port: 8084
- 10.10.0.9
- 10.10.0.10
conditions:
ready: true
---
apiVersion: v1
@ -148,19 +173,24 @@ spec:
task: whoamitcp-ipv6
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoamitcp-ipv6
name: whoamitcp-ipv6-abc
namespace: default
labels:
kubernetes.io/service-name: whoamitcp-ipv6
subsets:
addressType: IPv6
ports:
- name: myapp-ipv6
port: 8080
endpoints:
- addresses:
- ip: "fd00:10:244:0:1::3"
- ip: "2001:db8:85a3:8d3:1319:8a2e:370:7348"
ports:
- name: myapp-ipv6
port: 8080
- "fd00:10:244:0:1::3"
- "2001:db8:85a3:8d3:1319:8a2e:370:7348"
conditions:
ready: true
---
apiVersion: v1
@ -212,25 +242,30 @@ spec:
task: whoamitcp
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoamitcp-cross-ns
name: whoamitcp-cross-ns-abc
namespace: cross-ns
labels:
kubernetes.io/service-name: whoamitcp-cross-ns
subsets:
addressType: IPv4
ports:
- name: myapp
port: 8000
endpoints:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: myapp
port: 8000
- 10.10.0.1
- 10.10.0.2
conditions:
ready: true
---
apiVersion: v1
kind: Service
metadata:
name: whoamitcp-without-endpoints-subsets
name: whoamitcp-without-endpointslice-endpoints
namespace: default
spec:
@ -243,11 +278,16 @@ spec:
task: whoamitcp
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoamitcp-without-endpoints-subsets
name: whoamitcp-without-endpointslice-endpoints-abc
namespace: default
labels:
kubernetes.io/service-name: whoamitcp-without-endpointslice-endpoints
addressType: IPv4
endpoints: []
---
apiVersion: v1

View file

@ -11,5 +11,5 @@ spec:
routes:
- match: HostSNI(`foo.com`)
services:
- name: whoamitcp-without-endpoints-subsets
- name: whoamitcp-without-endpointslice-endpoints
port: 8000

View file

@ -13,19 +13,24 @@ spec:
task: whoamiudp
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoamiudp
name: whoamiudp-abc
namespace: default
labels:
kubernetes.io/service-name: whoamiudp
subsets:
addressType: IPv4
ports:
- name: myapp
port: 8000
endpoints:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: myapp
port: 8000
- 10.10.0.1
- 10.10.0.2
conditions:
ready: true
---
apiVersion: v1
@ -43,19 +48,24 @@ spec:
task: whoamiudp2
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoamiudp2
name: whoamiudp2-abc
namespace: default
labels:
kubernetes.io/service-name: whoamiudp2
subsets:
addressType: IPv4
ports:
- name: myapp2
port: 8080
endpoints:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
- name: myapp2
port: 8080
- 10.10.0.3
- 10.10.0.4
conditions:
ready: true
---
apiVersion: v1
@ -73,34 +83,44 @@ spec:
task: whoamiudp3
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoamiudp3
name: whoamiudp3-abc
namespace: ns3
labels:
kubernetes.io/service-name: whoamiudp3
subsets:
addressType: IPv4
ports:
- name: myapp3
port: 8083
endpoints:
- addresses:
- ip: 10.10.0.7
- ip: 10.10.0.8
ports:
- name: myapp3
port: 8083
- 10.10.0.7
- 10.10.0.8
conditions:
ready: true
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoamiudp3
name: whoamiudp3-abc
namespace: ns4
labels:
kubernetes.io/service-name: whoamiudp3
subsets:
addressType: IPv4
ports:
- name: myapp4
port: 8084
endpoints:
- addresses:
- ip: 10.10.0.9
- ip: 10.10.0.10
ports:
- name: myapp4
port: 8084
- 10.10.0.9
- 10.10.0.10
conditions:
ready: true
---
apiVersion: v1
@ -118,18 +138,23 @@ spec:
task: whoamiudp-ipv6
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoamiudp-ipv6
name: whoamiudp-ipv6-abc
namespace: default
labels:
kubernetes.io/service-name: whoamiudp-ipv6
subsets:
addressType: IPv6
ports:
- name: myapp-ipv6
port: 8080
endpoints:
- addresses:
- ip: "fd00:10:244:0:1::3"
ports:
- name: myapp-ipv6
port: 8080
- "fd00:10:244:0:1::3"
conditions:
ready: true
---
apiVersion: v1
@ -171,25 +196,30 @@ spec:
port: 80
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoamiudp-cross-ns
name: whoamiudp-cross-ns-abc
namespace: cross-ns
labels:
kubernetes.io/service-name: whoamiudp-cross-ns
subsets:
addressType: IPv4
ports:
- name: myapp
port: 8000
endpoints:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: myapp
port: 8000
- 10.10.0.1
- 10.10.0.2
conditions:
ready: true
---
apiVersion: v1
kind: Service
metadata:
name: whoamiudp-without-endpoints-subsets
name: whoamiudp-without-endpointslice-endpoints
namespace: default
spec:
@ -202,11 +232,16 @@ spec:
task: whoamiudp
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoamiudp-without-endpoints-subsets
name: whoamiudp-without-endpointslice-endpoints-abc
namespace: default
labels:
kubernetes.io/service-name: whoamiudp-without-endpointslice-endpoints
addressType: IPv4
endpoints: []
---
apiVersion: v1

View file

@ -10,5 +10,5 @@ spec:
routes:
- services:
- name: whoamiudp-without-endpoints-subsets
- name: whoamiudp-without-endpointslice-endpoints
port: 8000

View file

@ -0,0 +1,74 @@
apiVersion: v1
kind: Service
metadata:
name: whoami-svc-duplicated-endpointaddresses
namespace: default
spec:
ports:
- name: web
port: 8080
selector:
app: traefiklabs
task: whoami
---
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami-svc-duplicated-endpointaddresses-abc
namespace: default
labels:
kubernetes.io/service-name: whoami-svc-duplicated-endpointaddresses
addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses:
- 10.10.0.1
- 10.10.0.2
conditions:
ready: true
---
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami-svc-duplicated-endpointaddresses-def
namespace: default
labels:
kubernetes.io/service-name: whoami-svc-duplicated-endpointaddresses
addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses:
- 10.10.0.1
- 10.10.0.2
- 10.10.0.3
- 10.10.0.4
conditions:
ready: true
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: Host(`foo.com`) && PathPrefix(`/bar`)
kind: Rule
priority: 12
services:
- name: whoami-svc-duplicated-endpointaddresses
port: 8080

View file

@ -13,5 +13,5 @@ spec:
kind: Rule
priority: 12
services:
- name: whoami-without-endpoints-subsets
- name: whoami-without-endpointslice-endpoints
port: 80

View file

@ -30,7 +30,7 @@ metadata:
spec:
weighted:
services:
- name: whoami-without-endpoints-subsets
- name: whoami-without-endpointslice-endpoints
weight: 1
port: 80
@ -43,10 +43,10 @@ metadata:
spec:
mirroring:
name: whoami-without-endpoints-subsets
name: whoami-without-endpointslice-endpoints
port: 80
mirrors:
- name: whoami-without-endpoints-subsets
- name: whoami-without-endpointslice-endpoints
port: 80
- name: test-weighted
kind: TraefikService
@ -61,5 +61,5 @@ metadata:
spec:
errors:
service:
name: whoami-without-endpoints-subsets
name: whoami-without-endpointslice-endpoints
port: 80

View file

@ -1,17 +1,22 @@
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami4
name: whoami4-abc
namespace: default
labels:
kubernetes.io/service-name: whoami4
subsets:
addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: web
port: 8080
- 10.10.0.1
- 10.10.0.2
conditions:
ready: true
---
apiVersion: v1
@ -28,20 +33,25 @@ spec:
app: traefiklabs
task: whoami4
------
kind: Endpoints
apiVersion: v1
---
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami5
name: whoami5-abc
namespace: default
labels:
kubernetes.io/service-name: whoami5
subsets:
addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
- name: web
port: 8080
- 10.10.0.3
- 10.10.0.4
conditions:
ready: true
---
apiVersion: v1

View file

@ -1,17 +1,22 @@
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami4
name: whoami4-abc
namespace: default
labels:
kubernetes.io/service-name: whoami4
subsets:
addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: web
port: 8080
- 10.10.0.1
- 10.10.0.2
conditions:
ready: true
---
apiVersion: v1
@ -28,20 +33,25 @@ spec:
app: traefiklabs
task: whoami4
------
kind: Endpoints
apiVersion: v1
---
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami5
name: whoami5-abc
namespace: default
labels:
kubernetes.io/service-name: whoami5
subsets:
addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
- name: web
port: 8080
- 10.10.0.3
- 10.10.0.4
conditions:
ready: true
---
apiVersion: v1

View file

@ -0,0 +1,63 @@
apiVersion: v1
kind: Service
metadata:
name: whoami-svc-multiple-endpointaddresses
namespace: default
spec:
ports:
- name: web
port: 80
selector:
app: traefiklabs
task: whoami
---
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami-svc-multiple-endpointaddresses-abc
namespace: default
labels:
kubernetes.io/service-name: whoami-svc-multiple-endpointaddresses
addressType: IPv4
ports:
- name: web
port: 80
endpoints:
- addresses:
- 10.10.0.1
- 10.10.0.2
conditions:
ready: true
- addresses:
- 10.10.0.3
- 10.10.0.4
conditions:
ready: false
serving: true
- addresses:
- 10.10.0.5
- 10.10.0.6
conditions:
ready: true
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: Host(`foo.com`) && PathPrefix(`/bar`)
kind: Rule
priority: 12
services:
- name: whoami-svc-multiple-endpointaddresses
port: 80

View file

@ -0,0 +1,94 @@
apiVersion: v1
kind: Service
metadata:
name: whoami-svc-multiple-endpointslices
namespace: default
spec:
ports:
- name: web
port: 80
- name: web2
port: 8080
selector:
app: traefiklabs
task: whoami
---
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami-svc-multiple-endpointslices-abc
namespace: default
labels:
kubernetes.io/service-name: whoami-svc-multiple-endpointslices
addressType: IPv4
ports:
- name: web
port: 80
endpoints:
- addresses:
- 10.10.0.1
- 10.10.0.2
conditions:
ready: true
---
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami-svc-multiple-endpointslices-def
namespace: default
labels:
kubernetes.io/service-name: whoami-svc-multiple-endpointslices
addressType: IPv4
ports:
- name: web2
port: 8080
endpoints:
- addresses:
- 10.10.0.3
- 10.10.0.4
conditions:
ready: true
---
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami-svc-multiple-endpointslices-ghi
namespace: default
labels:
kubernetes.io/service-name: whoami-svc-multiple-endpointslices
addressType: IPv4
ports:
- name: web2
port: 8080
endpoints:
- addresses:
- 10.10.0.5
- 10.10.0.6
conditions:
ready: true
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: Host(`foo.com`) && PathPrefix(`/bar`)
kind: Rule
priority: 12
services:
- name: whoami-svc-multiple-endpointslices
port: 8080

View file

@ -1,54 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: whoami-svc-multiple-subsets
namespace: default
spec:
ports:
- name: web
port: 80
- name: web2
port: 8080
selector:
app: traefiklabs
task: whoami
---
kind: Endpoints
apiVersion: v1
metadata:
name: whoami-svc-multiple-subsets
namespace: default
subsets:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: web
port: 80
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
- name: web2
port: 8080
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: Host(`foo.com`) && PathPrefix(`/bar`)
kind: Rule
priority: 12
services:
- name: whoami-svc-multiple-subsets
port: 8080

View file

@ -1,47 +1,62 @@
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami6
name: whoami6-abc
namespace: baz
labels:
kubernetes.io/service-name: whoami6
subsets:
addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses:
- ip: 10.10.0.5
- ip: 10.10.0.6
ports:
- name: web
port: 8080
- 10.10.0.5
- 10.10.0.6
conditions:
ready: true
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami5
name: whoami5-abc
namespace: foo
labels:
kubernetes.io/service-name: whoami5
subsets:
addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
- name: web
port: 8080
- 10.10.0.3
- 10.10.0.4
conditions:
ready: true
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami4
name: whoami4-abc
namespace: foo
labels:
kubernetes.io/service-name: whoami4
subsets:
addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: web
port: 8080
- 10.10.0.1
- 10.10.0.2
conditions:
ready: true
---
apiVersion: v1

View file

@ -1,17 +1,22 @@
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami5
name: whoami5-abc
namespace: default
labels:
kubernetes.io/service-name: whoami5
subsets:
addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
- name: web
port: 8080
- 10.10.0.3
- 10.10.0.4
conditions:
ready: true
---
apiVersion: v1

View file

@ -1,62 +1,82 @@
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami4
name: whoami4-abc
namespace: default
labels:
kubernetes.io/service-name: whoami4
subsets:
addressType: IPv4
ports:
- name: web
port: 80
endpoints:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: web
port: 80
- 10.10.0.1
- 10.10.0.2
conditions:
ready: true
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami5
name: whoami5-abc
namespace: default
labels:
kubernetes.io/service-name: whoami5
subsets:
addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
- name: web
port: 8080
- 10.10.0.3
- 10.10.0.4
conditions:
ready: true
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami6
name: whoami6-abc
namespace: default
labels:
kubernetes.io/service-name: whoami6
subsets:
addressType: IPv4
ports:
- name: web
port: 80
endpoints:
- addresses:
- ip: 10.10.0.5
- ip: 10.10.0.6
ports:
- name: web
port: 80
- 10.10.0.5
- 10.10.0.6
conditions:
ready: true
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami7
name: whoami7-abc
namespace: default
labels:
kubernetes.io/service-name: whoami7
subsets:
addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses:
- ip: 10.10.0.7
- ip: 10.10.0.8
ports:
- name: web
port: 8080
- 10.10.0.7
- 10.10.0.8
conditions:
ready: true
---
apiVersion: v1

View file

@ -1,17 +1,22 @@
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami5
name: whoami5-abc
namespace: default
labels:
kubernetes.io/service-name: whoami5
subsets:
addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
- name: web
port: 8080
- 10.10.0.3
- 10.10.0.4
conditions:
ready: true
---
apiVersion: traefik.io/v1alpha1

View file

@ -1,32 +1,42 @@
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami5
name: whoami5-abc
namespace: default
labels:
kubernetes.io/service-name: whoami5
subsets:
addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
- name: web
port: 8080
- 10.10.0.3
- 10.10.0.4
conditions:
ready: true
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami4
name: whoami4-abc
namespace: default
labels:
kubernetes.io/service-name: whoami4
subsets:
addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: web
port: 8080
- 10.10.0.1
- 10.10.0.2
conditions:
ready: true
---
apiVersion: v1

View file

@ -1,17 +1,22 @@
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami5
name: whoami5-abc
namespace: default
labels:
kubernetes.io/service-name: whoami5
subsets:
addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
- name: web
port: 8080
- 10.10.0.3
- 10.10.0.4
conditions:
ready: true
---
apiVersion: v1

View file

@ -409,9 +409,8 @@ func (c configBuilder) loadServers(parentNamespace string, svc traefikv1alpha1.L
return nil, err
}
var servers []dynamic.Server
if service.Spec.Type != corev1.ServiceTypeExternalName && svc.HealthCheck != nil {
return nil, fmt.Errorf("HealthCheck allowed only for ExternalName services: %s/%s", namespace, sanitizedName)
return nil, fmt.Errorf("healthCheck allowed only for ExternalName services: %s/%s", namespace, sanitizedName)
}
if service.Spec.Type == corev1.ServiceTypeExternalName {
@ -426,9 +425,7 @@ func (c configBuilder) loadServers(parentNamespace string, svc traefikv1alpha1.L
hostPort := net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(svcPort.Port)))
return append(servers, dynamic.Server{
URL: fmt.Sprintf("%s://%s", protocol, hostPort),
}), nil
return []dynamic.Server{{URL: fmt.Sprintf("%s://%s", protocol, hostPort)}}, nil
}
nativeLB := c.NativeLBByDefault
@ -449,6 +446,7 @@ func (c configBuilder) loadServers(parentNamespace string, svc traefikv1alpha1.L
return []dynamic.Server{{URL: fmt.Sprintf("%s://%s", protocol, address)}}, nil
}
var servers []dynamic.Server
if service.Spec.Type == corev1.ServiceTypeNodePort && svc.NodePortLB {
nodes, nodesExists, nodesErr := c.client.GetNodes()
if nodesErr != nil {
@ -482,27 +480,20 @@ func (c configBuilder) loadServers(parentNamespace string, svc traefikv1alpha1.L
return servers, nil
}
endpoints, endpointsExists, endpointsErr := c.client.GetEndpoints(namespace, sanitizedName)
if endpointsErr != nil {
return nil, endpointsErr
}
if !endpointsExists {
return nil, fmt.Errorf("endpoints not found for %s/%s", namespace, sanitizedName)
endpointSlices, err := c.client.GetEndpointSlicesForService(namespace, sanitizedName)
if err != nil {
return nil, fmt.Errorf("getting endpointslices: %w", err)
}
if len(endpoints.Subsets) == 0 && !c.allowEmptyServices {
return nil, fmt.Errorf("subset not found for %s/%s", namespace, sanitizedName)
}
for _, subset := range endpoints.Subsets {
addresses := map[string]struct{}{}
for _, endpointSlice := range endpointSlices {
var port int32
for _, p := range subset.Ports {
if svcPort.Name == p.Name {
port = p.Port
for _, p := range endpointSlice.Ports {
if svcPort.Name == *p.Name {
port = *p.Port
break
}
}
if port == 0 {
continue
}
@ -512,15 +503,28 @@ func (c configBuilder) loadServers(parentNamespace string, svc traefikv1alpha1.L
return nil, err
}
for _, addr := range subset.Addresses {
hostPort := net.JoinHostPort(addr.IP, strconv.Itoa(int(port)))
for _, endpoint := range endpointSlice.Endpoints {
if endpoint.Conditions.Ready == nil || !*endpoint.Conditions.Ready {
continue
}
servers = append(servers, dynamic.Server{
URL: fmt.Sprintf("%s://%s", protocol, hostPort),
})
for _, address := range endpoint.Addresses {
if _, ok := addresses[address]; ok {
continue
}
addresses[address] = struct{}{}
servers = append(servers, dynamic.Server{
URL: fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, strconv.Itoa(int(port)))),
})
}
}
}
if len(servers) == 0 && !c.allowEmptyServices {
return nil, fmt.Errorf("no servers found for %s/%s", namespace, sanitizedName)
}
return servers, nil
}

View file

@ -238,7 +238,6 @@ func (p *Provider) loadTCPServers(client Client, namespace string, svc traefikv1
}
var servers []dynamic.TCPServer
if service.Spec.Type == corev1.ServiceTypeNodePort && svc.NodePortLB {
nodes, nodesExists, nodesErr := client.GetNodes()
if nodesErr != nil {
@ -284,40 +283,47 @@ func (p *Provider) loadTCPServers(client Client, namespace string, svc traefikv1
return []dynamic.TCPServer{{Address: address}}, nil
}
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name)
if endpointsErr != nil {
return nil, endpointsErr
endpointSlices, err := client.GetEndpointSlicesForService(namespace, svc.Name)
if err != nil {
return nil, fmt.Errorf("getting endpointslices: %w", err)
}
if !endpointsExists {
return nil, errors.New("endpoints not found")
}
if len(endpoints.Subsets) == 0 && !p.AllowEmptyServices {
return nil, errors.New("subset not found")
}
var port int32
for _, subset := range endpoints.Subsets {
for _, p := range subset.Ports {
if svcPort.Name == p.Name {
port = p.Port
addresses := map[string]struct{}{}
for _, endpointSlice := range endpointSlices {
var port int32
for _, p := range endpointSlice.Ports {
if svcPort.Name == *p.Name {
port = *p.Port
break
}
}
if port == 0 {
return nil, errors.New("cannot define a port")
continue
}
for _, addr := range subset.Addresses {
servers = append(servers, dynamic.TCPServer{
Address: net.JoinHostPort(addr.IP, strconv.Itoa(int(port))),
})
for _, endpoint := range endpointSlice.Endpoints {
if endpoint.Conditions.Ready == nil || !*endpoint.Conditions.Ready {
continue
}
for _, address := range endpoint.Addresses {
if _, ok := addresses[address]; ok {
continue
}
addresses[address] = struct{}{}
servers = append(servers, dynamic.TCPServer{
Address: net.JoinHostPort(address, strconv.Itoa(int(port))),
})
}
}
}
}
if len(servers) == 0 && !p.AllowEmptyServices {
return nil, fmt.Errorf("no servers found for %s/%s", namespace, svc.Name)
}
return servers, nil
}

View file

@ -4620,9 +4620,9 @@ func TestLoadIngressRoutes(t *testing.T) {
},
},
{
desc: "IngressRoute, service with multiple subsets",
desc: "IngressRoute, service with multiple endpoint addresses on endpointslice",
allowEmptyServices: true,
paths: []string{"services.yml", "with_multiple_subsets.yml"},
paths: []string{"services.yml", "with_multiple_endpointaddresses.yml"},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
@ -4648,6 +4648,66 @@ func TestLoadIngressRoutes(t *testing.T) {
"default-test-route-6b204d94623b3df4370c": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:80",
},
{
URL: "http://10.10.0.2:80",
},
{
URL: "http://10.10.0.5:80",
},
{
URL: "http://10.10.0.6:80",
},
},
PassHostHeader: Bool(true),
ResponseForwarding: &dynamic.ResponseForwarding{
FlushInterval: ptypes.Duration(100 * time.Millisecond),
},
},
},
},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "IngressRoute, service with duplicated endpointaddresses",
allowEmptyServices: true,
paths: []string{"services.yml", "with_duplicated_endpointaddresses.yml"},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
ServersTransports: map[string]*dynamic.TCPServersTransport{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"default-test-route-6b204d94623b3df4370c": {
EntryPoints: []string{"foo"},
Service: "default-test-route-6b204d94623b3df4370c",
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
Priority: 12,
},
},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
"default-test-route-6b204d94623b3df4370c": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:8080",
},
{
URL: "http://10.10.0.2:8080",
},
{
URL: "http://10.10.0.3:8080",
},
@ -4726,7 +4786,7 @@ func TestLoadIngressRoutes(t *testing.T) {
Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{
{
Name: "default-whoami-without-endpoints-subsets-80",
Name: "default-whoami-without-endpointslice-endpoints-80",
Weight: func(i int) *int { return &i }(1),
},
},
@ -4734,10 +4794,10 @@ func TestLoadIngressRoutes(t *testing.T) {
},
"default-test-mirror": {
Mirroring: &dynamic.Mirroring{
Service: "default-whoami-without-endpoints-subsets-80",
Service: "default-whoami-without-endpointslice-endpoints-80",
Mirrors: []dynamic.MirrorService{
{
Name: "default-whoami-without-endpoints-subsets-80",
Name: "default-whoami-without-endpointslice-endpoints-80",
},
{
Name: "default-test-weighted",
@ -4745,7 +4805,7 @@ func TestLoadIngressRoutes(t *testing.T) {
},
},
},
"default-whoami-without-endpoints-subsets-80": {
"default-whoami-without-endpointslice-endpoints-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
ResponseForwarding: &dynamic.ResponseForwarding{
@ -4799,6 +4859,87 @@ func TestLoadIngressRoutes(t *testing.T) {
}
}
func TestLoadIngressRoutes_multipleEndpointAddresses(t *testing.T) {
wantConf := &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
ServersTransports: map[string]*dynamic.TCPServersTransport{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"default-test-route-6b204d94623b3df4370c": {
EntryPoints: []string{"foo"},
Service: "default-test-route-6b204d94623b3df4370c",
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
Priority: 12,
},
},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
"default-test-route-6b204d94623b3df4370c": {
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
ResponseForwarding: &dynamic.ResponseForwarding{
FlushInterval: ptypes.Duration(100 * time.Millisecond),
},
},
},
},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
TLS: &dynamic.TLSConfiguration{},
}
wantServers := []dynamic.Server{
{
URL: "http://10.10.0.3:8080",
},
{
URL: "http://10.10.0.4:8080",
},
{
URL: "http://10.10.0.5:8080",
},
{
URL: "http://10.10.0.6:8080",
},
}
k8sObjects, crdObjects := readResources(t, []string{"services.yml", "with_multiple_endpointslices.yml"})
kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
crdClient := traefikcrdfake.NewSimpleClientset(crdObjects...)
client := newClientImpl(kubeClient, crdClient)
stopCh := make(chan struct{})
eventCh, err := client.WatchAll(nil, stopCh)
require.NoError(t, err)
if k8sObjects != nil || crdObjects != nil {
// just wait for the first event
<-eventCh
}
p := Provider{}
conf := p.loadConfigurationFromCRD(context.Background(), client)
service, ok := conf.HTTP.Services["default-test-route-6b204d94623b3df4370c"]
require.True(t, ok)
require.NotNil(t, service)
require.NotNil(t, service.LoadBalancer)
assert.ElementsMatch(t, wantServers, service.LoadBalancer.Servers)
service.LoadBalancer.Servers = nil
assert.Equal(t, wantConf, conf)
}
func TestLoadIngressRouteUDPs(t *testing.T) {
testCases := []struct {
desc string

View file

@ -122,7 +122,6 @@ func (p *Provider) loadUDPServers(client Client, namespace string, svc traefikv1
}
var servers []dynamic.UDPServer
if service.Spec.Type == corev1.ServiceTypeNodePort && svc.NodePortLB {
nodes, nodesExists, nodesErr := client.GetNodes()
if nodesErr != nil {
@ -168,39 +167,46 @@ func (p *Provider) loadUDPServers(client Client, namespace string, svc traefikv1
return []dynamic.UDPServer{{Address: address}}, nil
}
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name)
if endpointsErr != nil {
return nil, endpointsErr
endpointSlices, err := client.GetEndpointSlicesForService(namespace, svc.Name)
if err != nil {
return nil, fmt.Errorf("getting endpointslices: %w", err)
}
if !endpointsExists {
return nil, errors.New("endpoints not found")
}
if len(endpoints.Subsets) == 0 && !p.AllowEmptyServices {
return nil, errors.New("subset not found")
}
var port int32
for _, subset := range endpoints.Subsets {
for _, p := range subset.Ports {
if svcPort.Name == p.Name {
port = p.Port
addresses := map[string]struct{}{}
for _, endpointSlice := range endpointSlices {
var port int32
for _, p := range endpointSlice.Ports {
if svcPort.Name == *p.Name {
port = *p.Port
break
}
}
if port == 0 {
return nil, errors.New("cannot define a port")
continue
}
for _, addr := range subset.Addresses {
servers = append(servers, dynamic.UDPServer{
Address: net.JoinHostPort(addr.IP, strconv.Itoa(int(port))),
})
for _, endpoint := range endpointSlice.Endpoints {
if endpoint.Conditions.Ready == nil || !*endpoint.Conditions.Ready {
continue
}
for _, address := range endpoint.Addresses {
if _, ok := addresses[address]; ok {
continue
}
addresses[address] = struct{}{}
servers = append(servers, dynamic.UDPServer{
Address: net.JoinHostPort(address, strconv.Itoa(int(port))),
})
}
}
}
}
if len(servers) == 0 && !p.AllowEmptyServices {
return nil, fmt.Errorf("no servers found for %s/%s", namespace, svc.Name)
}
return servers, nil
}

View file

@ -11,9 +11,11 @@ import (
"github.com/rs/zerolog/log"
"github.com/traefik/traefik/v3/pkg/types"
corev1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
kerror "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
ktypes "k8s.io/apimachinery/pkg/types"
kinformers "k8s.io/client-go/informers"
kclientset "k8s.io/client-go/kubernetes"
@ -61,9 +63,9 @@ type Client interface {
ListTLSRoutes() ([]*gatev1alpha2.TLSRoute, error)
ListNamespaces(selector labels.Selector) ([]string, error)
ListReferenceGrants(namespace string) ([]*gatev1beta1.ReferenceGrant, error)
ListEndpointSlicesForService(namespace, serviceName string) ([]*discoveryv1.EndpointSlice, error)
GetService(namespace, name string) (*corev1.Service, bool, error)
GetSecret(namespace, name string) (*corev1.Secret, bool, error)
GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error)
}
type clientWrapper struct {
@ -222,7 +224,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
if err != nil {
return nil, err
}
_, err = factoryKube.Core().V1().Endpoints().Informer().AddEventHandler(eventHandler)
_, err = factoryKube.Discovery().V1().EndpointSlices().Informer().AddEventHandler(eventHandler)
if err != nil {
return nil, err
}
@ -543,16 +545,20 @@ func (c *clientWrapper) GetService(namespace, name string) (*corev1.Service, boo
return service, exist, err
}
// GetEndpoints returns the named endpoints from the given namespace.
func (c *clientWrapper) GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) {
// ListEndpointSlicesForService returns the EndpointSlices for the given service name in the given namespace.
func (c *clientWrapper) ListEndpointSlicesForService(namespace, serviceName string) ([]*discoveryv1.EndpointSlice, error) {
if !c.isWatchedNamespace(namespace) {
return nil, false, fmt.Errorf("failed to get endpoints %s/%s: namespace is not within watched namespaces", namespace, name)
return nil, fmt.Errorf("failed to get endpointslices for service %s/%s: namespace is not within watched namespaces", namespace, serviceName)
}
endpoint, err := c.factoriesKube[c.lookupNamespace(namespace)].Core().V1().Endpoints().Lister().Endpoints(namespace).Get(name)
exist, err := translateNotFoundError(err)
serviceLabelRequirement, err := labels.NewRequirement(discoveryv1.LabelServiceName, selection.Equals, []string{serviceName})
if err != nil {
return nil, fmt.Errorf("failed to create service label selector requirement: %w", err)
}
serviceSelector := labels.NewSelector()
serviceSelector = serviceSelector.Add(*serviceLabelRequirement)
return endpoint, exist, err
return c.factoriesKube[c.lookupNamespace(namespace)].Discovery().V1().EndpointSlices().Lister().EndpointSlices(namespace).List(serviceSelector)
}
// GetSecret returns the named secret from the given namespace.

View file

@ -17,21 +17,26 @@ spec:
task: whoami
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami
name: whoami-abc
namespace: default
labels:
kubernetes.io/service-name: whoami
subsets:
addressType: IPv4
ports:
- name: web
port: 80
- name: web2
port: 8080
endpoints:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: web
port: 80
- name: web2
port: 8000
- 10.10.0.1
- 10.10.0.2
conditions:
ready: true
---
apiVersion: v1
@ -53,21 +58,26 @@ spec:
task: whoami
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami-bar
name: whoami-bar-abc
namespace: bar
labels:
kubernetes.io/service-name: whoami-bar
subsets:
addressType: IPv4
ports:
- name: web
port: 80
- name: web2
port: 8000
endpoints:
- addresses:
- ip: 10.10.0.11
- ip: 10.10.0.12
ports:
- name: web
port: 80
- name: web2
port: 8000
- 10.10.0.11
- 10.10.0.12
conditions:
ready: true
---
apiVersion: v1
@ -86,19 +96,24 @@ spec:
task: whoami2
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami2
name: whoami2-abc
namespace: default
labels:
kubernetes.io/service-name: whoami2
subsets:
addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
- name: web
port: 8080
- 10.10.0.3
- 10.10.0.4
conditions:
ready: true
---
apiVersion: v1
@ -117,19 +132,24 @@ spec:
task: whoami2
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoamitls
name: whoamitls-abc
namespace: default
labels:
kubernetes.io/service-name: whoamitls
subsets:
addressType: IPv4
ports:
- name: websecure
port: 8443
endpoints:
- addresses:
- ip: 10.10.0.5
- ip: 10.10.0.6
ports:
- name: websecure
port: 8443
- 10.10.0.5
- 10.10.0.6
conditions:
ready: true
---
apiVersion: v1
@ -148,19 +168,24 @@ spec:
task: whoami3
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami3
name: whoami3-abc
namespace: default
labels:
kubernetes.io/service-name: whoami3
subsets:
addressType: IPv4
ports:
- name: websecure2
port: 8443
endpoints:
- addresses:
- ip: 10.10.0.7
- ip: 10.10.0.8
ports:
- name: websecure2
port: 8443
- 10.10.0.7
- 10.10.0.8
conditions:
ready: true
---
apiVersion: v1
@ -201,23 +226,28 @@ spec:
port: 443
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoamitcp
name: whoamitcp-abc
namespace: default
labels:
kubernetes.io/service-name: whoamitcp
subsets:
addressType: IPv4
ports:
- name: tcp-1
protocol: TCP
port: 9000
- name: tcp-2
protocol: TCP
port: 10000
endpoints:
- addresses:
- ip: 10.10.0.9
- ip: 10.10.0.10
ports:
- name: tcp-1
protocol: TCP
port: 9000
- name: tcp-2
protocol: TCP
port: 10000
- 10.10.0.9
- 10.10.0.10
conditions:
ready: true
---
apiVersion: v1
@ -236,23 +266,28 @@ spec:
name: tcp-2
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoamitcp-bar
name: whoamitcp-bar-abc
namespace: bar
labels:
kubernetes.io/service-name: whoamitcp-bar
subsets:
addressType: IPv4
ports:
- name: tcp-1
protocol: TCP
port: 9000
- name: tcp-2
protocol: TCP
port: 10000
endpoints:
- addresses:
- ip: 10.10.0.13
- ip: 10.10.0.14
ports:
- name: tcp-1
protocol: TCP
port: 9000
- name: tcp-2
protocol: TCP
port: 10000
- 10.10.0.13
- 10.10.0.14
conditions:
ready: true
---
apiVersion: v1

View file

@ -370,6 +370,10 @@ func (p *Provider) loadHTTPRouteFilterExtensionRef(namespace string, extensionRe
}
func (p *Provider) loadHTTPServers(namespace string, backendRef gatev1.HTTPBackendRef) (*dynamic.ServersLoadBalancer, error) {
if backendRef.Port == nil {
return nil, errors.New("port is required for Kubernetes Service reference")
}
service, exists, err := p.client.GetService(namespace, string(backendRef.Name))
if err != nil {
return nil, fmt.Errorf("getting service: %w", err)
@ -378,56 +382,58 @@ func (p *Provider) loadHTTPServers(namespace string, backendRef gatev1.HTTPBacke
return nil, errors.New("service not found")
}
var portSpec corev1.ServicePort
var match bool
var svcPort *corev1.ServicePort
for _, p := range service.Spec.Ports {
if backendRef.Port == nil || p.Port == int32(*backendRef.Port) {
portSpec = p
match = true
if p.Port == int32(*backendRef.Port) {
svcPort = &p
break
}
}
if !match {
return nil, errors.New("service port not found")
if svcPort == nil {
return nil, fmt.Errorf("service port %d not found", *backendRef.Port)
}
endpoints, endpointsExists, err := p.client.GetEndpoints(namespace, string(backendRef.Name))
endpointSlices, err := p.client.ListEndpointSlicesForService(namespace, string(backendRef.Name))
if err != nil {
return nil, fmt.Errorf("getting endpoints: %w", err)
return nil, fmt.Errorf("getting endpointslices: %w", err)
}
if !endpointsExists {
return nil, errors.New("endpoints not found")
}
if len(endpoints.Subsets) == 0 {
return nil, errors.New("subset not found")
if len(endpointSlices) == 0 {
return nil, errors.New("endpointslices not found")
}
lb := &dynamic.ServersLoadBalancer{}
lb.SetDefaults()
var port int32
var portStr string
for _, subset := range endpoints.Subsets {
for _, p := range subset.Ports {
if portSpec.Name == p.Name {
port = p.Port
protocol := getProtocol(*svcPort)
addresses := map[string]struct{}{}
for _, endpointSlice := range endpointSlices {
var port int32
for _, p := range endpointSlice.Ports {
if svcPort.Name == *p.Name {
port = *p.Port
break
}
}
if port == 0 {
return nil, errors.New("cannot define a port")
continue
}
protocol := getProtocol(portSpec)
for _, endpoint := range endpointSlice.Endpoints {
if endpoint.Conditions.Ready == nil || !*endpoint.Conditions.Ready {
continue
}
portStr = strconv.FormatInt(int64(port), 10)
for _, addr := range subset.Addresses {
lb.Servers = append(lb.Servers, dynamic.Server{
URL: fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(addr.IP, portStr)),
})
for _, address := range endpoint.Addresses {
if _, ok := addresses[address]; ok {
continue
}
addresses[address] = struct{}{}
lb.Servers = append(lb.Servers, dynamic.Server{
URL: fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, strconv.Itoa(int(port)))),
})
}
}
}

View file

@ -206,82 +206,71 @@ func (p *Provider) loadTCPServices(namespace string, backendRefs []gatev1.Backen
return nil, nil, fmt.Errorf("unsupported BackendRef %s/%s/%s", *backendRef.Group, *backendRef.Kind, backendRef.Name)
}
svc := dynamic.TCPService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{},
if backendRef.Port == nil {
return nil, nil, errors.New("port is required for Kubernetes Service reference")
}
service, exists, err := p.client.GetService(namespace, string(backendRef.Name))
if err != nil {
return nil, nil, err
return nil, nil, fmt.Errorf("getting service: %w", err)
}
if !exists {
return nil, nil, errors.New("service not found")
}
if len(service.Spec.Ports) > 1 && backendRef.Port == nil {
// If the port is unspecified and the backend is a Service
// object consisting of multiple port definitions, the route
// must be dropped from the Gateway. The controller should
// raise the "ResolvedRefs" condition on the Gateway with the
// "DroppedRoutes" reason. The gateway status for this route
// should be updated with a condition that describes the error
// more specifically.
log.Error().Msg("A multiple ports Kubernetes Service cannot be used if unspecified backendRef.Port")
continue
}
var portSpec corev1.ServicePort
var match bool
var svcPort *corev1.ServicePort
for _, p := range service.Spec.Ports {
if backendRef.Port == nil || p.Port == int32(*backendRef.Port) {
portSpec = p
match = true
if p.Port == int32(*backendRef.Port) {
svcPort = &p
break
}
}
if !match {
return nil, nil, errors.New("service port not found")
if svcPort == nil {
return nil, nil, fmt.Errorf("service port %d not found", *backendRef.Port)
}
endpoints, endpointsExists, endpointsErr := p.client.GetEndpoints(namespace, string(backendRef.Name))
if endpointsErr != nil {
return nil, nil, endpointsErr
endpointSlices, err := p.client.ListEndpointSlicesForService(namespace, string(backendRef.Name))
if err != nil {
return nil, nil, fmt.Errorf("getting endpointslices: %w", err)
}
if len(endpointSlices) == 0 {
return nil, nil, errors.New("endpointslices not found")
}
if !endpointsExists {
return nil, nil, errors.New("endpoints not found")
}
svc := dynamic.TCPService{LoadBalancer: &dynamic.TCPServersLoadBalancer{}}
if len(endpoints.Subsets) == 0 {
return nil, nil, errors.New("subset not found")
}
var port int32
var portStr string
for _, subset := range endpoints.Subsets {
for _, p := range subset.Ports {
if portSpec.Name == p.Name {
port = p.Port
addresses := map[string]struct{}{}
for _, endpointSlice := range endpointSlices {
var port int32
for _, p := range endpointSlice.Ports {
if svcPort.Name == *p.Name {
port = *p.Port
break
}
}
if port == 0 {
return nil, nil, errors.New("cannot define a port")
continue
}
portStr = strconv.FormatInt(int64(port), 10)
for _, addr := range subset.Addresses {
svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, dynamic.TCPServer{
Address: net.JoinHostPort(addr.IP, portStr),
})
for _, endpoint := range endpointSlice.Endpoints {
if endpoint.Conditions.Ready == nil || !*endpoint.Conditions.Ready {
continue
}
for _, address := range endpoint.Addresses {
if _, ok := addresses[address]; ok {
continue
}
addresses[address] = struct{}{}
svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, dynamic.TCPServer{
Address: net.JoinHostPort(address, strconv.Itoa(int(port))),
})
}
}
}
serviceName := provider.Normalize(service.Namespace + "-" + service.Name + "-" + portStr)
serviceName := provider.Normalize(service.Namespace + "-" + service.Name + "-" + strconv.Itoa(int(svcPort.Port)))
services[serviceName] = &svc
wrrSvc.Weighted.Services = append(wrrSvc.Weighted.Services, dynamic.TCPWRRService{Name: serviceName, Weight: &weight})

View file

@ -16,10 +16,12 @@ import (
"github.com/traefik/traefik/v3/pkg/types"
traefikversion "github.com/traefik/traefik/v3/pkg/version"
corev1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
netv1 "k8s.io/api/networking/v1"
kerror "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
kinformers "k8s.io/client-go/informers"
kclientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
@ -41,7 +43,7 @@ type Client interface {
GetService(namespace, name string) (*corev1.Service, bool, error)
GetSecret(namespace, name string) (*corev1.Secret, bool, error)
GetNodes() ([]*corev1.Node, bool, error)
GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error)
GetEndpointSlicesForService(namespace, serviceName string) ([]*discoveryv1.EndpointSlice, error)
UpdateIngressStatus(ing *netv1.Ingress, ingStatus []netv1.IngressLoadBalancerIngress) error
}
@ -185,7 +187,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
if err != nil {
return nil, err
}
_, err = factoryKube.Core().V1().Endpoints().Informer().AddEventHandler(eventHandler)
_, err = factoryKube.Discovery().V1().EndpointSlices().Informer().AddEventHandler(eventHandler)
if err != nil {
return nil, err
}
@ -340,15 +342,20 @@ func (c *clientWrapper) GetService(namespace, name string) (*corev1.Service, boo
return service, exist, err
}
// GetEndpoints returns the named endpoints from the given namespace.
func (c *clientWrapper) GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) {
// GetEndpointSlicesForService returns the EndpointSlices for the given service name in the given namespace.
func (c *clientWrapper) GetEndpointSlicesForService(namespace, serviceName string) ([]*discoveryv1.EndpointSlice, error) {
if !c.isWatchedNamespace(namespace) {
return nil, false, fmt.Errorf("failed to get endpoints %s/%s: namespace is not within watched namespaces", namespace, name)
return nil, fmt.Errorf("failed to get endpointslices for service %s/%s: namespace is not within watched namespaces", namespace, serviceName)
}
endpoint, err := c.factoriesKube[c.lookupNamespace(namespace)].Core().V1().Endpoints().Lister().Endpoints(namespace).Get(name)
exist, err := translateNotFoundError(err)
return endpoint, exist, err
serviceLabelRequirement, err := labels.NewRequirement(discoveryv1.LabelServiceName, selection.Equals, []string{serviceName})
if err != nil {
return nil, fmt.Errorf("failed to create service label selector requirement: %w", err)
}
serviceSelector := labels.NewSelector()
serviceSelector = serviceSelector.Add(*serviceLabelRequirement)
return c.factoriesKube[c.lookupNamespace(namespace)].Discovery().V1().EndpointSlices().Lister().EndpointSlices(namespace).List(serviceSelector)
}
// GetSecret returns the named secret from the given namespace.

View file

@ -6,6 +6,7 @@ import (
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s"
corev1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
netv1 "k8s.io/api/networking/v1"
)
@ -15,15 +16,15 @@ type clientMock struct {
ingresses []*netv1.Ingress
services []*corev1.Service
secrets []*corev1.Secret
endpoints []*corev1.Endpoints
endpointSlices []*discoveryv1.EndpointSlice
nodes []*corev1.Node
ingressClasses []*netv1.IngressClass
apiServiceError error
apiSecretError error
apiEndpointsError error
apiNodesError error
apiIngressStatusError error
apiServiceError error
apiSecretError error
apiEndpointSlicesError error
apiNodesError error
apiIngressStatusError error
watchChan chan interface{}
}
@ -43,8 +44,8 @@ func newClientMock(path string) clientMock {
c.services = append(c.services, o)
case *corev1.Secret:
c.secrets = append(c.secrets, o)
case *corev1.Endpoints:
c.endpoints = append(c.endpoints, o)
case *discoveryv1.EndpointSlice:
c.endpointSlices = append(c.endpointSlices, o)
case *corev1.Node:
c.nodes = append(c.nodes, o)
case *netv1.Ingress:
@ -76,18 +77,19 @@ func (c clientMock) GetService(namespace, name string) (*corev1.Service, bool, e
return nil, false, c.apiServiceError
}
func (c clientMock) GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) {
if c.apiEndpointsError != nil {
return nil, false, c.apiEndpointsError
func (c clientMock) GetEndpointSlicesForService(namespace, serviceName string) ([]*discoveryv1.EndpointSlice, error) {
if c.apiEndpointSlicesError != nil {
return nil, c.apiEndpointSlicesError
}
for _, endpoints := range c.endpoints {
if endpoints.Namespace == namespace && endpoints.Name == name {
return endpoints, true, nil
var result []*discoveryv1.EndpointSlice
for _, endpointSlice := range c.endpointSlices {
if endpointSlice.Namespace == namespace && endpointSlice.Labels[discoveryv1.LabelServiceName] == serviceName {
result = append(result, endpointSlice)
}
}
return &corev1.Endpoints{}, false, nil
return result, nil
}
func (c clientMock) GetNodes() ([]*corev1.Node, bool, error) {

View file

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
netv1 "k8s.io/api/networking/v1"
kerror "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -188,10 +189,10 @@ func TestClientIgnoresHelmOwnedSecrets(t *testing.T) {
assert.False(t, found)
}
func TestClientIgnoresEmptyEndpointUpdates(t *testing.T) {
emptyEndpoint := &corev1.Endpoints{
func TestClientIgnoresEmptyEndpointSliceUpdates(t *testing.T) {
emptyEndpointSlice := &discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
Name: "empty-endpoint",
Name: "empty-endpointslice",
Namespace: "test",
ResourceVersion: "1244",
Annotations: map[string]string{
@ -200,25 +201,31 @@ func TestClientIgnoresEmptyEndpointUpdates(t *testing.T) {
},
}
filledEndpoint := &corev1.Endpoints{
samplePortName := "testing"
samplePortNumber := int32(1337)
samplePortProtocol := corev1.ProtocolTCP
sampleAddressReady := true
filledEndpointSlice := &discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
Name: "filled-endpoint",
Name: "filled-endpointslice",
Namespace: "test",
ResourceVersion: "1234",
},
Subsets: []corev1.EndpointSubset{{
Addresses: []corev1.EndpointAddress{{
IP: "10.13.37.1",
}},
Ports: []corev1.EndpointPort{{
Name: "testing",
Port: 1337,
Protocol: "tcp",
}},
AddressType: discoveryv1.AddressTypeIPv4,
Endpoints: []discoveryv1.Endpoint{{
Addresses: []string{"10.13.37.1"},
Conditions: discoveryv1.EndpointConditions{
Ready: &sampleAddressReady,
},
}},
Ports: []discoveryv1.EndpointPort{{
Name: &samplePortName,
Port: &samplePortNumber,
Protocol: &samplePortProtocol,
}},
}
kubeClient := kubefake.NewSimpleClientset(emptyEndpoint, filledEndpoint)
kubeClient := kubefake.NewSimpleClientset(emptyEndpointSlice, filledEndpointSlice)
discovery, _ := kubeClient.Discovery().(*discoveryfake.FakeDiscovery)
discovery.FakedServerVersion = &kversion.Info{
@ -234,50 +241,72 @@ func TestClientIgnoresEmptyEndpointUpdates(t *testing.T) {
select {
case event := <-eventCh:
ep, ok := event.(*corev1.Endpoints)
ep, ok := event.(*discoveryv1.EndpointSlice)
require.True(t, ok)
assert.True(t, ep.Name == "empty-endpoint" || ep.Name == "filled-endpoint")
assert.True(t, ep.Name == "empty-endpointslice" || ep.Name == "filled-endpointslice")
case <-time.After(50 * time.Millisecond):
assert.Fail(t, "expected to receive event for endpoints")
assert.Fail(t, "expected to receive event for endpointslices")
}
emptyEndpoint, err = kubeClient.CoreV1().Endpoints("test").Get(context.TODO(), "empty-endpoint", metav1.GetOptions{})
emptyEndpointSlice, err = kubeClient.DiscoveryV1().EndpointSlices("test").Get(context.TODO(), "empty-endpointslice", metav1.GetOptions{})
assert.NoError(t, err)
// Update endpoint annotation and resource version (apparently not done by fake client itself)
// to show an update that should not trigger an update event on our eventCh.
// This reflects the behavior of kubernetes controllers which use endpoint annotations for leader election.
emptyEndpoint.Annotations["test-annotation"] = "___"
emptyEndpoint.ResourceVersion = "1245"
_, err = kubeClient.CoreV1().Endpoints("test").Update(context.TODO(), emptyEndpoint, metav1.UpdateOptions{})
emptyEndpointSlice.Annotations["test-annotation"] = "___"
emptyEndpointSlice.ResourceVersion = "1245"
_, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(context.TODO(), emptyEndpointSlice, metav1.UpdateOptions{})
require.NoError(t, err)
select {
case event := <-eventCh:
ep, ok := event.(*corev1.Endpoints)
ep, ok := event.(*discoveryv1.EndpointSlice)
require.True(t, ok)
assert.Fail(t, "didn't expect to receive event for empty endpoint update", ep.Name)
assert.Fail(t, "didn't expect to receive event for empty endpointslice update", ep.Name)
case <-time.After(50 * time.Millisecond):
}
filledEndpoint, err = kubeClient.CoreV1().Endpoints("test").Get(context.TODO(), "filled-endpoint", metav1.GetOptions{})
filledEndpointSlice, err = kubeClient.DiscoveryV1().EndpointSlices("test").Get(context.TODO(), "filled-endpointslice", metav1.GetOptions{})
assert.NoError(t, err)
filledEndpoint.Subsets[0].Addresses[0].IP = "10.13.37.2"
filledEndpoint.ResourceVersion = "1235"
_, err = kubeClient.CoreV1().Endpoints("test").Update(context.TODO(), filledEndpoint, metav1.UpdateOptions{})
filledEndpointSlice.Endpoints[0].Addresses[0] = "10.13.37.2"
filledEndpointSlice.ResourceVersion = "1235"
_, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(context.TODO(), filledEndpointSlice, metav1.UpdateOptions{})
require.NoError(t, err)
select {
case event := <-eventCh:
ep, ok := event.(*corev1.Endpoints)
ep, ok := event.(*discoveryv1.EndpointSlice)
require.True(t, ok)
assert.Equal(t, "filled-endpoint", ep.Name)
assert.Equal(t, "filled-endpointslice", ep.Name)
case <-time.After(50 * time.Millisecond):
assert.Fail(t, "expected to receive event for filled endpoint")
assert.Fail(t, "expected to receive event for filled endpointslice")
}
select {
case <-eventCh:
assert.Fail(t, "received more than one event")
case <-time.After(50 * time.Millisecond):
}
newPortNumber := int32(42)
filledEndpointSlice.Ports[0].Port = &newPortNumber
filledEndpointSlice.ResourceVersion = "1236"
_, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(context.TODO(), filledEndpointSlice, metav1.UpdateOptions{})
require.NoError(t, err)
select {
case event := <-eventCh:
ep, ok := event.(*discoveryv1.EndpointSlice)
require.True(t, ok)
assert.Equal(t, "filled-endpointslice", ep.Name)
case <-time.After(50 * time.Millisecond):
assert.Fail(t, "expected to receive event for filled endpointslice")
}
select {

View file

@ -1,32 +1,42 @@
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- name: tchouk
port: 8089
endpoints:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: tchouk
port: 8089
- 10.10.0.1
- 10.10.0.2
conditions:
ready: true
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: toto
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- name: tchouk
port: 8089
endpoints:
- addresses:
- ip: 10.11.0.1
- ip: 10.11.0.2
ports:
- name: tchouk
port: 8089
- 10.11.0.1
- 10.11.0.2
conditions:
ready: true
---
kind: Ingress

View file

@ -50,35 +50,41 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiversion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.30.0.1
ports:
- port: 8080
- addresses:
- ip: 10.41.0.1
ports:
- port: 8080
- 10.30.0.1
- 10.41.0.1
conditions:
ready: true
---
kind: Endpoints
apiversion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service2
name: service2-abc
namespace: testing
labels:
kubernetes.io/service-name: service2
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- addresses:
- ip: 10.21.0.1
ports:
- port: 8080
- 10.10.0.1
- 10.21.0.1
conditions:
ready: true

View file

@ -40,18 +40,21 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- addresses:
- ip: 10.21.0.1
ports:
- port: 8080
- 10.10.0.1
- 10.21.0.1
conditions:
ready: true

View file

@ -37,18 +37,21 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- addresses:
- ip: 10.21.0.1
ports:
- port: 8080
- 10.10.0.1
- 10.21.0.1
conditions:
ready: true

View file

@ -30,18 +30,21 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- addresses:
- ip: 10.21.0.1
ports:
- port: 8080
- 10.10.0.1
- 10.21.0.1
conditions:
ready: true

View file

@ -36,18 +36,21 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- addresses:
- ip: 10.21.0.1
ports:
- port: 8080
- 10.10.0.1
- 10.21.0.1
conditions:
ready: true

View file

@ -52,15 +52,20 @@ spec:
externalName: "2001:0db8:3c4d:0015:0000:0000:1a2f:2a3b"
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service-bar
name: service-bar-abc
namespace: testing
labels:
kubernetes.io/service-name: service-bar
subsets:
addressType: IPv6
ports:
- name: http
port: 8080
endpoints:
- addresses:
- ip: "2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b"
ports:
- name: http
port: 8080
- "2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b"
conditions:
ready: true

View file

@ -30,18 +30,21 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8443
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8443
- addresses:
- ip: 10.21.0.1
ports:
- port: 8443
- 10.10.0.1
- 10.21.0.1
conditions:
ready: true

View file

@ -31,20 +31,21 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- name: https
port: 8443
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- name: https
port: 8443
- addresses:
- ip: 10.21.0.1
ports:
- name: https
port: 8443
- 10.10.0.1
- 10.21.0.1
conditions:
ready: true

View file

@ -31,20 +31,21 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- name: https-foo
port: 8443
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- name: https-foo
port: 8443
- addresses:
- ip: 10.21.0.1
ports:
- name: https-foo
port: 8443
- 10.10.0.1
- 10.21.0.1
conditions:
ready: true

View file

@ -29,18 +29,21 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- addresses:
- ip: 10.21.0.1
ports:
- port: 8080
- 10.10.0.1
- 10.21.0.1
conditions:
ready: true

View file

@ -33,23 +33,42 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- name: tchouk
port: 8089
endpoints:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: tchouk
port: 8089
- 10.10.0.1
- 10.10.0.2
conditions:
ready: true
---
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1-def
namespace: testing
labels:
kubernetes.io/service-name: service1
addressType: IPv4
ports:
- name: carotte
port: 8090
endpoints:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
- ip: 10.10.0.3
ports:
- name: carotte
port: 8090
- 10.10.0.1
- 10.10.0.2
- 10.10.0.3
conditions:
ready: true

View file

@ -53,18 +53,21 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- addresses:
- ip: 10.21.0.1
ports:
- port: 8080
- 10.10.0.1
- 10.21.0.1
conditions:
ready: true

View file

@ -41,18 +41,21 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- addresses:
- ip: 10.21.0.1
ports:
- port: 8080
- 10.10.0.1
- 10.21.0.1
conditions:
ready: true

View file

@ -37,18 +37,21 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- addresses:
- ip: 10.21.0.1
ports:
- port: 8080
- 10.10.0.1
- 10.21.0.1
conditions:
ready: true

View file

@ -31,14 +31,20 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- 10.10.0.1
conditions:
ready: true

View file

@ -36,27 +36,38 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 80
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 80
- 10.10.0.1
conditions:
ready: true
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: defaultservice
name: defaultservice-abc
namespace: testing
labels:
kubernetes.io/service-name: defaultservice
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- 10.10.0.1
conditions:
ready: true

View file

@ -30,14 +30,20 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- 10.10.0.1
conditions:
ready: true

View file

@ -28,14 +28,20 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- 10.10.0.1
conditions:
ready: true

View file

@ -30,14 +30,20 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- 10.10.0.1
conditions:
ready: true

View file

@ -30,14 +30,20 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- 10.10.0.1
conditions:
ready: true

View file

@ -31,14 +31,20 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- 10.10.0.1
conditions:
ready: true

View file

@ -37,15 +37,20 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- 10.10.0.1
conditions:
ready: true

View file

@ -64,14 +64,20 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- 10.10.0.1
conditions:
ready: true

View file

@ -29,14 +29,20 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- 10.10.0.1
conditions:
ready: true

View file

@ -64,14 +64,20 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- 10.10.0.1
conditions:
ready: true

View file

@ -29,15 +29,20 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- name: foobar
port: 4711
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- name: foobar
port: 4711
- 10.10.0.1
conditions:
ready: true

View file

@ -31,14 +31,20 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- 10.10.0.1
conditions:
ready: true

View file

@ -31,15 +31,20 @@ spec:
type: ClusterIP
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: example-com
name: example-com-abc
namespace: testing
labels:
kubernetes.io/service-name: example-com
subsets:
addressType: IPv4
ports:
- name: http
port: 80
endpoints:
- addresses:
- ip: 10.11.0.1
ports:
- name: http
port: 80
- 10.11.0.1
conditions:
ready: true

View file

@ -30,8 +30,14 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
addressType: IPv4
ports: null
endpoints: []

View file

@ -40,24 +40,43 @@ spec:
type: ClusterIP
---
apiVersion: v1
kind: Endpoints
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
subsets:
labels:
kubernetes.io/service-name: service1
addressType: IPv4
ports:
- name: http-admin
port: 8079
protocol: TCP
endpoints:
- addresses:
- ip: 10.0.0.1
nodeName: admin.whoami.service1
ports:
- name: http-admin
port: 8079
protocol: TCP
- 10.0.0.1
conditions:
ready: true
nodeName: admin.whoami.service1
---
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1-def
namespace: testing
labels:
kubernetes.io/service-name: service1
addressType: IPv4
ports:
- name: http
port: 8080
protocol: TCP
endpoints:
- addresses:
- ip: 10.0.0.1
nodeName: whoami.service1
# targetRef:
ports:
- name: http
port: 8080
protocol: TCP
- 10.0.0.1
conditions:
ready: true
nodeName: whoami.service1

View file

@ -33,18 +33,23 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- name: carotte
port: 8090
- name: tchouk
port: 8089
endpoints:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: carotte
port: 8090
- name: tchouk
port: 8089
- 10.10.0.1
- 10.10.0.2
conditions:
ready: true

View file

@ -33,24 +33,23 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- name: carotte
port: 8090
- name: tchouk
port: 8089
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- name: carotte
port: 8090
- name: tchouk
port: 8089
- addresses:
- ip: 10.21.0.1
ports:
- name: carotte
port: 8090
- name: tchouk
port: 8089
- 10.10.0.1
- 10.21.0.1
conditions:
ready: true

View file

@ -33,24 +33,23 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- name: carotte
port: 8090
- name: tchouk
port: 8089
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- name: carotte
port: 8090
- name: tchouk
port: 8089
- addresses:
- ip: 10.21.0.1
ports:
- name: carotte
port: 8090
- name: tchouk
port: 8089
- 10.10.0.1
- 10.21.0.1
conditions:
ready: true

View file

@ -28,14 +28,20 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- 10.10.0.1
conditions:
ready: true

View file

@ -38,18 +38,21 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- addresses:
- ip: 10.21.0.1
ports:
- port: 8080
- 10.10.0.1
- 10.21.0.1
conditions:
ready: true

View file

@ -40,18 +40,23 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- name: carotte
port: 8090
- name: tchouk
port: 8089
endpoints:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: carotte
port: 8090
- name: tchouk
port: 8089
- 10.10.0.1
- 10.10.0.2
conditions:
ready: true

View file

@ -52,35 +52,41 @@ spec:
clusterIP: 10.1.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- addresses:
- ip: 10.21.0.1
ports:
- port: 8080
- 10.10.0.1
- 10.21.0.1
conditions:
ready: true
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service2
name: service2-abc
namespace: testing
labels:
kubernetes.io/service-name: service2
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.2
ports:
- port: 8080
- addresses:
- ip: 10.21.0.2
ports:
- port: 8080
- 10.10.0.2
- 10.21.0.2
conditions:
ready: true

View file

@ -30,15 +30,21 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8089
name: ""
endpoints:
- addresses:
- ip: 10.11.0.1
- ip: 10.11.0.2
ports:
- port: 8089
- 10.11.0.1
- 10.11.0.2
conditions:
ready: true

View file

@ -30,15 +30,21 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8089
name: ""
endpoints:
- addresses:
- ip: 10.11.0.1
- ip: 10.11.0.2
ports:
- port: 8089
- 10.11.0.1
- 10.11.0.2
conditions:
ready: true

View file

@ -30,14 +30,20 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- 10.10.0.1
conditions:
ready: true

View file

@ -31,14 +31,20 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- 10.10.0.1
conditions:
ready: true

View file

@ -24,18 +24,21 @@ spec:
clusterIP: 10.0.0.1
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1
name: service1-abc
namespace: testing
labels:
kubernetes.io/service-name: service1
subsets:
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080
- addresses:
- ip: 10.21.0.1
ports:
- port: 8080
- 10.10.0.1
- 10.21.0.1
conditions:
ready: true

View file

@ -83,15 +83,20 @@ spec:
type: ClusterIP
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: example-com
name: example-com-abc
namespace: testing
labels:
kubernetes.io/service-name: example-com
subsets:
addressType: IPv4
ports:
- name: http
port: 80
endpoints:
- addresses:
- ip: 10.11.0.1
ports:
- name: http
port: 80
- 10.11.0.1
conditions:
ready: true

View file

@ -651,36 +651,41 @@ func (p *Provider) loadService(client Client, namespace string, backend netv1.In
return svc, nil
}
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, backend.Service.Name)
if endpointsErr != nil {
return nil, endpointsErr
endpointSlices, err := client.GetEndpointSlicesForService(namespace, backend.Service.Name)
if err != nil {
return nil, fmt.Errorf("getting endpointslices: %w", err)
}
if !endpointsExists {
return nil, errors.New("endpoints not found")
}
for _, subset := range endpoints.Subsets {
addresses := map[string]struct{}{}
for _, endpointSlice := range endpointSlices {
var port int32
for _, p := range subset.Ports {
if portName == p.Name {
port = p.Port
for _, p := range endpointSlice.Ports {
if portName == *p.Name {
port = *p.Port
break
}
}
if port == 0 {
continue
}
protocol := getProtocol(portSpec, portName, svcConfig)
for _, addr := range subset.Addresses {
hostPort := net.JoinHostPort(addr.IP, strconv.Itoa(int(port)))
for _, endpoint := range endpointSlice.Endpoints {
if endpoint.Conditions.Ready == nil || !*endpoint.Conditions.Ready {
continue
}
svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, dynamic.Server{
URL: fmt.Sprintf("%s://%s", protocol, hostPort),
})
for _, address := range endpoint.Addresses {
if _, ok := addresses[address]; ok {
continue
}
addresses[address] = struct{}{}
svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, dynamic.Server{
URL: fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, strconv.Itoa(int(port)))),
})
}
}
}

View file

@ -1,7 +1,7 @@
package k8s
import (
corev1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -47,61 +47,54 @@ func objChanged(oldObj, newObj interface{}) bool {
return false
}
if _, ok := oldObj.(*corev1.Endpoints); ok {
return endpointsChanged(oldObj.(*corev1.Endpoints), newObj.(*corev1.Endpoints))
if _, ok := oldObj.(*discoveryv1.EndpointSlice); ok {
return endpointSliceChanged(oldObj.(*discoveryv1.EndpointSlice), newObj.(*discoveryv1.EndpointSlice))
}
return true
}
func endpointsChanged(a, b *corev1.Endpoints) bool {
if len(a.Subsets) != len(b.Subsets) {
// In some Kubernetes versions leader election is done by updating an endpoint annotation every second,
// if there are no changes to the endpoints addresses, ports, and there are no addresses defined for an endpoint
// the event can safely be ignored and won't cause unnecessary config reloads.
// TODO: check if Kubernetes is still using EndpointSlice for leader election, which seems to not be the case anymore.
func endpointSliceChanged(a, b *discoveryv1.EndpointSlice) bool {
if len(a.Ports) != len(b.Ports) {
return true
}
for i, sa := range a.Subsets {
sb := b.Subsets[i]
if subsetsChanged(sa, sb) {
return true
}
}
return false
}
func subsetsChanged(sa, sb corev1.EndpointSubset) bool {
if len(sa.Addresses) != len(sb.Addresses) {
return true
}
if len(sa.Ports) != len(sb.Ports) {
return true
}
// in Addresses and Ports, we should be able to rely on
// these being sorted and able to be compared
// they are supposed to be in a canonical format
for addr, aaddr := range sa.Addresses {
baddr := sb.Addresses[addr]
if aaddr.IP != baddr.IP {
return true
}
if aaddr.Hostname != baddr.Hostname {
return true
}
}
for port, aport := range sa.Ports {
bport := sb.Ports[port]
for i, aport := range a.Ports {
bport := b.Ports[i]
if aport.Name != bport.Name {
return true
}
if aport.Port != bport.Port {
return true
}
}
if aport.Protocol != bport.Protocol {
if len(a.Endpoints) != len(b.Endpoints) {
return true
}
for i, ea := range a.Endpoints {
eb := b.Endpoints[i]
if endpointChanged(ea, eb) {
return true
}
}
return false
}
func endpointChanged(a, b discoveryv1.Endpoint) bool {
if len(a.Addresses) != len(b.Addresses) {
return true
}
for i, aaddr := range a.Addresses {
baddr := b.Addresses[i]
if aaddr != baddr {
return true
}
}

View file

@ -4,12 +4,14 @@ import (
"testing"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
netv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func Test_detectChanges(t *testing.T) {
portA := int32(80)
portB := int32(8080)
tests := []struct {
name string
oldObj interface{}
@ -21,28 +23,28 @@ func Test_detectChanges(t *testing.T) {
want: true,
},
{
name: "With empty endpoints",
oldObj: &corev1.Endpoints{},
newObj: &corev1.Endpoints{},
name: "With empty endpointslice",
oldObj: &discoveryv1.EndpointSlice{},
newObj: &discoveryv1.EndpointSlice{},
},
{
name: "With old nil",
newObj: &corev1.Endpoints{},
newObj: &discoveryv1.EndpointSlice{},
want: true,
},
{
name: "With new nil",
oldObj: &corev1.Endpoints{},
oldObj: &discoveryv1.EndpointSlice{},
want: true,
},
{
name: "With same version",
oldObj: &corev1.Endpoints{
oldObj: &discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "1",
},
},
newObj: &corev1.Endpoints{
newObj: &discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "1",
},
@ -50,12 +52,12 @@ func Test_detectChanges(t *testing.T) {
},
{
name: "With different version",
oldObj: &corev1.Endpoints{
oldObj: &discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "1",
},
},
newObj: &corev1.Endpoints{
newObj: &discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "2",
},
@ -90,7 +92,7 @@ func Test_detectChanges(t *testing.T) {
},
{
name: "With same annotations",
oldObj: &corev1.Endpoints{
oldObj: &discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "1",
Annotations: map[string]string{
@ -98,7 +100,7 @@ func Test_detectChanges(t *testing.T) {
},
},
},
newObj: &corev1.Endpoints{
newObj: &discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "2",
Annotations: map[string]string{
@ -109,7 +111,7 @@ func Test_detectChanges(t *testing.T) {
},
{
name: "With different annotations",
oldObj: &corev1.Endpoints{
oldObj: &discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "1",
Annotations: map[string]string{
@ -117,7 +119,7 @@ func Test_detectChanges(t *testing.T) {
},
},
},
newObj: &corev1.Endpoints{
newObj: &discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "2",
Annotations: map[string]string{
@ -127,384 +129,94 @@ func Test_detectChanges(t *testing.T) {
},
},
{
name: "With same subsets",
oldObj: &corev1.Endpoints{
name: "With same endpoints and ports",
oldObj: &discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "1",
},
Subsets: []corev1.EndpointSubset{},
Endpoints: []discoveryv1.Endpoint{},
Ports: []discoveryv1.EndpointPort{},
},
newObj: &corev1.Endpoints{
newObj: &discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "1",
},
Subsets: []corev1.EndpointSubset{},
Endpoints: []discoveryv1.Endpoint{},
Ports: []discoveryv1.EndpointPort{},
},
},
{
name: "With different len of subsets",
oldObj: &corev1.Endpoints{
name: "With different len of endpoints",
oldObj: &discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "1",
},
Subsets: []corev1.EndpointSubset{},
Endpoints: []discoveryv1.Endpoint{},
},
newObj: &corev1.Endpoints{
newObj: &discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "2",
},
Subsets: []corev1.EndpointSubset{{}},
Endpoints: []discoveryv1.Endpoint{{}},
},
want: true,
},
{
name: "With same subsets with same len of addresses",
oldObj: &corev1.Endpoints{
name: "With different endpoints",
oldObj: &discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "1",
},
Subsets: []corev1.EndpointSubset{{
Addresses: []corev1.EndpointAddress{},
Endpoints: []discoveryv1.Endpoint{{
Addresses: []string{"10.10.10.10"},
}},
},
newObj: &corev1.Endpoints{
newObj: &discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "2",
},
Subsets: []corev1.EndpointSubset{{
Addresses: []corev1.EndpointAddress{},
}},
},
},
{
name: "With same subsets with different len of addresses",
oldObj: &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "1",
},
Subsets: []corev1.EndpointSubset{{
Addresses: []corev1.EndpointAddress{},
}},
},
newObj: &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "2",
},
Subsets: []corev1.EndpointSubset{{
Addresses: []corev1.EndpointAddress{{}},
Endpoints: []discoveryv1.Endpoint{{
Addresses: []string{"10.10.10.11"},
}},
},
want: true,
},
{
name: "With same subsets with same len of ports",
oldObj: &corev1.Endpoints{
name: "With different len of ports",
oldObj: &discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "1",
},
Subsets: []corev1.EndpointSubset{{
Ports: []corev1.EndpointPort{},
}},
Ports: []discoveryv1.EndpointPort{},
},
newObj: &corev1.Endpoints{
newObj: &discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "2",
},
Subsets: []corev1.EndpointSubset{{
Ports: []corev1.EndpointPort{},
}},
},
},
{
name: "With same subsets with different len of ports",
oldObj: &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "1",
},
Subsets: []corev1.EndpointSubset{{
Ports: []corev1.EndpointPort{},
}},
},
newObj: &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "2",
},
Subsets: []corev1.EndpointSubset{{
Ports: []corev1.EndpointPort{{}},
}},
Ports: []discoveryv1.EndpointPort{{}},
},
want: true,
},
{
name: "With same subsets with same len of addresses with same ip",
oldObj: &corev1.Endpoints{
name: "With different ports",
oldObj: &discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "1",
},
Subsets: []corev1.EndpointSubset{{
Addresses: []corev1.EndpointAddress{{
IP: "10.10.10.10",
}},
Ports: []discoveryv1.EndpointPort{{
Port: &portA,
}},
},
newObj: &corev1.Endpoints{
newObj: &discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "2",
},
Subsets: []corev1.EndpointSubset{{
Addresses: []corev1.EndpointAddress{{
IP: "10.10.10.10",
}},
}},
},
},
{
name: "With same subsets with same len of addresses with different ip",
oldObj: &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "1",
},
Subsets: []corev1.EndpointSubset{{
Addresses: []corev1.EndpointAddress{{
IP: "10.10.10.10",
}},
}},
},
newObj: &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "2",
},
Subsets: []corev1.EndpointSubset{{
Addresses: []corev1.EndpointAddress{{
IP: "10.10.10.42",
}},
Ports: []discoveryv1.EndpointPort{{
Port: &portB,
}},
},
want: true,
},
{
name: "With same subsets with same len of addresses with same hostname",
oldObj: &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "1",
},
Subsets: []corev1.EndpointSubset{{
Addresses: []corev1.EndpointAddress{{
Hostname: "foo",
}},
}},
},
newObj: &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "2",
},
Subsets: []corev1.EndpointSubset{{
Addresses: []corev1.EndpointAddress{{
Hostname: "foo",
}},
}},
},
},
{
name: "With same subsets with same len of addresses with same hostname",
oldObj: &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "1",
},
Subsets: []corev1.EndpointSubset{{
Addresses: []corev1.EndpointAddress{{
Hostname: "foo",
}},
}},
},
newObj: &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "2",
},
Subsets: []corev1.EndpointSubset{{
Addresses: []corev1.EndpointAddress{{
Hostname: "bar",
}},
}},
},
want: true,
},
{
name: "With same subsets with same len of port with same name",
oldObj: &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "1",
},
Subsets: []corev1.EndpointSubset{{
Ports: []corev1.EndpointPort{{
Name: "foo",
}},
}},
},
newObj: &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "2",
},
Subsets: []corev1.EndpointSubset{{
Ports: []corev1.EndpointPort{{
Name: "foo",
}},
}},
},
},
{
name: "With same subsets with same len of port with different name",
oldObj: &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "1",
},
Subsets: []corev1.EndpointSubset{{
Ports: []corev1.EndpointPort{{
Name: "foo",
}},
}},
},
newObj: &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "2",
},
Subsets: []corev1.EndpointSubset{{
Ports: []corev1.EndpointPort{{
Name: "bar",
}},
}},
},
want: true,
},
{
name: "With same subsets with same len of port with same port",
oldObj: &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "1",
},
Subsets: []corev1.EndpointSubset{{
Ports: []corev1.EndpointPort{{
Port: 4242,
}},
}},
},
newObj: &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "2",
},
Subsets: []corev1.EndpointSubset{{
Ports: []corev1.EndpointPort{{
Port: 4242,
}},
}},
},
},
{
name: "With same subsets with same len of port with different port",
oldObj: &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "1",
},
Subsets: []corev1.EndpointSubset{{
Ports: []corev1.EndpointPort{{
Port: 4242,
}},
}},
},
newObj: &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "2",
},
Subsets: []corev1.EndpointSubset{{
Ports: []corev1.EndpointPort{{
Port: 6969,
}},
}},
},
want: true,
},
{
name: "With same subsets with same len of port with same protocol",
oldObj: &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "1",
},
Subsets: []corev1.EndpointSubset{{
Ports: []corev1.EndpointPort{{
Protocol: "HTTP",
}},
}},
},
newObj: &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "2",
},
Subsets: []corev1.EndpointSubset{{
Ports: []corev1.EndpointPort{{
Protocol: "HTTP",
}},
}},
},
},
{
name: "With same subsets with same len of port with different protocol",
oldObj: &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "1",
},
Subsets: []corev1.EndpointSubset{{
Ports: []corev1.EndpointPort{{
Protocol: "HTTP",
}},
}},
},
newObj: &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "2",
},
Subsets: []corev1.EndpointSubset{{
Ports: []corev1.EndpointPort{{
Protocol: "TCP",
}},
}},
},
want: true,
},
{
name: "With same subsets with same subset",
oldObj: &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "1",
},
Subsets: []corev1.EndpointSubset{{
Addresses: []corev1.EndpointAddress{{
IP: "10.10.10.10",
Hostname: "foo",
}},
Ports: []corev1.EndpointPort{{
Name: "bar",
Port: 4242,
Protocol: "HTTP",
}},
}},
},
newObj: &corev1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "2",
},
Subsets: []corev1.EndpointSubset{{
Addresses: []corev1.EndpointAddress{{
IP: "10.10.10.10",
Hostname: "foo",
}},
Ports: []corev1.EndpointPort{{
Name: "bar",
Port: 4242,
Protocol: "HTTP",
}},
}},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {

View file

@ -12,7 +12,7 @@ import (
// MustParseYaml parses a YAML to objects.
func MustParseYaml(content []byte) []runtime.Object {
acceptedK8sTypes := regexp.MustCompile(`^(Namespace|Deployment|Endpoints|Node|Service|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|MiddlewareTCP|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport|ServersTransportTCP|GatewayClass|Gateway|HTTPRoute|TCPRoute|TLSRoute|ReferenceGrant)$`)
acceptedK8sTypes := regexp.MustCompile(`^(Namespace|Deployment|EndpointSlice|Node|Service|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|MiddlewareTCP|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport|ServersTransportTCP|GatewayClass|Gateway|HTTPRoute|TCPRoute|TLSRoute|ReferenceGrant)$`)
files := strings.Split(string(content), "---\n")
retVal := make([]runtime.Object, 0, len(files))