From ca1ed962097a189c75bb47c7893dbb9ea9801de7 Mon Sep 17 00:00:00 2001 From: Fabrizio Sestito Date: Tue, 16 Jun 2026 08:02:25 +0200 Subject: [PATCH 1/3] perf(controller): tune nodescan controller cache Signed-off-by: Fabrizio Sestito --- cmd/controller/main.go | 13 ++++ internal/controller/transform.go | 20 ++++++ internal/controller/transform_test.go | 89 +++++++++++++++++++++++++++ 3 files changed, 122 insertions(+) diff --git a/cmd/controller/main.go b/cmd/controller/main.go index f9a612bc9..76617a6b0 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -270,6 +270,19 @@ func main() { } } + if cfg.NodeScan { + cacheByObject[&corev1.Node{}] = cache.ByObject{ + Transform: controller.TransformStripNode, + // Read-only + UnsafeDisableDeepCopy: new(true), + } + cacheByObject[&storagev1alpha1.NodeSBOM{}] = cache.ByObject{ + Transform: storage.TransformStripNodeSBOM, + // Read-only + UnsafeDisableDeepCopy: new(true), + } + } + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, Metrics: metricsServerOptions, diff --git a/internal/controller/transform.go b/internal/controller/transform.go index 84e5cc9d8..b068c73c5 100644 --- a/internal/controller/transform.go +++ b/internal/controller/transform.go @@ -45,3 +45,23 @@ func TransformStripPod(object any) (any, error) { return cache.TransformStripManagedFields()(pod) } + +// TransformStripNode strips the Node object of all Spec and Status fields except OS and architecture, and managed fields. +// This is useful for caching scenarios where only the node OS and architecture are needed, reducing memory usage. +func TransformStripNode(object any) (any, error) { + node, ok := object.(*corev1.Node) + if !ok { + return object, fmt.Errorf("expected Node object, got %T", object) + } + + node.Annotations = nil + node.Spec = corev1.NodeSpec{} + node.Status = corev1.NodeStatus{ + NodeInfo: corev1.NodeSystemInfo{ + OperatingSystem: node.Status.NodeInfo.OperatingSystem, + Architecture: node.Status.NodeInfo.Architecture, + }, + } + + return cache.TransformStripManagedFields()(node) +} diff --git a/internal/controller/transform_test.go b/internal/controller/transform_test.go index a9e262c3c..cfe901f7d 100644 --- a/internal/controller/transform_test.go +++ b/internal/controller/transform_test.go @@ -110,3 +110,92 @@ func TestTransformStripPod(t *testing.T) { assert.Empty(t, resultPod.Status.PodIP) assert.Nil(t, resultPod.Status.ContainerStatuses) } + +func TestTransformStripNode(t *testing.T) { + node := &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + Labels: map[string]string{ + "kubernetes.io/os": "linux", + "kubernetes.io/arch": "amd64", + }, + Annotations: map[string]string{ + "node.alpha.kubernetes.io/ttl": "0", + }, + }, + Spec: corev1.NodeSpec{ + PodCIDR: "10.244.0.0/24", + ProviderID: "aws:///us-east-1a/i-0abcdef", + Unschedulable: true, + Taints: []corev1.Taint{ + {Key: "dedicated", Value: "gpu", Effect: corev1.TaintEffectNoSchedule}, + }, + }, + Status: corev1.NodeStatus{ + NodeInfo: corev1.NodeSystemInfo{ + OperatingSystem: "linux", + Architecture: "amd64", + KernelVersion: "5.15.0", + OSImage: "Ubuntu 22.04", + KubeletVersion: "v1.30.0", + }, + Capacity: corev1.ResourceList{ //nolint:exhaustive // this is just a test + corev1.ResourceCPU: resource.MustParse("4"), + corev1.ResourceMemory: resource.MustParse("8Gi"), + }, + Allocatable: corev1.ResourceList{ //nolint:exhaustive // this is just a test + corev1.ResourceCPU: resource.MustParse("3500m"), + corev1.ResourceMemory: resource.MustParse("7Gi"), + }, + Conditions: []corev1.NodeCondition{ + {Type: corev1.NodeReady, Status: corev1.ConditionTrue}, + }, + Addresses: []corev1.NodeAddress{ + {Type: corev1.NodeInternalIP, Address: "10.0.0.1"}, + }, + Images: []corev1.ContainerImage{ + {Names: []string{"nginx:1.25"}, SizeBytes: 12345}, + {Names: []string{"busybox:latest"}, SizeBytes: 6789}, + }, + VolumesInUse: []corev1.UniqueVolumeName{"v1", "v2"}, + VolumesAttached: []corev1.AttachedVolume{ + {Name: "v1", DevicePath: "/dev/sda"}, + }, + }, + } + + result, err := TransformStripNode(node) + require.NoError(t, err) + + resultNode := result.(*corev1.Node) + + // Name and Labels should be preserved + assert.Equal(t, "test-node", resultNode.Name) + assert.Equal(t, "linux", resultNode.Labels["kubernetes.io/os"]) + assert.Equal(t, "amd64", resultNode.Labels["kubernetes.io/arch"]) + + // Only OperatingSystem and Architecture from NodeInfo should be preserved + assert.Equal(t, "linux", resultNode.Status.NodeInfo.OperatingSystem) + assert.Equal(t, "amd64", resultNode.Status.NodeInfo.Architecture) + assert.Empty(t, resultNode.Status.NodeInfo.KernelVersion) + assert.Empty(t, resultNode.Status.NodeInfo.OSImage) + assert.Empty(t, resultNode.Status.NodeInfo.KubeletVersion) + + // Annotations should be stripped + assert.Nil(t, resultNode.Annotations) + + // Spec should be stripped entirely + assert.Empty(t, resultNode.Spec.PodCIDR) + assert.Empty(t, resultNode.Spec.ProviderID) + assert.False(t, resultNode.Spec.Unschedulable) + assert.Nil(t, resultNode.Spec.Taints) + + // All other Status fields should be stripped + assert.Empty(t, resultNode.Status.Capacity) + assert.Empty(t, resultNode.Status.Allocatable) + assert.Nil(t, resultNode.Status.Conditions) + assert.Nil(t, resultNode.Status.Addresses) + assert.Nil(t, resultNode.Status.Images) + assert.Nil(t, resultNode.Status.VolumesInUse) + assert.Nil(t, resultNode.Status.VolumesAttached) +} From 7e1817348466c408abf02dc0b4deb443d46db4b7 Mon Sep 17 00:00:00 2001 From: Fabrizio Sestito Date: Tue, 16 Jun 2026 08:09:59 +0200 Subject: [PATCH 2/3] fix: better TransformStripNode docstring Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Fabrizio Sestito --- internal/controller/transform.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/controller/transform.go b/internal/controller/transform.go index b068c73c5..5a5716c98 100644 --- a/internal/controller/transform.go +++ b/internal/controller/transform.go @@ -46,8 +46,8 @@ func TransformStripPod(object any) (any, error) { return cache.TransformStripManagedFields()(pod) } -// TransformStripNode strips the Node object of all Spec and Status fields except OS and architecture, and managed fields. -// This is useful for caching scenarios where only the node OS and architecture are needed, reducing memory usage. +// TransformStripNode strips a Node object to reduce cache memory usage. +// It clears Spec entirely, clears Status except for NodeInfo.OperatingSystem/Architecture, and strips Annotations and managed fields. func TransformStripNode(object any) (any, error) { node, ok := object.(*corev1.Node) if !ok { From ad7f27a01405ff3746c4a2a102b7d505615ebfa5 Mon Sep 17 00:00:00 2001 From: Fabrizio Sestito Date: Wed, 17 Jun 2026 08:23:34 +0200 Subject: [PATCH 3/3] chore(controller): extract cache builder to fix gocyclo lint Signed-off-by: Fabrizio Sestito --- cmd/controller/main.go | 154 +++++++++++++++++++---------------------- 1 file changed, 72 insertions(+), 82 deletions(-) diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 76617a6b0..a4f5ad2cb 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -1,19 +1,3 @@ -/* -Copyright 2024. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - package main import ( @@ -216,72 +200,7 @@ func main() { os.Exit(1) } - cacheByObject := map[client.Object]cache.ByObject{ - &metav1.PartialObjectMetadata{ - TypeMeta: metav1.TypeMeta{ - APIVersion: storagev1alpha1.SchemeGroupVersion.String(), - Kind: "VulnerabilityReport", - }, - }: { - // Read-only - UnsafeDisableDeepCopy: new(true), - }, - &metav1.PartialObjectMetadata{ - TypeMeta: metav1.TypeMeta{ - APIVersion: corev1.SchemeGroupVersion.String(), - Kind: "Namespace", - }, - }: { - // Read-only - UnsafeDisableDeepCopy: new(true), - }, - } - - if cfg.WorkloadScan { - cacheByObject[&storagev1alpha1.Image{}] = cache.ByObject{ - Label: labels.SelectorFromSet(labels.Set{api.LabelWorkloadScanKey: api.LabelWorkloadScanValue}), - Transform: storage.TransformStripImage, - } - cacheByObject[&storagev1alpha1.WorkloadScanReport{}] = cache.ByObject{ - Transform: storage.TransformStripWorkloadScanReport, - } - cacheByObject[&corev1.Pod{}] = cache.ByObject{ - Transform: controller.TransformStripPod, - // Read-only - UnsafeDisableDeepCopy: new(true), - } - cacheByObject[&metav1.PartialObjectMetadata{ - TypeMeta: metav1.TypeMeta{ - APIVersion: appsv1.SchemeGroupVersion.String(), - Kind: "ReplicaSet", - }, - }] = cache.ByObject{ - // Read-only - UnsafeDisableDeepCopy: new(true), - } - cacheByObject[&metav1.PartialObjectMetadata{ - TypeMeta: metav1.TypeMeta{ - APIVersion: batchv1.SchemeGroupVersion.String(), - Kind: "Job", - }, - }] = cache.ByObject{ - // Read-only - UnsafeDisableDeepCopy: new(true), - } - } - - if cfg.NodeScan { - cacheByObject[&corev1.Node{}] = cache.ByObject{ - Transform: controller.TransformStripNode, - // Read-only - UnsafeDisableDeepCopy: new(true), - } - cacheByObject[&storagev1alpha1.NodeSBOM{}] = cache.ByObject{ - Transform: storage.TransformStripNodeSBOM, - // Read-only - UnsafeDisableDeepCopy: new(true), - } - } + cacheByObject := buildCacheByObject(cfg) mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, @@ -430,3 +349,74 @@ func main() { os.Exit(1) } } + +func buildCacheByObject(cfg Config) map[client.Object]cache.ByObject { + cacheByObject := map[client.Object]cache.ByObject{ + &metav1.PartialObjectMetadata{ + TypeMeta: metav1.TypeMeta{ + APIVersion: storagev1alpha1.SchemeGroupVersion.String(), + Kind: "VulnerabilityReport", + }, + }: { + // Read-only + UnsafeDisableDeepCopy: new(true), + }, + &metav1.PartialObjectMetadata{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Namespace", + }, + }: { + // Read-only + UnsafeDisableDeepCopy: new(true), + }, + } + + if cfg.WorkloadScan { + cacheByObject[&storagev1alpha1.Image{}] = cache.ByObject{ + Label: labels.SelectorFromSet(labels.Set{api.LabelWorkloadScanKey: api.LabelWorkloadScanValue}), + Transform: storage.TransformStripImage, + } + cacheByObject[&storagev1alpha1.WorkloadScanReport{}] = cache.ByObject{ + Transform: storage.TransformStripWorkloadScanReport, + } + cacheByObject[&corev1.Pod{}] = cache.ByObject{ + Transform: controller.TransformStripPod, + // Read-only + UnsafeDisableDeepCopy: new(true), + } + cacheByObject[&metav1.PartialObjectMetadata{ + TypeMeta: metav1.TypeMeta{ + APIVersion: appsv1.SchemeGroupVersion.String(), + Kind: "ReplicaSet", + }, + }] = cache.ByObject{ + // Read-only + UnsafeDisableDeepCopy: new(true), + } + cacheByObject[&metav1.PartialObjectMetadata{ + TypeMeta: metav1.TypeMeta{ + APIVersion: batchv1.SchemeGroupVersion.String(), + Kind: "Job", + }, + }] = cache.ByObject{ + // Read-only + UnsafeDisableDeepCopy: new(true), + } + } + + if cfg.NodeScan { + cacheByObject[&corev1.Node{}] = cache.ByObject{ + Transform: controller.TransformStripNode, + // Read-only + UnsafeDisableDeepCopy: new(true), + } + cacheByObject[&storagev1alpha1.NodeSBOM{}] = cache.ByObject{ + Transform: storage.TransformStripNodeSBOM, + // Read-only + UnsafeDisableDeepCopy: new(true), + } + } + + return cacheByObject +}