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: resources:
- services - services
- endpoints
- secrets - secrets
verbs: verbs:
- get - get
- list - list
- watch - watch
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- list
- watch
- apiGroups: - apiGroups:
- extensions - extensions
- networking.k8s.io - 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, 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) 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. 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. 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: resources:
- services - services
- endpoints
- secrets - secrets
- nodes - nodes
verbs: verbs:
- get - get
- list - list
- watch - watch
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- list
- watch
- apiGroups: - apiGroups:
- extensions - extensions
- networking.k8s.io - networking.k8s.io

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -17,9 +17,11 @@ import (
"github.com/traefik/traefik/v3/pkg/types" "github.com/traefik/traefik/v3/pkg/types"
"github.com/traefik/traefik/v3/pkg/version" "github.com/traefik/traefik/v3/pkg/version"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
kerror "k8s.io/apimachinery/pkg/api/errors" kerror "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
kinformers "k8s.io/client-go/informers" kinformers "k8s.io/client-go/informers"
kclientset "k8s.io/client-go/kubernetes" kclientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
@ -46,7 +48,7 @@ type Client interface {
GetTLSStores() []*traefikv1alpha1.TLSStore GetTLSStores() []*traefikv1alpha1.TLSStore
GetService(namespace, name string) (*corev1.Service, bool, error) GetService(namespace, name string) (*corev1.Service, bool, error)
GetSecret(namespace, name string) (*corev1.Secret, 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) GetNodes() ([]*corev1.Node, bool, error)
} }
@ -219,7 +221,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, err = factoryKube.Core().V1().Endpoints().Informer().AddEventHandler(eventHandler) _, err = factoryKube.Discovery().V1().EndpointSlices().Informer().AddEventHandler(eventHandler)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -444,15 +446,20 @@ func (c *clientWrapper) GetService(namespace, name string) (*corev1.Service, boo
return service, exist, err return service, exist, err
} }
// GetEndpoints returns the named endpoints from the given namespace. // GetEndpointSlicesForService returns the EndpointSlices for the given service name in the given namespace.
func (c *clientWrapper) GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) { func (c *clientWrapper) GetEndpointSlicesForService(namespace, serviceName string) ([]*discoveryv1.EndpointSlice, error) {
if !c.isWatchedNamespace(namespace) { 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) serviceLabelRequirement, err := labels.NewRequirement(discoveryv1.LabelServiceName, selection.Equals, []string{serviceName})
exist, err := translateNotFoundError(err) if err != nil {
return endpoint, exist, err 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. // GetSecret returns the named secret from the given namespace.

View file

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

View file

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

View file

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

View file

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

View file

@ -10,5 +10,5 @@ spec:
routes: routes:
- services: - services:
- name: whoamiudp-without-endpoints-subsets - name: whoamiudp-without-endpointslice-endpoints
port: 8000 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 kind: Rule
priority: 12 priority: 12
services: services:
- name: whoami-without-endpoints-subsets - name: whoami-without-endpointslice-endpoints
port: 80 port: 80

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -238,7 +238,6 @@ func (p *Provider) loadTCPServers(client Client, namespace string, svc traefikv1
} }
var servers []dynamic.TCPServer var servers []dynamic.TCPServer
if service.Spec.Type == corev1.ServiceTypeNodePort && svc.NodePortLB { if service.Spec.Type == corev1.ServiceTypeNodePort && svc.NodePortLB {
nodes, nodesExists, nodesErr := client.GetNodes() nodes, nodesExists, nodesErr := client.GetNodes()
if nodesErr != nil { if nodesErr != nil {
@ -284,40 +283,47 @@ func (p *Provider) loadTCPServers(client Client, namespace string, svc traefikv1
return []dynamic.TCPServer{{Address: address}}, nil return []dynamic.TCPServer{{Address: address}}, nil
} }
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name) endpointSlices, err := client.GetEndpointSlicesForService(namespace, svc.Name)
if endpointsErr != nil { if err != nil {
return nil, endpointsErr return nil, fmt.Errorf("getting endpointslices: %w", err)
} }
if !endpointsExists { addresses := map[string]struct{}{}
return nil, errors.New("endpoints not found") for _, endpointSlice := range endpointSlices {
} var port int32
for _, p := range endpointSlice.Ports {
if len(endpoints.Subsets) == 0 && !p.AllowEmptyServices { if svcPort.Name == *p.Name {
return nil, errors.New("subset not found") port = *p.Port
}
var port int32
for _, subset := range endpoints.Subsets {
for _, p := range subset.Ports {
if svcPort.Name == p.Name {
port = p.Port
break break
} }
} }
if port == 0 { if port == 0 {
return nil, errors.New("cannot define a port") continue
} }
for _, addr := range subset.Addresses { for _, endpoint := range endpointSlice.Endpoints {
servers = append(servers, dynamic.TCPServer{ if endpoint.Conditions.Ready == nil || !*endpoint.Conditions.Ready {
Address: net.JoinHostPort(addr.IP, strconv.Itoa(int(port))), 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 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, allowEmptyServices: true,
paths: []string{"services.yml", "with_multiple_subsets.yml"}, paths: []string{"services.yml", "with_multiple_endpointaddresses.yml"},
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{ UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{}, Routers: map[string]*dynamic.UDPRouter{},
@ -4648,6 +4648,66 @@ func TestLoadIngressRoutes(t *testing.T) {
"default-test-route-6b204d94623b3df4370c": { "default-test-route-6b204d94623b3df4370c": {
LoadBalancer: &dynamic.ServersLoadBalancer{ LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{ 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", URL: "http://10.10.0.3:8080",
}, },
@ -4726,7 +4786,7 @@ func TestLoadIngressRoutes(t *testing.T) {
Weighted: &dynamic.WeightedRoundRobin{ Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{ 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), Weight: func(i int) *int { return &i }(1),
}, },
}, },
@ -4734,10 +4794,10 @@ func TestLoadIngressRoutes(t *testing.T) {
}, },
"default-test-mirror": { "default-test-mirror": {
Mirroring: &dynamic.Mirroring{ Mirroring: &dynamic.Mirroring{
Service: "default-whoami-without-endpoints-subsets-80", Service: "default-whoami-without-endpointslice-endpoints-80",
Mirrors: []dynamic.MirrorService{ Mirrors: []dynamic.MirrorService{
{ {
Name: "default-whoami-without-endpoints-subsets-80", Name: "default-whoami-without-endpointslice-endpoints-80",
}, },
{ {
Name: "default-test-weighted", 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{ LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true), PassHostHeader: Bool(true),
ResponseForwarding: &dynamic.ResponseForwarding{ 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) { func TestLoadIngressRouteUDPs(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string

View file

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

View file

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

View file

@ -17,21 +17,26 @@ spec:
task: whoami task: whoami
--- ---
kind: Endpoints kind: EndpointSlice
apiVersion: v1 apiVersion: discovery.k8s.io/v1
metadata: metadata:
name: whoami name: whoami-abc
namespace: default namespace: default
labels:
kubernetes.io/service-name: whoami
subsets: addressType: IPv4
ports:
- name: web
port: 80
- name: web2
port: 8080
endpoints:
- addresses: - addresses:
- ip: 10.10.0.1 - 10.10.0.1
- ip: 10.10.0.2 - 10.10.0.2
ports: conditions:
- name: web ready: true
port: 80
- name: web2
port: 8000
--- ---
apiVersion: v1 apiVersion: v1
@ -53,21 +58,26 @@ spec:
task: whoami task: whoami
--- ---
kind: Endpoints kind: EndpointSlice
apiVersion: v1 apiVersion: discovery.k8s.io/v1
metadata: metadata:
name: whoami-bar name: whoami-bar-abc
namespace: bar namespace: bar
labels:
kubernetes.io/service-name: whoami-bar
subsets: addressType: IPv4
ports:
- name: web
port: 80
- name: web2
port: 8000
endpoints:
- addresses: - addresses:
- ip: 10.10.0.11 - 10.10.0.11
- ip: 10.10.0.12 - 10.10.0.12
ports: conditions:
- name: web ready: true
port: 80
- name: web2
port: 8000
--- ---
apiVersion: v1 apiVersion: v1
@ -86,19 +96,24 @@ spec:
task: whoami2 task: whoami2
--- ---
kind: Endpoints kind: EndpointSlice
apiVersion: v1 apiVersion: discovery.k8s.io/v1
metadata: metadata:
name: whoami2 name: whoami2-abc
namespace: default namespace: default
labels:
kubernetes.io/service-name: whoami2
subsets: addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses: - addresses:
- ip: 10.10.0.3 - 10.10.0.3
- ip: 10.10.0.4 - 10.10.0.4
ports: conditions:
- name: web ready: true
port: 8080
--- ---
apiVersion: v1 apiVersion: v1
@ -117,19 +132,24 @@ spec:
task: whoami2 task: whoami2
--- ---
kind: Endpoints kind: EndpointSlice
apiVersion: v1 apiVersion: discovery.k8s.io/v1
metadata: metadata:
name: whoamitls name: whoamitls-abc
namespace: default namespace: default
labels:
kubernetes.io/service-name: whoamitls
subsets: addressType: IPv4
ports:
- name: websecure
port: 8443
endpoints:
- addresses: - addresses:
- ip: 10.10.0.5 - 10.10.0.5
- ip: 10.10.0.6 - 10.10.0.6
ports: conditions:
- name: websecure ready: true
port: 8443
--- ---
apiVersion: v1 apiVersion: v1
@ -148,19 +168,24 @@ spec:
task: whoami3 task: whoami3
--- ---
kind: Endpoints kind: EndpointSlice
apiVersion: v1 apiVersion: discovery.k8s.io/v1
metadata: metadata:
name: whoami3 name: whoami3-abc
namespace: default namespace: default
labels:
kubernetes.io/service-name: whoami3
subsets: addressType: IPv4
ports:
- name: websecure2
port: 8443
endpoints:
- addresses: - addresses:
- ip: 10.10.0.7 - 10.10.0.7
- ip: 10.10.0.8 - 10.10.0.8
ports: conditions:
- name: websecure2 ready: true
port: 8443
--- ---
apiVersion: v1 apiVersion: v1
@ -201,23 +226,28 @@ spec:
port: 443 port: 443
--- ---
kind: Endpoints kind: EndpointSlice
apiVersion: v1 apiVersion: discovery.k8s.io/v1
metadata: metadata:
name: whoamitcp name: whoamitcp-abc
namespace: default 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: - addresses:
- ip: 10.10.0.9 - 10.10.0.9
- ip: 10.10.0.10 - 10.10.0.10
ports: conditions:
- name: tcp-1 ready: true
protocol: TCP
port: 9000
- name: tcp-2
protocol: TCP
port: 10000
--- ---
apiVersion: v1 apiVersion: v1
@ -236,23 +266,28 @@ spec:
name: tcp-2 name: tcp-2
--- ---
kind: Endpoints kind: EndpointSlice
apiVersion: v1 apiVersion: discovery.k8s.io/v1
metadata: metadata:
name: whoamitcp-bar name: whoamitcp-bar-abc
namespace: bar 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: - addresses:
- ip: 10.10.0.13 - 10.10.0.13
- ip: 10.10.0.14 - 10.10.0.14
ports: conditions:
- name: tcp-1 ready: true
protocol: TCP
port: 9000
- name: tcp-2
protocol: TCP
port: 10000
--- ---
apiVersion: v1 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) { 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)) service, exists, err := p.client.GetService(namespace, string(backendRef.Name))
if err != nil { if err != nil {
return nil, fmt.Errorf("getting service: %w", err) 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") return nil, errors.New("service not found")
} }
var portSpec corev1.ServicePort var svcPort *corev1.ServicePort
var match bool
for _, p := range service.Spec.Ports { for _, p := range service.Spec.Ports {
if backendRef.Port == nil || p.Port == int32(*backendRef.Port) { if p.Port == int32(*backendRef.Port) {
portSpec = p svcPort = &p
match = true
break break
} }
} }
if !match { if svcPort == nil {
return nil, errors.New("service port not found") 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 { if err != nil {
return nil, fmt.Errorf("getting endpoints: %w", err) return nil, fmt.Errorf("getting endpointslices: %w", err)
} }
if !endpointsExists { if len(endpointSlices) == 0 {
return nil, errors.New("endpoints not found") return nil, errors.New("endpointslices not found")
}
if len(endpoints.Subsets) == 0 {
return nil, errors.New("subset not found")
} }
lb := &dynamic.ServersLoadBalancer{} lb := &dynamic.ServersLoadBalancer{}
lb.SetDefaults() lb.SetDefaults()
var port int32 protocol := getProtocol(*svcPort)
var portStr string
for _, subset := range endpoints.Subsets { addresses := map[string]struct{}{}
for _, p := range subset.Ports { for _, endpointSlice := range endpointSlices {
if portSpec.Name == p.Name { var port int32
port = p.Port for _, p := range endpointSlice.Ports {
if svcPort.Name == *p.Name {
port = *p.Port
break break
} }
} }
if port == 0 { 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 _, address := range endpoint.Addresses {
for _, addr := range subset.Addresses { if _, ok := addresses[address]; ok {
lb.Servers = append(lb.Servers, dynamic.Server{ continue
URL: fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(addr.IP, portStr)), }
})
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) return nil, nil, fmt.Errorf("unsupported BackendRef %s/%s/%s", *backendRef.Group, *backendRef.Kind, backendRef.Name)
} }
svc := dynamic.TCPService{ if backendRef.Port == nil {
LoadBalancer: &dynamic.TCPServersLoadBalancer{}, return nil, nil, errors.New("port is required for Kubernetes Service reference")
} }
service, exists, err := p.client.GetService(namespace, string(backendRef.Name)) service, exists, err := p.client.GetService(namespace, string(backendRef.Name))
if err != nil { if err != nil {
return nil, nil, err return nil, nil, fmt.Errorf("getting service: %w", err)
} }
if !exists { if !exists {
return nil, nil, errors.New("service not found") return nil, nil, errors.New("service not found")
} }
if len(service.Spec.Ports) > 1 && backendRef.Port == nil { var svcPort *corev1.ServicePort
// 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
for _, p := range service.Spec.Ports { for _, p := range service.Spec.Ports {
if backendRef.Port == nil || p.Port == int32(*backendRef.Port) { if p.Port == int32(*backendRef.Port) {
portSpec = p svcPort = &p
match = true
break break
} }
} }
if svcPort == nil {
if !match { return nil, nil, fmt.Errorf("service port %d not found", *backendRef.Port)
return nil, nil, errors.New("service port not found")
} }
endpoints, endpointsExists, endpointsErr := p.client.GetEndpoints(namespace, string(backendRef.Name)) endpointSlices, err := p.client.ListEndpointSlicesForService(namespace, string(backendRef.Name))
if endpointsErr != nil { if err != nil {
return nil, nil, endpointsErr return nil, nil, fmt.Errorf("getting endpointslices: %w", err)
}
if len(endpointSlices) == 0 {
return nil, nil, errors.New("endpointslices not found")
} }
if !endpointsExists { svc := dynamic.TCPService{LoadBalancer: &dynamic.TCPServersLoadBalancer{}}
return nil, nil, errors.New("endpoints not found")
}
if len(endpoints.Subsets) == 0 { addresses := map[string]struct{}{}
return nil, nil, errors.New("subset not found") for _, endpointSlice := range endpointSlices {
} var port int32
for _, p := range endpointSlice.Ports {
var port int32 if svcPort.Name == *p.Name {
var portStr string port = *p.Port
for _, subset := range endpoints.Subsets {
for _, p := range subset.Ports {
if portSpec.Name == p.Name {
port = p.Port
break break
} }
} }
if port == 0 { if port == 0 {
return nil, nil, errors.New("cannot define a port") continue
} }
portStr = strconv.FormatInt(int64(port), 10) for _, endpoint := range endpointSlice.Endpoints {
for _, addr := range subset.Addresses { if endpoint.Conditions.Ready == nil || !*endpoint.Conditions.Ready {
svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, dynamic.TCPServer{ continue
Address: net.JoinHostPort(addr.IP, portStr), }
})
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 services[serviceName] = &svc
wrrSvc.Weighted.Services = append(wrrSvc.Weighted.Services, dynamic.TCPWRRService{Name: serviceName, Weight: &weight}) 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" "github.com/traefik/traefik/v3/pkg/types"
traefikversion "github.com/traefik/traefik/v3/pkg/version" traefikversion "github.com/traefik/traefik/v3/pkg/version"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
netv1 "k8s.io/api/networking/v1" netv1 "k8s.io/api/networking/v1"
kerror "k8s.io/apimachinery/pkg/api/errors" kerror "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
kinformers "k8s.io/client-go/informers" kinformers "k8s.io/client-go/informers"
kclientset "k8s.io/client-go/kubernetes" kclientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
@ -41,7 +43,7 @@ type Client interface {
GetService(namespace, name string) (*corev1.Service, bool, error) GetService(namespace, name string) (*corev1.Service, bool, error)
GetSecret(namespace, name string) (*corev1.Secret, bool, error) GetSecret(namespace, name string) (*corev1.Secret, bool, error)
GetNodes() ([]*corev1.Node, 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 UpdateIngressStatus(ing *netv1.Ingress, ingStatus []netv1.IngressLoadBalancerIngress) error
} }
@ -185,7 +187,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, err = factoryKube.Core().V1().Endpoints().Informer().AddEventHandler(eventHandler) _, err = factoryKube.Discovery().V1().EndpointSlices().Informer().AddEventHandler(eventHandler)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -340,15 +342,20 @@ func (c *clientWrapper) GetService(namespace, name string) (*corev1.Service, boo
return service, exist, err return service, exist, err
} }
// GetEndpoints returns the named endpoints from the given namespace. // GetEndpointSlicesForService returns the EndpointSlices for the given service name in the given namespace.
func (c *clientWrapper) GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) { func (c *clientWrapper) GetEndpointSlicesForService(namespace, serviceName string) ([]*discoveryv1.EndpointSlice, error) {
if !c.isWatchedNamespace(namespace) { 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) serviceLabelRequirement, err := labels.NewRequirement(discoveryv1.LabelServiceName, selection.Equals, []string{serviceName})
exist, err := translateNotFoundError(err) if err != nil {
return endpoint, exist, err 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. // 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" "github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
netv1 "k8s.io/api/networking/v1" netv1 "k8s.io/api/networking/v1"
) )
@ -15,15 +16,15 @@ type clientMock struct {
ingresses []*netv1.Ingress ingresses []*netv1.Ingress
services []*corev1.Service services []*corev1.Service
secrets []*corev1.Secret secrets []*corev1.Secret
endpoints []*corev1.Endpoints endpointSlices []*discoveryv1.EndpointSlice
nodes []*corev1.Node nodes []*corev1.Node
ingressClasses []*netv1.IngressClass ingressClasses []*netv1.IngressClass
apiServiceError error apiServiceError error
apiSecretError error apiSecretError error
apiEndpointsError error apiEndpointSlicesError error
apiNodesError error apiNodesError error
apiIngressStatusError error apiIngressStatusError error
watchChan chan interface{} watchChan chan interface{}
} }
@ -43,8 +44,8 @@ func newClientMock(path string) clientMock {
c.services = append(c.services, o) c.services = append(c.services, o)
case *corev1.Secret: case *corev1.Secret:
c.secrets = append(c.secrets, o) c.secrets = append(c.secrets, o)
case *corev1.Endpoints: case *discoveryv1.EndpointSlice:
c.endpoints = append(c.endpoints, o) c.endpointSlices = append(c.endpointSlices, o)
case *corev1.Node: case *corev1.Node:
c.nodes = append(c.nodes, o) c.nodes = append(c.nodes, o)
case *netv1.Ingress: case *netv1.Ingress:
@ -76,18 +77,19 @@ func (c clientMock) GetService(namespace, name string) (*corev1.Service, bool, e
return nil, false, c.apiServiceError return nil, false, c.apiServiceError
} }
func (c clientMock) GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) { func (c clientMock) GetEndpointSlicesForService(namespace, serviceName string) ([]*discoveryv1.EndpointSlice, error) {
if c.apiEndpointsError != nil { if c.apiEndpointSlicesError != nil {
return nil, false, c.apiEndpointsError return nil, c.apiEndpointSlicesError
} }
for _, endpoints := range c.endpoints { var result []*discoveryv1.EndpointSlice
if endpoints.Namespace == namespace && endpoints.Name == name { for _, endpointSlice := range c.endpointSlices {
return endpoints, true, nil 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) { func (c clientMock) GetNodes() ([]*corev1.Node, bool, error) {

View file

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
netv1 "k8s.io/api/networking/v1" netv1 "k8s.io/api/networking/v1"
kerror "k8s.io/apimachinery/pkg/api/errors" kerror "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -188,10 +189,10 @@ func TestClientIgnoresHelmOwnedSecrets(t *testing.T) {
assert.False(t, found) assert.False(t, found)
} }
func TestClientIgnoresEmptyEndpointUpdates(t *testing.T) { func TestClientIgnoresEmptyEndpointSliceUpdates(t *testing.T) {
emptyEndpoint := &corev1.Endpoints{ emptyEndpointSlice := &discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "empty-endpoint", Name: "empty-endpointslice",
Namespace: "test", Namespace: "test",
ResourceVersion: "1244", ResourceVersion: "1244",
Annotations: map[string]string{ 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{ ObjectMeta: metav1.ObjectMeta{
Name: "filled-endpoint", Name: "filled-endpointslice",
Namespace: "test", Namespace: "test",
ResourceVersion: "1234", ResourceVersion: "1234",
}, },
Subsets: []corev1.EndpointSubset{{ AddressType: discoveryv1.AddressTypeIPv4,
Addresses: []corev1.EndpointAddress{{ Endpoints: []discoveryv1.Endpoint{{
IP: "10.13.37.1", Addresses: []string{"10.13.37.1"},
}}, Conditions: discoveryv1.EndpointConditions{
Ports: []corev1.EndpointPort{{ Ready: &sampleAddressReady,
Name: "testing", },
Port: 1337, }},
Protocol: "tcp", 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, _ := kubeClient.Discovery().(*discoveryfake.FakeDiscovery)
discovery.FakedServerVersion = &kversion.Info{ discovery.FakedServerVersion = &kversion.Info{
@ -234,50 +241,72 @@ func TestClientIgnoresEmptyEndpointUpdates(t *testing.T) {
select { select {
case event := <-eventCh: case event := <-eventCh:
ep, ok := event.(*corev1.Endpoints) ep, ok := event.(*discoveryv1.EndpointSlice)
require.True(t, ok) 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): 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) assert.NoError(t, err)
// Update endpoint annotation and resource version (apparently not done by fake client itself) // 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. // 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. // This reflects the behavior of kubernetes controllers which use endpoint annotations for leader election.
emptyEndpoint.Annotations["test-annotation"] = "___" emptyEndpointSlice.Annotations["test-annotation"] = "___"
emptyEndpoint.ResourceVersion = "1245" emptyEndpointSlice.ResourceVersion = "1245"
_, err = kubeClient.CoreV1().Endpoints("test").Update(context.TODO(), emptyEndpoint, metav1.UpdateOptions{}) _, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(context.TODO(), emptyEndpointSlice, metav1.UpdateOptions{})
require.NoError(t, err) require.NoError(t, err)
select { select {
case event := <-eventCh: case event := <-eventCh:
ep, ok := event.(*corev1.Endpoints) ep, ok := event.(*discoveryv1.EndpointSlice)
require.True(t, ok) 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): 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) assert.NoError(t, err)
filledEndpoint.Subsets[0].Addresses[0].IP = "10.13.37.2" filledEndpointSlice.Endpoints[0].Addresses[0] = "10.13.37.2"
filledEndpoint.ResourceVersion = "1235" filledEndpointSlice.ResourceVersion = "1235"
_, err = kubeClient.CoreV1().Endpoints("test").Update(context.TODO(), filledEndpoint, metav1.UpdateOptions{}) _, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(context.TODO(), filledEndpointSlice, metav1.UpdateOptions{})
require.NoError(t, err) require.NoError(t, err)
select { select {
case event := <-eventCh: case event := <-eventCh:
ep, ok := event.(*corev1.Endpoints) ep, ok := event.(*discoveryv1.EndpointSlice)
require.True(t, ok) require.True(t, ok)
assert.Equal(t, "filled-endpoint", ep.Name) assert.Equal(t, "filled-endpointslice", ep.Name)
case <-time.After(50 * time.Millisecond): 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 { select {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -651,36 +651,41 @@ func (p *Provider) loadService(client Client, namespace string, backend netv1.In
return svc, nil return svc, nil
} }
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, backend.Service.Name) endpointSlices, err := client.GetEndpointSlicesForService(namespace, backend.Service.Name)
if endpointsErr != nil { if err != nil {
return nil, endpointsErr return nil, fmt.Errorf("getting endpointslices: %w", err)
} }
if !endpointsExists { addresses := map[string]struct{}{}
return nil, errors.New("endpoints not found") for _, endpointSlice := range endpointSlices {
}
for _, subset := range endpoints.Subsets {
var port int32 var port int32
for _, p := range subset.Ports { for _, p := range endpointSlice.Ports {
if portName == p.Name { if portName == *p.Name {
port = p.Port port = *p.Port
break break
} }
} }
if port == 0 { if port == 0 {
continue continue
} }
protocol := getProtocol(portSpec, portName, svcConfig) protocol := getProtocol(portSpec, portName, svcConfig)
for _, addr := range subset.Addresses { for _, endpoint := range endpointSlice.Endpoints {
hostPort := net.JoinHostPort(addr.IP, strconv.Itoa(int(port))) if endpoint.Conditions.Ready == nil || !*endpoint.Conditions.Ready {
continue
}
svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, dynamic.Server{ for _, address := range endpoint.Addresses {
URL: fmt.Sprintf("%s://%s", protocol, hostPort), 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 package k8s
import ( import (
corev1 "k8s.io/api/core/v1" discoveryv1 "k8s.io/api/discovery/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
@ -47,61 +47,54 @@ func objChanged(oldObj, newObj interface{}) bool {
return false return false
} }
if _, ok := oldObj.(*corev1.Endpoints); ok { if _, ok := oldObj.(*discoveryv1.EndpointSlice); ok {
return endpointsChanged(oldObj.(*corev1.Endpoints), newObj.(*corev1.Endpoints)) return endpointSliceChanged(oldObj.(*discoveryv1.EndpointSlice), newObj.(*discoveryv1.EndpointSlice))
} }
return true return true
} }
func endpointsChanged(a, b *corev1.Endpoints) bool { // In some Kubernetes versions leader election is done by updating an endpoint annotation every second,
if len(a.Subsets) != len(b.Subsets) { // 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 return true
} }
for i, sa := range a.Subsets { for i, aport := range a.Ports {
sb := b.Subsets[i] bport := b.Ports[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]
if aport.Name != bport.Name { if aport.Name != bport.Name {
return true return true
} }
if aport.Port != bport.Port { if aport.Port != bport.Port {
return true 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 return true
} }
} }

View file

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

View file

@ -12,7 +12,7 @@ import (
// MustParseYaml parses a YAML to objects. // MustParseYaml parses a YAML to objects.
func MustParseYaml(content []byte) []runtime.Object { 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") files := strings.Split(string(content), "---\n")
retVal := make([]runtime.Object, 0, len(files)) retVal := make([]runtime.Object, 0, len(files))