From 5bfc7a0ce4422990b430fa167d5f567593cb8c4c Mon Sep 17 00:00:00 2001 From: Jitendra Yejare <11752425+jyejare@users.noreply.github.com> Date: Wed, 8 Apr 2026 22:43:55 +0530 Subject: [PATCH 1/2] fix: Harden informer cache with label selectors and memory optimizations Signed-off-by: Jitendra Yejare <11752425+jyejare@users.noreply.github.com> --- infra/feast-operator/cmd/main.go | 31 +++++++++++++++++ .../default/related_image_fs_patch.tmpl | 10 ++++-- .../default/related_image_fs_patch.yaml | 10 ++++-- .../config/manager/manager.yaml | 2 ++ infra/feast-operator/dist/install.yaml | 2 ++ .../internal/controller/authz/authz.go | 1 + .../controller/services/namespace_registry.go | 2 ++ .../internal/controller/services/services.go | 34 +++++++++++++------ .../controller/services/services_types.go | 5 +++ 9 files changed, 83 insertions(+), 14 deletions(-) diff --git a/infra/feast-operator/cmd/main.go b/infra/feast-operator/cmd/main.go index 70f5635fe33..617eb38cd67 100644 --- a/infra/feast-operator/cmd/main.go +++ b/infra/feast-operator/cmd/main.go @@ -25,11 +25,18 @@ import ( // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" + appsv1 "k8s.io/api/apps/v1" + autoscalingv2 "k8s.io/api/autoscaling/v2" + batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" + policyv1 "k8s.io/api/policy/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" @@ -59,6 +66,29 @@ func init() { // +kubebuilder:scaffold:scheme } +func newCacheOptions() cache.Options { + managedBySelector := labels.SelectorFromSet(labels.Set{ + services.ManagedByLabelKey: services.ManagedByLabelValue, + }) + managedByFilter := cache.ByObject{Label: managedBySelector} + + return cache.Options{ + DefaultTransform: cache.TransformStripManagedFields(), + ByObject: map[client.Object]cache.ByObject{ + &corev1.ConfigMap{}: managedByFilter, + &appsv1.Deployment{}: managedByFilter, + &corev1.Service{}: managedByFilter, + &corev1.ServiceAccount{}: managedByFilter, + &corev1.PersistentVolumeClaim{}: managedByFilter, + &rbacv1.RoleBinding{}: managedByFilter, + &rbacv1.Role{}: managedByFilter, + &batchv1.CronJob{}: managedByFilter, + &autoscalingv2.HorizontalPodAutoscaler{}: managedByFilter, + &policyv1.PodDisruptionBudget{}: managedByFilter, + }, + } +} + func main() { var metricsAddr string var enableLeaderElection bool @@ -145,6 +175,7 @@ func main() { // if you are doing or is intended to do any operation such as perform cleanups // after the manager stops then its usage might be unsafe. // LeaderElectionReleaseOnCancel: true, + Cache: newCacheOptions(), Client: client.Options{ Cache: &client.CacheOptions{ DisableFor: []client.Object{ diff --git a/infra/feast-operator/config/default/related_image_fs_patch.tmpl b/infra/feast-operator/config/default/related_image_fs_patch.tmpl index 23bf80c98ba..1a56d98af3b 100644 --- a/infra/feast-operator/config/default/related_image_fs_patch.tmpl +++ b/infra/feast-operator/config/default/related_image_fs_patch.tmpl @@ -1,10 +1,16 @@ +- op: test + path: "/spec/template/spec/containers/0/env/1/name" + value: RELATED_IMAGE_FEATURE_SERVER - op: replace - path: "/spec/template/spec/containers/0/env/0" + path: "/spec/template/spec/containers/0/env/1" value: name: RELATED_IMAGE_FEATURE_SERVER value: ${FS_IMG} +- op: test + path: "/spec/template/spec/containers/0/env/2/name" + value: RELATED_IMAGE_CRON_JOB - op: replace - path: "/spec/template/spec/containers/0/env/1" + path: "/spec/template/spec/containers/0/env/2" value: name: RELATED_IMAGE_CRON_JOB value: ${CJ_IMG} diff --git a/infra/feast-operator/config/default/related_image_fs_patch.yaml b/infra/feast-operator/config/default/related_image_fs_patch.yaml index 6de980383f0..372906b1097 100644 --- a/infra/feast-operator/config/default/related_image_fs_patch.yaml +++ b/infra/feast-operator/config/default/related_image_fs_patch.yaml @@ -1,10 +1,16 @@ +- op: test + path: "/spec/template/spec/containers/0/env/1/name" + value: RELATED_IMAGE_FEATURE_SERVER - op: replace - path: "/spec/template/spec/containers/0/env/0" + path: "/spec/template/spec/containers/0/env/1" value: name: RELATED_IMAGE_FEATURE_SERVER value: quay.io/feastdev/feature-server:0.62.0 +- op: test + path: "/spec/template/spec/containers/0/env/2/name" + value: RELATED_IMAGE_CRON_JOB - op: replace - path: "/spec/template/spec/containers/0/env/1" + path: "/spec/template/spec/containers/0/env/2" value: name: RELATED_IMAGE_CRON_JOB value: quay.io/openshift/origin-cli:4.17 diff --git a/infra/feast-operator/config/manager/manager.yaml b/infra/feast-operator/config/manager/manager.yaml index 2fddf4725ba..4213787e075 100644 --- a/infra/feast-operator/config/manager/manager.yaml +++ b/infra/feast-operator/config/manager/manager.yaml @@ -73,6 +73,8 @@ spec: drop: - "ALL" env: + - name: GOMEMLIMIT + value: "230MiB" - name: RELATED_IMAGE_FEATURE_SERVER value: feast:latest - name: RELATED_IMAGE_CRON_JOB diff --git a/infra/feast-operator/dist/install.yaml b/infra/feast-operator/dist/install.yaml index 93c6937f5e1..1031b7e216e 100644 --- a/infra/feast-operator/dist/install.yaml +++ b/infra/feast-operator/dist/install.yaml @@ -20864,6 +20864,8 @@ spec: command: - /manager env: + - name: GOMEMLIMIT + value: 230MiB - name: RELATED_IMAGE_FEATURE_SERVER value: quay.io/feastdev/feature-server:0.62.0 - name: RELATED_IMAGE_CRON_JOB diff --git a/infra/feast-operator/internal/controller/authz/authz.go b/infra/feast-operator/internal/controller/authz/authz.go index e9811c1c789..2007298ef84 100644 --- a/infra/feast-operator/internal/controller/authz/authz.go +++ b/infra/feast-operator/internal/controller/authz/authz.go @@ -331,6 +331,7 @@ func (authz *FeastAuthorization) getLabels() map[string]string { return map[string]string{ services.NameLabelKey: authz.Handler.FeatureStore.Name, services.ServiceTypeLabelKey: string(services.AuthzFeastType), + services.ManagedByLabelKey: services.ManagedByLabelValue, } } diff --git a/infra/feast-operator/internal/controller/services/namespace_registry.go b/infra/feast-operator/internal/controller/services/namespace_registry.go index c796f4ca6b6..6d83f70f1a0 100644 --- a/infra/feast-operator/internal/controller/services/namespace_registry.go +++ b/infra/feast-operator/internal/controller/services/namespace_registry.go @@ -179,6 +179,7 @@ func (feast *FeastServices) setNamespaceRegistryRoleBinding(rb *rbacv1.RoleBindi ObjectMeta: metav1.ObjectMeta{ Name: roleName, Namespace: rb.Namespace, + Labels: feast.getLabels(), }, } role.Rules = desiredRules @@ -205,6 +206,7 @@ func (feast *FeastServices) setNamespaceRegistryRoleBinding(rb *rbacv1.RoleBindi } } + rb.Labels = feast.getLabels() rb.RoleRef = rbacv1.RoleRef{ APIGroup: "rbac.authorization.k8s.io", Kind: "Role", diff --git a/infra/feast-operator/internal/controller/services/services.go b/infra/feast-operator/internal/controller/services/services.go index d8ff12a9348..c4eb9009cbb 100644 --- a/infra/feast-operator/internal/controller/services/services.go +++ b/infra/feast-operator/internal/controller/services/services.go @@ -408,9 +408,10 @@ func (feast *FeastServices) setDeployment(deploy *appsv1.Deployment) error { } deploy.Labels = feast.getLabels() + selectorLabels := feast.getSelectorLabels() deploy.Spec = appsv1.DeploymentSpec{ Replicas: replicas, - Selector: metav1.SetAsLabelSelector(deploy.GetLabels()), + Selector: metav1.SetAsLabelSelector(selectorLabels), Strategy: feast.getDeploymentStrategy(), Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ @@ -818,7 +819,7 @@ func (feast *FeastServices) setService(svc *corev1.Service, feastType FeastServi } svc.Spec = corev1.ServiceSpec{ - Selector: feast.getLabels(), + Selector: feast.getSelectorLabels(), Type: corev1.ServiceTypeClusterIP, Ports: []corev1.ServicePort{ { @@ -868,6 +869,7 @@ func (feast *FeastServices) setServiceAccount(sa *corev1.ServiceAccount) error { func (feast *FeastServices) createNewPVC(pvcCreate *feastdevv1.PvcCreate, feastType FeastServiceType) (*corev1.PersistentVolumeClaim, error) { pvc := feast.initPVC(feastType) + pvc.Labels = feast.getFeastTypeLabels(feastType) pvc.Spec = corev1.PersistentVolumeClaimSpec{ AccessModes: pvcCreate.AccessModes, @@ -976,7 +978,7 @@ func (feast *FeastServices) applyTopologySpread(podSpec *corev1.PodSpec) { MaxSkew: 1, TopologyKey: "topology.kubernetes.io/zone", WhenUnsatisfiable: corev1.ScheduleAnyway, - LabelSelector: metav1.SetAsLabelSelector(feast.getLabels()), + LabelSelector: metav1.SetAsLabelSelector(feast.getSelectorLabels()), }} } @@ -999,10 +1001,10 @@ func (feast *FeastServices) applyAffinity(podSpec *corev1.PodSpec) { Weight: 100, PodAffinityTerm: corev1.PodAffinityTerm{ TopologyKey: "kubernetes.io/hostname", - LabelSelector: metav1.SetAsLabelSelector(feast.getLabels()), - }, - }}, - }, + LabelSelector: metav1.SetAsLabelSelector(feast.getSelectorLabels()), + }, + }}, + }, } } @@ -1060,12 +1062,24 @@ func (feast *FeastServices) getFeastTypeLabels(feastType FeastServiceType) map[s return labels } -func (feast *FeastServices) getLabels() map[string]string { +// getSelectorLabels returns the minimal label set used for immutable selectors +// (Deployment spec.selector, Service spec.selector, TopologySpreadConstraints, PodAffinity). +// This must NOT change after initial resource creation. +func (feast *FeastServices) getSelectorLabels() map[string]string { return map[string]string{ NameLabelKey: feast.Handler.FeatureStore.Name, } } +// getLabels returns the full label set for mutable metadata (ObjectMeta.Labels). +// Includes the managed-by label used by the informer cache filter. +func (feast *FeastServices) getLabels() map[string]string { + return map[string]string{ + NameLabelKey: feast.Handler.FeatureStore.Name, + ManagedByLabelKey: ManagedByLabelValue, + } +} + func (feast *FeastServices) setServiceHostnames() error { feast.Handler.FeatureStore.Status.ServiceHostnames = feastdevv1.ServiceHostnames{} domain := svcDomain + ":" @@ -1438,10 +1452,10 @@ func IsDeploymentAvailable(conditions []appsv1.DeploymentCondition) bool { // container that is in a failing state. Returns empty string if no failure found. func (feast *FeastServices) GetPodContainerFailureMessage(deploy appsv1.Deployment) string { podList := corev1.PodList{} - labels := feast.getLabels() + selectorLabels := feast.getSelectorLabels() if err := feast.Handler.Client.List(feast.Handler.Context, &podList, client.InNamespace(deploy.Namespace), - client.MatchingLabels(labels), + client.MatchingLabels(selectorLabels), ); err != nil { return "" } diff --git a/infra/feast-operator/internal/controller/services/services_types.go b/infra/feast-operator/internal/controller/services/services_types.go index 5b4479698f3..e5939b6c212 100644 --- a/infra/feast-operator/internal/controller/services/services_types.go +++ b/infra/feast-operator/internal/controller/services/services_types.go @@ -103,6 +103,11 @@ const ( OidcMissingSecretError string = "missing OIDC secret: %s" ) +const ( + ManagedByLabelKey = "app.kubernetes.io/managed-by" + ManagedByLabelValue = "feast-operator" +) + var ( DefaultImage = "quay.io/feastdev/feature-server:" + feastversion.FeastVersion DefaultCronJobImage = "quay.io/openshift/origin-cli:4.17" From e1b57efc5254e954d272042aa716d292128523e2 Mon Sep 17 00:00:00 2001 From: Jitendra Yejare <11752425+jyejare@users.noreply.github.com> Date: Thu, 9 Apr 2026 14:37:27 +0530 Subject: [PATCH 2/2] Additional Fixes on caching with PVC and HPA Signed-off-by: Jitendra Yejare <11752425+jyejare@users.noreply.github.com> --- .../feast-operator.clusterserviceversion.yaml | 2 ++ infra/feast-operator/cmd/main.go | 14 +++++++++ .../default/related_image_fs_patch.tmpl | 30 +++++++++---------- .../default/related_image_fs_patch.yaml | 30 +++++++++---------- infra/feast-operator/dist/install.yaml | 4 +-- .../controller/services/namespace_registry.go | 30 +++++-------------- .../internal/controller/services/scaling.go | 5 ++-- .../internal/controller/services/services.go | 13 ++++---- 8 files changed, 63 insertions(+), 65 deletions(-) diff --git a/infra/feast-operator/bundle/manifests/feast-operator.clusterserviceversion.yaml b/infra/feast-operator/bundle/manifests/feast-operator.clusterserviceversion.yaml index 318c01d0ce8..188b27f3eba 100644 --- a/infra/feast-operator/bundle/manifests/feast-operator.clusterserviceversion.yaml +++ b/infra/feast-operator/bundle/manifests/feast-operator.clusterserviceversion.yaml @@ -266,6 +266,8 @@ spec: command: - /manager env: + - name: GOMEMLIMIT + value: "230MiB" - name: RELATED_IMAGE_FEATURE_SERVER value: quay.io/feastdev/feature-server:0.62.0 - name: RELATED_IMAGE_CRON_JOB diff --git a/infra/feast-operator/cmd/main.go b/infra/feast-operator/cmd/main.go index 617eb38cd67..ead6e93ce72 100644 --- a/infra/feast-operator/cmd/main.go +++ b/infra/feast-operator/cmd/main.go @@ -178,9 +178,23 @@ func main() { Cache: newCacheOptions(), Client: client.Options{ Cache: &client.CacheOptions{ + // Bypass the label-filtered informer cache for all reads so that + // pre-existing resources without the managed-by label are still + // visible to the reconciler. The ByObject cache filter above still + // restricts the watch to managed-by-labeled objects, limiting + // memory usage while avoiding upgrade deadlocks. DisableFor: []client.Object{ &corev1.ConfigMap{}, &corev1.Secret{}, + &appsv1.Deployment{}, + &corev1.Service{}, + &corev1.ServiceAccount{}, + &corev1.PersistentVolumeClaim{}, + &rbacv1.RoleBinding{}, + &rbacv1.Role{}, + &batchv1.CronJob{}, + &autoscalingv2.HorizontalPodAutoscaler{}, + &policyv1.PodDisruptionBudget{}, }, }, }, diff --git a/infra/feast-operator/config/default/related_image_fs_patch.tmpl b/infra/feast-operator/config/default/related_image_fs_patch.tmpl index 1a56d98af3b..11e127dab39 100644 --- a/infra/feast-operator/config/default/related_image_fs_patch.tmpl +++ b/infra/feast-operator/config/default/related_image_fs_patch.tmpl @@ -1,16 +1,14 @@ -- op: test - path: "/spec/template/spec/containers/0/env/1/name" - value: RELATED_IMAGE_FEATURE_SERVER -- op: replace - path: "/spec/template/spec/containers/0/env/1" - value: - name: RELATED_IMAGE_FEATURE_SERVER - value: ${FS_IMG} -- op: test - path: "/spec/template/spec/containers/0/env/2/name" - value: RELATED_IMAGE_CRON_JOB -- op: replace - path: "/spec/template/spec/containers/0/env/2" - value: - name: RELATED_IMAGE_CRON_JOB - value: ${CJ_IMG} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager +spec: + template: + spec: + containers: + - name: manager + env: + - name: RELATED_IMAGE_FEATURE_SERVER + value: ${FS_IMG} + - name: RELATED_IMAGE_CRON_JOB + value: ${CJ_IMG} diff --git a/infra/feast-operator/config/default/related_image_fs_patch.yaml b/infra/feast-operator/config/default/related_image_fs_patch.yaml index 372906b1097..45aecc81735 100644 --- a/infra/feast-operator/config/default/related_image_fs_patch.yaml +++ b/infra/feast-operator/config/default/related_image_fs_patch.yaml @@ -1,16 +1,14 @@ -- op: test - path: "/spec/template/spec/containers/0/env/1/name" - value: RELATED_IMAGE_FEATURE_SERVER -- op: replace - path: "/spec/template/spec/containers/0/env/1" - value: - name: RELATED_IMAGE_FEATURE_SERVER - value: quay.io/feastdev/feature-server:0.62.0 -- op: test - path: "/spec/template/spec/containers/0/env/2/name" - value: RELATED_IMAGE_CRON_JOB -- op: replace - path: "/spec/template/spec/containers/0/env/2" - value: - name: RELATED_IMAGE_CRON_JOB - value: quay.io/openshift/origin-cli:4.17 +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager +spec: + template: + spec: + containers: + - name: manager + env: + - name: RELATED_IMAGE_FEATURE_SERVER + value: quay.io/feastdev/feature-server:0.62.0 + - name: RELATED_IMAGE_CRON_JOB + value: quay.io/openshift/origin-cli:4.17 diff --git a/infra/feast-operator/dist/install.yaml b/infra/feast-operator/dist/install.yaml index 1031b7e216e..d20e5eea324 100644 --- a/infra/feast-operator/dist/install.yaml +++ b/infra/feast-operator/dist/install.yaml @@ -20864,12 +20864,12 @@ spec: command: - /manager env: - - name: GOMEMLIMIT - value: 230MiB - name: RELATED_IMAGE_FEATURE_SERVER value: quay.io/feastdev/feature-server:0.62.0 - name: RELATED_IMAGE_CRON_JOB value: quay.io/openshift/origin-cli:4.17 + - name: GOMEMLIMIT + value: 230MiB - name: OIDC_ISSUER_URL value: "" image: quay.io/feastdev/feast-operator:0.62.0 diff --git a/infra/feast-operator/internal/controller/services/namespace_registry.go b/infra/feast-operator/internal/controller/services/namespace_registry.go index 6d83f70f1a0..dcea98a5764 100644 --- a/infra/feast-operator/internal/controller/services/namespace_registry.go +++ b/infra/feast-operator/internal/controller/services/namespace_registry.go @@ -20,7 +20,6 @@ import ( "encoding/json" "fmt" "os" - "reflect" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -179,31 +178,16 @@ func (feast *FeastServices) setNamespaceRegistryRoleBinding(rb *rbacv1.RoleBindi ObjectMeta: metav1.ObjectMeta{ Name: roleName, Namespace: rb.Namespace, - Labels: feast.getLabels(), }, } - role.Rules = desiredRules + role.SetGroupVersionKind(rbacv1.SchemeGroupVersion.WithKind("Role")) - // Attempt to create; tolerate AlreadyExists so concurrent reconcilers don't fail. - if err := feast.Handler.Client.Create(feast.Handler.Context, role); err != nil && !apierrors.IsAlreadyExists(err) { - return fmt.Errorf("failed to create namespace registry Role: %w", err) - } - - // Re-fetch the authoritative copy to compare rules and obtain the latest resourceVersion. - existingRole := &rbacv1.Role{} - if err := feast.Handler.Client.Get(feast.Handler.Context, types.NamespacedName{ - Name: roleName, - Namespace: rb.Namespace, - }, existingRole); err != nil { - return fmt.Errorf("failed to get namespace registry Role: %w", err) - } - - if !reflect.DeepEqual(existingRole.Rules, desiredRules) { - existingRole.Rules = desiredRules - // On conflict the reconciler will re-queue automatically. - if err := feast.Handler.Client.Update(feast.Handler.Context, existingRole); err != nil { - return fmt.Errorf("failed to update namespace registry Role: %w", err) - } + if _, err := controllerutil.CreateOrUpdate(feast.Handler.Context, feast.Handler.Client, role, func() error { + role.Labels = feast.getLabels() + role.Rules = desiredRules + return nil + }); err != nil { + return fmt.Errorf("failed to reconcile namespace registry Role: %w", err) } rb.Labels = feast.getLabels() diff --git a/infra/feast-operator/internal/controller/services/scaling.go b/infra/feast-operator/internal/controller/services/scaling.go index ef1dd1f91d8..b02dc1eee07 100644 --- a/infra/feast-operator/internal/controller/services/scaling.go +++ b/infra/feast-operator/internal/controller/services/scaling.go @@ -231,7 +231,7 @@ func (feast *FeastServices) buildPDBApplyConfig() *pdbac.PodDisruptionBudgetAppl WithBlockOwnerDeletion(true), ). WithSpec(pdbac.PodDisruptionBudgetSpec(). - WithSelector(metaac.LabelSelector().WithMatchLabels(feast.getLabels())), + WithSelector(metaac.LabelSelector().WithMatchLabels(feast.getSelectorLabels())), ) if pdbConfig.MinAvailable != nil { @@ -249,8 +249,7 @@ func (feast *FeastServices) updateScalingStatus(deploy *appsv1.Deployment) { cr := feast.Handler.FeatureStore cr.Status.Replicas = deploy.Status.ReadyReplicas - labels := feast.getLabels() - cr.Status.Selector = metav1.FormatLabelSelector(metav1.SetAsLabelSelector(labels)) + cr.Status.Selector = metav1.FormatLabelSelector(metav1.SetAsLabelSelector(feast.getSelectorLabels())) if !isScalingEnabled(cr) { cr.Status.ScalingStatus = nil diff --git a/infra/feast-operator/internal/controller/services/services.go b/infra/feast-operator/internal/controller/services/services.go index c4eb9009cbb..16e4d769e36 100644 --- a/infra/feast-operator/internal/controller/services/services.go +++ b/infra/feast-operator/internal/controller/services/services.go @@ -384,10 +384,13 @@ func (feast *FeastServices) createPVC(pvcCreate *feastdevv1.PvcCreate, feastType } // PVCs are immutable, so we only create... we don't update an existing one. + // Treat AlreadyExists as success: a pre-existing PVC without the managed-by label + // won't appear in the filtered cache (Client.Get returns NotFound), but Create + // will hit AlreadyExists on the API server — both cases mean the PVC is present. err = feast.Handler.Client.Get(feast.Handler.Context, client.ObjectKeyFromObject(pvc), pvc) if err != nil && apierrors.IsNotFound(err) { err = feast.Handler.Client.Create(feast.Handler.Context, pvc) - if err != nil { + if err != nil && !apierrors.IsAlreadyExists(err) { return err } logger.Info("Successfully created", "PersistentVolumeClaim", pvc.Name) @@ -1001,10 +1004,10 @@ func (feast *FeastServices) applyAffinity(podSpec *corev1.PodSpec) { Weight: 100, PodAffinityTerm: corev1.PodAffinityTerm{ TopologyKey: "kubernetes.io/hostname", - LabelSelector: metav1.SetAsLabelSelector(feast.getSelectorLabels()), - }, - }}, - }, + LabelSelector: metav1.SetAsLabelSelector(feast.getSelectorLabels()), + }, + }}, + }, } }