This paper is divided into four parts:
(1). Cache package source code analysis and the use of Informer;
(2). Informers package source code parsing and the use of SharedInformerFactory, and the best practice of Informer in the actual use;
(3). Implementation of custom resource (CRD) Informer;
(4). The dynamic package source code parsing and DynamicSharedInformerFactory use;
This is part 4.
All code used in this article can be found in Articles /archive/ dive-into-Kubernetes-Informer at Main · wbsnail/articles · GitHub.
preface
In the previous part, I introduced the implementation mechanism and use of Kubernetes Informer with the source code. Thanks to the design of Kubernetes, we can also use the same way to manipulate built-in resource types and custom resources. But is there a situation where we need to control resources dynamically, and the type of resources is not determined before the code runs?
There are scenarios, such as kubeBigbrother, a project I recently started that monitors specific field changes to a specified resource type in a configuration, and there is no way to determine which resource Informer needs to be started and which does not before it runs. Using the switch statement to determine resource types is not practical because Kubernetes has so many resources built into it, and I would like to be able to support custom resource monitoring. So I needed to find a way to run Informer dynamically.
👮♂️ If you haven’t seen the previous section, I suggest you finish it first.
The dynamic package
Fortunately, client-Go has a Dynamic package that implements this part of the functionality we need.
package main
import (
"fmt"
"github.com/spongeprojects/magicconch"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/dynamic/dynamicinformer"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/clientcmd"
"os"
)
func main(a) {
if len(os.Args) < 2 {
fmt.Println("resource missing")
return
}
/ / resources, such as "configmaps. V1.", "deployments. V1. Apps", "rabbits.v1.stable.wbsnail.com"
resource := os.Args[1]
kubeconfig := os.Getenv("KUBECONFIG")
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
magicconch.Must(err)
// Note that dynamicClient is created instead of clientset
dynamicClient, err := dynamic.NewForConfig(config)
magicconch.Must(err)
/ / here is also same DynamicSharedInformerFactory, not SharedInformerFactory
informerFactory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(
dynamicClient, 0."tmp".nil)
// ParseResourceArg provided by the schema package resolves the GroupVersionResource from the resource description string
gvr, _ := schema.ParseResourceArg(resource)
if gvr == nil {
fmt.Println("cannot parse gvr")
return
}
// Use GVR to dynamically generate Informer
informer := informerFactory.ForResource(*gvr).Informer()
// Familiar code, familiar taste, but the obJ type received seems to be different
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
/ / * unstructured. Unstructured class is all public methods Kubernetes resource type of abstraction,
// Provide access to all public attributes like GetName, GetNamespace, GetLabels, etc.
s, ok := obj.(*unstructured.Unstructured)
if! ok {return
}
fmt.Printf("created: %s\n", s.GetName())
},
UpdateFunc: func(oldObj, newObj interface{}) {
oldS, ok1 := oldObj.(*unstructured.Unstructured)
newS, ok2 := newObj.(*unstructured.Unstructured)
if! ok1 || ! ok2 {return
}
// To access fields other than public attributes, you can use some of the helper methods provided by unstructured packages:
oldColor, ok1, err1 := unstructured.NestedString(oldS.Object, "spec"."color")
newColor, ok2, err2 := unstructured.NestedString(newS.Object, "spec"."color")
if! ok1 || ! ok2 || err1 ! =nil|| err2 ! =nil {
fmt.Printf("updated: %s\n", newS.GetName())
}
fmt.Printf("updated: %s, old color: %s, new color: %s\n", newS.GetName(), oldColor, newColor)
},
DeleteFunc: func(obj interface{}) {
s, ok := obj.(*unstructured.Unstructured)
if! ok {return
}
fmt.Printf("deleted: %s\n", s.GetName())
},
})
stopCh := make(chan struct{})
defer close(stopCh)
fmt.Println("Start syncing....")
go informerFactory.Start(stopCh)
<-stopCh
}
Copy the code
Needless to say, it’s all in the code comments. You can see that none of the resource types are introduced.
Try running it:
❯ go run.configmaps.v1. Start syncing.... created: demo created: demo1 deleted: demo created: demoCopy the code
Try using it to monitor the custom resource Rabbit we created in the previous section:
❯ go run. Rabbits.v1.stable.wbsnail.com Start syncing... created: judy created: bugs updated: judy, old color: white, new color: blackCopy the code
With the help of dynamic package, it is very easy to realize the monitoring of dynamic resource types. The reason is that Kubernetes unified interface for built-in resources and custom resources, which makes it easy to abstract out the unified processing mode, reflecting the superiority of Kubernetes resource type design.
conclusion
In the content of the above, I analyzed the dynamic package source code for everyone, and introduces the use of DynamicSharedInformerFactory, about “Kubernetes Informer source code parsing and depth of the use of” series of all content to the end. If you read this series, you can develop a stable and reliable Kubernetes controller 👍
Use the original address: Kubernetes Informer source code parsing and depth (4/4) : the use of the dynamic package source code parsing and DynamicSharedInformerFactory
The resources
Bitnami Engineering: A deep dive into Kubernetes controllers
Bitnami Engineering: Kubewatch, an example of Kubernetes custom controller
Dynamic Kubernetes Informers | FireHydrant
Go at master · Kubernetes /client-go · GitHub
GitHub – kubernetes/sample-controller: Repository for sample controller. Complements sample-apiserver
Kubernetes Deep Dive: Code Generation for CustomResources
How to generate client codes for Kubernetes Custom Resource Definitions (CRD) | by Roger Liang | ITNEXT