Toggle support for Gateway API experimental channel

This commit is contained in:
Manuel Zapf 2024-04-02 17:32:04 +02:00 committed by GitHub
parent fc897f6756
commit c84b510f0d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 140 additions and 44 deletions

View file

@ -92,6 +92,34 @@ Docker provider `tls.CAOptional` option has been removed in v3, as TLS client au
The `tls.caOptional` option should be removed from the Docker provider static configuration. The `tls.caOptional` option should be removed from the Docker provider static configuration.
### Kubernetes Gateway API
#### Experimental Channel Resources (TLSRoute and TCPRoute)
In v3, the Kubernetes Gateway API provider does not enable support for the experimental channel API resources by default.
##### Remediation
The `experimentalChannel` option should be used to enable the support for the experimental channel API resources.
??? example "An example usage of the Kubernetes Gateway API provider with experimental channel support enabled"
```yaml tab="File (YAML)"
providers:
kubernetesGateway:
experimentalChannel: true
```
```toml tab="File (TOML)"
[providers.kubernetesGateway]
experimentalChannel = true
# ...
```
```bash tab="CLI"
--providers.kubernetesgateway.experimentalchannel=true
```
### Experimental Configuration ### Experimental Configuration
#### HTTP3 #### HTTP3

View file

@ -212,6 +212,29 @@ providers:
--providers.kubernetesgateway.namespaces=default,production --providers.kubernetesgateway.namespaces=default,production
``` ```
### `experimentalChannel`
_Optional, Default: false_
Toggles support for the Experimental Channel resources ([Gateway API release channels documentation](https://gateway-api.sigs.k8s.io/concepts/versioning/#release-channels)).
This option currently enables support for `TCPRoute` and `TLSRoute`.
```yaml tab="File (YAML)"
providers:
kubernetesGateway:
experimentalChannel: true
```
```toml tab="File (TOML)"
[providers.kubernetesGateway]
experimentalChannel = true
# ...
```
```bash tab="CLI"
--providers.kubernetesgateway.experimentalchannel=true
```
### `labelselector` ### `labelselector`
_Optional, Default: ""_ _Optional, Default: ""_

View file

@ -729,6 +729,9 @@ Kubernetes certificate authority file path (not needed for in-cluster client).
`--providers.kubernetesgateway.endpoint`: `--providers.kubernetesgateway.endpoint`:
Kubernetes server endpoint (required for external cluster client). Kubernetes server endpoint (required for external cluster client).
`--providers.kubernetesgateway.experimentalchannel`:
Toggles Experimental Channel resources support (TCPRoute, TLSRoute...). (Default: ```false```)
`--providers.kubernetesgateway.labelselector`: `--providers.kubernetesgateway.labelselector`:
Kubernetes label selector to select specific GatewayClasses. Kubernetes label selector to select specific GatewayClasses.

View file

@ -729,6 +729,9 @@ Kubernetes certificate authority file path (not needed for in-cluster client).
`TRAEFIK_PROVIDERS_KUBERNETESGATEWAY_ENDPOINT`: `TRAEFIK_PROVIDERS_KUBERNETESGATEWAY_ENDPOINT`:
Kubernetes server endpoint (required for external cluster client). Kubernetes server endpoint (required for external cluster client).
`TRAEFIK_PROVIDERS_KUBERNETESGATEWAY_EXPERIMENTALCHANNEL`:
Toggles Experimental Channel resources support (TCPRoute, TLSRoute...). (Default: ```false```)
`TRAEFIK_PROVIDERS_KUBERNETESGATEWAY_LABELSELECTOR`: `TRAEFIK_PROVIDERS_KUBERNETESGATEWAY_LABELSELECTOR`:
Kubernetes label selector to select specific GatewayClasses. Kubernetes label selector to select specific GatewayClasses.

View file

@ -146,6 +146,7 @@
namespaces = ["foobar", "foobar"] namespaces = ["foobar", "foobar"]
labelSelector = "foobar" labelSelector = "foobar"
throttleDuration = "42s" throttleDuration = "42s"
experimentalChannel = true
[providers.rest] [providers.rest]
insecure = true insecure = true
[providers.consulCatalog] [providers.consulCatalog]

View file

@ -163,6 +163,7 @@ providers:
- foobar - foobar
labelSelector: foobar labelSelector: foobar
throttleDuration: 42s throttleDuration: 42s
experimentalChannel: true
rest: rest:
insecure: true insecure: true
consulCatalog: consulCatalog:

View file

@ -27,3 +27,4 @@
address = ":8443" address = ":8443"
[providers.kubernetesGateway] [providers.kubernetesGateway]
experimentalChannel = true

View file

@ -75,7 +75,8 @@ type clientWrapper struct {
isNamespaceAll bool isNamespaceAll bool
watchedNamespaces []string watchedNamespaces []string
labelSelector string labelSelector string
experimentalChannel bool
} }
func createClientFromConfig(c *rest.Config) (*clientWrapper, error) { func createClientFromConfig(c *rest.Config) (*clientWrapper, error) {
@ -196,19 +197,22 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, err = factoryGateway.Gateway().V1alpha2().TCPRoutes().Informer().AddEventHandler(eventHandler)
if err != nil {
return nil, err
}
_, err = factoryGateway.Gateway().V1alpha2().TLSRoutes().Informer().AddEventHandler(eventHandler)
if err != nil {
return nil, err
}
_, err = factoryGateway.Gateway().V1beta1().ReferenceGrants().Informer().AddEventHandler(eventHandler) _, err = factoryGateway.Gateway().V1beta1().ReferenceGrants().Informer().AddEventHandler(eventHandler)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if c.experimentalChannel {
_, err = factoryGateway.Gateway().V1alpha2().TCPRoutes().Informer().AddEventHandler(eventHandler)
if err != nil {
return nil, err
}
_, err = factoryGateway.Gateway().V1alpha2().TLSRoutes().Informer().AddEventHandler(eventHandler)
if err != nil {
return nil, err
}
}
factoryKube := kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithNamespace(ns)) factoryKube := kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithNamespace(ns))
_, err = factoryKube.Core().V1().Services().Informer().AddEventHandler(eventHandler) _, err = factoryKube.Core().V1().Services().Informer().AddEventHandler(eventHandler)
if err != nil { if err != nil {

View file

@ -51,13 +51,15 @@ const (
// Provider holds configurations of the provider. // Provider holds configurations of the provider.
type Provider struct { type Provider struct {
Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
Token types.FileOrContent `description:"Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty" loggable:"false"` Token types.FileOrContent `description:"Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty" loggable:"false"`
CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"` CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"`
Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"` Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"`
LabelSelector string `description:"Kubernetes label selector to select specific GatewayClasses." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"` LabelSelector string `description:"Kubernetes label selector to select specific GatewayClasses." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"`
ThrottleDuration ptypes.Duration `description:"Kubernetes refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"` ThrottleDuration ptypes.Duration `description:"Kubernetes refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
EntryPoints map[string]Entrypoint `json:"-" toml:"-" yaml:"-" label:"-" file:"-"` ExperimentalChannel bool `description:"Toggles Experimental Channel resources support (TCPRoute, TLSRoute...)." json:"experimentalChannel,omitempty" toml:"experimentalChannel,omitempty" yaml:"experimentalChannel,omitempty" export:"true"`
EntryPoints map[string]Entrypoint `json:"-" toml:"-" yaml:"-" label:"-" file:"-"`
// groupKindFilterFuncs is the list of allowed Group and Kinds for the Filter ExtensionRef objects. // groupKindFilterFuncs is the list of allowed Group and Kinds for the Filter ExtensionRef objects.
groupKindFilterFuncs map[string]map[string]BuildFilterFunc groupKindFilterFuncs map[string]map[string]BuildFilterFunc
@ -155,6 +157,7 @@ func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) {
} }
client.labelSelector = p.LabelSelector client.labelSelector = p.LabelSelector
client.experimentalChannel = p.ExperimentalChannel
return client, nil return client, nil
} }
@ -396,7 +399,7 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway *
// AttachedRoutes: 0 TODO Set to number of Routes associated with a Listener regardless of Gateway or Route status // AttachedRoutes: 0 TODO Set to number of Routes associated with a Listener regardless of Gateway or Route status
} }
supportedKinds, conditions := supportedRouteKinds(listener.Protocol) supportedKinds, conditions := supportedRouteKinds(listener.Protocol, p.ExperimentalChannel)
if len(conditions) > 0 { if len(conditions) > 0 {
listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, conditions...) listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, conditions...)
continue continue
@ -716,21 +719,41 @@ func (p *Provider) entryPointName(port gatev1.PortNumber, protocol gatev1.Protoc
return "", fmt.Errorf("no matching entryPoint for port %d and protocol %q", port, protocol) return "", fmt.Errorf("no matching entryPoint for port %d and protocol %q", port, protocol)
} }
func supportedRouteKinds(protocol gatev1.ProtocolType) ([]gatev1.RouteGroupKind, []metav1.Condition) { func supportedRouteKinds(protocol gatev1.ProtocolType, experimentalChannel bool) ([]gatev1.RouteGroupKind, []metav1.Condition) {
group := gatev1.Group(gatev1.GroupName) group := gatev1.Group(gatev1.GroupName)
switch protocol { switch protocol {
case gatev1.TCPProtocolType: case gatev1.TCPProtocolType:
return []gatev1.RouteGroupKind{{Kind: kindTCPRoute, Group: &group}}, nil if experimentalChannel {
return []gatev1.RouteGroupKind{{Kind: kindTCPRoute, Group: &group}}, nil
}
return nil, []metav1.Condition{{
Type: string(gatev1.ListenerConditionConflicted),
Status: metav1.ConditionFalse,
LastTransitionTime: metav1.Now(),
Reason: string(gatev1.ListenerReasonInvalidRouteKinds),
Message: fmt.Sprintf("Protocol %q requires the experimental channel support to be enabled, please use the `experimentalChannel` option", protocol),
}}
case gatev1.HTTPProtocolType, gatev1.HTTPSProtocolType: case gatev1.HTTPProtocolType, gatev1.HTTPSProtocolType:
return []gatev1.RouteGroupKind{{Kind: kindHTTPRoute, Group: &group}}, nil return []gatev1.RouteGroupKind{{Kind: kindHTTPRoute, Group: &group}}, nil
case gatev1.TLSProtocolType: case gatev1.TLSProtocolType:
return []gatev1.RouteGroupKind{ if experimentalChannel {
{Kind: kindTCPRoute, Group: &group}, return []gatev1.RouteGroupKind{
{Kind: kindTLSRoute, Group: &group}, {Kind: kindTCPRoute, Group: &group},
}, nil {Kind: kindTLSRoute, Group: &group},
}, nil
}
return nil, []metav1.Condition{{
Type: string(gatev1.ListenerConditionConflicted),
Status: metav1.ConditionFalse,
LastTransitionTime: metav1.Now(),
Reason: string(gatev1.ListenerReasonInvalidRouteKinds),
Message: fmt.Sprintf("Protocol %q requires the experimental channel support to be enabled, please use the `experimentalChannel` option", protocol),
}}
} }
return nil, []metav1.Condition{{ return nil, []metav1.Condition{{

View file

@ -24,12 +24,12 @@ var _ provider.Provider = (*Provider)(nil)
func TestLoadHTTPRoutes(t *testing.T) { func TestLoadHTTPRoutes(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
ingressClass string ingressClass string
paths []string paths []string
namespaces []string expected *dynamic.Configuration
expected *dynamic.Configuration entryPoints map[string]Entrypoint
entryPoints map[string]Entrypoint experimentalChannel bool
}{ }{
{ {
desc: "Empty", desc: "Empty",
@ -476,6 +476,7 @@ func TestLoadHTTPRoutes(t *testing.T) {
entryPoints: map[string]Entrypoint{"TCP": { entryPoints: map[string]Entrypoint{"TCP": {
Address: ":8080", Address: ":8080",
}}, }},
experimentalChannel: true,
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{ UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{}, Routers: map[string]*dynamic.UDPRouter{},
@ -1664,7 +1665,7 @@ func TestLoadHTTPRoutes(t *testing.T) {
return return
} }
p := Provider{EntryPoints: test.entryPoints} p := Provider{EntryPoints: test.entryPoints, ExperimentalChannel: test.experimentalChannel}
conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...)) conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...))
assert.Equal(t, test.expected, conf) assert.Equal(t, test.expected, conf)
@ -2905,7 +2906,7 @@ func TestLoadTCPRoutes(t *testing.T) {
return return
} }
p := Provider{EntryPoints: test.entryPoints} p := Provider{EntryPoints: test.entryPoints, ExperimentalChannel: true}
conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...)) conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...))
assert.Equal(t, test.expected, conf) assert.Equal(t, test.expected, conf)
}) })
@ -4034,7 +4035,7 @@ func TestLoadTLSRoutes(t *testing.T) {
return return
} }
p := Provider{EntryPoints: test.entryPoints} p := Provider{EntryPoints: test.entryPoints, ExperimentalChannel: true}
conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...)) conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...))
assert.Equal(t, test.expected, conf) assert.Equal(t, test.expected, conf)
}) })
@ -4043,11 +4044,12 @@ func TestLoadTLSRoutes(t *testing.T) {
func TestLoadMixedRoutes(t *testing.T) { func TestLoadMixedRoutes(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
ingressClass string ingressClass string
paths []string paths []string
expected *dynamic.Configuration expected *dynamic.Configuration
entryPoints map[string]Entrypoint entryPoints map[string]Entrypoint
experimentalChannel bool
}{ }{
{ {
desc: "Empty", desc: "Empty",
@ -4159,6 +4161,7 @@ func TestLoadMixedRoutes(t *testing.T) {
"tls-1": {Address: ":10000"}, "tls-1": {Address: ":10000"},
"tls-2": {Address: ":11000"}, "tls-2": {Address: ":11000"},
}, },
experimentalChannel: true,
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{ UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{}, Routers: map[string]*dynamic.UDPRouter{},
@ -4343,6 +4346,7 @@ func TestLoadMixedRoutes(t *testing.T) {
"tls-1": {Address: ":10000"}, "tls-1": {Address: ":10000"},
"tls-2": {Address: ":11000"}, "tls-2": {Address: ":11000"},
}, },
experimentalChannel: true,
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{ UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{}, Routers: map[string]*dynamic.UDPRouter{},
@ -4499,6 +4503,7 @@ func TestLoadMixedRoutes(t *testing.T) {
"tls-1": {Address: ":10000"}, "tls-1": {Address: ":10000"},
"tls-2": {Address: ":11000"}, "tls-2": {Address: ":11000"},
}, },
experimentalChannel: true,
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{ UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{}, Routers: map[string]*dynamic.UDPRouter{},
@ -4749,6 +4754,7 @@ func TestLoadMixedRoutes(t *testing.T) {
"tls-1": {Address: ":10000"}, "tls-1": {Address: ":10000"},
"tls-2": {Address: ":11000"}, "tls-2": {Address: ":11000"},
}, },
experimentalChannel: true,
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{ UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{}, Routers: map[string]*dynamic.UDPRouter{},
@ -4904,6 +4910,7 @@ func TestLoadMixedRoutes(t *testing.T) {
"tcp": {Address: ":9000"}, "tcp": {Address: ":9000"},
"tls": {Address: ":10000"}, "tls": {Address: ":10000"},
}, },
experimentalChannel: true,
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{ UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{}, Routers: map[string]*dynamic.UDPRouter{},
@ -5042,7 +5049,7 @@ func TestLoadMixedRoutes(t *testing.T) {
return return
} }
p := Provider{EntryPoints: test.entryPoints} p := Provider{EntryPoints: test.entryPoints, ExperimentalChannel: test.experimentalChannel}
conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...)) conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...))
assert.Equal(t, test.expected, conf) assert.Equal(t, test.expected, conf)
}) })
@ -5051,11 +5058,12 @@ func TestLoadMixedRoutes(t *testing.T) {
func TestLoadRoutesWithReferenceGrants(t *testing.T) { func TestLoadRoutesWithReferenceGrants(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
ingressClass string ingressClass string
paths []string paths []string
expected *dynamic.Configuration expected *dynamic.Configuration
entryPoints map[string]Entrypoint entryPoints map[string]Entrypoint
experimentalChannel bool
}{ }{
{ {
desc: "Empty", desc: "Empty",
@ -5163,6 +5171,7 @@ func TestLoadRoutesWithReferenceGrants(t *testing.T) {
entryPoints: map[string]Entrypoint{ entryPoints: map[string]Entrypoint{
"tls": {Address: ":9000"}, "tls": {Address: ":9000"},
}, },
experimentalChannel: true,
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{ UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{}, Routers: map[string]*dynamic.UDPRouter{},
@ -5232,7 +5241,7 @@ func TestLoadRoutesWithReferenceGrants(t *testing.T) {
return return
} }
p := Provider{EntryPoints: test.entryPoints} p := Provider{EntryPoints: test.entryPoints, ExperimentalChannel: test.experimentalChannel}
conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...)) conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...))
assert.Equal(t, test.expected, conf) assert.Equal(t, test.expected, conf)
}) })