Merge branch 'master' of github.com:traefik/traefik
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
baalajimaestro 2024-06-25 06:38:43 +05:30
commit fd015c5a9b
Signed by: baalajimaestro
GPG key ID: F93C394FE9BBAFD5
101 changed files with 20922 additions and 13475 deletions

View file

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

View file

@ -0,0 +1,53 @@
---
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
...
```
#### Gateway API: KubernetesGateway Provider
In v3.1, the KubernetesGateway Provider is no longer an experimental feature.
It can be enabled without the associated `experimental.kubernetesgateway` option, which is now deprecated.
??? example "An example of the experimental `kubernetesgateway` option"
```yaml tab="File (YAML)"
experimental:
kubernetesgateway: true
```
```toml tab="File (TOML)"
[experimental]
kubernetesgateway=true
```
```bash tab="CLI"
--experimental.kubernetesgateway=true
```
##### Remediation
The `kubernetesgateway` option should be removed from the experimental section of the static configuration.
To configure `kubernetesgateway`, please check out the [KubernetesGateway Provider documentation](../providers/kubernetes-gateway.md).

View file

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

View file

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

View file

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

View file

@ -211,7 +211,7 @@ WriteTimeout is the maximum duration before timing out writes of the response. I
Timeout defines how long to wait on an idle session before releasing the related resources. (Default: ```3```)
`--experimental.kubernetesgateway`:
Allow the Kubernetes gateway api provider usage. (Default: ```false```)
(Deprecated) Allow the Kubernetes gateway api provider usage. (Default: ```false```)
`--experimental.localplugins.<name>`:
Local plugins configuration. (Default: ```false```)

View file

@ -211,7 +211,7 @@ WriteTimeout is the maximum duration before timing out writes of the response. I
Timeout defines how long to wait on an idle session before releasing the related resources. (Default: ```3```)
`TRAEFIK_EXPERIMENTAL_KUBERNETESGATEWAY`:
Allow the Kubernetes gateway api provider usage. (Default: ```false```)
(Deprecated) Allow the Kubernetes gateway api provider usage. (Default: ```false```)
`TRAEFIK_EXPERIMENTAL_LOCALPLUGINS_<NAME>`:
Local plugins configuration. (Default: ```false```)

View file

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

View file

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

42
go.mod
View file

@ -1,6 +1,6 @@
module github.com/traefik/traefik/v3
go 1.22
go 1.22.4
require (
github.com/BurntSushi/toml v1.4.0
@ -22,7 +22,7 @@ require (
github.com/golang/protobuf v1.5.4
github.com/google/go-github/v28 v28.1.1
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.5.0
github.com/gorilla/websocket v1.5.1
github.com/hashicorp/consul/api v1.26.1
github.com/hashicorp/go-hclog v1.6.3
github.com/hashicorp/go-multierror v1.1.1
@ -79,7 +79,7 @@ require (
go.opentelemetry.io/otel/sdk v1.27.0
go.opentelemetry.io/otel/sdk/metric v1.27.0
go.opentelemetry.io/otel/trace v1.27.0
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f
golang.org/x/mod v0.18.0
golang.org/x/net v0.26.0
golang.org/x/sys v0.21.0
@ -88,14 +88,14 @@ require (
golang.org/x/tools v0.22.0
google.golang.org/grpc v1.64.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.29.2
k8s.io/apiextensions-apiserver v0.28.3
k8s.io/apimachinery v0.29.2
k8s.io/client-go v0.29.2
k8s.io/utils v0.0.0-20230726121419-3b25d923346b
k8s.io/api v0.30.0
k8s.io/apiextensions-apiserver v0.30.0
k8s.io/apimachinery v0.30.0
k8s.io/client-go v0.30.0
k8s.io/utils v0.0.0-20240423183400-0849a56e8f22
mvdan.cc/xurls/v2 v2.5.0
sigs.k8s.io/controller-runtime v0.16.3
sigs.k8s.io/gateway-api v1.0.0
sigs.k8s.io/controller-runtime v0.18.0
sigs.k8s.io/gateway-api v1.1.0
)
require (
@ -165,9 +165,9 @@ require (
github.com/distribution/reference v0.5.0 // indirect
github.com/dnsimple/dnsimple-go v1.7.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/emicklei/go-restful/v3 v3.12.0 // indirect
github.com/evanphx/json-patch v5.7.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.7.0 // indirect
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
github.com/exoscale/egoscale v0.102.3 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
@ -178,11 +178,11 @@ require (
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-logr/zapr v1.2.4 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/jsonpointer v0.20.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-resty/resty/v2 v2.11.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
@ -316,9 +316,9 @@ require (
github.com/yandex-cloud/go-sdk v0.0.0-20240318084659-dfa50323a0b4 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
github.com/zeebo/errs v1.2.2 // indirect
go.etcd.io/etcd/api/v3 v3.5.9 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect
go.etcd.io/etcd/client/v3 v3.5.9 // indirect
go.etcd.io/etcd/api/v3 v3.5.10 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.10 // indirect
go.etcd.io/etcd/client/v3 v3.5.10 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
go.opentelemetry.io/contrib/propagators/aws v1.27.0 // indirect
@ -347,8 +347,8 @@ require (
gopkg.in/ns1/ns1-go.v2 v2.9.1 // indirect
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/klog/v2 v2.110.1 // indirect
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
k8s.io/klog/v2 v2.120.1 // indirect
k8s.io/kube-openapi v0.0.0-20240423202451-8948a665c108 // indirect
nhooyr.io/websocket v1.8.7 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect

96
go.sum
View file

@ -151,7 +151,6 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 h1:M/1u4HBpwLuMtjlxuI2y6HoVLzF
github.com/aws/aws-sdk-go-v2/service/sts v1.28.12/go.mod h1:kcfd+eTdEi/40FIbLq4Hif3XMXnl5b/+t/KTfLt9xIk=
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@ -282,8 +281,8 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o=
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk=
github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@ -295,8 +294,8 @@ github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI=
github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc=
github.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
github.com/exoscale/egoscale v0.102.3 h1:DYqN2ipoLKpiFoprRGQkp2av/Ze7sUYYlGhi1N62tfY=
github.com/exoscale/egoscale v0.102.3/go.mod h1:RPf2Gah6up+6kAEayHTQwqapzXlm93f0VQas/UEGU5c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@ -349,26 +348,22 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG
github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo=
github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA=
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ=
github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
@ -519,8 +514,8 @@ github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf h1:C1GPyPJrOlJlIrcaBBiBpDsqZena2Ks8spa5xZqr1XQ=
github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf/go.mod h1:zXqxTI6jXDdKnlf8s+nT+3c8LrwUEy3yNpO4XJL90lA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
@ -874,8 +869,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo=
github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0=
github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk=
github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
@ -1168,12 +1163,12 @@ github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtC
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.etcd.io/etcd/api/v3 v3.5.9 h1:4wSsluwyTbGGmyjJktOf3wFQoTBIURXHnq9n/G/JQHs=
go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k=
go.etcd.io/etcd/client/pkg/v3 v3.5.9 h1:oidDC4+YEuSIQbsR94rY9gur91UPL6DnxDCIYd2IGsE=
go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4=
go.etcd.io/etcd/client/v3 v3.5.9 h1:r5xghnU7CwbUxD/fbUtRyJGaYNfDun8sp/gTr1hew6E=
go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA=
go.etcd.io/etcd/api/v3 v3.5.10 h1:szRajuUUbLyppkhs9K6BRtjY37l66XQQmw7oZRANE4k=
go.etcd.io/etcd/api/v3 v3.5.10/go.mod h1:TidfmT4Uycad3NM/o25fG3J07odo4GBB9hoxaodFCtI=
go.etcd.io/etcd/client/pkg/v3 v3.5.10 h1:kfYIdQftBnbAq8pUWFXfpuuxFSKzlmM5cSn76JByiT0=
go.etcd.io/etcd/client/pkg/v3 v3.5.10/go.mod h1:DYivfIviIuQ8+/lCq4vcxuseg2P2XbHygkKwFo9fc8U=
go.etcd.io/etcd/client/v3 v3.5.10 h1:W9TXNZ+oB3MCd/8UjxHTWK5J9Nquw9fQBLJd5ne5/Ao=
go.etcd.io/etcd/client/v3 v3.5.10/go.mod h1:RVeBnDz2PUEZqTpgqwAtUd8nAPf5kjyFyND7P1VkOKc=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@ -1221,18 +1216,15 @@ go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naR
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/ratelimit v0.3.0 h1:IdZd9wqvFXnvLvSEBo0KPcGfkoBGNkpTHlrE3Rcjkjw=
@ -1240,7 +1232,6 @@ go.uber.org/ratelimit v0.3.0/go.mod h1:So5LG7CV1zWpY1sHe+DXTJqQvOx+FFPFaAs2SnoyB
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
@ -1280,8 +1271,8 @@ golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 h1:985EYyeCOxTpcgOTJpflJUwOeEz0CQOdPt73OzpE9F8=
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@ -1507,7 +1498,6 @@ golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4X
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
@ -1517,8 +1507,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM=
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
@ -1650,20 +1638,20 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
k8s.io/api v0.29.2 h1:hBC7B9+MU+ptchxEqTNW2DkUosJpp1P+Wn6YncZ474A=
k8s.io/api v0.29.2/go.mod h1:sdIaaKuU7P44aoyyLlikSLayT6Vb7bvJNCX105xZXY0=
k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08=
k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc=
k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8=
k8s.io/apimachinery v0.29.2/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU=
k8s.io/client-go v0.29.2 h1:FEg85el1TeZp+/vYJM7hkDlSTFZ+c5nnK44DJ4FyoRg=
k8s.io/client-go v0.29.2/go.mod h1:knlvFZE58VpqbQpJNbCbctTVXcd35mMyAAwBdpt4jrA=
k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0=
k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA=
k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE=
k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs=
k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y=
k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA=
k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ=
k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY=
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240423202451-8948a665c108 h1:Q8Z7VlGhcJgBHJHYugJ/K/7iB8a2eSxCyxdVjJp+lLY=
k8s.io/kube-openapi v0.0.0-20240423202451-8948a665c108/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
k8s.io/utils v0.0.0-20240423183400-0849a56e8f22 h1:ao5hUqGhsqdm+bYbjH/pRkCs0unBGe9UyDahzs9zQzQ=
k8s.io/utils v0.0.0-20240423183400-0849a56e8f22/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8=
mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE=
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
@ -1671,10 +1659,10 @@ nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4=
sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0=
sigs.k8s.io/gateway-api v1.0.0 h1:iPTStSv41+d9p0xFydll6d7f7MOBGuqXM6p2/zVYMAs=
sigs.k8s.io/gateway-api v1.0.0/go.mod h1:4cUgr0Lnp5FZ0Cdq8FdRwCvpiWws7LVhLHGIudLlf4c=
sigs.k8s.io/controller-runtime v0.18.0 h1:Z7jKuX784TQSUL1TIyeuF7j8KXZ4RtSX0YgtjKcSTME=
sigs.k8s.io/controller-runtime v0.18.0/go.mod h1:tuAt1+wbVsXIT8lPtk5RURxqAnq7xkpv2Mhttslg7Hw=
sigs.k8s.io/gateway-api v1.1.0 h1:DsLDXCi6jR+Xz8/xd0Z1PYl2Pn0TyaFMOPPZIj4inDM=
sigs.k8s.io/gateway-api v1.1.0/go.mod h1:ZH4lHrL2sDi0FHZ9jjneb8kKnGzFWyrTya35sWUTrRs=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

View file

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"slices"
@ -18,6 +19,7 @@ import (
"github.com/traefik/traefik/v3/integration/try"
"github.com/traefik/traefik/v3/pkg/version"
"gopkg.in/yaml.v3"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/util/sets"
kclientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
@ -27,10 +29,12 @@ import (
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
conformanceV1alpha1 "sigs.k8s.io/gateway-api/conformance/apis/v1alpha1"
"sigs.k8s.io/gateway-api/conformance"
v1 "sigs.k8s.io/gateway-api/conformance/apis/v1"
"sigs.k8s.io/gateway-api/conformance/tests"
"sigs.k8s.io/gateway-api/conformance/utils/config"
ksuite "sigs.k8s.io/gateway-api/conformance/utils/suite"
"sigs.k8s.io/gateway-api/pkg/features"
)
const (
@ -84,7 +88,7 @@ func (s *K8sConformanceSuite) SetupSuite() {
s.k3sContainer, err = k3s.RunContainer(ctx,
testcontainers.WithImage(k3sImage),
k3s.WithManifest("./fixtures/k8s-conformance/00-experimental-v1.0.0.yml"),
k3s.WithManifest("./fixtures/k8s-conformance/00-experimental-v1.1.0.yml"),
k3s.WithManifest("./fixtures/k8s-conformance/01-rbac.yml"),
k3s.WithManifest("./fixtures/k8s-conformance/02-traefik.yml"),
network.WithNetwork(nil, s.network),
@ -122,15 +126,19 @@ func (s *K8sConformanceSuite) SetupSuite() {
s.T().Fatalf("Error initializing Kubernetes REST client: %v", err)
}
if err = gatev1alpha2.AddToScheme(s.kubeClient.Scheme()); err != nil {
if err = gatev1alpha2.Install(s.kubeClient.Scheme()); err != nil {
s.T().Fatal(err)
}
if err = gatev1beta1.AddToScheme(s.kubeClient.Scheme()); err != nil {
if err = gatev1beta1.Install(s.kubeClient.Scheme()); err != nil {
s.T().Fatal(err)
}
if err = gatev1.AddToScheme(s.kubeClient.Scheme()); err != nil {
if err = gatev1.Install(s.kubeClient.Scheme()); err != nil {
s.T().Fatal(err)
}
if err = apiextensionsv1.AddToScheme(s.kubeClient.Scheme()); err != nil {
s.T().Fatal(err)
}
}
@ -169,81 +177,54 @@ func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() {
err = try.GetRequest("http://"+k3sContainerIP+":9000/api/entrypoints", 10*time.Second, try.BodyContains(`"name":"web"`))
require.NoError(s.T(), err)
opts := ksuite.Options{
cSuite, err := ksuite.NewConformanceTestSuite(ksuite.ConformanceOptions{
Client: s.kubeClient,
Clientset: s.clientSet,
GatewayClassName: "traefik",
Debug: true,
CleanupBaseResources: true,
TimeoutConfig: config.TimeoutConfig{
CreateTimeout: 5 * time.Second,
DeleteTimeout: 5 * time.Second,
GetTimeout: 5 * time.Second,
GatewayMustHaveAddress: 5 * time.Second,
GatewayMustHaveCondition: 5 * time.Second,
GatewayStatusMustHaveListeners: 10 * time.Second,
GatewayListenersMustHaveCondition: 5 * time.Second,
GWCMustBeAccepted: 60 * time.Second, // Pod creation in k3s cluster can be long.
HTTPRouteMustNotHaveParents: 5 * time.Second,
HTTPRouteMustHaveCondition: 5 * time.Second,
TLSRouteMustHaveCondition: 5 * time.Second,
RouteMustHaveParents: 5 * time.Second,
ManifestFetchTimeout: 5 * time.Second,
MaxTimeToConsistency: 5 * time.Second,
NamespacesMustBeReady: 60 * time.Second, // Pod creation in k3s cluster can be long.
RequestTimeout: 5 * time.Second,
LatestObservedGenerationSet: 5 * time.Second,
RequiredConsecutiveSuccesses: 0,
},
SupportedFeatures: sets.New(ksuite.SupportGateway,
ksuite.SupportGatewayPort8080,
ksuite.SupportHTTPRoute,
ksuite.SupportHTTPRouteQueryParamMatching,
ksuite.SupportHTTPRouteMethodMatching,
ksuite.SupportHTTPRoutePortRedirect,
ksuite.SupportHTTPRouteSchemeRedirect,
ksuite.SupportHTTPRouteHostRewrite,
ksuite.SupportHTTPRoutePathRewrite,
ksuite.SupportHTTPRoutePathRedirect,
),
ExemptFeatures: sets.New(
ksuite.SupportHTTPRouteRequestTimeout,
ksuite.SupportHTTPRouteBackendTimeout,
ksuite.SupportHTTPRouteResponseHeaderModification,
ksuite.SupportHTTPRouteRequestMirror,
ksuite.SupportHTTPRouteRequestMultipleMirrors,
),
TimeoutConfig: config.DefaultTimeoutConfig(),
ManifestFS: []fs.FS{&conformance.Manifests},
EnableAllSupportedFeatures: false,
RunTest: *k8sConformanceRunTest,
// Until the feature are all supported, following tests are skipped.
SkipTests: []string{
tests.HTTPRouteMethodMatching.ShortName,
tests.HTTPRouteQueryParamMatching.ShortName,
},
}
cSuite, err := ksuite.NewExperimentalConformanceTestSuite(ksuite.ExperimentalConformanceOptions{
Options: opts,
Implementation: conformanceV1alpha1.Implementation{
Implementation: v1.Implementation{
Organization: "traefik",
Project: "traefik",
URL: "https://traefik.io/",
Version: version.Version,
Contact: []string{"@traefik/maintainers"},
},
ConformanceProfiles: sets.New(ksuite.HTTPConformanceProfileName),
ConformanceProfiles: sets.New(ksuite.GatewayHTTPConformanceProfileName),
SupportedFeatures: sets.New(
features.SupportGateway,
features.SupportGatewayPort8080,
features.SupportHTTPRoute,
features.SupportHTTPRouteQueryParamMatching,
features.SupportHTTPRouteMethodMatching,
features.SupportHTTPRoutePortRedirect,
features.SupportHTTPRouteSchemeRedirect,
features.SupportHTTPRouteHostRewrite,
features.SupportHTTPRoutePathRewrite,
features.SupportHTTPRoutePathRedirect,
),
ExemptFeatures: sets.New(
features.SupportHTTPRouteRequestTimeout,
features.SupportHTTPRouteBackendTimeout,
features.SupportHTTPRouteResponseHeaderModification,
features.SupportHTTPRouteRequestMirror,
features.SupportHTTPRouteRequestMultipleMirrors,
),
})
require.NoError(s.T(), err)
cSuite.Setup(s.T())
cSuite.Setup(s.T(), tests.ConformanceTests)
err = cSuite.Run(s.T(), tests.ConformanceTests)
require.NoError(s.T(), err)
report, err := cSuite.Report()
require.NoError(s.T(), err, "failed generating conformance report")
report.GatewayAPIVersion = "1.0.0"
rawReport, err := yaml.Marshal(report)
require.NoError(s.T(), err)
s.T().Logf("Conformance report:\n%s", string(rawReport))

View file

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

View file

@ -459,6 +459,7 @@ func (h *http) deprecationNotice(logger zerolog.Logger) bool {
type experimental struct {
HTTP3 *bool `json:"http3,omitempty" toml:"http3,omitempty" yaml:"http3,omitempty"`
KubernetesGateway *bool `json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty"`
}
func (e *experimental) deprecationNotice(logger zerolog.Logger) bool {
@ -469,11 +470,17 @@ func (e *experimental) deprecationNotice(logger zerolog.Logger) bool {
if e.HTTP3 != nil {
logger.Error().Msg("HTTP3 is not an experimental feature in v3 and the associated enablement has been removed." +
"Please remove its usage from the static configuration for Traefik to start." +
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#http3-experimental-configuration")
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3-details/#http3")
return true
}
if e.KubernetesGateway != nil {
logger.Error().Msg("KubernetesGateway provider is not an experimental feature starting with v3.1." +
"Please remove its usage from the static configuration." +
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v3/#gateway-api-kubernetesgateway-provider")
}
return false
}

View file

@ -19,6 +19,7 @@ func TestDeprecationNotice(t *testing.T) {
tests := []struct {
desc string
config configuration
wantCompatible bool
}{
{
desc: "Docker provider swarmMode option is incompatible",
@ -196,6 +197,15 @@ func TestDeprecationNotice(t *testing.T) {
},
},
},
{
desc: "Experimental KubernetesGateway enablement configuration is compatible",
config: configuration{
Experimental: &experimental{
KubernetesGateway: ptr(true),
},
},
wantCompatible: true,
},
{
desc: "Tracing SpanNameLimit option is incompatible",
config: configuration{
@ -278,7 +288,8 @@ func TestDeprecationNotice(t *testing.T) {
})
logger := log.With().Logger().Hook(testHook)
assert.True(t, test.config.deprecationNotice(logger))
assert.Equal(t, !test.wantCompatible, test.config.deprecationNotice(logger))
assert.True(t, gotLog)
assert.Equal(t, zerolog.ErrorLevel, gotLevel)
})

View file

@ -7,5 +7,6 @@ type Experimental struct {
Plugins map[string]plugins.Descriptor `description:"Plugins configuration." json:"plugins,omitempty" toml:"plugins,omitempty" yaml:"plugins,omitempty" export:"true"`
LocalPlugins map[string]plugins.LocalDescriptor `description:"Local plugins configuration." json:"localPlugins,omitempty" toml:"localPlugins,omitempty" yaml:"localPlugins,omitempty" export:"true"`
KubernetesGateway bool `description:"Allow the Kubernetes gateway api provider usage." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" export:"true"`
// Deprecated: KubernetesGateway provider is not an experimental feature starting with v3.1. Please remove its usage from the static configuration.
KubernetesGateway bool `description:"(Deprecated) Allow the Kubernetes gateway api provider usage." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" export:"true"`
}

View file

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

View file

@ -13,19 +13,24 @@ spec:
task: whoami
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami
name: whoami-abc
namespace: default
labels:
kubernetes.io/service-name: whoami
subsets:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
addressType: IPv4
ports:
- name: web
port: 80
endpoints:
- addresses:
- 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:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses:
- 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:
- addresses:
- ip: 10.10.0.5
- ip: 10.10.0.6
ports:
addressType: IPv4
ports:
- name: websecure
port: 8443
endpoints:
- addresses:
- 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:
- addresses:
- ip: 10.10.0.7
- ip: 10.10.0.8
ports:
addressType: IPv4
ports:
- name: websecure2
port: 8443
endpoints:
- addresses:
- 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:
- addresses:
- ip: "2001:db8:85a3:8d3:1319:8a2e:370:7348"
ports:
addressType: IPv6
ports:
- name: web
port: 8080
endpoints:
- addresses:
- "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:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
addressType: IPv4
ports:
- name: web
port: 80
endpoints:
- addresses:
- 10.10.0.1
- 10.10.0.2
conditions:
ready: true
---
apiVersion: v1
kind: Service
metadata:
name: whoami-without-endpoints-subsets
name: whoami-without-endpointslice-endpoints
namespace: default
spec:
@ -247,11 +276,16 @@ spec:
task: whoami
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami-without-endpoints-subsets
name: whoami-without-endpointslice-endpoints-abc
namespace: default
labels:
kubernetes.io/service-name: whoami-without-endpointslice-endpoints
addressType: IPv4
endpoints: []
---
apiVersion: v1

View file

@ -13,19 +13,24 @@ spec:
task: whoamitcp
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoamitcp
name: whoamitcp-abc
namespace: default
labels:
kubernetes.io/service-name: whoamitcp
subsets:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
addressType: IPv4
ports:
- name: myapp
port: 8000
endpoints:
- addresses:
- 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:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
addressType: IPv4
ports:
- name: myapp2
port: 8080
endpoints:
- addresses:
- 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:
- addresses:
- ip: 10.10.0.5
- ip: 10.10.0.6
ports:
addressType: IPv4
ports:
- name: websecure
port: 443
endpoints:
- addresses:
- 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:
- addresses:
- ip: 10.10.0.7
- ip: 10.10.0.8
ports:
addressType: IPv4
ports:
- name: myapp3
port: 8083
endpoints:
- addresses:
- 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:
- addresses:
- ip: 10.10.0.9
- ip: 10.10.0.10
ports:
addressType: IPv4
ports:
- name: myapp4
port: 8084
endpoints:
- addresses:
- 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:
- addresses:
- ip: "fd00:10:244:0:1::3"
- ip: "2001:db8:85a3:8d3:1319:8a2e:370:7348"
ports:
addressType: IPv6
ports:
- name: myapp-ipv6
port: 8080
endpoints:
- addresses:
- "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:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
addressType: IPv4
ports:
- name: myapp
port: 8000
endpoints:
- addresses:
- 10.10.0.1
- 10.10.0.2
conditions:
ready: true
---
apiVersion: v1
kind: Service
metadata:
name: whoamitcp-without-endpoints-subsets
name: whoamitcp-without-endpointslice-endpoints
namespace: default
spec:
@ -243,11 +278,16 @@ spec:
task: whoamitcp
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoamitcp-without-endpoints-subsets
name: whoamitcp-without-endpointslice-endpoints-abc
namespace: default
labels:
kubernetes.io/service-name: whoamitcp-without-endpointslice-endpoints
addressType: IPv4
endpoints: []
---
apiVersion: v1

View file

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

View file

@ -13,19 +13,24 @@ spec:
task: whoamiudp
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoamiudp
name: whoamiudp-abc
namespace: default
labels:
kubernetes.io/service-name: whoamiudp
subsets:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
addressType: IPv4
ports:
- name: myapp
port: 8000
endpoints:
- addresses:
- 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:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
addressType: IPv4
ports:
- name: myapp2
port: 8080
endpoints:
- addresses:
- 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:
- addresses:
- ip: 10.10.0.7
- ip: 10.10.0.8
ports:
addressType: IPv4
ports:
- name: myapp3
port: 8083
endpoints:
- addresses:
- 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:
- addresses:
- ip: 10.10.0.9
- ip: 10.10.0.10
ports:
addressType: IPv4
ports:
- name: myapp4
port: 8084
endpoints:
- addresses:
- 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:
- addresses:
- ip: "fd00:10:244:0:1::3"
ports:
addressType: IPv6
ports:
- name: myapp-ipv6
port: 8080
endpoints:
- addresses:
- "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:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
addressType: IPv4
ports:
- name: myapp
port: 8000
endpoints:
- addresses:
- 10.10.0.1
- 10.10.0.2
conditions:
ready: true
---
apiVersion: v1
kind: Service
metadata:
name: whoamiudp-without-endpoints-subsets
name: whoamiudp-without-endpointslice-endpoints
namespace: default
spec:
@ -202,11 +232,16 @@ spec:
task: whoamiudp
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoamiudp-without-endpoints-subsets
name: whoamiudp-without-endpointslice-endpoints-abc
namespace: default
labels:
kubernetes.io/service-name: whoamiudp-without-endpointslice-endpoints
addressType: IPv4
endpoints: []
---
apiVersion: v1

View file

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

View file

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

View file

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

View file

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

View file

@ -1,17 +1,22 @@
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami4
name: whoami4-abc
namespace: default
labels:
kubernetes.io/service-name: whoami4
subsets:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses:
- 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:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses:
- 10.10.0.3
- 10.10.0.4
conditions:
ready: true
---
apiVersion: v1

View file

@ -1,17 +1,22 @@
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami4
name: whoami4-abc
namespace: default
labels:
kubernetes.io/service-name: whoami4
subsets:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses:
- 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:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses:
- 10.10.0.3
- 10.10.0.4
conditions:
ready: true
---
apiVersion: v1

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,50 @@
---
kind: GatewayClass
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: my-gateway-class
spec:
controllerName: traefik.io/gateway-controller
---
kind: Gateway
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: my-gateway
namespace: default
spec:
gatewayClassName: my-gateway-class
listeners: # Use GatewayClass defaults for listener definition.
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Same
---
kind: HTTPRoute
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: http-app-1
namespace: default
spec:
parentRefs:
- name: my-gateway
kind: Gateway
group: gateway.networking.k8s.io
hostnames:
- "foo.com"
rules:
- matches:
- method: GET
path:
type: PathPrefix
value: /foo
backendRefs:
- name: whoami
port: 80
weight: 1
kind: Service
group: ""

View file

@ -0,0 +1,56 @@
---
kind: GatewayClass
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: my-gateway-class
spec:
controllerName: traefik.io/gateway-controller
---
kind: Gateway
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: my-gateway
namespace: default
spec:
gatewayClassName: my-gateway-class
listeners: # Use GatewayClass defaults for listener definition.
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Same
---
kind: HTTPRoute
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: http-app-1
namespace: default
spec:
parentRefs:
- name: my-gateway
kind: Gateway
group: gateway.networking.k8s.io
hostnames:
- "foo.com"
rules:
- matches:
- queryParams:
- type: Exact
name: foo
value: bar
- type: RegularExpression
name: baz
value: buz
path:
type: PathPrefix
value: /foo
backendRefs:
- name: whoami
port: 80
weight: 1
kind: Service
group: ""

View file

@ -17,21 +17,26 @@ spec:
task: whoami
---
kind: Endpoints
apiVersion: v1
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: whoami
name: whoami-abc
namespace: default
labels:
kubernetes.io/service-name: whoami
subsets:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
addressType: IPv4
ports:
- name: web
port: 80
- name: web2
port: 8000
port: 8080
endpoints:
- addresses:
- 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:
- addresses:
- ip: 10.10.0.11
- ip: 10.10.0.12
ports:
addressType: IPv4
ports:
- name: web
port: 80
- name: web2
port: 8000
endpoints:
- addresses:
- 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:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
addressType: IPv4
ports:
- name: web
port: 8080
endpoints:
- addresses:
- 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:
- addresses:
- ip: 10.10.0.5
- ip: 10.10.0.6
ports:
addressType: IPv4
ports:
- name: websecure
port: 8443
endpoints:
- addresses:
- 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:
- addresses:
- ip: 10.10.0.7
- ip: 10.10.0.8
ports:
addressType: IPv4
ports:
- name: websecure2
port: 8443
endpoints:
- addresses:
- 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:
- addresses:
- ip: 10.10.0.9
- ip: 10.10.0.10
ports:
addressType: IPv4
ports:
- name: tcp-1
protocol: TCP
port: 9000
- name: tcp-2
protocol: TCP
port: 10000
endpoints:
- addresses:
- 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:
- addresses:
- ip: 10.10.0.13
- ip: 10.10.0.14
ports:
addressType: IPv4
ports:
- name: tcp-1
protocol: TCP
port: 9000
- name: tcp-2
protocol: TCP
port: 10000
endpoints:
- addresses:
- 10.10.0.13
- 10.10.0.14
conditions:
ready: true
---
apiVersion: v1

View file

@ -370,6 +370,10 @@ func (p *Provider) loadHTTPRouteFilterExtensionRef(namespace string, extensionRe
}
func (p *Provider) loadHTTPServers(namespace string, backendRef gatev1.HTTPBackendRef) (*dynamic.ServersLoadBalancer, error) {
if backendRef.Port == nil {
return nil, errors.New("port is required for Kubernetes Service reference")
}
service, exists, err := p.client.GetService(namespace, string(backendRef.Name))
if err != nil {
return nil, fmt.Errorf("getting service: %w", err)
@ -378,58 +382,60 @@ 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()
protocol := getProtocol(*svcPort)
addresses := map[string]struct{}{}
for _, endpointSlice := range endpointSlices {
var port int32
var portStr string
for _, subset := range endpoints.Subsets {
for _, p := range subset.Ports {
if portSpec.Name == p.Name {
port = p.Port
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 {
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(addr.IP, portStr)),
URL: fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, strconv.Itoa(int(port)))),
})
}
}
}
return lb, nil
}
@ -469,11 +475,11 @@ func buildHostRule(hostnames []gatev1.Hostname) (string, int) {
// The current priority computing is rather naive but aims to fulfill Conformance tests suite requirement.
// The priority is computed to match the following precedence order:
//
// * "Exact" path match. (+100000)
// * "Prefix" path match with largest number of characters. (+10000) PathRegex (+1000)
// * Method match. (not implemented)
// * Largest number of header matches. (+100 each) or with PathRegex (+10 each)
// * Largest number of query param matches. (not implemented)
// * "Exact" path match (+100000).
// * "Prefix" path match with largest number of characters (+10000 + nb_characters*100).
// * Method match (+1000).
// * Largest number of header matches (+100 each).
// * Largest number of query param matches (+10 each).
//
// In case of multiple matches for a route, the maximum priority among all matches is retain.
func buildMatchRule(hostnames []gatev1.Hostname, match gatev1.HTTPRouteMatch) (string, int) {
@ -489,10 +495,19 @@ func buildMatchRule(hostnames []gatev1.Hostname, match gatev1.HTTPRouteMatch) (s
matchRules = append(matchRules, pathRule)
priority += pathPriority
if match.Method != nil {
matchRules = append(matchRules, fmt.Sprintf("Method(`%s`)", *match.Method))
priority += 1000
}
headerRules, headersPriority := buildHeaderRules(match.Headers)
matchRules = append(matchRules, headerRules...)
priority += headersPriority
queryParamRules, queryParamsPriority := buildQueryParamRules(match.QueryParams)
matchRules = append(matchRules, queryParamRules...)
priority += queryParamsPriority
matchRulesStr := strings.Join(matchRules, " && ")
hostRule, hostPriority := buildHostRule(hostnames)
@ -525,7 +540,7 @@ func buildPathRule(pathMatch gatev1.HTTPPathMatch) (string, int) {
return fmt.Sprintf("(Path(`%[1]s`) || PathPrefix(`%[1]s/`))", pv), 10000 + len(pathValue)*100
case gatev1.PathMatchRegularExpression:
return fmt.Sprintf("PathRegexp(`%s`)", pathValue), 1000 + len(pathValue)*100
return fmt.Sprintf("PathRegexp(`%s`)", pathValue), 10000 + len(pathValue)*100
default:
return "PathPrefix(`/`)", 1
@ -533,18 +548,38 @@ func buildPathRule(pathMatch gatev1.HTTPPathMatch) (string, int) {
}
func buildHeaderRules(headers []gatev1.HTTPHeaderMatch) ([]string, int) {
var rules []string
var priority int
var (
rules []string
priority int
)
for _, header := range headers {
typ := ptr.Deref(header.Type, gatev1.HeaderMatchExact)
switch typ {
case gatev1.HeaderMatchExact:
rules = append(rules, fmt.Sprintf("Header(`%s`,`%s`)", header.Name, header.Value))
priority += 100
case gatev1.HeaderMatchRegularExpression:
rules = append(rules, fmt.Sprintf("HeaderRegexp(`%s`,`%s`)", header.Name, header.Value))
priority += 10
}
priority += 100
}
return rules, priority
}
func buildQueryParamRules(queryParams []gatev1.HTTPQueryParamMatch) ([]string, int) {
var (
rules []string
priority int
)
for _, qp := range queryParams {
typ := ptr.Deref(qp.Type, gatev1.QueryParamMatchExact)
switch typ {
case gatev1.QueryParamMatchExact:
rules = append(rules, fmt.Sprintf("Query(`%s`,`%s`)", qp.Name, qp.Value))
case gatev1.QueryParamMatchRegularExpression:
rules = append(rules, fmt.Sprintf("QueryRegexp(`%s`,`%s`)", qp.Name, qp.Value))
}
priority += 10
}
return rules, priority

View file

@ -1288,7 +1288,7 @@ func TestLoadHTTPRoutes(t *testing.T) {
"default-http-app-1-my-gateway-web-2-d23f7039bc8036fb918c": {
EntryPoints: []string{"web"},
Rule: "Host(`foo.com`) && PathRegexp(`^/buzz/[0-9]+$`)",
Priority: 2408,
Priority: 11408,
RuleSyntax: "v3",
Service: "default-http-app-1-my-gateway-web-2-wrr",
},
@ -1354,6 +1354,128 @@ func TestLoadHTTPRoutes(t *testing.T) {
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Simple HTTPRoute, with method matching",
paths: []string{"services.yml", "httproute/with_method_matching.yml"},
entryPoints: map[string]Entrypoint{"web": {
Address: ":80",
}},
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-http-app-1-my-gateway-web-0-74ad70a7cf090becdd3c": {
EntryPoints: []string{"web"},
Rule: "Host(`foo.com`) && (Path(`/foo`) || PathPrefix(`/foo/`)) && Method(`GET`)",
Priority: 11408,
RuleSyntax: "v3",
Service: "default-http-app-1-my-gateway-web-0-wrr",
},
},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
"default-http-app-1-my-gateway-web-0-wrr": {
Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{
{
Name: "default-whoami-80",
Weight: ptr.To(1),
},
},
},
},
"default-whoami-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:80",
},
{
URL: "http://10.10.0.2:80",
},
},
PassHostHeader: ptr.To(true),
ResponseForwarding: &dynamic.ResponseForwarding{
FlushInterval: ptypes.Duration(100 * time.Millisecond),
},
},
},
},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Simple HTTPRoute, with query param matching",
paths: []string{"services.yml", "httproute/with_query_param_matching.yml"},
entryPoints: map[string]Entrypoint{"web": {
Address: ":80",
}},
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-http-app-1-my-gateway-web-0-bb7b03c9610e982fd627": {
EntryPoints: []string{"web"},
Rule: "Host(`foo.com`) && (Path(`/foo`) || PathPrefix(`/foo/`)) && Query(`foo`,`bar`) && QueryRegexp(`baz`,`buz`)",
Priority: 10428,
RuleSyntax: "v3",
Service: "default-http-app-1-my-gateway-web-0-wrr",
},
},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
"default-http-app-1-my-gateway-web-0-wrr": {
Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{
{
Name: "default-whoami-80",
Weight: ptr.To(1),
},
},
},
},
"default-whoami-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:80",
},
{
URL: "http://10.10.0.2:80",
},
},
PassHostHeader: ptr.To(true),
ResponseForwarding: &dynamic.ResponseForwarding{
FlushInterval: ptypes.Duration(100 * time.Millisecond),
},
},
},
},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "HTTPRoute with Same namespace selector",
paths: []string{"services.yml", "httproute/with_namespace_same.yml"},

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -651,38 +651,43 @@ 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
}
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, hostPort),
URL: fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, strconv.Itoa(int(port)))),
})
}
}
}
return svc, nil
}

View file

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

View file

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

Some files were not shown because too many files have changed in this diff Show more