本文分 4 部分,分别是:
(1). cache 包源码解析与 Informer 的使用;
(2). informers 包源码解析与 SharedInformerFactory 的使用,以及 Informer 在实际使用中的最佳实践;
(3). 实现自定义资源 (CRD) Informer;
(4). dynamic 包源码解析与 DynamicSharedInformerFactory 的使用;
这里是第 4 部分。
本文使用的所有代码都可以在 articles/archive/dive-into-kubernetes-informer at main · wbsnail/articles · GitHub 找到。
前言
在前面的部分中我结合源码介绍了 Kubernetes Informer 的实现机制与使用,得益于 Kubernetes 的设计,我们还可以使用相同的方式操作内置资源类型和自定义资源。但有没有一种情况,我们需要动态地控制资源,资源的类型在代码运行前还不确定?
是有这样的场景的,比如我最近开始的项目 kubebigbrother,作用是监控配置中指定的资源类型的若干特定字段变化,在它运行之前是没有办法确定哪些资源的 Informer 是需要启动的,哪些是不需要启动的。使用 switch 语句判断资源类型是不现实的,因为 Kubernetes 内置的资源非常多,况且我还希望能够支持自定义资源的监控。因此,我需要找到一个办法,动态地运行 Informer。
👮♂️ 如果你没有看过前一部分,建议先看完它。
dynamic 包
很幸运地,client-go 中有一个 dynamic 包,它实现了我们需要的这部分功能。
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() {
if len(os.Args) < 2 {
fmt.Println("resource missing")
return
}
// 资源,比如 "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)
// 注意创建了 dynamicClient, 而不是 clientset
dynamicClient, err := dynamic.NewForConfig(config)
magicconch.Must(err)
// 同样这里也是 DynamicSharedInformerFactory, 而不是 SharedInformerFactory
informerFactory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(
dynamicClient, 0, "tmp", nil)
// 通过 schema 包提供的 ParseResourceArg 由资源描述字符串解析出 GroupVersionResource
gvr, _ := schema.ParseResourceArg(resource)
if gvr == nil {
fmt.Println("cannot parse gvr")
return
}
// 使用 gvr 动态生成 Informer
informer := informerFactory.ForResource(*gvr).Informer()
// 熟悉的代码,熟悉的味道,只是收到的 obj 类型好像不太一样
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
// *unstructured.Unstructured 类是所有 Kubernetes 资源类型公共方法的抽象,
// 提供所有对公共属性的访问方法,像 GetName, GetNamespace, GetLabels 等等,
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
}
// 要访问公共属性外的字段,可以借助 unstructured 包提供的一些助手方法:
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
}
不用多说了,全都在代码注释中。可以看到没有引入任何一个资源类型。
尝试运行一下:
❯ go run . configmaps.v1.
Start syncing....
created: demo
created: demo1
deleted: demo
created: demo
尝试用它监控一下我们上一部分创建的自定义资源 Rabbit:
❯ go run . rabbits.v1.stable.wbsnail.com
Start syncing....
created: judy
created: bugs
updated: judy, old color: white, new color: black
借助 dynamic 包,我们非常非常简单地就实现了对动态资源类型的监控,究其原因,还是 Kubernetes 对内置资源和自定义资源统一的接口,使得能够很容易抽象出统一的处理方式,体现了 Kubernetes 资源类型设计的优越性。
总结
在以上的内容中,我给大家分析了 dynamic 包的源码,并介绍了 DynamicSharedInformerFactory 的使用,有关 "Kubernetes Informer 源码解析与深度使用" 系列的全部内容到此结束。如果你看完了这个系列,相信你可以独立开发出稳定可靠的 Kubernetes 控制器 👍
参考资料
Bitnami Engineering: A deep dive into Kubernetes controllers
Bitnami Engineering: Kubewatch, an example of Kubernetes custom controller
Dynamic Kubernetes Informers | FireHydrant
client-go/main.go at master · kubernetes/client-go · GitHub