Skip to content

[shell-operator] Let an embedding application transform objects before caching - #962

Open
yalosev wants to merge 1 commit into
mainfrom
informer-object-transform
Open

yalosev wants to merge 1 commit into
mainfrom
informer-object-transform

Conversation

@yalosev

@yalosev yalosev commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

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>
@yalosev yalosev added the enhancement New feature or request label Nov 3, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant