Add option to the Ingress provider to disable IngressClass lookup

This commit is contained in:
jandillenkofer 2022-12-22 16:30:05 +01:00 committed by GitHub
parent d046af2e91
commit 8c98234c07
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 263 additions and 17 deletions

View file

@ -344,6 +344,35 @@ providers:
--providers.kubernetesingress.ingressclass=traefik-internal --providers.kubernetesingress.ingressclass=traefik-internal
``` ```
### `disableIngressClassLookup`
_Optional, Default: false_
If the parameter is set to `true`,
Traefik will not discover IngressClasses in the cluster.
By doing so, it alleviates the requirement of giving Traefik the rights to look IngressClasses up.
Furthermore, when this option is set to `true`,
Traefik is not able to handle Ingresses with IngressClass references,
therefore such Ingresses will be ignored.
Please note that annotations are not affected by this option.
```yaml tab="File (YAML)"
providers:
kubernetesIngress:
disableIngressClassLookup: true
# ...
```
```toml tab="File (TOML)"
[providers.kubernetesIngress]
disableIngressClassLookup = true
# ...
```
```bash tab="CLI"
--providers.kubernetesingress.disableingressclasslookup=true
```
### `ingressEndpoint` ### `ingressEndpoint`
#### `hostname` #### `hostname`

View file

@ -735,6 +735,9 @@ Allow ExternalName services. (Default: ```false```)
`--providers.kubernetesingress.certauthfilepath`: `--providers.kubernetesingress.certauthfilepath`:
Kubernetes certificate authority file path (not needed for in-cluster client). Kubernetes certificate authority file path (not needed for in-cluster client).
`--providers.kubernetesingress.disableingressclasslookup`:
Disables the lookup of IngressClasses. (Default: ```false```)
`--providers.kubernetesingress.endpoint`: `--providers.kubernetesingress.endpoint`:
Kubernetes server endpoint (required for external cluster client). Kubernetes server endpoint (required for external cluster client).

View file

@ -735,6 +735,9 @@ Allow ExternalName services. (Default: ```false```)
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_CERTAUTHFILEPATH`: `TRAEFIK_PROVIDERS_KUBERNETESINGRESS_CERTAUTHFILEPATH`:
Kubernetes certificate authority file path (not needed for in-cluster client). Kubernetes certificate authority file path (not needed for in-cluster client).
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_DISABLEINGRESSCLASSLOOKUP`:
Disables the lookup of IngressClasses. (Default: ```false```)
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_ENDPOINT`: `TRAEFIK_PROVIDERS_KUBERNETESINGRESS_ENDPOINT`:
Kubernetes server endpoint (required for external cluster client). Kubernetes server endpoint (required for external cluster client).

View file

@ -108,6 +108,7 @@
throttleDuration = "42s" throttleDuration = "42s"
allowEmptyServices = true allowEmptyServices = true
allowExternalNameServices = true allowExternalNameServices = true
disableIngressClassLookup = true
[providers.kubernetesIngress.ingressEndpoint] [providers.kubernetesIngress.ingressEndpoint]
ip = "foobar" ip = "foobar"
hostname = "foobar" hostname = "foobar"

View file

@ -117,6 +117,7 @@ providers:
throttleDuration: 42s throttleDuration: 42s
allowEmptyServices: true allowEmptyServices: true
allowExternalNameServices: true allowExternalNameServices: true
disableIngressClassLookup: true
ingressEndpoint: ingressEndpoint:
ip: foobar ip: foobar
hostname: foobar hostname: foobar

View file

@ -0,0 +1,18 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
noColor = true
[api]
insecure = true
[entryPoints]
[entryPoints.web]
address = ":8000"
[providers.kubernetesIngress]
ingressClass = "traefik-keep"
disableIngressClassLookup = true

View file

@ -128,6 +128,17 @@ func (s *K8sSuite) TestIngressclass(c *check.C) {
testConfiguration(c, "testdata/rawdata-ingressclass.json", "8080") testConfiguration(c, "testdata/rawdata-ingressclass.json", "8080")
} }
func (s *K8sSuite) TestDisableIngressclassLookup(c *check.C) {
cmd, display := s.traefikCmd(withConfigFile("fixtures/k8s_ingressclass_disabled.toml"))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
testConfiguration(c, "testdata/rawdata-ingressclass-disabled.json", "8080")
}
func testConfiguration(c *check.C, path, apiPort string) { func testConfiguration(c *check.C, path, apiPort string) {
err := try.GetRequest("http://127.0.0.1:"+apiPort+"/api/entrypoints", 20*time.Second, try.BodyContains(`"name":"web"`)) err := try.GetRequest("http://127.0.0.1:"+apiPort+"/api/entrypoints", 20*time.Second, try.BodyContains(`"name":"web"`))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)

View file

@ -0,0 +1,74 @@
{
"routers": {
"api@internal": {
"entryPoints": [
"traefik"
],
"service": "api@internal",
"rule": "PathPrefix(`/api`)",
"priority": 2147483646,
"status": "enabled",
"using": [
"traefik"
]
},
"dashboard@internal": {
"entryPoints": [
"traefik"
],
"middlewares": [
"dashboard_redirect@internal",
"dashboard_stripprefix@internal"
],
"service": "dashboard@internal",
"rule": "PathPrefix(`/`)",
"priority": 2147483645,
"status": "enabled",
"using": [
"traefik"
]
}
},
"middlewares": {
"dashboard_redirect@internal": {
"redirectRegex": {
"regex": "^(http:\\/\\/(\\[[\\w:.]+\\]|[\\w\\._-]+)(:\\d+)?)\\/$",
"replacement": "${1}/dashboard/",
"permanent": true
},
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
},
"dashboard_stripprefix@internal": {
"stripPrefix": {
"prefixes": [
"/dashboard/",
"/dashboard"
]
},
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
}
},
"services": {
"api@internal": {
"status": "enabled",
"usedBy": [
"api@internal"
]
},
"dashboard@internal": {
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
},
"noop@internal": {
"status": "enabled"
}
}
}

View file

@ -50,15 +50,16 @@ type Client interface {
} }
type clientWrapper struct { type clientWrapper struct {
clientset kubernetes.Interface clientset kubernetes.Interface
factoriesKube map[string]informers.SharedInformerFactory factoriesKube map[string]informers.SharedInformerFactory
factoriesSecret map[string]informers.SharedInformerFactory factoriesSecret map[string]informers.SharedInformerFactory
factoriesIngress map[string]informers.SharedInformerFactory factoriesIngress map[string]informers.SharedInformerFactory
clusterFactory informers.SharedInformerFactory clusterFactory informers.SharedInformerFactory
ingressLabelSelector string ingressLabelSelector string
isNamespaceAll bool isNamespaceAll bool
watchedNamespaces []string disableIngressClassInformer bool
serverVersion *version.Version watchedNamespaces []string
serverVersion *version.Version
} }
// newInClusterClient returns a new Provider client that is expected to run // newInClusterClient returns a new Provider client that is expected to run
@ -214,7 +215,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
} }
} }
if supportsIngressClass(serverVersion) { if !c.disableIngressClassInformer && supportsIngressClass(serverVersion) {
c.clusterFactory = informers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod) c.clusterFactory = informers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod)
if supportsNetworkingV1Ingress(serverVersion) { if supportsNetworkingV1Ingress(serverVersion) {

View file

@ -48,6 +48,7 @@ type Provider struct {
ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"` ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
AllowEmptyServices bool `description:"Allow creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"` AllowEmptyServices bool `description:"Allow creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"`
AllowExternalNameServices bool `description:"Allow ExternalName services." json:"allowExternalNameServices,omitempty" toml:"allowExternalNameServices,omitempty" yaml:"allowExternalNameServices,omitempty" export:"true"` AllowExternalNameServices bool `description:"Allow ExternalName services." json:"allowExternalNameServices,omitempty" toml:"allowExternalNameServices,omitempty" yaml:"allowExternalNameServices,omitempty" export:"true"`
DisableIngressClassLookup bool `description:"Disables the lookup of IngressClasses." json:"disableIngressClassLookup,omitempty" toml:"disableIngressClassLookup,omitempty" yaml:"disableIngressClassLookup,omitempty" export:"true"`
lastConfiguration safe.Safe lastConfiguration safe.Safe
} }
@ -91,6 +92,7 @@ func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) {
} }
cl.ingressLabelSelector = p.LabelSelector cl.ingressLabelSelector = p.LabelSelector
cl.disableIngressClassInformer = p.DisableIngressClassLookup
return cl, nil return cl, nil
} }
@ -195,7 +197,7 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
var ingressClasses []*networkingv1.IngressClass var ingressClasses []*networkingv1.IngressClass
if supportsIngressClass(serverVersion) { if !p.DisableIngressClassLookup && supportsIngressClass(serverVersion) {
ics, err := client.GetIngressClasses() ics, err := client.GetIngressClasses()
if err != nil { if err != nil {
log.Ctx(ctx).Warn().Err(err).Msg("Failed to list ingress classes") log.Ctx(ctx).Warn().Err(err).Msg("Failed to list ingress classes")

View file

@ -26,11 +26,12 @@ func Bool(v bool) *bool { return &v }
func TestLoadConfigurationFromIngresses(t *testing.T) { func TestLoadConfigurationFromIngresses(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
ingressClass string ingressClass string
serverVersion string serverVersion string
expected *dynamic.Configuration expected *dynamic.Configuration
allowEmptyServices bool allowEmptyServices bool
disableIngressClassLookup bool
}{ }{
{ {
desc: "Empty ingresses", desc: "Empty ingresses",
@ -1392,6 +1393,40 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
}, },
}, },
}, },
{
// Duplicate test case with the same fixture as the one above, but with the disableIngressClassLookup option to true.
// Showing that disabling the ingressClass discovery still allow the discovery of ingresses with ingress annotation.
desc: "v18 Ingress with ingress annotation",
serverVersion: "v1.18",
disableIngressClassLookup: true,
expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{
"testing-bar": {
Rule: "PathPrefix(`/bar`)",
Service: "testing-service1-80",
},
},
Services: map[string]*dynamic.Service{
"testing-service1-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
ResponseForwarding: &dynamic.ResponseForwarding{
FlushInterval: ptypes.Duration(100 * time.Millisecond),
},
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:8080",
},
},
},
},
},
},
},
},
{ {
desc: "v18 Ingress with ingressClasses filter", desc: "v18 Ingress with ingressClasses filter",
serverVersion: "v1.18", serverVersion: "v1.18",
@ -1424,6 +1459,22 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
}, },
}, },
}, },
{
// Duplicate test case with the same fixture as the one above, but with the disableIngressClassLookup option to true.
// Showing that disabling the ingressClass discovery avoid discovering Ingresses with an IngressClass.
desc: "v18 Ingress with ingressClasses filter",
serverVersion: "v1.18",
ingressClass: "traefik-lb2",
disableIngressClassLookup: true,
expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{},
Services: map[string]*dynamic.Service{},
},
},
},
{ {
desc: "v19 Ingress with prefix pathType", desc: "v19 Ingress with prefix pathType",
serverVersion: "v1.19", serverVersion: "v1.19",
@ -1610,6 +1661,39 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
}, },
}, },
}, },
{
// Duplicate test case with the same fixture as the one above, but with the disableIngressClassLookup option to true.
// Showing that disabling the ingressClass discovery still allow the discovery of ingresses with ingress annotation.
desc: "v19 Ingress with ingress annotation",
serverVersion: "v1.19",
expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{
"testing-bar": {
Rule: "PathPrefix(`/bar`)",
Service: "testing-service1-80",
},
},
Services: map[string]*dynamic.Service{
"testing-service1-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
ResponseForwarding: &dynamic.ResponseForwarding{
FlushInterval: ptypes.Duration(100 * time.Millisecond),
},
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:8080",
},
},
},
},
},
},
},
},
{ {
desc: "v19 Ingress with ingressClass", desc: "v19 Ingress with ingressClass",
serverVersion: "v1.19", serverVersion: "v1.19",
@ -1641,6 +1725,21 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
}, },
}, },
}, },
{
// Duplicate test case with the same fixture as the one above, but with the disableIngressClassLookup option to true.
// Showing that disabling the ingressClass discovery avoid discovering Ingresses with an IngressClass.
desc: "v19 Ingress with ingressClass",
disableIngressClassLookup: true,
serverVersion: "v1.19",
expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{},
Services: map[string]*dynamic.Service{},
},
},
},
{ {
desc: "v19 Ingress with ingressClassv1", desc: "v19 Ingress with ingressClassv1",
serverVersion: "v1.19", serverVersion: "v1.19",
@ -1783,7 +1882,11 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
} }
clientMock := newClientMock(serverVersion, paths...) clientMock := newClientMock(serverVersion, paths...)
p := Provider{IngressClass: test.ingressClass, AllowEmptyServices: test.allowEmptyServices} p := Provider{
IngressClass: test.ingressClass,
AllowEmptyServices: test.allowEmptyServices,
DisableIngressClassLookup: test.disableIngressClassLookup,
}
conf := p.loadConfigurationFromIngresses(context.Background(), clientMock) conf := p.loadConfigurationFromIngresses(context.Background(), clientMock)
assert.Equal(t, test.expected, conf) assert.Equal(t, test.expected, conf)