09 May 2021 16:22 +0000

本文分 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

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


Loading comments...