From 85be7af4a2008975066ea49be4c2b037f2f7f75e Mon Sep 17 00:00:00 2001 From: Piotr Maksymiuk Date: Fri, 22 May 2026 12:39:36 +0200 Subject: [PATCH] kubernetes: strip managedFields in informers Drop metadata.managedFields from Kubernetes objects as they enter informer caches. Traefik does not read managedFields, and keeping them increases cached object size plus downstream copy and comparison cost for watched resources. Assisted-by: Claude Opus 4.8 Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/provider/kubernetes/crd/client.go | 8 ++-- pkg/provider/kubernetes/gateway/client.go | 10 ++--- .../kubernetes/ingress-nginx/client.go | 10 ++--- pkg/provider/kubernetes/ingress/client.go | 8 ++-- pkg/provider/kubernetes/k8s/transform.go | 16 +++++++ pkg/provider/kubernetes/k8s/transform_test.go | 43 +++++++++++++++++++ pkg/provider/kubernetes/knative/client.go | 4 +- 7 files changed, 79 insertions(+), 20 deletions(-) create mode 100644 pkg/provider/kubernetes/k8s/transform.go create mode 100644 pkg/provider/kubernetes/k8s/transform_test.go diff --git a/pkg/provider/kubernetes/crd/client.go b/pkg/provider/kubernetes/crd/client.go index 19896b7273..296201502d 100644 --- a/pkg/provider/kubernetes/crd/client.go +++ b/pkg/provider/kubernetes/crd/client.go @@ -177,7 +177,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< } for _, ns := range namespaces { - factoryCrd := traefikinformers.NewSharedInformerFactoryWithOptions(c.csCrd, resyncPeriod, traefikinformers.WithNamespace(ns), traefikinformers.WithTweakListOptions(matchesLabelSelector)) + factoryCrd := traefikinformers.NewSharedInformerFactoryWithOptions(c.csCrd, resyncPeriod, traefikinformers.WithNamespace(ns), traefikinformers.WithTweakListOptions(matchesLabelSelector), traefikinformers.WithTransform(k8s.StripManagedFields)) _, err := factoryCrd.Traefik().V1alpha1().IngressRoutes().Informer().AddEventHandler(eventHandler) if err != nil { return nil, err @@ -219,7 +219,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< return nil, err } - factoryKube := kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithNamespace(ns)) + factoryKube := kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTransform(k8s.StripManagedFields)) _, err = factoryKube.Core().V1().Services().Informer().AddEventHandler(eventHandler) if err != nil { return nil, err @@ -233,7 +233,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< return nil, err } - factorySecret := kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTweakListOptions(notOwnedByHelm)) + factorySecret := kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTweakListOptions(notOwnedByHelm), kinformers.WithTransform(k8s.StripManagedFields)) _, err = factorySecret.Core().V1().Secrets().Informer().AddEventHandler(eventHandler) if err != nil { return nil, err @@ -271,7 +271,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< } if !c.disableClusterScopeInformer { - c.clusterScopeFactory = kinformers.NewSharedInformerFactory(c.csKube, resyncPeriod) + c.clusterScopeFactory = kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithTransform(k8s.StripManagedFields)) _, err := c.clusterScopeFactory.Core().V1().Nodes().Informer().AddEventHandler(eventHandler) if err != nil { return nil, err diff --git a/pkg/provider/kubernetes/gateway/client.go b/pkg/provider/kubernetes/gateway/client.go index 34d15d5724..f55df849b2 100644 --- a/pkg/provider/kubernetes/gateway/client.go +++ b/pkg/provider/kubernetes/gateway/client.go @@ -148,20 +148,20 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< options.LabelSelector = c.labelSelector } - c.factoryNamespace = kinformers.NewSharedInformerFactory(c.csKube, resyncPeriod) + c.factoryNamespace = kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithTransform(k8s.StripManagedFields)) _, err := c.factoryNamespace.Core().V1().Namespaces().Informer().AddEventHandler(eventHandler) if err != nil { return nil, err } - c.factoryGatewayClass = gateinformers.NewSharedInformerFactoryWithOptions(c.csGateway, resyncPeriod, gateinformers.WithTweakListOptions(labelSelectorOptions)) + c.factoryGatewayClass = gateinformers.NewSharedInformerFactoryWithOptions(c.csGateway, resyncPeriod, gateinformers.WithTweakListOptions(labelSelectorOptions), gateinformers.WithTransform(k8s.StripManagedFields)) _, err = c.factoryGatewayClass.Gateway().V1().GatewayClasses().Informer().AddEventHandler(eventHandler) if err != nil { return nil, err } for _, ns := range namespaces { - factoryKube := kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithNamespace(ns)) + factoryKube := kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTransform(k8s.StripManagedFields)) _, err = factoryKube.Core().V1().Services().Informer().AddEventHandler(eventHandler) if err != nil { return nil, err @@ -171,7 +171,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< return nil, err } - factoryGateway := gateinformers.NewSharedInformerFactoryWithOptions(c.csGateway, resyncPeriod, gateinformers.WithNamespace(ns)) + factoryGateway := gateinformers.NewSharedInformerFactoryWithOptions(c.csGateway, resyncPeriod, gateinformers.WithNamespace(ns), gateinformers.WithTransform(k8s.StripManagedFields)) _, err = factoryGateway.Gateway().V1().Gateways().Informer().AddEventHandler(eventHandler) if err != nil { return nil, err @@ -209,7 +209,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< } } - factorySecret := kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTweakListOptions(notOwnedByHelm)) + factorySecret := kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTweakListOptions(notOwnedByHelm), kinformers.WithTransform(k8s.StripManagedFields)) _, err = factorySecret.Core().V1().Secrets().Informer().AddEventHandler(eventHandler) if err != nil { return nil, err diff --git a/pkg/provider/kubernetes/ingress-nginx/client.go b/pkg/provider/kubernetes/ingress-nginx/client.go index dd04754f26..9d723e38e0 100644 --- a/pkg/provider/kubernetes/ingress-nginx/client.go +++ b/pkg/provider/kubernetes/ingress-nginx/client.go @@ -158,7 +158,7 @@ func (c *clientWrapper) WatchAll(ctx context.Context, namespace, namespaceSelect } for _, ns := range c.watchedNamespaces { - factoryIngress := kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, kinformers.WithNamespace(ns)) + factoryIngress := kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTransform(k8s.StripManagedFields)) _, err := factoryIngress.Networking().V1().Ingresses().Informer().AddEventHandler(eventHandler) if err != nil { @@ -167,7 +167,7 @@ func (c *clientWrapper) WatchAll(ctx context.Context, namespace, namespaceSelect c.factoriesIngress[ns] = factoryIngress - factoryKube := kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, kinformers.WithNamespace(ns)) + factoryKube := kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTransform(k8s.StripManagedFields)) _, err = factoryKube.Core().V1().Services().Informer().AddEventHandler(eventHandler) if err != nil { return nil, err @@ -178,14 +178,14 @@ func (c *clientWrapper) WatchAll(ctx context.Context, namespace, namespaceSelect } c.factoriesKube[ns] = factoryKube - factorySecret := kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTweakListOptions(notOwnedByHelm)) + factorySecret := kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTweakListOptions(notOwnedByHelm), kinformers.WithTransform(k8s.StripManagedFields)) _, err = factorySecret.Core().V1().Secrets().Informer().AddEventHandler(eventHandler) if err != nil { return nil, err } c.factoriesSecret[ns] = factorySecret - factoryConfigMap := kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTweakListOptions(notOwnedByHelm)) + factoryConfigMap := kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTweakListOptions(notOwnedByHelm), kinformers.WithTransform(k8s.StripManagedFields)) _, err = factoryConfigMap.Core().V1().ConfigMaps().Informer().AddEventHandler(eventHandler) if err != nil { return nil, err @@ -226,7 +226,7 @@ func (c *clientWrapper) WatchAll(ctx context.Context, namespace, namespaceSelect } } - c.clusterScopeFactory = kinformers.NewSharedInformerFactory(c.clientset, resyncPeriod) + c.clusterScopeFactory = kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, kinformers.WithTransform(k8s.StripManagedFields)) if !c.ignoreIngressClasses { _, err = c.clusterScopeFactory.Networking().V1().IngressClasses().Informer().AddEventHandler(eventHandler) diff --git a/pkg/provider/kubernetes/ingress/client.go b/pkg/provider/kubernetes/ingress/client.go index 9645409acf..49efbe9aa1 100644 --- a/pkg/provider/kubernetes/ingress/client.go +++ b/pkg/provider/kubernetes/ingress/client.go @@ -158,7 +158,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< } for _, ns := range namespaces { - factoryIngress := kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTweakListOptions(matchesLabelSelector)) + factoryIngress := kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTweakListOptions(matchesLabelSelector), kinformers.WithTransform(k8s.StripManagedFields)) _, err := factoryIngress.Networking().V1().Ingresses().Informer().AddEventHandler(eventHandler) if err != nil { @@ -167,7 +167,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< c.factoriesIngress[ns] = factoryIngress - factoryKube := kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, kinformers.WithNamespace(ns)) + factoryKube := kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTransform(k8s.StripManagedFields)) _, err = factoryKube.Core().V1().Services().Informer().AddEventHandler(eventHandler) if err != nil { return nil, err @@ -178,7 +178,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< } c.factoriesKube[ns] = factoryKube - factorySecret := kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTweakListOptions(notOwnedByHelm)) + factorySecret := kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTweakListOptions(notOwnedByHelm), kinformers.WithTransform(k8s.StripManagedFields)) _, err = factorySecret.Core().V1().Secrets().Informer().AddEventHandler(eventHandler) if err != nil { return nil, err @@ -213,7 +213,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< } if !c.disableIngressClassInformer || !c.disableClusterScopeInformer { - c.clusterScopeFactory = kinformers.NewSharedInformerFactory(c.clientset, resyncPeriod) + c.clusterScopeFactory = kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, kinformers.WithTransform(k8s.StripManagedFields)) _, err := c.clusterScopeFactory.Networking().V1().IngressClasses().Informer().AddEventHandler(eventHandler) if err != nil { diff --git a/pkg/provider/kubernetes/k8s/transform.go b/pkg/provider/kubernetes/k8s/transform.go new file mode 100644 index 0000000000..4092203c73 --- /dev/null +++ b/pkg/provider/kubernetes/k8s/transform.go @@ -0,0 +1,16 @@ +package k8s + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// StripManagedFields drops metadata.managedFields as objects enter the informer cache. +// Traefik never reads them, and they inflate the cache footprint and the cost of copying +// and comparing cached objects, which matters under heavy resource churn. +func StripManagedFields(obj any) (any, error) { + object, ok := obj.(metav1.Object) + if !ok { + return obj, nil + } + + object.SetManagedFields(nil) + return obj, nil +} diff --git a/pkg/provider/kubernetes/k8s/transform_test.go b/pkg/provider/kubernetes/k8s/transform_test.go new file mode 100644 index 0000000000..9f90343e82 --- /dev/null +++ b/pkg/provider/kubernetes/k8s/transform_test.go @@ -0,0 +1,43 @@ +package k8s + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestStripManagedFields(t *testing.T) { + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "whoami", + ManagedFields: []metav1.ManagedFieldsEntry{ + { + Manager: "kubectl", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "v1", + Time: &metav1.Time{Time: time.Now()}, + }, + }, + }, + } + + got, err := StripManagedFields(service) + require.NoError(t, err) + + assert.Same(t, service, got) + assert.Nil(t, service.ManagedFields) + assert.Equal(t, "whoami", service.Name) +} + +func TestStripManagedFieldsIgnoresNonKubernetesObjects(t *testing.T) { + obj := struct{ Name string }{Name: "whoami"} + + got, err := StripManagedFields(obj) + require.NoError(t, err) + + assert.Equal(t, obj, got) +} diff --git a/pkg/provider/kubernetes/knative/client.go b/pkg/provider/kubernetes/knative/client.go index a56b646ad4..2d60dc32a2 100644 --- a/pkg/provider/kubernetes/knative/client.go +++ b/pkg/provider/kubernetes/knative/client.go @@ -122,13 +122,13 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< for _, ns := range namespaces { factory := knativenetworkinginformers.NewSharedInformerFactoryWithOptions(c.csKnativeNetworking, resyncPeriod, knativenetworkinginformers.WithNamespace(ns), knativenetworkinginformers.WithTweakListOptions(func(opts *metav1.ListOptions) { opts.LabelSelector = c.labelSelector - })) + }), knativenetworkinginformers.WithTransform(k8s.StripManagedFields)) _, err := factory.Networking().V1alpha1().Ingresses().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), kinformers.WithTransform(k8s.StripManagedFields)) _, err = factoryKube.Core().V1().Services().Informer().AddEventHandler(eventHandler) if err != nil { return nil, err