Skip to content

[shell-operator] feat: let an embedding application transform objects - #964

Merged
ldmonster merged 1 commit into
release-1.20from
informer-object-transform-release-1.20
Nov 6, 2026
Merged

ldmonster merged 1 commit into
release-1.20from
informer-object-transform-release-1.20

Conversation

@fuldaxxx

@fuldaxxx fuldaxxx commented Nov 5, 2026 •

Copy link
Copy Markdown
Member

Overview

Add the WithObjectTransform option. It lets an application that embeds shell-operator transform every Kubernetes object before the shared informers cache it and the hooks filter it.

What this PR does / why we need it

Informers cache full objects, including fields that no hook reads. On large clusters these fields take most of the operator memory. A heap profile of deckhouse-controller with the DeckhouseHighMemoryUsage alert firing showed 2.8 GB of 3.2 GB of heap held by informer caches, about half of it in metadata.managedFields. Node objects also carry status.images: every node update makes each Go hook filter convert the whole image list.

Which fields are safe to drop depends on the hooks, so shell-operator keeps objects as is by default. An embedding application opts in with a function:

import (
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

	shelloperator "github.com/flant/shell-operator/pkg/shell-operator"
)

// stripUnusedFields drops fields that none of our hooks read.
// It must be idempotent: client-go may apply it to an object again.
func stripUnusedFields(obj any) (any, error) {
	u, ok := obj.(*unstructured.Unstructured)
	if !ok {
		// Keep anything else, e.g. cache.DeletedFinalStateUnknown, as is.
		return obj, nil
	}

	unstructured.RemoveNestedField(u.Object, "metadata", "managedFields")

	if u.GetAPIVersion() == "v1" && u.GetKind() == "Node" {
		unstructured.RemoveNestedField(u.Object, "status", "images")
	}

	return u, nil
}

op, err := shelloperator.NewShellOperator(ctx, cfg,
	shelloperator.WithObjectTransform(stripUnusedFields),
)

The same option works with NewBareShellOperator: it is applied when SetupEventManagers creates the kube events manager.

What changes:

  • FactoryStore.WithTransform stores the function, and every new shared informer gets it through SetTransform. It covers the cache and the watch events.
  • loadExistedObjects applies the same function to the initial list. Without it, the first snapshot checksums would differ from the later watch events, and hooks would get one spurious event per object.
  • kubeEventsManager.WithObjectTransform passes the function to the store. The KubeEventsManager interface is unchanged.

Special notes for your reviewer

  • The transform must return *unstructured.Unstructured for unstructured input. Otherwise the initial list fails with an error naming the returned type.
  • Informers already running when the transform is set keep their objects as is. The option is meant to be set at construction time.
  • TestFactoryStore_TransformsObjects covers both paths. It fails when either the informer transform or the initial list transform is disabled.
  • Follow-up: an addon-operator option that passes the function through, then deckhouse uses it to drop managedFields and node status.images.

… before caching

Add the WithObjectTransform option. The function is applied to every
Kubernetes object before the shared informers cache it and the hooks
filter it, so an embedding application can drop fields no hook reads,
such as metadata.managedFields, and cut the informer cache size.

The transform is set on every shared informer and also applied to the
initial list in loadExistedObjects, so the first snapshot checksums match
the later watch events. Without the option objects are kept as is.

Signed-off-by: Yuriy Losev <yuriy.losev@flant.com>
(cherry picked from commit 53d7481)
@fuldaxxx
fuldaxxx requested a review from ldmonster November 5, 2026 11:44
@fuldaxxx fuldaxxx self-assigned this Nov 5, 2026
@fuldaxxx fuldaxxx added the dependencies Pull requests that update a dependency file label Nov 5, 2026
@fuldaxxx
fuldaxxx marked this pull request as ready for review November 6, 2026 08:08
@ldmonster
ldmonster merged commit 5a63704 into release-1.20 Nov 6, 2026
13 checks passed
@ldmonster
ldmonster deleted the informer-object-transform-release-1.20 branch November 6, 2026 11:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants