Skip to content

Commit 7ab70c8

Browse files
committed
helm-operator: reduce cache memory footprint
Make use of label selectors used by informers for both the primary CR and for chart manifest objects Signed-off-by: Joe Lanford <[email protected]>
1 parent e859a56 commit 7ab70c8

File tree

7 files changed

+102
-81
lines changed

7 files changed

+102
-81
lines changed

go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ require (
4747
sigs.k8s.io/yaml v1.3.0
4848
)
4949

50+
replace sigs.k8s.io/controller-runtime => github.com/joelanford/controller-runtime v0.9.0-alpha.1.0.20230324193000-1dcfec4f9c4c
51+
5052
require (
5153
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
5254
github.com/BurntSushi/toml v1.2.0 // indirect

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,8 @@ github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
610610
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
611611
github.com/joefitzgerald/rainbow-reporter v0.1.0 h1:AuMG652zjdzI0YCCnXAqATtRBpGXMcAnrajcaTrSeuo=
612612
github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8=
613+
github.com/joelanford/controller-runtime v0.9.0-alpha.1.0.20230324193000-1dcfec4f9c4c h1:kngRvCr+6sJhYlnjg34Spfl3RHBDJHMr80xIljwaxQ8=
614+
github.com/joelanford/controller-runtime v0.9.0-alpha.1.0.20230324193000-1dcfec4f9c4c/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0=
613615
github.com/joelanford/ignore v0.0.0-20210607151042-0d25dc18b62d h1:A2/B900ip/Z20TzkLeGRNy1s6J2HmH9AmGt+dHyqb4I=
614616
github.com/joelanford/ignore v0.0.0-20210607151042-0d25dc18b62d/go.mod h1:7HQupe4vyNxMKXmM5DFuwXHsqwMyglcYmZBtlDPIcZ8=
615617
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
@@ -1710,8 +1712,6 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
17101712
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
17111713
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.35 h1:+xBL5uTc+BkPBwmMi3vYfUJjq+N3K+H6PXeETwf5cPI=
17121714
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.35/go.mod h1:WxjusMwXlKzfAs4p9km6XJRndVt2FROgMVCE4cdohFo=
1713-
sigs.k8s.io/controller-runtime v0.14.5 h1:6xaWFqzT5KuAQ9ufgUaj1G/+C4Y1GRkhrxl+BJ9i+5s=
1714-
sigs.k8s.io/controller-runtime v0.14.5/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0=
17151715
sigs.k8s.io/controller-tools v0.11.3 h1:T1xzLkog9saiyQSLz1XOImu4OcbdXWytc5cmYsBeBiE=
17161716
sigs.k8s.io/controller-tools v0.11.3/go.mod h1:qcfX7jfcfYD/b7lAhvqAyTbt/px4GpvN88WKLFFv7p8=
17171717
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k=

internal/cmd/helm-operator/run/cmd.go

+48-5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525

2626
"github.com/spf13/cobra"
2727
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+
apimachruntime "k8s.io/apimachinery/pkg/runtime"
2829
"k8s.io/client-go/rest"
2930
ctrl "sigs.k8s.io/controller-runtime"
3031
"sigs.k8s.io/controller-runtime/pkg/cache"
@@ -45,6 +46,10 @@ import (
4546
"github.com/operator-framework/operator-sdk/internal/helm/watches"
4647
"github.com/operator-framework/operator-sdk/internal/util/k8sutil"
4748
sdkVersion "github.com/operator-framework/operator-sdk/internal/version"
49+
"helm.sh/helm/v3/pkg/chart/loader"
50+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
51+
"k8s.io/apimachinery/pkg/labels"
52+
"k8s.io/apimachinery/pkg/selection"
4853
)
4954

5055
var log = logf.Log.WithName("cmd")
@@ -136,6 +141,49 @@ func run(cmd *cobra.Command, f *flags.Flags) {
136141
// Set default manager options
137142
options = f.ToManagerOptions(options)
138143

144+
if options.Scheme == nil {
145+
options.Scheme = apimachruntime.NewScheme()
146+
}
147+
148+
ws, err := watches.Load(f.WatchesFile)
149+
if err != nil {
150+
log.Error(err, "Failed to create new manager factories.")
151+
os.Exit(1)
152+
}
153+
154+
selectorsByObject := cache.SelectorsByObject{}
155+
chartNames := make([]string, 0, len(ws))
156+
for _, w := range ws {
157+
options.Scheme.AddKnownTypeWithName(w.GroupVersionKind, &unstructured.Unstructured{})
158+
159+
crObj := &unstructured.Unstructured{}
160+
crObj.SetGroupVersionKind(w.GroupVersionKind)
161+
sel, err := metav1.LabelSelectorAsSelector(&w.Selector)
162+
if err != nil {
163+
log.Error(err, "Unable to parse selector")
164+
os.Exit(1)
165+
}
166+
selectorsByObject[crObj] = cache.ObjectSelector{Label: sel}
167+
168+
chrt, err := loader.LoadDir(w.ChartDir)
169+
if err != nil {
170+
log.Error(err, "Unable to load chart", "directory", w.ChartDir)
171+
os.Exit(1)
172+
}
173+
chartNames = append(chartNames, chrt.Name())
174+
}
175+
176+
req, err := labels.NewRequirement("helm.sdk.operatorframework.io/chart", selection.In, chartNames)
177+
if err != nil {
178+
log.Error(err, "Unable to create label requirement for cache default selector")
179+
os.Exit(1)
180+
}
181+
defaultSelector := labels.NewSelector().Add(*req)
182+
options.NewCache = cache.BuilderWithOptions(cache.Options{
183+
SelectorsByObject: selectorsByObject,
184+
DefaultSelector: cache.ObjectSelector{Label: defaultSelector},
185+
})
186+
139187
if options.NewClient == nil {
140188
options.NewClient = func(cache cache.Cache, config *rest.Config, options client.Options, uncachedObjects ...client.Object) (client.Client, error) {
141189
// Create the Client for Write operations.
@@ -189,11 +237,6 @@ func run(cmd *cobra.Command, f *flags.Flags) {
189237
os.Exit(1)
190238
}
191239

192-
ws, err := watches.Load(f.WatchesFile)
193-
if err != nil {
194-
log.Error(err, "Failed to create new manager factories.")
195-
os.Exit(1)
196-
}
197240
acg, err := helmClient.NewActionConfigGetter(mgr.GetConfig(), mgr.GetRESTMapper(), mgr.GetLogger())
198241
if err != nil {
199242
log.Error(err, "Failed to create Helm action config getter")

internal/helm/client/client.go

+42
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,45 @@ func containsResourcePolicyKeep(annotations map[string]string) bool {
105105
resourcePolicyType = strings.ToLower(strings.TrimSpace(resourcePolicyType))
106106
return resourcePolicyType == kube.KeepPolicy
107107
}
108+
109+
type labelInjectingClient struct {
110+
kube.Interface
111+
labels map[string]string
112+
}
113+
114+
func NewLabelInjectingClient(base kube.Interface, labels map[string]string) kube.Interface {
115+
return &labelInjectingClient{
116+
Interface: base,
117+
labels: labels,
118+
}
119+
}
120+
121+
func (c *labelInjectingClient) Build(reader io.Reader, validate bool) (kube.ResourceList, error) {
122+
resourceList, err := c.Interface.Build(reader, validate)
123+
if err != nil {
124+
return resourceList, err
125+
}
126+
err = resourceList.Visit(func(r *resource.Info, err error) error {
127+
if err != nil {
128+
return err
129+
}
130+
objMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(r.Object)
131+
if err != nil {
132+
return err
133+
}
134+
u := &unstructured.Unstructured{Object: objMap}
135+
labels := u.GetLabels()
136+
if labels == nil {
137+
labels = map[string]string{}
138+
}
139+
for k, v := range c.labels {
140+
labels[k] = v
141+
}
142+
u.SetLabels(labels)
143+
return nil
144+
})
145+
if err != nil {
146+
return nil, err
147+
}
148+
return resourceList, nil
149+
}

internal/helm/controller/controller.go

+4-35
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ package controller
1616

1717
import (
1818
"fmt"
19-
"reflect"
2019
"strings"
2120
"sync"
2221
"time"
@@ -31,7 +30,6 @@ import (
3130
crthandler "sigs.k8s.io/controller-runtime/pkg/handler"
3231
logf "sigs.k8s.io/controller-runtime/pkg/log"
3332
"sigs.k8s.io/controller-runtime/pkg/manager"
34-
ctrlpredicate "sigs.k8s.io/controller-runtime/pkg/predicate"
3533
"sigs.k8s.io/controller-runtime/pkg/source"
3634
"sigs.k8s.io/yaml"
3735

@@ -69,10 +67,6 @@ func Add(mgr manager.Manager, options WatchOptions) error {
6967
OverrideValues: options.OverrideValues,
7068
}
7169

72-
// Register the GVK with the schema
73-
mgr.GetScheme().AddKnownTypeWithName(options.GVK, &unstructured.Unstructured{})
74-
metav1.AddToGroupVersion(mgr.GetScheme(), options.GVK.GroupVersion())
75-
7670
c, err := controller.New(controllerName, mgr, controller.Options{
7771
Reconciler: r,
7872
MaxConcurrentReconciles: options.MaxConcurrentReconciles,
@@ -84,18 +78,7 @@ func Add(mgr manager.Manager, options WatchOptions) error {
8478
o := &unstructured.Unstructured{}
8579
o.SetGroupVersionKind(options.GVK)
8680

87-
var preds []ctrlpredicate.Predicate
88-
p, err := parsePredicateSelector(options.Selector)
89-
90-
if err != nil {
91-
return err
92-
}
93-
94-
if p != nil {
95-
preds = append(preds, p)
96-
}
97-
98-
if err := c.Watch(&source.Kind{Type: o}, &libhandler.InstrumentedEnqueueRequestForObject{}, preds...); err != nil {
81+
if err := c.Watch(&source.Kind{Type: o}, &libhandler.InstrumentedEnqueueRequestForObject{}); err != nil {
9982
return err
10083
}
10184

@@ -108,29 +91,15 @@ func Add(mgr manager.Manager, options WatchOptions) error {
10891
return nil
10992
}
11093

111-
// parsePredicateSelector parses the selector in the WatchOptions and creates a predicate
112-
// that is used to filter resources based on the specified selector
113-
func parsePredicateSelector(selector metav1.LabelSelector) (ctrlpredicate.Predicate, error) {
114-
// If a selector has been specified in watches.yaml, add it to the watch's predicates.
115-
if !reflect.ValueOf(selector).IsZero() {
116-
p, err := ctrlpredicate.LabelSelectorPredicate(selector)
117-
if err != nil {
118-
return nil, fmt.Errorf("error constructing predicate from watches selector: %v", err)
119-
}
120-
return p, nil
121-
}
122-
return nil, nil
123-
}
124-
12594
// watchDependentResources adds a release hook function to the HelmOperatorReconciler
12695
// that adds watches for resources in released Helm charts.
12796
func watchDependentResources(mgr manager.Manager, r *HelmOperatorReconciler, c controller.Controller) {
128-
owner := &unstructured.Unstructured{}
129-
owner.SetGroupVersionKind(r.GVK)
130-
13197
var m sync.RWMutex
13298
watches := map[schema.GroupVersionKind]struct{}{}
13399
releaseHook := func(release *rpb.Release) error {
100+
owner := &unstructured.Unstructured{}
101+
owner.SetGroupVersionKind(r.GVK)
102+
owner.SetNamespace(release.Namespace)
134103
resources := releaseutil.SplitManifests(release.Manifest)
135104
for _, resource := range resources {
136105
var u unstructured.Unstructured

internal/helm/controller/controller_test.go

-39
This file was deleted.

internal/helm/release/manager_factory.go

+4
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ func (f managerFactory) NewManager(cr *unstructured.Unstructured, overrideValues
5858
return nil, fmt.Errorf("failed to load chart dir: %w", err)
5959
}
6060

61+
actionConfig.KubeClient = client.NewLabelInjectingClient(actionConfig.KubeClient, map[string]string{
62+
"helm.sdk.operatorframework.io/chart": crChart.Name(),
63+
})
64+
6165
releaseName, err := getReleaseName(actionConfig.Releases, crChart.Name(), cr)
6266
if err != nil {
6367
return nil, fmt.Errorf("failed to get helm release name: %w", err)

0 commit comments

Comments
 (0)