From 8c98234c07395f3397b2b49921b496b83b2be1ae Mon Sep 17 00:00:00 2001 From: jandillenkofer <56083248+jandillenkofer@users.noreply.github.com> Date: Thu, 22 Dec 2022 16:30:05 +0100 Subject: [PATCH] Add option to the Ingress provider to disable IngressClass lookup --- docs/content/providers/kubernetes-ingress.md | 29 +++++ .../reference/static-configuration/cli-ref.md | 3 + .../reference/static-configuration/env-ref.md | 3 + .../reference/static-configuration/file.toml | 1 + .../reference/static-configuration/file.yaml | 1 + .../fixtures/k8s_ingressclass_disabled.toml | 18 +++ integration/k8s_test.go | 11 ++ .../rawdata-ingressclass-disabled.json | 74 +++++++++++ pkg/provider/kubernetes/ingress/client.go | 21 ++-- pkg/provider/kubernetes/ingress/kubernetes.go | 4 +- .../kubernetes/ingress/kubernetes_test.go | 115 +++++++++++++++++- 11 files changed, 263 insertions(+), 17 deletions(-) create mode 100644 integration/fixtures/k8s_ingressclass_disabled.toml create mode 100644 integration/testdata/rawdata-ingressclass-disabled.json diff --git a/docs/content/providers/kubernetes-ingress.md b/docs/content/providers/kubernetes-ingress.md index a35017cc5..4d915a596 100644 --- a/docs/content/providers/kubernetes-ingress.md +++ b/docs/content/providers/kubernetes-ingress.md @@ -344,6 +344,35 @@ providers: --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` #### `hostname` diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index 00bb3af7f..fa5c9759f 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -735,6 +735,9 @@ Allow ExternalName services. (Default: ```false```) `--providers.kubernetesingress.certauthfilepath`: Kubernetes certificate authority file path (not needed for in-cluster client). +`--providers.kubernetesingress.disableingressclasslookup`: +Disables the lookup of IngressClasses. (Default: ```false```) + `--providers.kubernetesingress.endpoint`: Kubernetes server endpoint (required for external cluster client). diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index b6a7429a8..4a951b462 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -735,6 +735,9 @@ Allow ExternalName services. (Default: ```false```) `TRAEFIK_PROVIDERS_KUBERNETESINGRESS_CERTAUTHFILEPATH`: 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`: Kubernetes server endpoint (required for external cluster client). diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index 29ccadce7..8bff3f0a2 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -108,6 +108,7 @@ throttleDuration = "42s" allowEmptyServices = true allowExternalNameServices = true + disableIngressClassLookup = true [providers.kubernetesIngress.ingressEndpoint] ip = "foobar" hostname = "foobar" diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index 10284c874..e6b100632 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -117,6 +117,7 @@ providers: throttleDuration: 42s allowEmptyServices: true allowExternalNameServices: true + disableIngressClassLookup: true ingressEndpoint: ip: foobar hostname: foobar diff --git a/integration/fixtures/k8s_ingressclass_disabled.toml b/integration/fixtures/k8s_ingressclass_disabled.toml new file mode 100644 index 000000000..8afc19e97 --- /dev/null +++ b/integration/fixtures/k8s_ingressclass_disabled.toml @@ -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 diff --git a/integration/k8s_test.go b/integration/k8s_test.go index bfb73d93c..13c64a2e0 100644 --- a/integration/k8s_test.go +++ b/integration/k8s_test.go @@ -128,6 +128,17 @@ func (s *K8sSuite) TestIngressclass(c *check.C) { 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) { err := try.GetRequest("http://127.0.0.1:"+apiPort+"/api/entrypoints", 20*time.Second, try.BodyContains(`"name":"web"`)) c.Assert(err, checker.IsNil) diff --git a/integration/testdata/rawdata-ingressclass-disabled.json b/integration/testdata/rawdata-ingressclass-disabled.json new file mode 100644 index 000000000..14649419c --- /dev/null +++ b/integration/testdata/rawdata-ingressclass-disabled.json @@ -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" + } + } +} \ No newline at end of file diff --git a/pkg/provider/kubernetes/ingress/client.go b/pkg/provider/kubernetes/ingress/client.go index eccd39657..2ac486724 100644 --- a/pkg/provider/kubernetes/ingress/client.go +++ b/pkg/provider/kubernetes/ingress/client.go @@ -50,15 +50,16 @@ type Client interface { } type clientWrapper struct { - clientset kubernetes.Interface - factoriesKube map[string]informers.SharedInformerFactory - factoriesSecret map[string]informers.SharedInformerFactory - factoriesIngress map[string]informers.SharedInformerFactory - clusterFactory informers.SharedInformerFactory - ingressLabelSelector string - isNamespaceAll bool - watchedNamespaces []string - serverVersion *version.Version + clientset kubernetes.Interface + factoriesKube map[string]informers.SharedInformerFactory + factoriesSecret map[string]informers.SharedInformerFactory + factoriesIngress map[string]informers.SharedInformerFactory + clusterFactory informers.SharedInformerFactory + ingressLabelSelector string + isNamespaceAll bool + disableIngressClassInformer bool + watchedNamespaces []string + serverVersion *version.Version } // 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) if supportsNetworkingV1Ingress(serverVersion) { diff --git a/pkg/provider/kubernetes/ingress/kubernetes.go b/pkg/provider/kubernetes/ingress/kubernetes.go index 95a71c12d..54a4be395 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes.go +++ b/pkg/provider/kubernetes/ingress/kubernetes.go @@ -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"` 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"` + DisableIngressClassLookup bool `description:"Disables the lookup of IngressClasses." json:"disableIngressClassLookup,omitempty" toml:"disableIngressClassLookup,omitempty" yaml:"disableIngressClassLookup,omitempty" export:"true"` lastConfiguration safe.Safe } @@ -91,6 +92,7 @@ func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) { } cl.ingressLabelSelector = p.LabelSelector + cl.disableIngressClassInformer = p.DisableIngressClassLookup return cl, nil } @@ -195,7 +197,7 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl var ingressClasses []*networkingv1.IngressClass - if supportsIngressClass(serverVersion) { + if !p.DisableIngressClassLookup && supportsIngressClass(serverVersion) { ics, err := client.GetIngressClasses() if err != nil { log.Ctx(ctx).Warn().Err(err).Msg("Failed to list ingress classes") diff --git a/pkg/provider/kubernetes/ingress/kubernetes_test.go b/pkg/provider/kubernetes/ingress/kubernetes_test.go index 3b032f996..3c21f1519 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes_test.go +++ b/pkg/provider/kubernetes/ingress/kubernetes_test.go @@ -26,11 +26,12 @@ func Bool(v bool) *bool { return &v } func TestLoadConfigurationFromIngresses(t *testing.T) { testCases := []struct { - desc string - ingressClass string - serverVersion string - expected *dynamic.Configuration - allowEmptyServices bool + desc string + ingressClass string + serverVersion string + expected *dynamic.Configuration + allowEmptyServices bool + disableIngressClassLookup bool }{ { 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", 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", 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", 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", serverVersion: "v1.19", @@ -1783,7 +1882,11 @@ func TestLoadConfigurationFromIngresses(t *testing.T) { } 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) assert.Equal(t, test.expected, conf)