Try   HackMD

client-go Basic Usage

(without using Shared Informer)

https://pkg.go.dev/k8s.io/client-go@v0.20.0/kubernetes

Listing and getting ConfigMaps

clientset := kubernetes.New(cfg)
resp, err := clientset.CoreV1().ConfigMaps(ns).List(...)
cm, err := clientset.CoreV1().ConfigMaps(ns).Get(...)

Each call to List or Get makes a new connection to the API server and reads from etcd. These calls are subject to API Priority and Fairness, rate limits, exponential backoff, etc.

Watching ConfigMap changes

w, err := clientset.CoreV1().ConfigMaps(ns).Watch(...) for e := range w.ResultChan() { cm, ok := e.Object.(*corev1.ConfigMap) switch e.Type { case watch.Added: // TODO: handle new ConfigMap case watch.Modified: // TODO: handle updated ConfigMap } }

And to stop:

w.Stop()

Each call to Watch opens a long-running connection to the API server, which is closed when you Stop.

Optimizing Reads

https://pkg.go.dev/k8s.io/client-go@v0.20.0/informers#NewSharedInformerFactoryWithOptions

Instead of using the client directly, you can pass it to a SharedInformerFactory, which you can use to create "Informers", which you can use to watch for updates:

sif := NewSharedInformerFactoryWithOptions(...) cminf := sif.Core().V1().ConfigMaps() stopCh := make(chan struct{}) inf := cminf.Informer() inf.AddEventHandler(cache.ResourceEventHandlerFuncs{ OnAdd: func(obj interface{}) { cm, ok := obj.(*corev1.ConfigMap) }, OnUpdate: func(oldCM, newCM interface{}) { // TODO: do something with the update. }, OnDelete: func(obj interface{}) { ... }, }) inf.Run(stopCh)

And to stop the watcher:

close(stopCh)

Or, to list or get objects:

// List all ConfigMaps in a namespace, or get one by name. lis := cminf.Lister() resp, err := lis.ConfigMaps(ns).List(...) cm, err := lis.ConfigMaps(ns).Get(...) ...

These calls are served from the shared informer's cache, which it keeps up-to-date by initiating a single Watch request.

Benefits of SharedInformerFactory

  • update events give you both old and new, don't require another API call to get previous version
    • old can also be ignored, it didn't really cost anything to read it
  • it does a single Watch on the type and populates a shared indexed cache
  • clients of the Informer and Lister pull from the cache

NewSharedInformerFactoryWithOptions can also take a TweakListOptions to further limit the scope of the watch, if you only care about objects with a specific label, for example.

Disadvantages

  • sometimes the cache can be out-of-date, especially immediately after an update
    • in Tekton's experience, sometimes a second or two; not usually minutes
    • you can build in retries to account for that
    • fallback to direct client-go Get/List/Watch if you absolutely need consistent reads

Make sure to use a single SharedInformerFactory which you pass around and reuse, which lets all informers/listers created from it share a cache and index. Each SharedInformerFactory makes its own Watch connections and keeps its own cache.