-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable filtered list watches as watches #244
Comments
+1 I've run into a similar issue with a controller I'm writing. I wound up starting a goroutine when the controller gets added to watch/act on certain objects that are created and labeled by another controller outside of mine. |
we'd have to specify this at the manager level, but it should be possible to pass restricting items. |
(since caches are shared) |
we could also let users manually specify auxiliary caches |
if anyone has ideas for a design doc, I'd love to see them. |
/kind feature this has been coming up a lot lately, and seems like a good thing to tackle in the list of related issues. |
i.e. one sketch: Have a list-options watch predicate, smartly detect the presence of that (in some generic way) and pass it down to
That last one might solve multiple problems in one fell swoop, but I'd need to see a design sketch to make sure that it's not too arcane. |
I will follow this to understand how this is properly implemented and any questions from a user or testing perspective please ask and I'll do my best to assist. In the interim I've implemented this for my own use case.
|
That's just like a predicate, though -- it doesn't actually get you most of the benefits of the filtering that people are asking for here, since it doesn't ask for server-side filtering. |
I know... As I said above the code block
|
Ack, just wanted to make sure we were on the same page :-) |
Issues go stale after 90d of inactivity. If this issue is safe to close now please do so with Send feedback to sig-testing, kubernetes/test-infra and/or fejta. |
Stale issues rot after 30d of inactivity. If this issue is safe to close now please do so with Send feedback to sig-testing, kubernetes/test-infra and/or fejta. |
/remove-lifecycle stale |
Rotten issues close after 30d of inactivity. Send feedback to sig-testing, kubernetes/test-infra and/or fejta. |
@fejta-bot: Closing this issue. In response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
/reopen |
@alvaroaleman: Reopened this issue. In response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
bumping this feature request:
I think this is a viable option, but I wonder if we could have a more straightforward pattern like a user specifies a watch with a label. If they do, then we use the label-filtered cache for that specific GVK. If another watch is created without that label-filter, then we will error out and not establish the watch. The most significant caveat that I see is the client will only use the label filtered cache. We would need to make that clear in the documentation, or is there some other mechanism to alert the user to this. |
The controller-runtime is missing filtering it's cache by labels or fields [1], this means that all the kubernetes-nmstate-handlers will read all the nodes and nodenetworkstates every period, clearly dies does not scale since kubernetes-nmstate-handler runs at as daemonset meaning that there is one handler running at every node so the bigger the cluster the bigger the problem. This change replace the default controller-runtime cache with an implementation that can be configured to use some field selectors depending on the resource, this way we can filter by "metadata.name" using the node name for "node" and "nodenetworkstate" so only one instance of them is feteched. [1] kubernetes-sigs/controller-runtime#244 Signed-off-by: Quique Llorente <[email protected]>
The controller-runtime is missing filtering it's cache by labels or fields [1], this means that all the kubernetes-nmstate-handlers will read all the nodes and nodenetworkstates every period, clearly dies does not scale since kubernetes-nmstate-handler runs at as daemonset meaning that there is one handler running at every node so the bigger the cluster the bigger the problem. This change replace the default controller-runtime cache with an implementation that can be configured to use some field selectors depending on the resource, this way we can filter by "metadata.name" using the node name for "node" and "nodenetworkstate" so only one instance of them is feteched. [1] kubernetes-sigs/controller-runtime#244 Signed-off-by: Quique Llorente <[email protected]>
The controller-runtime is missing filtering it's cache by labels or fields [1], this means that all the kubernetes-nmstate-handlers will read all the nodes and nodenetworkstates every period, clearly dies does not scale since kubernetes-nmstate-handler runs at as daemonset meaning that there is one handler running at every node so the bigger the cluster the bigger the problem. This change replace the default controller-runtime cache with an implementation that can be configured to use some field selectors depending on the resource, this way we can filter by "metadata.name" using the node name for "node" and "nodenetworkstate" so only one instance of them is feteched. [1] kubernetes-sigs/controller-runtime#244 Signed-off-by: Quique Llorente <[email protected]>
Any updates on this? |
I have try a PR to specify a fieldselector at manager build time #1404. |
The controller-runtime is missing filtering it's cache by labels or fields [1], this means that all the kubernetes-nmstate-handlers will read all the nodes and nodenetworkstates every period, clearly dies does not scale since kubernetes-nmstate-handler runs at as daemonset meaning that there is one handler running at every node so the bigger the cluster the bigger the problem. This change replace the default controller-runtime cache with an implementation that can be configured to use some field selectors depending on the resource, this way we can filter by "metadata.name" using the node name for "node" and "nodenetworkstate" so only one instance of them is feteched. [1] kubernetes-sigs/controller-runtime#244 Signed-off-by: Quique Llorente <[email protected]>
New PR: #1435 |
#1435 is now merged, so is this resolved? |
It is being worked into a release currently @invidian Looking at the tags there is a v0.9.0-beta available if you want to test. |
Thanks, will do! |
@estroz: Closing this issue. In response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
It seems like the current solution doesn't address the use case mentioned in It should be possible to create a namespace scoped client in ListFunc and WatchFunc if there is a field selector for |
@roee88 what was merged allows to set a FieldSelector so yes, this is possible |
To clarify, I suspect that without affecting the parameters to NamespaceIfScoped, the selector opts have no effect on the required rbac (cluster role bindings vs role bindings). For example here: controller-runtime/pkg/cache/internal/informers_map.go Lines 282 to 288 in 64b1c72
My question is whether changing the code here makes sense. Specifically for the case where ip.namespace is empty, the value from a cc @shlomitk1 |
That sounds reasonable |
My recommendation is to do something similar to this PR in external-secrets: Disable Caching Manager's Default ClientYou can disable the cache for specific resource kind in your For example, your outer import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"log/slog"
"os"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
)
func main() {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
restConfig := ctrl.GetConfigOrDie()
scheme := runtime.NewScheme()
// add default Kubernetes types to the scheme
if err := clientgoscheme.AddToScheme(scheme); err != nil {
logger.Error("failed to add Kubernetes types to scheme", "error", err)
os.Exit(1)
}
// add your custom types to the scheme
//if err := XXXXv1beta1.AddToScheme(scheme); err != nil {
// logger.Error("failed to add XXXX types to scheme", "error", err)
// os.Exit(1)
//}
ctrlOpts := ctrl.Options{
Scheme: scheme,
Client: client.Options{
Cache: &client.CacheOptions{
// this disables caching for all Secrets and ConfigMaps
// as caching all secrets can take a LOT of memory in a large cluster
DisableFor: []client.Object{
&corev1.Secret{},
&corev1.ConfigMap{},
},
},
},
Metrics: metricsserver.Options{
BindAddress: "0", // not required, just for example
},
HealthProbeBindAddress: "0", // not required, just for example
LeaderElection: false, // not required, just for example
}
// create a new manager
mgr, err := ctrl.NewManager(restConfig, ctrlOpts)
if err != nil {
logger.Error("unable to create manager", "error", err)
os.Exit(1)
}
//
// ADD THE BuildManagedSecretClient() FUNCTION HERE
// SEE THE NEXT SNIPPET FOR AN EXAMPLE
//
// start the manager
logger.Info("starting manager")
err = mgr.Start(ctrl.SetupSignalHandler())
if err != nil {
logger.Error("unable to start manager", "error", err)
os.Exit(1)
}
} Example of Filtered Cached ClientYou can construct a new filtered client using (WARNING: a client which uses a filtered cache will be completely unable to see resources in the cluster which don't match the cache, even if they exist) For example, here is the one from external-secrets which only caches secrets with the import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
)
func BuildManagedSecretClient(mgr ctrl.Manager) (client.Client, error) {
// secrets we manage will have the `reconcile.external-secrets.io/managed=true` label
managedLabelReq, _ := labels.NewRequirement("reconcile.external-secrets.io/managed", selection.Equals, []string{"true"})
managedLabelSelector := labels.NewSelector().Add(*managedLabelReq)
// create a new cache with a label selector for managed secrets
// NOTE: this means that the cache/client will be unable to see secrets without the "managed" label
secretCacheOpts := cache.Options{
HTTPClient: mgr.GetHTTPClient(),
Scheme: mgr.GetScheme(),
Mapper: mgr.GetRESTMapper(),
ByObject: map[client.Object]cache.ByObject{
&corev1.Secret{}: {
Label: managedLabelSelector,
},
},
// this requires us to explicitly start an informer for each object type
// and helps avoid people mistakenly using the secret client for other resources
ReaderFailOnMissingInformer: true,
}
secretCache, err := cache.New(mgr.GetConfig(), secretCacheOpts)
if err != nil {
return nil, err
}
// start an informer for secrets
// this is required because we set ReaderFailOnMissingInformer to true
_, err = secretCache.GetInformer(context.Background(), &corev1.Secret{})
if err != nil {
return nil, err
}
// add the secret cache to the manager, so that it starts at the same time
err = mgr.Add(secretCache)
if err != nil {
return nil, err
}
// create a new client that uses the secret cache
secretClient, err := client.New(mgr.GetConfig(), client.Options{
HTTPClient: mgr.GetHTTPClient(),
Scheme: mgr.GetScheme(),
Mapper: mgr.GetRESTMapper(),
Cache: &client.CacheOptions{
Reader: secretCache,
},
})
if err != nil {
return nil, err
}
return secretClient, nil
} |
When setting up watches during initialization it's currently not possible to filter by any selectors (which is possible using list watches).
For example it is not possible to only watch pods with specific labels (e.g having the label
pod-type: my-controller-type
). The current behavior results in very broad caching, which might not be desirable for large Kubernetes deployments.In some scenarios an operator could contain multiple controllers, and they all share caches, so keying caches on GVK's alone might be problematic if they want to watch the same resource type, but with different filters.
When doing
List
/Get
, how would one decide which of the caches to use? It seems that perhaps this needs to be an explicit choice by the operator developers?The text was updated successfully, but these errors were encountered: