sigs.k8s.io/controller-runtime@v0.18.2/designs/use-selectors-at-cache.md (about)

     1  # Filter cache ListWatch using selectors
     2  
     3  ## Motivation
     4  
     5  Controller-Runtime controllers use a cache to subscribe to events from
     6  Kubernetes objects and to read those objects more efficiently by avoiding
     7  to call out to the API. This cache is backed by Kubernetes informers.
     8  
     9  The only way to filter this cache is by namespace and resource type.
    10  In cases where a controller is only interested in a small subset of objects
    11  (for example all pods on a node), this might end up not being efficient enough.
    12  
    13  Requests to a client backed by a filtered cache for objects that do not match
    14  the filter will never return anything, so we need to make sure that we properly
    15  warn users to only use this when they are sure they know what they are doing.
    16  
    17  This proposal sidesteps the issue of "How to we plug this into the cache-backed
    18  client so that users get feedback when they request something that is
    19  not matching the caches filter" by only implementing the filter logic in the
    20  cache package. This allows advanced users to combine a filtered cache with the
    21  already existing `NewCacheFunc` option in the manager and cluster package,
    22  while simultaneously hiding it from newer users that might not be aware of the
    23  implications and the associated foot-shoot potential.
    24  
    25  The only alternative today to get a filtered cache with controller-runtime is
    26  to build it out-of tree. Because such a cache would mostly copy the existing
    27  cache and add just some options, this is not great for consumers.
    28  
    29  This proposal is related to the following issue [2]
    30  
    31  ## Proposal
    32  
    33  Add a new selector code at `pkg/cache/internal/selector.go` with common structs
    34  and helpers
    35  
    36  ```golang
    37  package internal
    38  
    39  ...
    40  
    41  // SelectorsByObject associate a runtime.Object to a field/label selector
    42  type SelectorsByObject map[client.Object]Selector
    43  
    44  // SelectorsByGVK associate a GroupVersionResource to a field/label selector
    45  type SelectorsByGVK map[schema.GroupVersionKind]Selector
    46  
    47  // Selector specify the label/field selector to fill in ListOptions
    48  type Selector struct {
    49  	Label labels.Selector
    50  	Field fields.Selector
    51  }
    52  
    53  // ApplyToList fill in ListOptions LabelSelector and FieldSelector if needed
    54  func (s Selector) ApplyToList(listOpts *metav1.ListOptions) {
    55  ...
    56  }
    57  ```
    58  
    59  Add a type alias to `pkg/cache/cache.go` to internal
    60  
    61  ```golang
    62  type SelectorsByObject internal.SelectorsByObject
    63  ```
    64  
    65  Extend `cache.Options` as follows:
    66  
    67  ```golang
    68  type Options struct {
    69  	Scheme            *runtime.Scheme
    70  	Mapper            meta.RESTMapper
    71  	Resync            *time.Duration
    72  	Namespace         string
    73  	SelectorsByObject SelectorsByObject
    74  }
    75  ```
    76  
    77  Add new builder function that will return a cache constructor using the passed
    78  cache.Options, users can set SelectorsByObject there to filter out cache, it
    79  will convert SelectorByObject to SelectorsByGVK
    80  
    81  ```golang
    82  func BuilderWithOptions(options cache.Options) NewCacheFunc {
    83  ...
    84  }
    85  ```
    86  
    87  is passed to informer's ListWatch and add the filtering option:
    88  
    89  ```golang
    90  
    91  # At pkg/cache/internal/informers_map.go
    92  
    93  ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) {
    94              ip.selectors[gvk].ApplyToList(&opts)
    95  ...
    96  ```
    97  
    98  Here is a PR with the implementatin at the `pkg/cache` part [3]
    99  
   100  ## Example
   101  
   102  User will override `NewCache` function to make clear that they know exactly the
   103  implications of using a different cache than the default one
   104  
   105  ```golang
   106   ctrl.Options.NewCache = cache.BuilderWithOptions(cache.Options{
   107                              SelectorsByObject: cache.SelectorsByObject{
   108                                      &corev1.Node{}: {
   109                                          Field: fields.SelectorFromSet(fields.Set{"metadata.name": "node01"}),
   110                                      }
   111                                      &v1beta1.NodeNetworkState{}: {
   112                                          Field: fields.SelectorFromSet(fields.Set{"metadata.name": "node01"}),
   113                                          Label: labels.SelectorFromSet(labels.Set{"app": "kubernetes-nmstate})",
   114                                      }
   115                                  }
   116                              }
   117                          )
   118  ```
   119  
   120  [1] https://github.com/nmstate/kubernetes-nmstate/pull/687
   121  [2] https://github.com/kubernetes-sigs/controller-runtime/issues/244
   122  [3] https://github.com/kubernetes-sigs/controller-runtime/pull/1404