diff --git a/docs/content/getting-started/quick-start-with-kubernetes.md b/docs/content/getting-started/quick-start-with-kubernetes.md index 30530522d..ddd714b8f 100644 --- a/docs/content/getting-started/quick-start-with-kubernetes.md +++ b/docs/content/getting-started/quick-start-with-kubernetes.md @@ -35,12 +35,18 @@ rules: - "" resources: - services - - endpoints - secrets verbs: - get - list - watch + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch - apiGroups: - extensions - networking.k8s.io diff --git a/docs/content/migration/v3.md b/docs/content/migration/v3.md new file mode 100644 index 000000000..00277395f --- /dev/null +++ b/docs/content/migration/v3.md @@ -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 + ... +``` diff --git a/docs/content/providers/kubernetes-crd.md b/docs/content/providers/kubernetes-crd.md index 2f60a5868..8d15cc32e 100644 --- a/docs/content/providers/kubernetes-crd.md +++ b/docs/content/providers/kubernetes-crd.md @@ -183,7 +183,7 @@ _Optional, Default: ""_ A label selector can be defined to filter on specific resource objects only, this applies only to Traefik [Custom Resources](../routing/providers/kubernetes-crd.md#custom-resource-definition-crd) -and has no effect on Kubernetes `Secrets`, `Endpoints` and `Services`. +and has no effect on Kubernetes `Secrets`, `EndpointSlices` and `Services`. If left empty, Traefik processes all resource objects in the configured namespaces. See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml index 6d0afb7a3..ef6dcab97 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml @@ -8,13 +8,19 @@ rules: - "" resources: - services - - endpoints - secrets - nodes verbs: - get - list - watch + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch - apiGroups: - extensions - networking.k8s.io diff --git a/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml b/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml index 52e719f1b..4f54608e7 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml @@ -15,12 +15,18 @@ rules: - "" resources: - services - - endpoints - secrets verbs: - get - list - watch + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch - apiGroups: - gateway.networking.k8s.io resources: diff --git a/docs/content/routing/providers/kubernetes-ingress.md b/docs/content/routing/providers/kubernetes-ingress.md index f428be1dd..cfa3f3a4a 100644 --- a/docs/content/routing/providers/kubernetes-ingress.md +++ b/docs/content/routing/providers/kubernetes-ingress.md @@ -29,12 +29,18 @@ which in turn will create the resulting routers, services, handlers, etc. - "" resources: - services - - endpoints - secrets verbs: - get - list - watch + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch - apiGroups: - extensions - networking.k8s.io @@ -427,12 +433,19 @@ This way, any Ingress attached to this Entrypoint will have TLS termination by d - "" resources: - services - - endpoints - secrets verbs: - get - list - watch + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - extensions - networking.k8s.io @@ -612,12 +625,19 @@ For more options, please refer to the available [annotations](#on-ingress). - "" resources: - services - - endpoints - secrets verbs: - get - list - watch + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - extensions - networking.k8s.io diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 5cac2369f..17a4bfefc 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -172,6 +172,7 @@ nav: - 'HTTP Challenge': 'user-guides/docker-compose/acme-http/index.md' - 'DNS Challenge': 'user-guides/docker-compose/acme-dns/index.md' - 'Migration': + - 'Traefik v3 minor migrations': 'migration/v3.md' - 'Traefik v2 to v3': - 'Migration guide': 'migration/v2-to-v3.md' - 'Configuration changes for v3': 'migration/v2-to-v3-details.md' diff --git a/integration/fixtures/k8s-conformance/01-rbac.yml b/integration/fixtures/k8s-conformance/01-rbac.yml index 004691004..3492bb453 100644 --- a/integration/fixtures/k8s-conformance/01-rbac.yml +++ b/integration/fixtures/k8s-conformance/01-rbac.yml @@ -15,12 +15,19 @@ rules: - "" resources: - services - - endpoints - secrets verbs: - get - list - watch + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - gateway.networking.k8s.io resources: diff --git a/integration/resources/compose/k8s.yml b/integration/resources/compose/k8s.yml index a34f72bd7..f14b7abdb 100644 --- a/integration/resources/compose/k8s.yml +++ b/integration/resources/compose/k8s.yml @@ -1,7 +1,7 @@ version: "3.8" services: server: - image: rancher/k3s:v1.20.15-k3s1 + image: rancher/k3s:v1.21.14-k3s1 privileged: true command: - server @@ -26,7 +26,7 @@ services: - ./fixtures/k8s:/var/lib/rancher/k3s/server/manifests node: - image: rancher/k3s:v1.20.15-k3s1 + image: rancher/k3s:v1.21.14-k3s1 privileged: true environment: K3S_TOKEN: somethingtotallyrandom diff --git a/pkg/provider/kubernetes/crd/client.go b/pkg/provider/kubernetes/crd/client.go index 27bf10048..a52f76497 100644 --- a/pkg/provider/kubernetes/crd/client.go +++ b/pkg/provider/kubernetes/crd/client.go @@ -17,9 +17,11 @@ import ( "github.com/traefik/traefik/v3/pkg/types" "github.com/traefik/traefik/v3/pkg/version" corev1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" kerror "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" kinformers "k8s.io/client-go/informers" kclientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -46,7 +48,7 @@ type Client interface { GetTLSStores() []*traefikv1alpha1.TLSStore GetService(namespace, name string) (*corev1.Service, bool, error) GetSecret(namespace, name string) (*corev1.Secret, bool, error) - GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) + GetEndpointSlicesForService(namespace, serviceName string) ([]*discoveryv1.EndpointSlice, error) GetNodes() ([]*corev1.Node, bool, error) } @@ -219,7 +221,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< if err != nil { return nil, err } - _, err = factoryKube.Core().V1().Endpoints().Informer().AddEventHandler(eventHandler) + _, err = factoryKube.Discovery().V1().EndpointSlices().Informer().AddEventHandler(eventHandler) if err != nil { return nil, err } @@ -444,15 +446,20 @@ func (c *clientWrapper) GetService(namespace, name string) (*corev1.Service, boo return service, exist, err } -// GetEndpoints returns the named endpoints from the given namespace. -func (c *clientWrapper) GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) { +// GetEndpointSlicesForService returns the EndpointSlices for the given service name in the given namespace. +func (c *clientWrapper) GetEndpointSlicesForService(namespace, serviceName string) ([]*discoveryv1.EndpointSlice, error) { if !c.isWatchedNamespace(namespace) { - return nil, false, fmt.Errorf("failed to get endpoints %s/%s: namespace is not within watched namespaces", namespace, name) + return nil, fmt.Errorf("failed to get endpointslices for service %s/%s: namespace is not within watched namespaces", namespace, serviceName) } - endpoint, err := c.factoriesKube[c.lookupNamespace(namespace)].Core().V1().Endpoints().Lister().Endpoints(namespace).Get(name) - exist, err := translateNotFoundError(err) - return endpoint, exist, err + serviceLabelRequirement, err := labels.NewRequirement(discoveryv1.LabelServiceName, selection.Equals, []string{serviceName}) + if err != nil { + return nil, fmt.Errorf("failed to create service label selector requirement: %w", err) + } + serviceSelector := labels.NewSelector() + serviceSelector = serviceSelector.Add(*serviceLabelRequirement) + + return c.factoriesKube[c.lookupNamespace(namespace)].Discovery().V1().EndpointSlices().Lister().EndpointSlices(namespace).List(serviceSelector) } // GetSecret returns the named secret from the given namespace. diff --git a/pkg/provider/kubernetes/crd/fixtures/services.yml b/pkg/provider/kubernetes/crd/fixtures/services.yml index 825bb9106..86094ef8a 100644 --- a/pkg/provider/kubernetes/crd/fixtures/services.yml +++ b/pkg/provider/kubernetes/crd/fixtures/services.yml @@ -13,19 +13,24 @@ spec: task: whoami --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami + name: whoami-abc namespace: default + labels: + kubernetes.io/service-name: whoami -subsets: +addressType: IPv4 +ports: + - name: web + port: 80 +endpoints: - addresses: - - ip: 10.10.0.1 - - ip: 10.10.0.2 - ports: - - name: web - port: 80 + - 10.10.0.1 + - 10.10.0.2 + conditions: + ready: true --- apiVersion: v1 @@ -43,19 +48,24 @@ spec: task: whoami2 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami2 + name: whoami2-abc namespace: default + labels: + kubernetes.io/service-name: whoami2 -subsets: +addressType: IPv4 +ports: + - name: web + port: 8080 +endpoints: - addresses: - - ip: 10.10.0.3 - - ip: 10.10.0.4 - ports: - - name: web - port: 8080 + - 10.10.0.3 + - 10.10.0.4 + conditions: + ready: true --- apiVersion: v1 @@ -74,19 +84,24 @@ spec: task: whoami2 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoamitls + name: whoamitls-abc namespace: default + labels: + kubernetes.io/service-name: whoamitls -subsets: +addressType: IPv4 +ports: + - name: websecure + port: 8443 +endpoints: - addresses: - - ip: 10.10.0.5 - - ip: 10.10.0.6 - ports: - - name: websecure - port: 8443 + - 10.10.0.5 + - 10.10.0.6 + conditions: + ready: true --- apiVersion: v1 @@ -99,25 +114,29 @@ spec: ports: - name: websecure2 port: 8443 - scheme: https selector: app: traefiklabs task: whoami3 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami3 + name: whoami3-abc namespace: default + labels: + kubernetes.io/service-name: whoami3 -subsets: +addressType: IPv4 +ports: + - name: websecure2 + port: 8443 +endpoints: - addresses: - - ip: 10.10.0.7 - - ip: 10.10.0.8 - ports: - - name: websecure2 - port: 8443 + - 10.10.0.7 + - 10.10.0.8 + conditions: + ready: true --- apiVersion: v1 @@ -135,18 +154,23 @@ spec: task: whoami-ipv6 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami-ipv6 + name: whoami-ipv6-abc namespace: default + labels: + kubernetes.io/service-name: whoami-ipv6 -subsets: +addressType: IPv6 +ports: + - name: web + port: 8080 +endpoints: - addresses: - - ip: "2001:db8:85a3:8d3:1319:8a2e:370:7348" - ports: - - name: web - port: 8080 + - "2001:db8:85a3:8d3:1319:8a2e:370:7348" + conditions: + ready: true --- apiVersion: v1 @@ -216,25 +240,30 @@ spec: task: whoami --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami-svc + name: whoami-svc-abc namespace: cross-ns + labels: + kubernetes.io/service-name: whoami-svc -subsets: +addressType: IPv4 +ports: + - name: web + port: 80 +endpoints: - addresses: - - ip: 10.10.0.1 - - ip: 10.10.0.2 - ports: - - name: web - port: 80 + - 10.10.0.1 + - 10.10.0.2 + conditions: + ready: true --- apiVersion: v1 kind: Service metadata: - name: whoami-without-endpoints-subsets + name: whoami-without-endpointslice-endpoints namespace: default spec: @@ -247,11 +276,16 @@ spec: task: whoami --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami-without-endpoints-subsets + name: whoami-without-endpointslice-endpoints-abc namespace: default + labels: + kubernetes.io/service-name: whoami-without-endpointslice-endpoints + +addressType: IPv4 +endpoints: [] --- apiVersion: v1 diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/services.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/services.yml index 40a2e90e5..8c29006e8 100644 --- a/pkg/provider/kubernetes/crd/fixtures/tcp/services.yml +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/services.yml @@ -13,19 +13,24 @@ spec: task: whoamitcp --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoamitcp + name: whoamitcp-abc namespace: default + labels: + kubernetes.io/service-name: whoamitcp -subsets: +addressType: IPv4 +ports: + - name: myapp + port: 8000 +endpoints: - addresses: - - ip: 10.10.0.1 - - ip: 10.10.0.2 - ports: - - name: myapp - port: 8000 + - 10.10.0.1 + - 10.10.0.2 + conditions: + ready: true --- apiVersion: v1 @@ -43,19 +48,24 @@ spec: task: whoamitcp2 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoamitcp2 + name: whoamitcp2-abc namespace: default + labels: + kubernetes.io/service-name: whoamitcp2 -subsets: +addressType: IPv4 +ports: + - name: myapp2 + port: 8080 +endpoints: - addresses: - - ip: 10.10.0.3 - - ip: 10.10.0.4 - ports: - - name: myapp2 - port: 8080 + - 10.10.0.3 + - 10.10.0.4 + conditions: + ready: true --- apiVersion: v1 @@ -73,19 +83,24 @@ spec: task: whoamitcptls2 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoamitcptls + name: whoamitcptls-abc namespace: default + labels: + kubernetes.io/service-name: whoamitcptls -subsets: +addressType: IPv4 +ports: + - name: websecure + port: 443 +endpoints: - addresses: - - ip: 10.10.0.5 - - ip: 10.10.0.6 - ports: - - name: websecure - port: 443 + - 10.10.0.5 + - 10.10.0.6 + conditions: + ready: true --- apiVersion: v1 @@ -103,34 +118,44 @@ spec: task: whoamitcp3 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoamitcp3 + name: whoamitcp3-abc namespace: ns3 + labels: + kubernetes.io/service-name: whoamitcp3 -subsets: +addressType: IPv4 +ports: + - name: myapp3 + port: 8083 +endpoints: - addresses: - - ip: 10.10.0.7 - - ip: 10.10.0.8 - ports: - - name: myapp3 - port: 8083 + - 10.10.0.7 + - 10.10.0.8 + conditions: + ready: true --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoamitcp3 + name: whoamitcp3-abc namespace: ns4 + labels: + kubernetes.io/service-name: whoamitcp3 -subsets: +addressType: IPv4 +ports: + - name: myapp4 + port: 8084 +endpoints: - addresses: - - ip: 10.10.0.9 - - ip: 10.10.0.10 - ports: - - name: myapp4 - port: 8084 + - 10.10.0.9 + - 10.10.0.10 + conditions: + ready: true --- apiVersion: v1 @@ -148,19 +173,24 @@ spec: task: whoamitcp-ipv6 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoamitcp-ipv6 + name: whoamitcp-ipv6-abc namespace: default + labels: + kubernetes.io/service-name: whoamitcp-ipv6 -subsets: +addressType: IPv6 +ports: + - name: myapp-ipv6 + port: 8080 +endpoints: - addresses: - - ip: "fd00:10:244:0:1::3" - - ip: "2001:db8:85a3:8d3:1319:8a2e:370:7348" - ports: - - name: myapp-ipv6 - port: 8080 + - "fd00:10:244:0:1::3" + - "2001:db8:85a3:8d3:1319:8a2e:370:7348" + conditions: + ready: true --- apiVersion: v1 @@ -212,25 +242,30 @@ spec: task: whoamitcp --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoamitcp-cross-ns + name: whoamitcp-cross-ns-abc namespace: cross-ns + labels: + kubernetes.io/service-name: whoamitcp-cross-ns -subsets: +addressType: IPv4 +ports: + - name: myapp + port: 8000 +endpoints: - addresses: - - ip: 10.10.0.1 - - ip: 10.10.0.2 - ports: - - name: myapp - port: 8000 + - 10.10.0.1 + - 10.10.0.2 + conditions: + ready: true --- apiVersion: v1 kind: Service metadata: - name: whoamitcp-without-endpoints-subsets + name: whoamitcp-without-endpointslice-endpoints namespace: default spec: @@ -243,11 +278,16 @@ spec: task: whoamitcp --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoamitcp-without-endpoints-subsets + name: whoamitcp-without-endpointslice-endpoints-abc namespace: default + labels: + kubernetes.io/service-name: whoamitcp-without-endpointslice-endpoints + +addressType: IPv4 +endpoints: [] --- apiVersion: v1 diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_empty_services.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_empty_services.yml index 55202d867..37daa91c1 100644 --- a/pkg/provider/kubernetes/crd/fixtures/tcp/with_empty_services.yml +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_empty_services.yml @@ -11,5 +11,5 @@ spec: routes: - match: HostSNI(`foo.com`) services: - - name: whoamitcp-without-endpoints-subsets + - name: whoamitcp-without-endpointslice-endpoints port: 8000 diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/services.yml b/pkg/provider/kubernetes/crd/fixtures/udp/services.yml index 969281886..f77643d36 100644 --- a/pkg/provider/kubernetes/crd/fixtures/udp/services.yml +++ b/pkg/provider/kubernetes/crd/fixtures/udp/services.yml @@ -13,19 +13,24 @@ spec: task: whoamiudp --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoamiudp + name: whoamiudp-abc namespace: default + labels: + kubernetes.io/service-name: whoamiudp -subsets: +addressType: IPv4 +ports: + - name: myapp + port: 8000 +endpoints: - addresses: - - ip: 10.10.0.1 - - ip: 10.10.0.2 - ports: - - name: myapp - port: 8000 + - 10.10.0.1 + - 10.10.0.2 + conditions: + ready: true --- apiVersion: v1 @@ -43,19 +48,24 @@ spec: task: whoamiudp2 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoamiudp2 + name: whoamiudp2-abc namespace: default + labels: + kubernetes.io/service-name: whoamiudp2 -subsets: +addressType: IPv4 +ports: + - name: myapp2 + port: 8080 +endpoints: - addresses: - - ip: 10.10.0.3 - - ip: 10.10.0.4 - ports: - - name: myapp2 - port: 8080 + - 10.10.0.3 + - 10.10.0.4 + conditions: + ready: true --- apiVersion: v1 @@ -73,34 +83,44 @@ spec: task: whoamiudp3 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoamiudp3 + name: whoamiudp3-abc namespace: ns3 + labels: + kubernetes.io/service-name: whoamiudp3 -subsets: +addressType: IPv4 +ports: + - name: myapp3 + port: 8083 +endpoints: - addresses: - - ip: 10.10.0.7 - - ip: 10.10.0.8 - ports: - - name: myapp3 - port: 8083 + - 10.10.0.7 + - 10.10.0.8 + conditions: + ready: true --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoamiudp3 + name: whoamiudp3-abc namespace: ns4 + labels: + kubernetes.io/service-name: whoamiudp3 -subsets: +addressType: IPv4 +ports: + - name: myapp4 + port: 8084 +endpoints: - addresses: - - ip: 10.10.0.9 - - ip: 10.10.0.10 - ports: - - name: myapp4 - port: 8084 + - 10.10.0.9 + - 10.10.0.10 + conditions: + ready: true --- apiVersion: v1 @@ -118,18 +138,23 @@ spec: task: whoamiudp-ipv6 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoamiudp-ipv6 + name: whoamiudp-ipv6-abc namespace: default + labels: + kubernetes.io/service-name: whoamiudp-ipv6 -subsets: +addressType: IPv6 +ports: + - name: myapp-ipv6 + port: 8080 +endpoints: - addresses: - - ip: "fd00:10:244:0:1::3" - ports: - - name: myapp-ipv6 - port: 8080 + - "fd00:10:244:0:1::3" + conditions: + ready: true --- apiVersion: v1 @@ -171,25 +196,30 @@ spec: port: 80 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoamiudp-cross-ns + name: whoamiudp-cross-ns-abc namespace: cross-ns + labels: + kubernetes.io/service-name: whoamiudp-cross-ns -subsets: +addressType: IPv4 +ports: + - name: myapp + port: 8000 +endpoints: - addresses: - - ip: 10.10.0.1 - - ip: 10.10.0.2 - ports: - - name: myapp - port: 8000 + - 10.10.0.1 + - 10.10.0.2 + conditions: + ready: true --- apiVersion: v1 kind: Service metadata: - name: whoamiudp-without-endpoints-subsets + name: whoamiudp-without-endpointslice-endpoints namespace: default spec: @@ -202,11 +232,16 @@ spec: task: whoamiudp --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoamiudp-without-endpoints-subsets + name: whoamiudp-without-endpointslice-endpoints-abc namespace: default + labels: + kubernetes.io/service-name: whoamiudp-without-endpointslice-endpoints + +addressType: IPv4 +endpoints: [] --- apiVersion: v1 diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/with_empty_services.yml b/pkg/provider/kubernetes/crd/fixtures/udp/with_empty_services.yml index dc588d5e9..535d24b58 100644 --- a/pkg/provider/kubernetes/crd/fixtures/udp/with_empty_services.yml +++ b/pkg/provider/kubernetes/crd/fixtures/udp/with_empty_services.yml @@ -10,5 +10,5 @@ spec: routes: - services: - - name: whoamiudp-without-endpoints-subsets + - name: whoamiudp-without-endpointslice-endpoints port: 8000 diff --git a/pkg/provider/kubernetes/crd/fixtures/with_duplicated_endpointaddresses.yml b/pkg/provider/kubernetes/crd/fixtures/with_duplicated_endpointaddresses.yml new file mode 100644 index 000000000..5054fc566 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_duplicated_endpointaddresses.yml @@ -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 diff --git a/pkg/provider/kubernetes/crd/fixtures/with_empty_services.yml b/pkg/provider/kubernetes/crd/fixtures/with_empty_services.yml index b3ff215d1..6ccf7202b 100644 --- a/pkg/provider/kubernetes/crd/fixtures/with_empty_services.yml +++ b/pkg/provider/kubernetes/crd/fixtures/with_empty_services.yml @@ -13,5 +13,5 @@ spec: kind: Rule priority: 12 services: - - name: whoami-without-endpoints-subsets + - name: whoami-without-endpointslice-endpoints port: 80 diff --git a/pkg/provider/kubernetes/crd/fixtures/with_empty_services_ts.yml b/pkg/provider/kubernetes/crd/fixtures/with_empty_services_ts.yml index bc4f9626c..dbf8f366d 100644 --- a/pkg/provider/kubernetes/crd/fixtures/with_empty_services_ts.yml +++ b/pkg/provider/kubernetes/crd/fixtures/with_empty_services_ts.yml @@ -30,7 +30,7 @@ metadata: spec: weighted: services: - - name: whoami-without-endpoints-subsets + - name: whoami-without-endpointslice-endpoints weight: 1 port: 80 @@ -43,10 +43,10 @@ metadata: spec: mirroring: - name: whoami-without-endpoints-subsets + name: whoami-without-endpointslice-endpoints port: 80 mirrors: - - name: whoami-without-endpoints-subsets + - name: whoami-without-endpointslice-endpoints port: 80 - name: test-weighted kind: TraefikService @@ -61,5 +61,5 @@ metadata: spec: errors: service: - name: whoami-without-endpoints-subsets + name: whoami-without-endpointslice-endpoints port: 80 diff --git a/pkg/provider/kubernetes/crd/fixtures/with_mirroring.yml b/pkg/provider/kubernetes/crd/fixtures/with_mirroring.yml index a1ead36d8..015ed95d0 100644 --- a/pkg/provider/kubernetes/crd/fixtures/with_mirroring.yml +++ b/pkg/provider/kubernetes/crd/fixtures/with_mirroring.yml @@ -1,17 +1,22 @@ --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami4 + name: whoami4-abc namespace: default + labels: + kubernetes.io/service-name: whoami4 -subsets: +addressType: IPv4 +ports: + - name: web + port: 8080 +endpoints: - addresses: - - ip: 10.10.0.1 - - ip: 10.10.0.2 - ports: - - name: web - port: 8080 + - 10.10.0.1 + - 10.10.0.2 + conditions: + ready: true --- apiVersion: v1 @@ -28,20 +33,25 @@ spec: app: traefiklabs task: whoami4 ------- -kind: Endpoints -apiVersion: v1 +--- +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami5 + name: whoami5-abc namespace: default + labels: + kubernetes.io/service-name: whoami5 -subsets: +addressType: IPv4 +ports: + - name: web + port: 8080 +endpoints: - addresses: - - ip: 10.10.0.3 - - ip: 10.10.0.4 - ports: - - name: web - port: 8080 + - 10.10.0.3 + - 10.10.0.4 + conditions: + ready: true --- apiVersion: v1 diff --git a/pkg/provider/kubernetes/crd/fixtures/with_mirroring2.yml b/pkg/provider/kubernetes/crd/fixtures/with_mirroring2.yml index 5f498a296..dc303e600 100644 --- a/pkg/provider/kubernetes/crd/fixtures/with_mirroring2.yml +++ b/pkg/provider/kubernetes/crd/fixtures/with_mirroring2.yml @@ -1,17 +1,22 @@ --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami4 + name: whoami4-abc namespace: default + labels: + kubernetes.io/service-name: whoami4 -subsets: +addressType: IPv4 +ports: + - name: web + port: 8080 +endpoints: - addresses: - - ip: 10.10.0.1 - - ip: 10.10.0.2 - ports: - - name: web - port: 8080 + - 10.10.0.1 + - 10.10.0.2 + conditions: + ready: true --- apiVersion: v1 @@ -28,20 +33,25 @@ spec: app: traefiklabs task: whoami4 ------- -kind: Endpoints -apiVersion: v1 +--- +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami5 + name: whoami5-abc namespace: default + labels: + kubernetes.io/service-name: whoami5 -subsets: +addressType: IPv4 +ports: + - name: web + port: 8080 +endpoints: - addresses: - - ip: 10.10.0.3 - - ip: 10.10.0.4 - ports: - - name: web - port: 8080 + - 10.10.0.3 + - 10.10.0.4 + conditions: + ready: true --- apiVersion: v1 diff --git a/pkg/provider/kubernetes/crd/fixtures/with_multiple_endpointaddresses.yml b/pkg/provider/kubernetes/crd/fixtures/with_multiple_endpointaddresses.yml new file mode 100644 index 000000000..6e7e8e70e --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_multiple_endpointaddresses.yml @@ -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 diff --git a/pkg/provider/kubernetes/crd/fixtures/with_multiple_endpointslices.yml b/pkg/provider/kubernetes/crd/fixtures/with_multiple_endpointslices.yml new file mode 100644 index 000000000..f1753c24f --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_multiple_endpointslices.yml @@ -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 diff --git a/pkg/provider/kubernetes/crd/fixtures/with_multiple_subsets.yml b/pkg/provider/kubernetes/crd/fixtures/with_multiple_subsets.yml deleted file mode 100644 index 4d700d7ad..000000000 --- a/pkg/provider/kubernetes/crd/fixtures/with_multiple_subsets.yml +++ /dev/null @@ -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 diff --git a/pkg/provider/kubernetes/crd/fixtures/with_namespaces.yml b/pkg/provider/kubernetes/crd/fixtures/with_namespaces.yml index d8d4867e9..3d60d4fc7 100644 --- a/pkg/provider/kubernetes/crd/fixtures/with_namespaces.yml +++ b/pkg/provider/kubernetes/crd/fixtures/with_namespaces.yml @@ -1,47 +1,62 @@ --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami6 + name: whoami6-abc namespace: baz + labels: + kubernetes.io/service-name: whoami6 -subsets: +addressType: IPv4 +ports: + - name: web + port: 8080 +endpoints: - addresses: - - ip: 10.10.0.5 - - ip: 10.10.0.6 - ports: - - name: web - port: 8080 + - 10.10.0.5 + - 10.10.0.6 + conditions: + ready: true --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami5 + name: whoami5-abc namespace: foo + labels: + kubernetes.io/service-name: whoami5 -subsets: +addressType: IPv4 +ports: + - name: web + port: 8080 +endpoints: - addresses: - - ip: 10.10.0.3 - - ip: 10.10.0.4 - ports: - - name: web - port: 8080 + - 10.10.0.3 + - 10.10.0.4 + conditions: + ready: true --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami4 + name: whoami4-abc namespace: foo + labels: + kubernetes.io/service-name: whoami4 -subsets: +addressType: IPv4 +ports: + - name: web + port: 8080 +endpoints: - addresses: - - ip: 10.10.0.1 - - ip: 10.10.0.2 - ports: - - name: web - port: 8080 + - 10.10.0.1 + - 10.10.0.2 + conditions: + ready: true --- apiVersion: v1 diff --git a/pkg/provider/kubernetes/crd/fixtures/with_services_lb0.yml b/pkg/provider/kubernetes/crd/fixtures/with_services_lb0.yml index d4f7b7d94..d45e91f5d 100644 --- a/pkg/provider/kubernetes/crd/fixtures/with_services_lb0.yml +++ b/pkg/provider/kubernetes/crd/fixtures/with_services_lb0.yml @@ -1,17 +1,22 @@ --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami5 + name: whoami5-abc namespace: default + labels: + kubernetes.io/service-name: whoami5 -subsets: +addressType: IPv4 +ports: + - name: web + port: 8080 +endpoints: - addresses: - - ip: 10.10.0.3 - - ip: 10.10.0.4 - ports: - - name: web - port: 8080 + - 10.10.0.3 + - 10.10.0.4 + conditions: + ready: true --- apiVersion: v1 diff --git a/pkg/provider/kubernetes/crd/fixtures/with_services_lb1.yml b/pkg/provider/kubernetes/crd/fixtures/with_services_lb1.yml index d863c3f1f..e981ee880 100644 --- a/pkg/provider/kubernetes/crd/fixtures/with_services_lb1.yml +++ b/pkg/provider/kubernetes/crd/fixtures/with_services_lb1.yml @@ -1,62 +1,82 @@ --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami4 + name: whoami4-abc namespace: default + labels: + kubernetes.io/service-name: whoami4 -subsets: +addressType: IPv4 +ports: + - name: web + port: 80 +endpoints: - addresses: - - ip: 10.10.0.1 - - ip: 10.10.0.2 - ports: - - name: web - port: 80 + - 10.10.0.1 + - 10.10.0.2 + conditions: + ready: true --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami5 + name: whoami5-abc namespace: default + labels: + kubernetes.io/service-name: whoami5 -subsets: +addressType: IPv4 +ports: + - name: web + port: 8080 +endpoints: - addresses: - - ip: 10.10.0.3 - - ip: 10.10.0.4 - ports: - - name: web - port: 8080 + - 10.10.0.3 + - 10.10.0.4 + conditions: + ready: true --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami6 + name: whoami6-abc namespace: default + labels: + kubernetes.io/service-name: whoami6 -subsets: +addressType: IPv4 +ports: + - name: web + port: 80 +endpoints: - addresses: - - ip: 10.10.0.5 - - ip: 10.10.0.6 - ports: - - name: web - port: 80 + - 10.10.0.5 + - 10.10.0.6 + conditions: + ready: true --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami7 + name: whoami7-abc namespace: default + labels: + kubernetes.io/service-name: whoami7 -subsets: +addressType: IPv4 +ports: + - name: web + port: 8080 +endpoints: - addresses: - - ip: 10.10.0.7 - - ip: 10.10.0.8 - ports: - - name: web - port: 8080 + - 10.10.0.7 + - 10.10.0.8 + conditions: + ready: true --- apiVersion: v1 diff --git a/pkg/provider/kubernetes/crd/fixtures/with_services_lb2.yml b/pkg/provider/kubernetes/crd/fixtures/with_services_lb2.yml index c1732c18b..dfd7b5790 100644 --- a/pkg/provider/kubernetes/crd/fixtures/with_services_lb2.yml +++ b/pkg/provider/kubernetes/crd/fixtures/with_services_lb2.yml @@ -1,17 +1,22 @@ --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami5 + name: whoami5-abc namespace: default + labels: + kubernetes.io/service-name: whoami5 -subsets: +addressType: IPv4 +ports: + - name: web + port: 8080 +endpoints: - addresses: - - ip: 10.10.0.3 - - ip: 10.10.0.4 - ports: - - name: web - port: 8080 + - 10.10.0.3 + - 10.10.0.4 + conditions: + ready: true --- apiVersion: traefik.io/v1alpha1 diff --git a/pkg/provider/kubernetes/crd/fixtures/with_services_lb3.yml b/pkg/provider/kubernetes/crd/fixtures/with_services_lb3.yml index 8b15d9fd9..4a7eb19db 100644 --- a/pkg/provider/kubernetes/crd/fixtures/with_services_lb3.yml +++ b/pkg/provider/kubernetes/crd/fixtures/with_services_lb3.yml @@ -1,32 +1,42 @@ --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami5 + name: whoami5-abc namespace: default + labels: + kubernetes.io/service-name: whoami5 -subsets: +addressType: IPv4 +ports: + - name: web + port: 8080 +endpoints: - addresses: - - ip: 10.10.0.3 - - ip: 10.10.0.4 - ports: - - name: web - port: 8080 + - 10.10.0.3 + - 10.10.0.4 + conditions: + ready: true --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami4 + name: whoami4-abc namespace: default + labels: + kubernetes.io/service-name: whoami4 -subsets: +addressType: IPv4 +ports: + - name: web + port: 8080 +endpoints: - addresses: - - ip: 10.10.0.1 - - ip: 10.10.0.2 - ports: - - name: web - port: 8080 + - 10.10.0.1 + - 10.10.0.2 + conditions: + ready: true --- apiVersion: v1 diff --git a/pkg/provider/kubernetes/crd/fixtures/with_services_only.yml b/pkg/provider/kubernetes/crd/fixtures/with_services_only.yml index c98462ee9..0b7132681 100644 --- a/pkg/provider/kubernetes/crd/fixtures/with_services_only.yml +++ b/pkg/provider/kubernetes/crd/fixtures/with_services_only.yml @@ -1,17 +1,22 @@ --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami5 + name: whoami5-abc namespace: default + labels: + kubernetes.io/service-name: whoami5 -subsets: +addressType: IPv4 +ports: + - name: web + port: 8080 +endpoints: - addresses: - - ip: 10.10.0.3 - - ip: 10.10.0.4 - ports: - - name: web - port: 8080 + - 10.10.0.3 + - 10.10.0.4 + conditions: + ready: true --- apiVersion: v1 diff --git a/pkg/provider/kubernetes/crd/kubernetes_http.go b/pkg/provider/kubernetes/crd/kubernetes_http.go index 1d5eab777..85c9e0ab5 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_http.go +++ b/pkg/provider/kubernetes/crd/kubernetes_http.go @@ -409,9 +409,8 @@ func (c configBuilder) loadServers(parentNamespace string, svc traefikv1alpha1.L return nil, err } - var servers []dynamic.Server if service.Spec.Type != corev1.ServiceTypeExternalName && svc.HealthCheck != nil { - return nil, fmt.Errorf("HealthCheck allowed only for ExternalName services: %s/%s", namespace, sanitizedName) + return nil, fmt.Errorf("healthCheck allowed only for ExternalName services: %s/%s", namespace, sanitizedName) } if service.Spec.Type == corev1.ServiceTypeExternalName { @@ -426,9 +425,7 @@ func (c configBuilder) loadServers(parentNamespace string, svc traefikv1alpha1.L hostPort := net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(svcPort.Port))) - return append(servers, dynamic.Server{ - URL: fmt.Sprintf("%s://%s", protocol, hostPort), - }), nil + return []dynamic.Server{{URL: fmt.Sprintf("%s://%s", protocol, hostPort)}}, nil } nativeLB := c.NativeLBByDefault @@ -449,6 +446,7 @@ func (c configBuilder) loadServers(parentNamespace string, svc traefikv1alpha1.L return []dynamic.Server{{URL: fmt.Sprintf("%s://%s", protocol, address)}}, nil } + var servers []dynamic.Server if service.Spec.Type == corev1.ServiceTypeNodePort && svc.NodePortLB { nodes, nodesExists, nodesErr := c.client.GetNodes() if nodesErr != nil { @@ -482,27 +480,20 @@ func (c configBuilder) loadServers(parentNamespace string, svc traefikv1alpha1.L return servers, nil } - endpoints, endpointsExists, endpointsErr := c.client.GetEndpoints(namespace, sanitizedName) - if endpointsErr != nil { - return nil, endpointsErr - } - if !endpointsExists { - return nil, fmt.Errorf("endpoints not found for %s/%s", namespace, sanitizedName) + endpointSlices, err := c.client.GetEndpointSlicesForService(namespace, sanitizedName) + if err != nil { + return nil, fmt.Errorf("getting endpointslices: %w", err) } - if len(endpoints.Subsets) == 0 && !c.allowEmptyServices { - return nil, fmt.Errorf("subset not found for %s/%s", namespace, sanitizedName) - } - - for _, subset := range endpoints.Subsets { + addresses := map[string]struct{}{} + for _, endpointSlice := range endpointSlices { var port int32 - for _, p := range subset.Ports { - if svcPort.Name == p.Name { - port = p.Port + for _, p := range endpointSlice.Ports { + if svcPort.Name == *p.Name { + port = *p.Port break } } - if port == 0 { continue } @@ -512,15 +503,28 @@ func (c configBuilder) loadServers(parentNamespace string, svc traefikv1alpha1.L return nil, err } - for _, addr := range subset.Addresses { - hostPort := net.JoinHostPort(addr.IP, strconv.Itoa(int(port))) + for _, endpoint := range endpointSlice.Endpoints { + if endpoint.Conditions.Ready == nil || !*endpoint.Conditions.Ready { + continue + } - servers = append(servers, dynamic.Server{ - URL: fmt.Sprintf("%s://%s", protocol, hostPort), - }) + for _, address := range endpoint.Addresses { + if _, ok := addresses[address]; ok { + continue + } + + addresses[address] = struct{}{} + servers = append(servers, dynamic.Server{ + URL: fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, strconv.Itoa(int(port)))), + }) + } } } + if len(servers) == 0 && !c.allowEmptyServices { + return nil, fmt.Errorf("no servers found for %s/%s", namespace, sanitizedName) + } + return servers, nil } diff --git a/pkg/provider/kubernetes/crd/kubernetes_tcp.go b/pkg/provider/kubernetes/crd/kubernetes_tcp.go index 20b0fe917..5cc35343d 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_tcp.go +++ b/pkg/provider/kubernetes/crd/kubernetes_tcp.go @@ -238,7 +238,6 @@ func (p *Provider) loadTCPServers(client Client, namespace string, svc traefikv1 } var servers []dynamic.TCPServer - if service.Spec.Type == corev1.ServiceTypeNodePort && svc.NodePortLB { nodes, nodesExists, nodesErr := client.GetNodes() if nodesErr != nil { @@ -284,40 +283,47 @@ func (p *Provider) loadTCPServers(client Client, namespace string, svc traefikv1 return []dynamic.TCPServer{{Address: address}}, nil } - endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name) - if endpointsErr != nil { - return nil, endpointsErr + endpointSlices, err := client.GetEndpointSlicesForService(namespace, svc.Name) + if err != nil { + return nil, fmt.Errorf("getting endpointslices: %w", err) } - if !endpointsExists { - return nil, errors.New("endpoints not found") - } - - if len(endpoints.Subsets) == 0 && !p.AllowEmptyServices { - return nil, errors.New("subset not found") - } - - var port int32 - for _, subset := range endpoints.Subsets { - for _, p := range subset.Ports { - if svcPort.Name == p.Name { - port = p.Port + addresses := map[string]struct{}{} + for _, endpointSlice := range endpointSlices { + var port int32 + for _, p := range endpointSlice.Ports { + if svcPort.Name == *p.Name { + port = *p.Port break } } - if port == 0 { - return nil, errors.New("cannot define a port") + continue } - for _, addr := range subset.Addresses { - servers = append(servers, dynamic.TCPServer{ - Address: net.JoinHostPort(addr.IP, strconv.Itoa(int(port))), - }) + for _, endpoint := range endpointSlice.Endpoints { + if endpoint.Conditions.Ready == nil || !*endpoint.Conditions.Ready { + continue + } + + for _, address := range endpoint.Addresses { + if _, ok := addresses[address]; ok { + continue + } + + addresses[address] = struct{}{} + servers = append(servers, dynamic.TCPServer{ + Address: net.JoinHostPort(address, strconv.Itoa(int(port))), + }) + } } } } + if len(servers) == 0 && !p.AllowEmptyServices { + return nil, fmt.Errorf("no servers found for %s/%s", namespace, svc.Name) + } + return servers, nil } diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index 4b886b7e8..8f53e1a0c 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -4620,9 +4620,9 @@ func TestLoadIngressRoutes(t *testing.T) { }, }, { - desc: "IngressRoute, service with multiple subsets", + desc: "IngressRoute, service with multiple endpoint addresses on endpointslice", allowEmptyServices: true, - paths: []string{"services.yml", "with_multiple_subsets.yml"}, + paths: []string{"services.yml", "with_multiple_endpointaddresses.yml"}, expected: &dynamic.Configuration{ UDP: &dynamic.UDPConfiguration{ Routers: map[string]*dynamic.UDPRouter{}, @@ -4648,6 +4648,66 @@ func TestLoadIngressRoutes(t *testing.T) { "default-test-route-6b204d94623b3df4370c": { LoadBalancer: &dynamic.ServersLoadBalancer{ Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + { + URL: "http://10.10.0.5:80", + }, + { + URL: "http://10.10.0.6:80", + }, + }, + PassHostHeader: Bool(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "IngressRoute, service with duplicated endpointaddresses", + allowEmptyServices: true, + paths: []string{"services.yml", "with_duplicated_endpointaddresses.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-test-route-6b204d94623b3df4370c": { + EntryPoints: []string{"foo"}, + Service: "default-test-route-6b204d94623b3df4370c", + Rule: "Host(`foo.com`) && PathPrefix(`/bar`)", + Priority: 12, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-test-route-6b204d94623b3df4370c": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:8080", + }, + { + URL: "http://10.10.0.2:8080", + }, { URL: "http://10.10.0.3:8080", }, @@ -4726,7 +4786,7 @@ func TestLoadIngressRoutes(t *testing.T) { Weighted: &dynamic.WeightedRoundRobin{ Services: []dynamic.WRRService{ { - Name: "default-whoami-without-endpoints-subsets-80", + Name: "default-whoami-without-endpointslice-endpoints-80", Weight: func(i int) *int { return &i }(1), }, }, @@ -4734,10 +4794,10 @@ func TestLoadIngressRoutes(t *testing.T) { }, "default-test-mirror": { Mirroring: &dynamic.Mirroring{ - Service: "default-whoami-without-endpoints-subsets-80", + Service: "default-whoami-without-endpointslice-endpoints-80", Mirrors: []dynamic.MirrorService{ { - Name: "default-whoami-without-endpoints-subsets-80", + Name: "default-whoami-without-endpointslice-endpoints-80", }, { Name: "default-test-weighted", @@ -4745,7 +4805,7 @@ func TestLoadIngressRoutes(t *testing.T) { }, }, }, - "default-whoami-without-endpoints-subsets-80": { + "default-whoami-without-endpointslice-endpoints-80": { LoadBalancer: &dynamic.ServersLoadBalancer{ PassHostHeader: Bool(true), ResponseForwarding: &dynamic.ResponseForwarding{ @@ -4799,6 +4859,87 @@ func TestLoadIngressRoutes(t *testing.T) { } } +func TestLoadIngressRoutes_multipleEndpointAddresses(t *testing.T) { + wantConf := &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-test-route-6b204d94623b3df4370c": { + EntryPoints: []string{"foo"}, + Service: "default-test-route-6b204d94623b3df4370c", + Rule: "Host(`foo.com`) && PathPrefix(`/bar`)", + Priority: 12, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-test-route-6b204d94623b3df4370c": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: Bool(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + } + wantServers := []dynamic.Server{ + { + URL: "http://10.10.0.3:8080", + }, + { + URL: "http://10.10.0.4:8080", + }, + { + URL: "http://10.10.0.5:8080", + }, + { + URL: "http://10.10.0.6:8080", + }, + } + + k8sObjects, crdObjects := readResources(t, []string{"services.yml", "with_multiple_endpointslices.yml"}) + + kubeClient := kubefake.NewSimpleClientset(k8sObjects...) + crdClient := traefikcrdfake.NewSimpleClientset(crdObjects...) + + client := newClientImpl(kubeClient, crdClient) + + stopCh := make(chan struct{}) + + eventCh, err := client.WatchAll(nil, stopCh) + require.NoError(t, err) + + if k8sObjects != nil || crdObjects != nil { + // just wait for the first event + <-eventCh + } + + p := Provider{} + conf := p.loadConfigurationFromCRD(context.Background(), client) + + service, ok := conf.HTTP.Services["default-test-route-6b204d94623b3df4370c"] + require.True(t, ok) + require.NotNil(t, service) + require.NotNil(t, service.LoadBalancer) + assert.ElementsMatch(t, wantServers, service.LoadBalancer.Servers) + + service.LoadBalancer.Servers = nil + assert.Equal(t, wantConf, conf) +} + func TestLoadIngressRouteUDPs(t *testing.T) { testCases := []struct { desc string diff --git a/pkg/provider/kubernetes/crd/kubernetes_udp.go b/pkg/provider/kubernetes/crd/kubernetes_udp.go index 17a0ffaa5..8ab7a3838 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_udp.go +++ b/pkg/provider/kubernetes/crd/kubernetes_udp.go @@ -122,7 +122,6 @@ func (p *Provider) loadUDPServers(client Client, namespace string, svc traefikv1 } var servers []dynamic.UDPServer - if service.Spec.Type == corev1.ServiceTypeNodePort && svc.NodePortLB { nodes, nodesExists, nodesErr := client.GetNodes() if nodesErr != nil { @@ -168,39 +167,46 @@ func (p *Provider) loadUDPServers(client Client, namespace string, svc traefikv1 return []dynamic.UDPServer{{Address: address}}, nil } - endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name) - if endpointsErr != nil { - return nil, endpointsErr + endpointSlices, err := client.GetEndpointSlicesForService(namespace, svc.Name) + if err != nil { + return nil, fmt.Errorf("getting endpointslices: %w", err) } - if !endpointsExists { - return nil, errors.New("endpoints not found") - } - - if len(endpoints.Subsets) == 0 && !p.AllowEmptyServices { - return nil, errors.New("subset not found") - } - - var port int32 - for _, subset := range endpoints.Subsets { - for _, p := range subset.Ports { - if svcPort.Name == p.Name { - port = p.Port + addresses := map[string]struct{}{} + for _, endpointSlice := range endpointSlices { + var port int32 + for _, p := range endpointSlice.Ports { + if svcPort.Name == *p.Name { + port = *p.Port break } } - if port == 0 { - return nil, errors.New("cannot define a port") + continue } - for _, addr := range subset.Addresses { - servers = append(servers, dynamic.UDPServer{ - Address: net.JoinHostPort(addr.IP, strconv.Itoa(int(port))), - }) + for _, endpoint := range endpointSlice.Endpoints { + if endpoint.Conditions.Ready == nil || !*endpoint.Conditions.Ready { + continue + } + + for _, address := range endpoint.Addresses { + if _, ok := addresses[address]; ok { + continue + } + + addresses[address] = struct{}{} + servers = append(servers, dynamic.UDPServer{ + Address: net.JoinHostPort(address, strconv.Itoa(int(port))), + }) + } } } } + if len(servers) == 0 && !p.AllowEmptyServices { + return nil, fmt.Errorf("no servers found for %s/%s", namespace, svc.Name) + } + return servers, nil } diff --git a/pkg/provider/kubernetes/gateway/client.go b/pkg/provider/kubernetes/gateway/client.go index 7c2756886..e91fc949f 100644 --- a/pkg/provider/kubernetes/gateway/client.go +++ b/pkg/provider/kubernetes/gateway/client.go @@ -11,9 +11,11 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/types" corev1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" kerror "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" ktypes "k8s.io/apimachinery/pkg/types" kinformers "k8s.io/client-go/informers" kclientset "k8s.io/client-go/kubernetes" @@ -61,9 +63,9 @@ type Client interface { ListTLSRoutes() ([]*gatev1alpha2.TLSRoute, error) ListNamespaces(selector labels.Selector) ([]string, error) ListReferenceGrants(namespace string) ([]*gatev1beta1.ReferenceGrant, error) + ListEndpointSlicesForService(namespace, serviceName string) ([]*discoveryv1.EndpointSlice, error) GetService(namespace, name string) (*corev1.Service, bool, error) GetSecret(namespace, name string) (*corev1.Secret, bool, error) - GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) } type clientWrapper struct { @@ -222,7 +224,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< if err != nil { return nil, err } - _, err = factoryKube.Core().V1().Endpoints().Informer().AddEventHandler(eventHandler) + _, err = factoryKube.Discovery().V1().EndpointSlices().Informer().AddEventHandler(eventHandler) if err != nil { return nil, err } @@ -543,16 +545,20 @@ func (c *clientWrapper) GetService(namespace, name string) (*corev1.Service, boo return service, exist, err } -// GetEndpoints returns the named endpoints from the given namespace. -func (c *clientWrapper) GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) { +// ListEndpointSlicesForService returns the EndpointSlices for the given service name in the given namespace. +func (c *clientWrapper) ListEndpointSlicesForService(namespace, serviceName string) ([]*discoveryv1.EndpointSlice, error) { if !c.isWatchedNamespace(namespace) { - return nil, false, fmt.Errorf("failed to get endpoints %s/%s: namespace is not within watched namespaces", namespace, name) + return nil, fmt.Errorf("failed to get endpointslices for service %s/%s: namespace is not within watched namespaces", namespace, serviceName) } - endpoint, err := c.factoriesKube[c.lookupNamespace(namespace)].Core().V1().Endpoints().Lister().Endpoints(namespace).Get(name) - exist, err := translateNotFoundError(err) + serviceLabelRequirement, err := labels.NewRequirement(discoveryv1.LabelServiceName, selection.Equals, []string{serviceName}) + if err != nil { + return nil, fmt.Errorf("failed to create service label selector requirement: %w", err) + } + serviceSelector := labels.NewSelector() + serviceSelector = serviceSelector.Add(*serviceLabelRequirement) - return endpoint, exist, err + return c.factoriesKube[c.lookupNamespace(namespace)].Discovery().V1().EndpointSlices().Lister().EndpointSlices(namespace).List(serviceSelector) } // GetSecret returns the named secret from the given namespace. diff --git a/pkg/provider/kubernetes/gateway/fixtures/services.yml b/pkg/provider/kubernetes/gateway/fixtures/services.yml index d9ebf1e6b..8a4cb3b72 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/services.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/services.yml @@ -17,21 +17,26 @@ spec: task: whoami --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami + name: whoami-abc namespace: default + labels: + kubernetes.io/service-name: whoami -subsets: +addressType: IPv4 +ports: + - name: web + port: 80 + - name: web2 + port: 8080 +endpoints: - addresses: - - ip: 10.10.0.1 - - ip: 10.10.0.2 - ports: - - name: web - port: 80 - - name: web2 - port: 8000 + - 10.10.0.1 + - 10.10.0.2 + conditions: + ready: true --- apiVersion: v1 @@ -53,21 +58,26 @@ spec: task: whoami --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami-bar + name: whoami-bar-abc namespace: bar + labels: + kubernetes.io/service-name: whoami-bar -subsets: +addressType: IPv4 +ports: + - name: web + port: 80 + - name: web2 + port: 8000 +endpoints: - addresses: - - ip: 10.10.0.11 - - ip: 10.10.0.12 - ports: - - name: web - port: 80 - - name: web2 - port: 8000 + - 10.10.0.11 + - 10.10.0.12 + conditions: + ready: true --- apiVersion: v1 @@ -86,19 +96,24 @@ spec: task: whoami2 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami2 + name: whoami2-abc namespace: default + labels: + kubernetes.io/service-name: whoami2 -subsets: +addressType: IPv4 +ports: + - name: web + port: 8080 +endpoints: - addresses: - - ip: 10.10.0.3 - - ip: 10.10.0.4 - ports: - - name: web - port: 8080 + - 10.10.0.3 + - 10.10.0.4 + conditions: + ready: true --- apiVersion: v1 @@ -117,19 +132,24 @@ spec: task: whoami2 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoamitls + name: whoamitls-abc namespace: default + labels: + kubernetes.io/service-name: whoamitls -subsets: +addressType: IPv4 +ports: + - name: websecure + port: 8443 +endpoints: - addresses: - - ip: 10.10.0.5 - - ip: 10.10.0.6 - ports: - - name: websecure - port: 8443 + - 10.10.0.5 + - 10.10.0.6 + conditions: + ready: true --- apiVersion: v1 @@ -148,19 +168,24 @@ spec: task: whoami3 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoami3 + name: whoami3-abc namespace: default + labels: + kubernetes.io/service-name: whoami3 -subsets: +addressType: IPv4 +ports: + - name: websecure2 + port: 8443 +endpoints: - addresses: - - ip: 10.10.0.7 - - ip: 10.10.0.8 - ports: - - name: websecure2 - port: 8443 + - 10.10.0.7 + - 10.10.0.8 + conditions: + ready: true --- apiVersion: v1 @@ -201,23 +226,28 @@ spec: port: 443 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoamitcp + name: whoamitcp-abc namespace: default + labels: + kubernetes.io/service-name: whoamitcp -subsets: +addressType: IPv4 +ports: + - name: tcp-1 + protocol: TCP + port: 9000 + - name: tcp-2 + protocol: TCP + port: 10000 +endpoints: - addresses: - - ip: 10.10.0.9 - - ip: 10.10.0.10 - ports: - - name: tcp-1 - protocol: TCP - port: 9000 - - name: tcp-2 - protocol: TCP - port: 10000 + - 10.10.0.9 + - 10.10.0.10 + conditions: + ready: true --- apiVersion: v1 @@ -236,23 +266,28 @@ spec: name: tcp-2 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: whoamitcp-bar + name: whoamitcp-bar-abc namespace: bar + labels: + kubernetes.io/service-name: whoamitcp-bar -subsets: +addressType: IPv4 +ports: + - name: tcp-1 + protocol: TCP + port: 9000 + - name: tcp-2 + protocol: TCP + port: 10000 +endpoints: - addresses: - - ip: 10.10.0.13 - - ip: 10.10.0.14 - ports: - - name: tcp-1 - protocol: TCP - port: 9000 - - name: tcp-2 - protocol: TCP - port: 10000 + - 10.10.0.13 + - 10.10.0.14 + conditions: + ready: true --- apiVersion: v1 diff --git a/pkg/provider/kubernetes/gateway/httproute.go b/pkg/provider/kubernetes/gateway/httproute.go index 484016daf..535c4a1b7 100644 --- a/pkg/provider/kubernetes/gateway/httproute.go +++ b/pkg/provider/kubernetes/gateway/httproute.go @@ -370,6 +370,10 @@ func (p *Provider) loadHTTPRouteFilterExtensionRef(namespace string, extensionRe } func (p *Provider) loadHTTPServers(namespace string, backendRef gatev1.HTTPBackendRef) (*dynamic.ServersLoadBalancer, error) { + if backendRef.Port == nil { + return nil, errors.New("port is required for Kubernetes Service reference") + } + service, exists, err := p.client.GetService(namespace, string(backendRef.Name)) if err != nil { return nil, fmt.Errorf("getting service: %w", err) @@ -378,56 +382,58 @@ func (p *Provider) loadHTTPServers(namespace string, backendRef gatev1.HTTPBacke return nil, errors.New("service not found") } - var portSpec corev1.ServicePort - var match bool - + var svcPort *corev1.ServicePort for _, p := range service.Spec.Ports { - if backendRef.Port == nil || p.Port == int32(*backendRef.Port) { - portSpec = p - match = true + if p.Port == int32(*backendRef.Port) { + svcPort = &p break } } - if !match { - return nil, errors.New("service port not found") + if svcPort == nil { + return nil, fmt.Errorf("service port %d not found", *backendRef.Port) } - endpoints, endpointsExists, err := p.client.GetEndpoints(namespace, string(backendRef.Name)) + endpointSlices, err := p.client.ListEndpointSlicesForService(namespace, string(backendRef.Name)) if err != nil { - return nil, fmt.Errorf("getting endpoints: %w", err) + return nil, fmt.Errorf("getting endpointslices: %w", err) } - if !endpointsExists { - return nil, errors.New("endpoints not found") - } - - if len(endpoints.Subsets) == 0 { - return nil, errors.New("subset not found") + if len(endpointSlices) == 0 { + return nil, errors.New("endpointslices not found") } lb := &dynamic.ServersLoadBalancer{} lb.SetDefaults() - var port int32 - var portStr string - for _, subset := range endpoints.Subsets { - for _, p := range subset.Ports { - if portSpec.Name == p.Name { - port = p.Port + protocol := getProtocol(*svcPort) + + addresses := map[string]struct{}{} + for _, endpointSlice := range endpointSlices { + var port int32 + for _, p := range endpointSlice.Ports { + if svcPort.Name == *p.Name { + port = *p.Port break } } - if port == 0 { - return nil, errors.New("cannot define a port") + continue } - protocol := getProtocol(portSpec) + for _, endpoint := range endpointSlice.Endpoints { + if endpoint.Conditions.Ready == nil || !*endpoint.Conditions.Ready { + continue + } - portStr = strconv.FormatInt(int64(port), 10) - for _, addr := range subset.Addresses { - lb.Servers = append(lb.Servers, dynamic.Server{ - URL: fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(addr.IP, portStr)), - }) + for _, address := range endpoint.Addresses { + if _, ok := addresses[address]; ok { + continue + } + + addresses[address] = struct{}{} + lb.Servers = append(lb.Servers, dynamic.Server{ + URL: fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, strconv.Itoa(int(port)))), + }) + } } } diff --git a/pkg/provider/kubernetes/gateway/tcproute.go b/pkg/provider/kubernetes/gateway/tcproute.go index cdbb155ff..c74355d6f 100644 --- a/pkg/provider/kubernetes/gateway/tcproute.go +++ b/pkg/provider/kubernetes/gateway/tcproute.go @@ -206,82 +206,71 @@ func (p *Provider) loadTCPServices(namespace string, backendRefs []gatev1.Backen return nil, nil, fmt.Errorf("unsupported BackendRef %s/%s/%s", *backendRef.Group, *backendRef.Kind, backendRef.Name) } - svc := dynamic.TCPService{ - LoadBalancer: &dynamic.TCPServersLoadBalancer{}, + if backendRef.Port == nil { + return nil, nil, errors.New("port is required for Kubernetes Service reference") } service, exists, err := p.client.GetService(namespace, string(backendRef.Name)) if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("getting service: %w", err) } - if !exists { return nil, nil, errors.New("service not found") } - if len(service.Spec.Ports) > 1 && backendRef.Port == nil { - // If the port is unspecified and the backend is a Service - // object consisting of multiple port definitions, the route - // must be dropped from the Gateway. The controller should - // raise the "ResolvedRefs" condition on the Gateway with the - // "DroppedRoutes" reason. The gateway status for this route - // should be updated with a condition that describes the error - // more specifically. - log.Error().Msg("A multiple ports Kubernetes Service cannot be used if unspecified backendRef.Port") - continue - } - - var portSpec corev1.ServicePort - var match bool - + var svcPort *corev1.ServicePort for _, p := range service.Spec.Ports { - if backendRef.Port == nil || p.Port == int32(*backendRef.Port) { - portSpec = p - match = true + if p.Port == int32(*backendRef.Port) { + svcPort = &p break } } - - if !match { - return nil, nil, errors.New("service port not found") + if svcPort == nil { + return nil, nil, fmt.Errorf("service port %d not found", *backendRef.Port) } - endpoints, endpointsExists, endpointsErr := p.client.GetEndpoints(namespace, string(backendRef.Name)) - if endpointsErr != nil { - return nil, nil, endpointsErr + endpointSlices, err := p.client.ListEndpointSlicesForService(namespace, string(backendRef.Name)) + if err != nil { + return nil, nil, fmt.Errorf("getting endpointslices: %w", err) + } + if len(endpointSlices) == 0 { + return nil, nil, errors.New("endpointslices not found") } - if !endpointsExists { - return nil, nil, errors.New("endpoints not found") - } + svc := dynamic.TCPService{LoadBalancer: &dynamic.TCPServersLoadBalancer{}} - if len(endpoints.Subsets) == 0 { - return nil, nil, errors.New("subset not found") - } - - var port int32 - var portStr string - for _, subset := range endpoints.Subsets { - for _, p := range subset.Ports { - if portSpec.Name == p.Name { - port = p.Port + addresses := map[string]struct{}{} + for _, endpointSlice := range endpointSlices { + var port int32 + for _, p := range endpointSlice.Ports { + if svcPort.Name == *p.Name { + port = *p.Port break } } - if port == 0 { - return nil, nil, errors.New("cannot define a port") + continue } - portStr = strconv.FormatInt(int64(port), 10) - for _, addr := range subset.Addresses { - svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, dynamic.TCPServer{ - Address: net.JoinHostPort(addr.IP, portStr), - }) + for _, endpoint := range endpointSlice.Endpoints { + if endpoint.Conditions.Ready == nil || !*endpoint.Conditions.Ready { + continue + } + + for _, address := range endpoint.Addresses { + if _, ok := addresses[address]; ok { + continue + } + + addresses[address] = struct{}{} + svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, dynamic.TCPServer{ + Address: net.JoinHostPort(address, strconv.Itoa(int(port))), + }) + } } } - serviceName := provider.Normalize(service.Namespace + "-" + service.Name + "-" + portStr) + serviceName := provider.Normalize(service.Namespace + "-" + service.Name + "-" + strconv.Itoa(int(svcPort.Port))) services[serviceName] = &svc wrrSvc.Weighted.Services = append(wrrSvc.Weighted.Services, dynamic.TCPWRRService{Name: serviceName, Weight: &weight}) diff --git a/pkg/provider/kubernetes/ingress/client.go b/pkg/provider/kubernetes/ingress/client.go index d7959d571..0e16aea9e 100644 --- a/pkg/provider/kubernetes/ingress/client.go +++ b/pkg/provider/kubernetes/ingress/client.go @@ -16,10 +16,12 @@ import ( "github.com/traefik/traefik/v3/pkg/types" traefikversion "github.com/traefik/traefik/v3/pkg/version" corev1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" netv1 "k8s.io/api/networking/v1" kerror "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" kinformers "k8s.io/client-go/informers" kclientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -41,7 +43,7 @@ type Client interface { GetService(namespace, name string) (*corev1.Service, bool, error) GetSecret(namespace, name string) (*corev1.Secret, bool, error) GetNodes() ([]*corev1.Node, bool, error) - GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) + GetEndpointSlicesForService(namespace, serviceName string) ([]*discoveryv1.EndpointSlice, error) UpdateIngressStatus(ing *netv1.Ingress, ingStatus []netv1.IngressLoadBalancerIngress) error } @@ -185,7 +187,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< if err != nil { return nil, err } - _, err = factoryKube.Core().V1().Endpoints().Informer().AddEventHandler(eventHandler) + _, err = factoryKube.Discovery().V1().EndpointSlices().Informer().AddEventHandler(eventHandler) if err != nil { return nil, err } @@ -340,15 +342,20 @@ func (c *clientWrapper) GetService(namespace, name string) (*corev1.Service, boo return service, exist, err } -// GetEndpoints returns the named endpoints from the given namespace. -func (c *clientWrapper) GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) { +// GetEndpointSlicesForService returns the EndpointSlices for the given service name in the given namespace. +func (c *clientWrapper) GetEndpointSlicesForService(namespace, serviceName string) ([]*discoveryv1.EndpointSlice, error) { if !c.isWatchedNamespace(namespace) { - return nil, false, fmt.Errorf("failed to get endpoints %s/%s: namespace is not within watched namespaces", namespace, name) + return nil, fmt.Errorf("failed to get endpointslices for service %s/%s: namespace is not within watched namespaces", namespace, serviceName) } - endpoint, err := c.factoriesKube[c.lookupNamespace(namespace)].Core().V1().Endpoints().Lister().Endpoints(namespace).Get(name) - exist, err := translateNotFoundError(err) - return endpoint, exist, err + serviceLabelRequirement, err := labels.NewRequirement(discoveryv1.LabelServiceName, selection.Equals, []string{serviceName}) + if err != nil { + return nil, fmt.Errorf("failed to create service label selector requirement: %w", err) + } + serviceSelector := labels.NewSelector() + serviceSelector = serviceSelector.Add(*serviceLabelRequirement) + + return c.factoriesKube[c.lookupNamespace(namespace)].Discovery().V1().EndpointSlices().Lister().EndpointSlices(namespace).List(serviceSelector) } // GetSecret returns the named secret from the given namespace. diff --git a/pkg/provider/kubernetes/ingress/client_mock_test.go b/pkg/provider/kubernetes/ingress/client_mock_test.go index 02b8ec4c0..28b0a3501 100644 --- a/pkg/provider/kubernetes/ingress/client_mock_test.go +++ b/pkg/provider/kubernetes/ingress/client_mock_test.go @@ -6,6 +6,7 @@ import ( "github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s" corev1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" netv1 "k8s.io/api/networking/v1" ) @@ -15,15 +16,15 @@ type clientMock struct { ingresses []*netv1.Ingress services []*corev1.Service secrets []*corev1.Secret - endpoints []*corev1.Endpoints + endpointSlices []*discoveryv1.EndpointSlice nodes []*corev1.Node ingressClasses []*netv1.IngressClass - apiServiceError error - apiSecretError error - apiEndpointsError error - apiNodesError error - apiIngressStatusError error + apiServiceError error + apiSecretError error + apiEndpointSlicesError error + apiNodesError error + apiIngressStatusError error watchChan chan interface{} } @@ -43,8 +44,8 @@ func newClientMock(path string) clientMock { c.services = append(c.services, o) case *corev1.Secret: c.secrets = append(c.secrets, o) - case *corev1.Endpoints: - c.endpoints = append(c.endpoints, o) + case *discoveryv1.EndpointSlice: + c.endpointSlices = append(c.endpointSlices, o) case *corev1.Node: c.nodes = append(c.nodes, o) case *netv1.Ingress: @@ -76,18 +77,19 @@ func (c clientMock) GetService(namespace, name string) (*corev1.Service, bool, e return nil, false, c.apiServiceError } -func (c clientMock) GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) { - if c.apiEndpointsError != nil { - return nil, false, c.apiEndpointsError +func (c clientMock) GetEndpointSlicesForService(namespace, serviceName string) ([]*discoveryv1.EndpointSlice, error) { + if c.apiEndpointSlicesError != nil { + return nil, c.apiEndpointSlicesError } - for _, endpoints := range c.endpoints { - if endpoints.Namespace == namespace && endpoints.Name == name { - return endpoints, true, nil + var result []*discoveryv1.EndpointSlice + for _, endpointSlice := range c.endpointSlices { + if endpointSlice.Namespace == namespace && endpointSlice.Labels[discoveryv1.LabelServiceName] == serviceName { + result = append(result, endpointSlice) } } - return &corev1.Endpoints{}, false, nil + return result, nil } func (c clientMock) GetNodes() ([]*corev1.Node, bool, error) { diff --git a/pkg/provider/kubernetes/ingress/client_test.go b/pkg/provider/kubernetes/ingress/client_test.go index 27e1bbd37..24240f277 100644 --- a/pkg/provider/kubernetes/ingress/client_test.go +++ b/pkg/provider/kubernetes/ingress/client_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" netv1 "k8s.io/api/networking/v1" kerror "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -188,10 +189,10 @@ func TestClientIgnoresHelmOwnedSecrets(t *testing.T) { assert.False(t, found) } -func TestClientIgnoresEmptyEndpointUpdates(t *testing.T) { - emptyEndpoint := &corev1.Endpoints{ +func TestClientIgnoresEmptyEndpointSliceUpdates(t *testing.T) { + emptyEndpointSlice := &discoveryv1.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ - Name: "empty-endpoint", + Name: "empty-endpointslice", Namespace: "test", ResourceVersion: "1244", Annotations: map[string]string{ @@ -200,25 +201,31 @@ func TestClientIgnoresEmptyEndpointUpdates(t *testing.T) { }, } - filledEndpoint := &corev1.Endpoints{ + samplePortName := "testing" + samplePortNumber := int32(1337) + samplePortProtocol := corev1.ProtocolTCP + sampleAddressReady := true + filledEndpointSlice := &discoveryv1.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ - Name: "filled-endpoint", + Name: "filled-endpointslice", Namespace: "test", ResourceVersion: "1234", }, - Subsets: []corev1.EndpointSubset{{ - Addresses: []corev1.EndpointAddress{{ - IP: "10.13.37.1", - }}, - Ports: []corev1.EndpointPort{{ - Name: "testing", - Port: 1337, - Protocol: "tcp", - }}, + AddressType: discoveryv1.AddressTypeIPv4, + Endpoints: []discoveryv1.Endpoint{{ + Addresses: []string{"10.13.37.1"}, + Conditions: discoveryv1.EndpointConditions{ + Ready: &sampleAddressReady, + }, + }}, + Ports: []discoveryv1.EndpointPort{{ + Name: &samplePortName, + Port: &samplePortNumber, + Protocol: &samplePortProtocol, }}, } - kubeClient := kubefake.NewSimpleClientset(emptyEndpoint, filledEndpoint) + kubeClient := kubefake.NewSimpleClientset(emptyEndpointSlice, filledEndpointSlice) discovery, _ := kubeClient.Discovery().(*discoveryfake.FakeDiscovery) discovery.FakedServerVersion = &kversion.Info{ @@ -234,50 +241,72 @@ func TestClientIgnoresEmptyEndpointUpdates(t *testing.T) { select { case event := <-eventCh: - ep, ok := event.(*corev1.Endpoints) + ep, ok := event.(*discoveryv1.EndpointSlice) require.True(t, ok) - assert.True(t, ep.Name == "empty-endpoint" || ep.Name == "filled-endpoint") + assert.True(t, ep.Name == "empty-endpointslice" || ep.Name == "filled-endpointslice") case <-time.After(50 * time.Millisecond): - assert.Fail(t, "expected to receive event for endpoints") + assert.Fail(t, "expected to receive event for endpointslices") } - emptyEndpoint, err = kubeClient.CoreV1().Endpoints("test").Get(context.TODO(), "empty-endpoint", metav1.GetOptions{}) + emptyEndpointSlice, err = kubeClient.DiscoveryV1().EndpointSlices("test").Get(context.TODO(), "empty-endpointslice", metav1.GetOptions{}) assert.NoError(t, err) // Update endpoint annotation and resource version (apparently not done by fake client itself) // to show an update that should not trigger an update event on our eventCh. // This reflects the behavior of kubernetes controllers which use endpoint annotations for leader election. - emptyEndpoint.Annotations["test-annotation"] = "___" - emptyEndpoint.ResourceVersion = "1245" - _, err = kubeClient.CoreV1().Endpoints("test").Update(context.TODO(), emptyEndpoint, metav1.UpdateOptions{}) + emptyEndpointSlice.Annotations["test-annotation"] = "___" + emptyEndpointSlice.ResourceVersion = "1245" + _, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(context.TODO(), emptyEndpointSlice, metav1.UpdateOptions{}) require.NoError(t, err) select { case event := <-eventCh: - ep, ok := event.(*corev1.Endpoints) + ep, ok := event.(*discoveryv1.EndpointSlice) require.True(t, ok) - assert.Fail(t, "didn't expect to receive event for empty endpoint update", ep.Name) + assert.Fail(t, "didn't expect to receive event for empty endpointslice update", ep.Name) case <-time.After(50 * time.Millisecond): } - filledEndpoint, err = kubeClient.CoreV1().Endpoints("test").Get(context.TODO(), "filled-endpoint", metav1.GetOptions{}) + filledEndpointSlice, err = kubeClient.DiscoveryV1().EndpointSlices("test").Get(context.TODO(), "filled-endpointslice", metav1.GetOptions{}) assert.NoError(t, err) - filledEndpoint.Subsets[0].Addresses[0].IP = "10.13.37.2" - filledEndpoint.ResourceVersion = "1235" - _, err = kubeClient.CoreV1().Endpoints("test").Update(context.TODO(), filledEndpoint, metav1.UpdateOptions{}) + filledEndpointSlice.Endpoints[0].Addresses[0] = "10.13.37.2" + filledEndpointSlice.ResourceVersion = "1235" + _, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(context.TODO(), filledEndpointSlice, metav1.UpdateOptions{}) require.NoError(t, err) select { case event := <-eventCh: - ep, ok := event.(*corev1.Endpoints) + ep, ok := event.(*discoveryv1.EndpointSlice) require.True(t, ok) - assert.Equal(t, "filled-endpoint", ep.Name) + assert.Equal(t, "filled-endpointslice", ep.Name) case <-time.After(50 * time.Millisecond): - assert.Fail(t, "expected to receive event for filled endpoint") + assert.Fail(t, "expected to receive event for filled endpointslice") + } + + select { + case <-eventCh: + assert.Fail(t, "received more than one event") + case <-time.After(50 * time.Millisecond): + } + + newPortNumber := int32(42) + filledEndpointSlice.Ports[0].Port = &newPortNumber + filledEndpointSlice.ResourceVersion = "1236" + _, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(context.TODO(), filledEndpointSlice, metav1.UpdateOptions{}) + require.NoError(t, err) + + select { + case event := <-eventCh: + ep, ok := event.(*discoveryv1.EndpointSlice) + require.True(t, ok) + + assert.Equal(t, "filled-endpointslice", ep.Name) + case <-time.After(50 * time.Millisecond): + assert.Fail(t, "expected to receive event for filled endpointslice") } select { diff --git a/pkg/provider/kubernetes/ingress/fixtures/2-ingresses-in-different-namespace-with-same-service-name.yml b/pkg/provider/kubernetes/ingress/fixtures/2-ingresses-in-different-namespace-with-same-service-name.yml index 616e2e3a8..afc2b3af3 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/2-ingresses-in-different-namespace-with-same-service-name.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/2-ingresses-in-different-namespace-with-same-service-name.yml @@ -1,32 +1,42 @@ --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - name: tchouk + port: 8089 +endpoints: - addresses: - - ip: 10.10.0.1 - - ip: 10.10.0.2 - ports: - - name: tchouk - port: 8089 + - 10.10.0.1 + - 10.10.0.2 + conditions: + ready: true --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: toto + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - name: tchouk + port: 8089 +endpoints: - addresses: - - ip: 10.11.0.1 - - ip: 10.11.0.2 - ports: - - name: tchouk - port: 8089 + - 10.11.0.1 + - 10.11.0.2 + conditions: + ready: true --- kind: Ingress diff --git a/pkg/provider/kubernetes/ingress/fixtures/Double-Single-Service-Ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/Double-Single-Service-Ingress.yml index b002d0fc5..95d47901a 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Double-Single-Service-Ingress.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Double-Single-Service-Ingress.yml @@ -50,35 +50,41 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiversion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.30.0.1 - ports: - - port: 8080 - - addresses: - - ip: 10.41.0.1 - ports: - - port: 8080 + - 10.30.0.1 + - 10.41.0.1 + conditions: + ready: true --- -kind: Endpoints -apiversion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service2 + name: service2-abc namespace: testing + labels: + kubernetes.io/service-name: service2 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 - - addresses: - - ip: 10.21.0.1 - ports: - - port: 8080 + - 10.10.0.1 + - 10.21.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-Two-rules-with-one-host-and-one-path.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-Two-rules-with-one-host-and-one-path.yml index 14b050fbd..c5dd091de 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-Two-rules-with-one-host-and-one-path.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-Two-rules-with-one-host-and-one-path.yml @@ -40,18 +40,21 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 - - addresses: - - ip: 10.21.0.1 - ports: - - port: 8080 + - 10.10.0.1 + - 10.21.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-one-rule-with-one-host-and-two-paths.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-one-rule-with-one-host-and-two-paths.yml index 675ddac66..def6f18a2 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-one-rule-with-one-host-and-two-paths.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-one-rule-with-one-host-and-two-paths.yml @@ -37,18 +37,21 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 - - addresses: - - ip: 10.21.0.1 - ports: - - port: 8080 + - 10.10.0.1 + - 10.21.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-one-rule-with-one-path-and-one-host.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-one-rule-with-one-path-and-one-host.yml index 2cb84cb33..fbddeff76 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-one-rule-with-one-path-and-one-host.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-one-rule-with-one-path-and-one-host.yml @@ -30,18 +30,21 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 - - addresses: - - ip: 10.21.0.1 - ports: - - port: 8080 + - 10.10.0.1 + - 10.21.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-one-rule-with-two-paths.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-one-rule-with-two-paths.yml index 8cd87d4c4..a7cbfe1d5 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-one-rule-with-two-paths.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-one-rule-with-two-paths.yml @@ -36,18 +36,21 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 - - addresses: - - ip: 10.21.0.1 - ports: - - port: 8080 + - 10.10.0.1 + - 10.21.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-IPv6-endpoints.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-IPv6-endpoints.yml index 340f79a60..aacdb0dbb 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-IPv6-endpoints.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-IPv6-endpoints.yml @@ -52,15 +52,20 @@ spec: externalName: "2001:0db8:3c4d:0015:0000:0000:1a2f:2a3b" --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service-bar + name: service-bar-abc namespace: testing + labels: + kubernetes.io/service-name: service-bar -subsets: +addressType: IPv6 +ports: + - name: http + port: 8080 +endpoints: - addresses: - - ip: "2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b" - ports: - - name: http - port: 8080 + - "2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b" + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-a-basic-rule-on-one-path-with-https-(port-==-443).yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-a-basic-rule-on-one-path-with-https-(port-==-443).yml index 0b3f020db..ec36dfbc5 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-a-basic-rule-on-one-path-with-https-(port-==-443).yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-a-basic-rule-on-one-path-with-https-(port-==-443).yml @@ -30,18 +30,21 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8443 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8443 - - addresses: - - ip: 10.21.0.1 - ports: - - port: 8443 + - 10.10.0.1 + - 10.21.0.1 + conditions: + ready: true \ No newline at end of file diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-a-basic-rule-on-one-path-with-https-(portname-==-https).yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-a-basic-rule-on-one-path-with-https-(portname-==-https).yml index 46227d191..659b70411 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-a-basic-rule-on-one-path-with-https-(portname-==-https).yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-a-basic-rule-on-one-path-with-https-(portname-==-https).yml @@ -31,20 +31,21 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - name: https + port: 8443 +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - name: https - port: 8443 - - addresses: - - ip: 10.21.0.1 - ports: - - name: https - port: 8443 + - 10.10.0.1 + - 10.21.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-a-basic-rule-on-one-path-with-https-(portname-starts-with-https).yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-a-basic-rule-on-one-path-with-https-(portname-starts-with-https).yml index 3cce7e424..6a5469730 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-a-basic-rule-on-one-path-with-https-(portname-starts-with-https).yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-a-basic-rule-on-one-path-with-https-(portname-starts-with-https).yml @@ -31,20 +31,21 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - name: https-foo + port: 8443 +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - name: https-foo - port: 8443 - - addresses: - - ip: 10.21.0.1 - ports: - - name: https-foo - port: 8443 + - 10.10.0.1 + - 10.21.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-a-basic-rule-on-one-path.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-a-basic-rule-on-one-path.yml index fcfbc08b1..1b64e2dbe 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-a-basic-rule-on-one-path.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-a-basic-rule-on-one-path.yml @@ -29,18 +29,21 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 - - addresses: - - ip: 10.21.0.1 - ports: - - port: 8080 + - 10.10.0.1 + - 10.21.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-a-named-port-matching-subset-of-service-pods.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-a-named-port-matching-subset-of-service-pods.yml index 8c65a2271..8e4a133e5 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-a-named-port-matching-subset-of-service-pods.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-a-named-port-matching-subset-of-service-pods.yml @@ -33,23 +33,42 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - name: tchouk + port: 8089 +endpoints: - addresses: - - ip: 10.10.0.1 - - ip: 10.10.0.2 - ports: - - name: tchouk - port: 8089 + - 10.10.0.1 + - 10.10.0.2 + conditions: + ready: true + +--- +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 +metadata: + name: service1-def + namespace: testing + labels: + kubernetes.io/service-name: service1 + +addressType: IPv4 +ports: + - name: carotte + port: 8090 +endpoints: - addresses: - - ip: 10.10.0.1 - - ip: 10.10.0.2 - - ip: 10.10.0.3 - ports: - - name: carotte - port: 8090 + - 10.10.0.1 + - 10.10.0.2 + - 10.10.0.3 + conditions: + ready: true \ No newline at end of file diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-annotations.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-annotations.yml index 8acc12c2f..bf4679f30 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-annotations.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-annotations.yml @@ -53,18 +53,21 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 - - addresses: - - ip: 10.21.0.1 - ports: - - port: 8080 + - 10.10.0.1 + - 10.21.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-host.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-host.yml index b9d2b116d..655cdc26b 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-host.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-host.yml @@ -41,18 +41,21 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 - - addresses: - - ip: 10.21.0.1 - ports: - - port: 8080 + - 10.10.0.1 + - 10.21.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-path.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-path.yml index d0ef9b15d..f7f1459cf 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-path.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-path.yml @@ -37,18 +37,21 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 - - addresses: - - ip: 10.21.0.1 - ports: - - port: 8080 + - 10.10.0.1 + - 10.21.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-default-traefik-ingressClass.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-default-traefik-ingressClass.yml index 96ad4c8fd..42f94bf41 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-default-traefik-ingressClass.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-default-traefik-ingressClass.yml @@ -31,14 +31,20 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 + - 10.10.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-defaultbackend.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-defaultbackend.yml index 7a9e31231..bcd8db9aa 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-defaultbackend.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-defaultbackend.yml @@ -36,27 +36,38 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 80 +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 80 + - 10.10.0.1 + conditions: + ready: true --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: defaultservice + name: defaultservice-abc namespace: testing + labels: + kubernetes.io/service-name: defaultservice -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 + - 10.10.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-empty-pathType.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-empty-pathType.yml index e4557b59b..f08124d72 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-empty-pathType.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-empty-pathType.yml @@ -30,14 +30,20 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 + - 10.10.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-exact-pathType.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-exact-pathType.yml index cf33330da..b75c29d44 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-exact-pathType.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-exact-pathType.yml @@ -28,14 +28,20 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 + - 10.10.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-implementationSpecific-pathType.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-implementationSpecific-pathType.yml index f410cd5f3..833b2878a 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-implementationSpecific-pathType.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-implementationSpecific-pathType.yml @@ -30,14 +30,20 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 + - 10.10.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-ingress-annotation.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-ingress-annotation.yml index 3f9a500f5..1da2d0bfd 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-ingress-annotation.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-ingress-annotation.yml @@ -30,14 +30,20 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 + - 10.10.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-ingressClass-without-annotation.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-ingressClass-without-annotation.yml index 96ad4c8fd..42f94bf41 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-ingressClass-without-annotation.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-ingressClass-without-annotation.yml @@ -31,14 +31,20 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 + - 10.10.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-ingressClass.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-ingressClass.yml index e272a505a..a954db81c 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-ingressClass.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-ingressClass.yml @@ -37,15 +37,20 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 - + - 10.10.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-ingressClasses-filter.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-ingressClasses-filter.yml index edc1b2a05..89aa5340b 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-ingressClasses-filter.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-ingressClasses-filter.yml @@ -64,14 +64,20 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 + - 10.10.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-missing-ingressClass.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-missing-ingressClass.yml index 26473bbb3..2b8df5376 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-missing-ingressClass.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-missing-ingressClass.yml @@ -29,14 +29,20 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 + - 10.10.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-multiple-ingressClasses.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-multiple-ingressClasses.yml index edc1b2a05..89aa5340b 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-multiple-ingressClasses.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-multiple-ingressClasses.yml @@ -64,14 +64,20 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 + - 10.10.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-named-port.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-named-port.yml index 4d0394000..59c31a7cc 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-named-port.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-named-port.yml @@ -29,15 +29,20 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - name: foobar + port: 4711 +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - name: foobar - port: 4711 + - 10.10.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-non-matching-provider-traefik-ingressClass-and-annotation.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-non-matching-provider-traefik-ingressClass-and-annotation.yml index 7c69c842b..6f571621f 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-non-matching-provider-traefik-ingressClass-and-annotation.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-non-matching-provider-traefik-ingressClass-and-annotation.yml @@ -31,14 +31,20 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 + - 10.10.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-one-host-without-path.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-one-host-without-path.yml index 8de5910ad..52a370bbc 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-one-host-without-path.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-one-host-without-path.yml @@ -31,15 +31,20 @@ spec: type: ClusterIP --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: example-com + name: example-com-abc namespace: testing + labels: + kubernetes.io/service-name: example-com -subsets: +addressType: IPv4 +ports: + - name: http + port: 80 +endpoints: - addresses: - - ip: 10.11.0.1 - ports: - - name: http - port: 80 + - 10.11.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-one-service-without-endpoints-subset.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-one-service-without-endpoints-subset.yml index f0d9ffc8b..4cf788ea1 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-one-service-without-endpoints-subset.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-one-service-without-endpoints-subset.yml @@ -30,8 +30,14 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 + +addressType: IPv4 +ports: null +endpoints: [] diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-port-invalid-for-one-service.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-port-invalid-for-one-service.yml index ccf4c9557..cbfdf3b2f 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-port-invalid-for-one-service.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-port-invalid-for-one-service.yml @@ -40,24 +40,43 @@ spec: type: ClusterIP --- -apiVersion: v1 -kind: Endpoints +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing -subsets: + labels: + kubernetes.io/service-name: service1 + +addressType: IPv4 +ports: + - name: http-admin + port: 8079 + protocol: TCP +endpoints: - addresses: - - ip: 10.0.0.1 - nodeName: admin.whoami.service1 - ports: - - name: http-admin - port: 8079 - protocol: TCP + - 10.0.0.1 + conditions: + ready: true + nodeName: admin.whoami.service1 + +--- +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 +metadata: + name: service1-def + namespace: testing + labels: + kubernetes.io/service-name: service1 + +addressType: IPv4 +ports: + - name: http + port: 8080 + protocol: TCP +endpoints: - addresses: - - ip: 10.0.0.1 - nodeName: whoami.service1 - # targetRef: - ports: - - name: http - port: 8080 - protocol: TCP + - 10.0.0.1 + conditions: + ready: true + nodeName: whoami.service1 \ No newline at end of file diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-port-name-in-backend-and-2-pod-replica.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-port-name-in-backend-and-2-pod-replica.yml index 10216d602..79a3f892c 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-port-name-in-backend-and-2-pod-replica.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-port-name-in-backend-and-2-pod-replica.yml @@ -33,18 +33,23 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - name: carotte + port: 8090 + - name: tchouk + port: 8089 +endpoints: - addresses: - - ip: 10.10.0.1 - - ip: 10.10.0.2 - ports: - - name: carotte - port: 8090 - - name: tchouk - port: 8089 + - 10.10.0.1 + - 10.10.0.2 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-port-name-in-backend-and-no-pod-replica.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-port-name-in-backend-and-no-pod-replica.yml index d9ea086f4..c263c46e4 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-port-name-in-backend-and-no-pod-replica.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-port-name-in-backend-and-no-pod-replica.yml @@ -33,24 +33,23 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - name: carotte + port: 8090 + - name: tchouk + port: 8089 +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - name: carotte - port: 8090 - - name: tchouk - port: 8089 - - addresses: - - ip: 10.21.0.1 - ports: - - name: carotte - port: 8090 - - name: tchouk - port: 8089 + - 10.10.0.1 + - 10.21.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-port-value-in-backend-and-no-pod-replica.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-port-value-in-backend-and-no-pod-replica.yml index 073f5faa5..2c9db02e3 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-port-value-in-backend-and-no-pod-replica.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-port-value-in-backend-and-no-pod-replica.yml @@ -33,24 +33,23 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - name: carotte + port: 8090 + - name: tchouk + port: 8089 +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - name: carotte - port: 8090 - - name: tchouk - port: 8089 - - addresses: - - ip: 10.21.0.1 - ports: - - name: carotte - port: 8090 - - name: tchouk - port: 8089 + - 10.10.0.1 + - 10.21.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-prefix-pathType.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-prefix-pathType.yml index ca66982c8..c2d5fd6ba 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-prefix-pathType.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-prefix-pathType.yml @@ -28,14 +28,20 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 + - 10.10.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-two-different-rules-with-one-path.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-two-different-rules-with-one-path.yml index 9f3a5d477..98fcb9307 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-two-different-rules-with-one-path.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-two-different-rules-with-one-path.yml @@ -38,18 +38,21 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 - - addresses: - - ip: 10.21.0.1 - ports: - - port: 8080 + - 10.10.0.1 + - 10.21.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-two-paths-using-same-service-and-different-port-name.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-two-paths-using-same-service-and-different-port-name.yml index 8474965be..495afdbd9 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-two-paths-using-same-service-and-different-port-name.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-two-paths-using-same-service-and-different-port-name.yml @@ -40,18 +40,23 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - name: carotte + port: 8090 + - name: tchouk + port: 8089 +endpoints: - addresses: - - ip: 10.10.0.1 - - ip: 10.10.0.2 - ports: - - name: carotte - port: 8090 - - name: tchouk - port: 8089 + - 10.10.0.1 + - 10.10.0.2 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-two-services.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-two-services.yml index 55b9e4218..a11030881 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-two-services.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-two-services.yml @@ -52,35 +52,41 @@ spec: clusterIP: 10.1.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 - - addresses: - - ip: 10.21.0.1 - ports: - - port: 8080 + - 10.10.0.1 + - 10.21.0.1 + conditions: + ready: true --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service2 + name: service2-abc namespace: testing + labels: + kubernetes.io/service-name: service2 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.2 - ports: - - port: 8080 - - addresses: - - ip: 10.21.0.2 - ports: - - port: 8080 + - 10.10.0.2 + - 10.21.0.2 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-unknown-service-port-name.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-unknown-service-port-name.yml index b22892ff5..c51486086 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-unknown-service-port-name.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-unknown-service-port-name.yml @@ -30,15 +30,21 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8089 + name: "" +endpoints: - addresses: - - ip: 10.11.0.1 - - ip: 10.11.0.2 - ports: - - port: 8089 + - 10.11.0.1 + - 10.11.0.2 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-unknown-service-port.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-unknown-service-port.yml index 7ef564b1a..744ea812a 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-unknown-service-port.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-unknown-service-port.yml @@ -30,15 +30,21 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8089 + name: "" +endpoints: - addresses: - - ip: 10.11.0.1 - - ip: 10.11.0.2 - ports: - - port: 8089 + - 10.11.0.1 + - 10.11.0.2 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-wildcard-host.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-wildcard-host.yml index ff18bfd83..e0df96260 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-wildcard-host.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-wildcard-host.yml @@ -30,14 +30,20 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 + - 10.10.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-without-provider-traefik-ingressClass-and-unknown-annotation.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-without-provider-traefik-ingressClass-and-unknown-annotation.yml index e2312d68e..aadced090 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Ingress-without-provider-traefik-ingressClass-and-unknown-annotation.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-without-provider-traefik-ingressClass-and-unknown-annotation.yml @@ -31,14 +31,20 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 + - 10.10.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/Single-Service-Ingress-(without-any-rules).yml b/pkg/provider/kubernetes/ingress/fixtures/Single-Service-Ingress-(without-any-rules).yml index 7ba4b0196..38b99a0d6 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/Single-Service-Ingress-(without-any-rules).yml +++ b/pkg/provider/kubernetes/ingress/fixtures/Single-Service-Ingress-(without-any-rules).yml @@ -24,18 +24,21 @@ spec: clusterIP: 10.0.0.1 --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: service1 + name: service1-abc namespace: testing + labels: + kubernetes.io/service-name: service1 -subsets: +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: - addresses: - - ip: 10.10.0.1 - ports: - - port: 8080 - - addresses: - - ip: 10.21.0.1 - ports: - - port: 8080 + - 10.10.0.1 + - 10.21.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/fixtures/TLS-support.yml b/pkg/provider/kubernetes/ingress/fixtures/TLS-support.yml index 6760730cd..c645ba44d 100644 --- a/pkg/provider/kubernetes/ingress/fixtures/TLS-support.yml +++ b/pkg/provider/kubernetes/ingress/fixtures/TLS-support.yml @@ -83,15 +83,20 @@ spec: type: ClusterIP --- -kind: Endpoints -apiVersion: v1 +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 metadata: - name: example-com + name: example-com-abc namespace: testing + labels: + kubernetes.io/service-name: example-com -subsets: +addressType: IPv4 +ports: + - name: http + port: 80 +endpoints: - addresses: - - ip: 10.11.0.1 - ports: - - name: http - port: 80 + - 10.11.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/kubernetes.go b/pkg/provider/kubernetes/ingress/kubernetes.go index b952c32d7..7ae9c6b8b 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes.go +++ b/pkg/provider/kubernetes/ingress/kubernetes.go @@ -651,36 +651,41 @@ func (p *Provider) loadService(client Client, namespace string, backend netv1.In return svc, nil } - endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, backend.Service.Name) - if endpointsErr != nil { - return nil, endpointsErr + endpointSlices, err := client.GetEndpointSlicesForService(namespace, backend.Service.Name) + if err != nil { + return nil, fmt.Errorf("getting endpointslices: %w", err) } - if !endpointsExists { - return nil, errors.New("endpoints not found") - } - - for _, subset := range endpoints.Subsets { + addresses := map[string]struct{}{} + for _, endpointSlice := range endpointSlices { var port int32 - for _, p := range subset.Ports { - if portName == p.Name { - port = p.Port + for _, p := range endpointSlice.Ports { + if portName == *p.Name { + port = *p.Port break } } - if port == 0 { continue } protocol := getProtocol(portSpec, portName, svcConfig) - for _, addr := range subset.Addresses { - hostPort := net.JoinHostPort(addr.IP, strconv.Itoa(int(port))) + for _, endpoint := range endpointSlice.Endpoints { + if endpoint.Conditions.Ready == nil || !*endpoint.Conditions.Ready { + continue + } - svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, dynamic.Server{ - URL: fmt.Sprintf("%s://%s", protocol, hostPort), - }) + for _, address := range endpoint.Addresses { + if _, ok := addresses[address]; ok { + continue + } + + addresses[address] = struct{}{} + svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, dynamic.Server{ + URL: fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, strconv.Itoa(int(port)))), + }) + } } } diff --git a/pkg/provider/kubernetes/k8s/event_handler.go b/pkg/provider/kubernetes/k8s/event_handler.go index 14475c18c..1de67ec5e 100644 --- a/pkg/provider/kubernetes/k8s/event_handler.go +++ b/pkg/provider/kubernetes/k8s/event_handler.go @@ -1,7 +1,7 @@ package k8s import ( - corev1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -47,61 +47,54 @@ func objChanged(oldObj, newObj interface{}) bool { return false } - if _, ok := oldObj.(*corev1.Endpoints); ok { - return endpointsChanged(oldObj.(*corev1.Endpoints), newObj.(*corev1.Endpoints)) + if _, ok := oldObj.(*discoveryv1.EndpointSlice); ok { + return endpointSliceChanged(oldObj.(*discoveryv1.EndpointSlice), newObj.(*discoveryv1.EndpointSlice)) } return true } -func endpointsChanged(a, b *corev1.Endpoints) bool { - if len(a.Subsets) != len(b.Subsets) { +// In some Kubernetes versions leader election is done by updating an endpoint annotation every second, +// if there are no changes to the endpoints addresses, ports, and there are no addresses defined for an endpoint +// the event can safely be ignored and won't cause unnecessary config reloads. +// TODO: check if Kubernetes is still using EndpointSlice for leader election, which seems to not be the case anymore. +func endpointSliceChanged(a, b *discoveryv1.EndpointSlice) bool { + if len(a.Ports) != len(b.Ports) { return true } - for i, sa := range a.Subsets { - sb := b.Subsets[i] - if subsetsChanged(sa, sb) { - return true - } - } - - return false -} - -func subsetsChanged(sa, sb corev1.EndpointSubset) bool { - if len(sa.Addresses) != len(sb.Addresses) { - return true - } - - if len(sa.Ports) != len(sb.Ports) { - return true - } - - // in Addresses and Ports, we should be able to rely on - // these being sorted and able to be compared - // they are supposed to be in a canonical format - for addr, aaddr := range sa.Addresses { - baddr := sb.Addresses[addr] - if aaddr.IP != baddr.IP { - return true - } - - if aaddr.Hostname != baddr.Hostname { - return true - } - } - - for port, aport := range sa.Ports { - bport := sb.Ports[port] + for i, aport := range a.Ports { + bport := b.Ports[i] if aport.Name != bport.Name { return true } if aport.Port != bport.Port { return true } + } - if aport.Protocol != bport.Protocol { + if len(a.Endpoints) != len(b.Endpoints) { + return true + } + + for i, ea := range a.Endpoints { + eb := b.Endpoints[i] + if endpointChanged(ea, eb) { + return true + } + } + + return false +} + +func endpointChanged(a, b discoveryv1.Endpoint) bool { + if len(a.Addresses) != len(b.Addresses) { + return true + } + + for i, aaddr := range a.Addresses { + baddr := b.Addresses[i] + if aaddr != baddr { return true } } diff --git a/pkg/provider/kubernetes/k8s/event_handler_test.go b/pkg/provider/kubernetes/k8s/event_handler_test.go index 323b4f550..bcde5cc48 100644 --- a/pkg/provider/kubernetes/k8s/event_handler_test.go +++ b/pkg/provider/kubernetes/k8s/event_handler_test.go @@ -4,12 +4,14 @@ import ( "testing" "github.com/stretchr/testify/assert" - corev1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" netv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func Test_detectChanges(t *testing.T) { + portA := int32(80) + portB := int32(8080) tests := []struct { name string oldObj interface{} @@ -21,28 +23,28 @@ func Test_detectChanges(t *testing.T) { want: true, }, { - name: "With empty endpoints", - oldObj: &corev1.Endpoints{}, - newObj: &corev1.Endpoints{}, + name: "With empty endpointslice", + oldObj: &discoveryv1.EndpointSlice{}, + newObj: &discoveryv1.EndpointSlice{}, }, { name: "With old nil", - newObj: &corev1.Endpoints{}, + newObj: &discoveryv1.EndpointSlice{}, want: true, }, { name: "With new nil", - oldObj: &corev1.Endpoints{}, + oldObj: &discoveryv1.EndpointSlice{}, want: true, }, { name: "With same version", - oldObj: &corev1.Endpoints{ + oldObj: &discoveryv1.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ ResourceVersion: "1", }, }, - newObj: &corev1.Endpoints{ + newObj: &discoveryv1.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ ResourceVersion: "1", }, @@ -50,12 +52,12 @@ func Test_detectChanges(t *testing.T) { }, { name: "With different version", - oldObj: &corev1.Endpoints{ + oldObj: &discoveryv1.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ ResourceVersion: "1", }, }, - newObj: &corev1.Endpoints{ + newObj: &discoveryv1.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ ResourceVersion: "2", }, @@ -90,7 +92,7 @@ func Test_detectChanges(t *testing.T) { }, { name: "With same annotations", - oldObj: &corev1.Endpoints{ + oldObj: &discoveryv1.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ ResourceVersion: "1", Annotations: map[string]string{ @@ -98,7 +100,7 @@ func Test_detectChanges(t *testing.T) { }, }, }, - newObj: &corev1.Endpoints{ + newObj: &discoveryv1.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ ResourceVersion: "2", Annotations: map[string]string{ @@ -109,7 +111,7 @@ func Test_detectChanges(t *testing.T) { }, { name: "With different annotations", - oldObj: &corev1.Endpoints{ + oldObj: &discoveryv1.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ ResourceVersion: "1", Annotations: map[string]string{ @@ -117,7 +119,7 @@ func Test_detectChanges(t *testing.T) { }, }, }, - newObj: &corev1.Endpoints{ + newObj: &discoveryv1.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ ResourceVersion: "2", Annotations: map[string]string{ @@ -127,384 +129,94 @@ func Test_detectChanges(t *testing.T) { }, }, { - name: "With same subsets", - oldObj: &corev1.Endpoints{ + name: "With same endpoints and ports", + oldObj: &discoveryv1.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ ResourceVersion: "1", }, - Subsets: []corev1.EndpointSubset{}, + Endpoints: []discoveryv1.Endpoint{}, + Ports: []discoveryv1.EndpointPort{}, }, - newObj: &corev1.Endpoints{ + newObj: &discoveryv1.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ ResourceVersion: "1", }, - Subsets: []corev1.EndpointSubset{}, + Endpoints: []discoveryv1.Endpoint{}, + Ports: []discoveryv1.EndpointPort{}, }, }, { - name: "With different len of subsets", - oldObj: &corev1.Endpoints{ + name: "With different len of endpoints", + oldObj: &discoveryv1.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ ResourceVersion: "1", }, - Subsets: []corev1.EndpointSubset{}, + Endpoints: []discoveryv1.Endpoint{}, }, - newObj: &corev1.Endpoints{ + newObj: &discoveryv1.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ ResourceVersion: "2", }, - Subsets: []corev1.EndpointSubset{{}}, + Endpoints: []discoveryv1.Endpoint{{}}, }, want: true, }, { - name: "With same subsets with same len of addresses", - oldObj: &corev1.Endpoints{ + name: "With different endpoints", + oldObj: &discoveryv1.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ ResourceVersion: "1", }, - Subsets: []corev1.EndpointSubset{{ - Addresses: []corev1.EndpointAddress{}, + Endpoints: []discoveryv1.Endpoint{{ + Addresses: []string{"10.10.10.10"}, }}, }, - newObj: &corev1.Endpoints{ + newObj: &discoveryv1.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ ResourceVersion: "2", }, - Subsets: []corev1.EndpointSubset{{ - Addresses: []corev1.EndpointAddress{}, - }}, - }, - }, - { - name: "With same subsets with different len of addresses", - oldObj: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "1", - }, - Subsets: []corev1.EndpointSubset{{ - Addresses: []corev1.EndpointAddress{}, - }}, - }, - newObj: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "2", - }, - Subsets: []corev1.EndpointSubset{{ - Addresses: []corev1.EndpointAddress{{}}, + Endpoints: []discoveryv1.Endpoint{{ + Addresses: []string{"10.10.10.11"}, }}, }, want: true, }, { - name: "With same subsets with same len of ports", - oldObj: &corev1.Endpoints{ + name: "With different len of ports", + oldObj: &discoveryv1.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ ResourceVersion: "1", }, - Subsets: []corev1.EndpointSubset{{ - Ports: []corev1.EndpointPort{}, - }}, + Ports: []discoveryv1.EndpointPort{}, }, - newObj: &corev1.Endpoints{ + newObj: &discoveryv1.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ ResourceVersion: "2", }, - Subsets: []corev1.EndpointSubset{{ - Ports: []corev1.EndpointPort{}, - }}, - }, - }, - { - name: "With same subsets with different len of ports", - oldObj: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "1", - }, - Subsets: []corev1.EndpointSubset{{ - Ports: []corev1.EndpointPort{}, - }}, - }, - newObj: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "2", - }, - Subsets: []corev1.EndpointSubset{{ - Ports: []corev1.EndpointPort{{}}, - }}, + Ports: []discoveryv1.EndpointPort{{}}, }, want: true, }, { - name: "With same subsets with same len of addresses with same ip", - oldObj: &corev1.Endpoints{ + name: "With different ports", + oldObj: &discoveryv1.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ ResourceVersion: "1", }, - Subsets: []corev1.EndpointSubset{{ - Addresses: []corev1.EndpointAddress{{ - IP: "10.10.10.10", - }}, + Ports: []discoveryv1.EndpointPort{{ + Port: &portA, }}, }, - newObj: &corev1.Endpoints{ + newObj: &discoveryv1.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ ResourceVersion: "2", }, - Subsets: []corev1.EndpointSubset{{ - Addresses: []corev1.EndpointAddress{{ - IP: "10.10.10.10", - }}, - }}, - }, - }, - { - name: "With same subsets with same len of addresses with different ip", - oldObj: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "1", - }, - Subsets: []corev1.EndpointSubset{{ - Addresses: []corev1.EndpointAddress{{ - IP: "10.10.10.10", - }}, - }}, - }, - newObj: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "2", - }, - Subsets: []corev1.EndpointSubset{{ - Addresses: []corev1.EndpointAddress{{ - IP: "10.10.10.42", - }}, + Ports: []discoveryv1.EndpointPort{{ + Port: &portB, }}, }, want: true, }, - { - name: "With same subsets with same len of addresses with same hostname", - oldObj: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "1", - }, - Subsets: []corev1.EndpointSubset{{ - Addresses: []corev1.EndpointAddress{{ - Hostname: "foo", - }}, - }}, - }, - newObj: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "2", - }, - Subsets: []corev1.EndpointSubset{{ - Addresses: []corev1.EndpointAddress{{ - Hostname: "foo", - }}, - }}, - }, - }, - { - name: "With same subsets with same len of addresses with same hostname", - oldObj: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "1", - }, - Subsets: []corev1.EndpointSubset{{ - Addresses: []corev1.EndpointAddress{{ - Hostname: "foo", - }}, - }}, - }, - newObj: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "2", - }, - Subsets: []corev1.EndpointSubset{{ - Addresses: []corev1.EndpointAddress{{ - Hostname: "bar", - }}, - }}, - }, - want: true, - }, - { - name: "With same subsets with same len of port with same name", - oldObj: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "1", - }, - Subsets: []corev1.EndpointSubset{{ - Ports: []corev1.EndpointPort{{ - Name: "foo", - }}, - }}, - }, - newObj: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "2", - }, - Subsets: []corev1.EndpointSubset{{ - Ports: []corev1.EndpointPort{{ - Name: "foo", - }}, - }}, - }, - }, - { - name: "With same subsets with same len of port with different name", - oldObj: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "1", - }, - Subsets: []corev1.EndpointSubset{{ - Ports: []corev1.EndpointPort{{ - Name: "foo", - }}, - }}, - }, - newObj: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "2", - }, - Subsets: []corev1.EndpointSubset{{ - Ports: []corev1.EndpointPort{{ - Name: "bar", - }}, - }}, - }, - want: true, - }, - { - name: "With same subsets with same len of port with same port", - oldObj: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "1", - }, - Subsets: []corev1.EndpointSubset{{ - Ports: []corev1.EndpointPort{{ - Port: 4242, - }}, - }}, - }, - newObj: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "2", - }, - Subsets: []corev1.EndpointSubset{{ - Ports: []corev1.EndpointPort{{ - Port: 4242, - }}, - }}, - }, - }, - { - name: "With same subsets with same len of port with different port", - oldObj: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "1", - }, - Subsets: []corev1.EndpointSubset{{ - Ports: []corev1.EndpointPort{{ - Port: 4242, - }}, - }}, - }, - newObj: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "2", - }, - Subsets: []corev1.EndpointSubset{{ - Ports: []corev1.EndpointPort{{ - Port: 6969, - }}, - }}, - }, - want: true, - }, - { - name: "With same subsets with same len of port with same protocol", - oldObj: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "1", - }, - Subsets: []corev1.EndpointSubset{{ - Ports: []corev1.EndpointPort{{ - Protocol: "HTTP", - }}, - }}, - }, - newObj: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "2", - }, - Subsets: []corev1.EndpointSubset{{ - Ports: []corev1.EndpointPort{{ - Protocol: "HTTP", - }}, - }}, - }, - }, - { - name: "With same subsets with same len of port with different protocol", - oldObj: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "1", - }, - Subsets: []corev1.EndpointSubset{{ - Ports: []corev1.EndpointPort{{ - Protocol: "HTTP", - }}, - }}, - }, - newObj: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "2", - }, - Subsets: []corev1.EndpointSubset{{ - Ports: []corev1.EndpointPort{{ - Protocol: "TCP", - }}, - }}, - }, - want: true, - }, - { - name: "With same subsets with same subset", - oldObj: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "1", - }, - Subsets: []corev1.EndpointSubset{{ - Addresses: []corev1.EndpointAddress{{ - IP: "10.10.10.10", - Hostname: "foo", - }}, - Ports: []corev1.EndpointPort{{ - Name: "bar", - Port: 4242, - Protocol: "HTTP", - }}, - }}, - }, - newObj: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "2", - }, - Subsets: []corev1.EndpointSubset{{ - Addresses: []corev1.EndpointAddress{{ - IP: "10.10.10.10", - Hostname: "foo", - }}, - Ports: []corev1.EndpointPort{{ - Name: "bar", - Port: 4242, - Protocol: "HTTP", - }}, - }}, - }, - }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { diff --git a/pkg/provider/kubernetes/k8s/parser.go b/pkg/provider/kubernetes/k8s/parser.go index 1f1a0d0e9..0c50ab61e 100644 --- a/pkg/provider/kubernetes/k8s/parser.go +++ b/pkg/provider/kubernetes/k8s/parser.go @@ -12,7 +12,7 @@ import ( // MustParseYaml parses a YAML to objects. func MustParseYaml(content []byte) []runtime.Object { - acceptedK8sTypes := regexp.MustCompile(`^(Namespace|Deployment|Endpoints|Node|Service|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|MiddlewareTCP|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport|ServersTransportTCP|GatewayClass|Gateway|HTTPRoute|TCPRoute|TLSRoute|ReferenceGrant)$`) + acceptedK8sTypes := regexp.MustCompile(`^(Namespace|Deployment|EndpointSlice|Node|Service|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|MiddlewareTCP|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport|ServersTransportTCP|GatewayClass|Gateway|HTTPRoute|TCPRoute|TLSRoute|ReferenceGrant)$`) files := strings.Split(string(content), "---\n") retVal := make([]runtime.Object, 0, len(files))