Kubernetes Informer Pattern
Hello! This is the second post of the blog. Today I will talk about informer pattern in Kubernetes.
Traditionally, Kubernetes resources are being retrieved via the APIServer through http requests, typically via clientset.{API_VERSION}.{RESOURCE}.{ACTION}. This creates excessive pressure when the API is called again to check for updates in the Kubernetes cluster.
The informer pattern is designed to solve this problem.
The informer pattern allow us to alleviate presure on the API server through the usage of cache and events
There are 2 parts to this pattern.
- K8s custom controller
- client-go
I will mainly go through the implementation of client-go. Under the hood, it comprises of a cache and an indexer. Client-go will only call 2 APIs, List/Watch and will only call them once during the resync period, and then it adds the data into the cache. The client-go indexes the data based on namespace, label and annotation.
Below is an simple implementation, which retrieves the cluster authentication via .kube/config and then create the informer factory using NewSharedInformerFactory
// main.go
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
config.APIPath = "/apis/argoproj.io/v1alpha1"
if err != nil {
panic(err.Error())
}
argoClientSet, err := argoClientSet.NewForConfig(config)
if err != nil {
klog.Fatal(err)
}
// stop signal for informer
stopSignal := make(chan struct{})
defer close(stopSignal)
// Create the argo workflow informer factory
argoFactory := argoWorkflows.NewSharedInformerFactory(argoClientSet, time.Hour*24)
// Create the workflow informer
informer := argoFactory.Argoproj().V1alpha1().Workflows().Informer()
Lastly, create an API endpoint, this is to allow users to directly access the data cache created by the informer pattern.
func (api *API) GetWorkflows(context *gin.Context) {
var request Request
statusCode, err := rest.ValidateRequest(context, &request)
if err != nil {
rest.Response(context, statusCode, err.Error())
}
var results []interface{}
for _, workflow := range request.Workflows {
workflows, _, err := api.informer.GetStore().GetByKey(workflow.Key)
if err != nil {
rest.Response(context, statusCode, err.Error())
}
results = append(results, workflows)
}
rest.Response(context, http.StatusOK, results)
}
I have benchmarked it by listing Argo workflow resources, comparing the time taken to retrieve the results via ArgoWorkflow SDK and client-go.
SDK | client-go | +/- Performance | |
10 workflows | 6.84s | 892ms | +7.60x |
20 workflows | 8.30s | 2.10s | +3.95x |
That's all for now. See you in the next one !