[k8s源码]4.informer

Informer 是 client-go 库中的一个核心组件,它提供了一种高效的方式来监视 Kubernetes 集群中资源的变化。Informer 通过 Watch 机制与 API Server 建立长连接,初次同步时会获取资源的完整列表,之后只接收增量更新,大大减少了网络流量。

使用informer可以减少 API 调用: 避免频繁轮询,降低 API Server 压力。

  1. 实时性: 能快速获知资源变化
  2. 缓存: Informer 在本地维护资源缓存,减少查询延迟
  3. 事件处理: 提供 Add/Update/Delete 事件的回调机制

从下图我们可以看到 Informer 包含2个蓝色组件,分别是Reflector,Indexer。其中 Reflector 是用来和 apiserver 建立链接,实时获取最新的数据并交给 Informer,Informer 拿到数据存放到 Indexer 中并通知下游 controller。

代码工厂

这里的podinformer是一个专门用来监视和更新pod信息的informer,是通过工厂informer来实例化的一个informer,以下是一个实例来解释代码工厂的概念:

这里的studentInformer是一种特殊的informer, 里面有一个informerfactory和一个自有的函数,这里使用函数类型func()作为字段,可以在结构体中保存一些动态生成的逻辑或者回调函数,使得结构体的行为可以根据这些函数的不同实现而变化。这种方式可以帮助实现更灵活和可扩展的程序设计。

Go 复制代码
package main

import (
    "fmt"
)

type Student struct {
    Name string
    Age  int
}

type InformerFactory struct {
    // 一些工厂的字段
}

type StudentInformer struct {
    factory InformerFactory
    defaultInformer func() string
}

func (s *StudentInformer) Informer() string {
    return s.factory.InformerFor(s.defaultInformer)
}

func (f InformerFactory) InformerFor(informerFunc func() string) string {
    // 这里我们简单返回调用的结果
    return informerFunc()
}

func defaultStudentInformer() string {
    return "Student Informer Initialized"
}

func main() {
    factory := InformerFactory{}
    studentInformer := StudentInformer{
        factory: factory,
        defaultInformer: defaultStudentInformer,
    }
    
    result := studentInformer.Informer()
    fmt.Println(result) // 输出: Student Informer Initialized
}

这里InformerFor 方法需要在未来根据不同的情况返回不同的 informer 结果,而不是简单地调用一个预定义的函数,这种设计就显得更加合理。此外,通过 InformerFor 方法,可以对 InformerFactory 进行更多的管理和配置,比如添加日志记录、性能监控等逻辑,而不需要修改 StudentInformer 结构体本身。所以这里使用了informer和informerFor两个方法。

PodInformer

通过上面的例子,可以更好的理解这里的代码逻辑,PodInformer使用了一个工厂方法internalinterfaces.SharedInformerFactory,他的informer方法调用了SharedInformerFactory中的informerFor方法。

Go 复制代码
type PodInformer interface {
	Informer() cache.SharedIndexInformer
	Lister() v1.PodLister
}

type podInformer struct {
	factory          internalinterfaces.SharedInformerFactory
	tweakListOptions internalinterfaces.TweakListOptionsFunc
	namespace        string
}

// 返回一个 sharedIndexInformer ,本质上是调用了 sharedInformerFactory 的 InformerFor 方法,
//我在下面贴出来了,sharedInformerFactory 是个工厂方法,这里初始化了 podInformer 并存储在 //factory 的 informers map 中
func (f *podInformer) Informer() cache.SharedIndexInformer {
	return f.factory.InformerFor(&corev1.Pod{}, f.defaultInformer)
}

// 返回一个 PodList , 可以用来列出 indexer 中的数据,这里的 indexer 是本地的,
//因此是拿出的pod是只读的,无法修改
func (f *podInformer) Lister() v1.PodLister {
	return v1.NewPodLister(f.Informer().GetIndexer())
}

// 根据传入的对象类型 obj 和创建函数 newFunc,返回一个对应的 SharedIndexInformer 实例。
//如果已经存在相同类型的 informer,则直接返回现有的实例;否则,根据参数创建一个新的实例,
//并在需要时进行存储,以便后续重复使用,从而提高效率和资源利用率。
func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer {
	f.lock.Lock()
	defer f.lock.Unlock()

	informerType := reflect.TypeOf(obj)
	informer, exists := f.informers[informerType]
	if exists {
		return informer
	}

	resyncPeriod, exists := f.customResync[informerType]
	if !exists {
		resyncPeriod = f.defaultResync
	}

	informer = newFunc(f.client, resyncPeriod)
	informer.SetTransform(f.transform)
	f.informers[informerType] = informer

	return informer
}
Informer构造方法

构造方法(NewPodInformer 和 NewFilteredPodInformer)是创建实际 Informer 的地方,而 podInformer 结构体则是对这个 Informer 的一个封装,提供了更高层次的抽象和额外的功能(如 Lister)。podInformer 的 defaultInformer 方法是连接这两部分的桥梁,它在需要时调用构造方法来创建新的 Informer。这种设计允许灵活地创建和管理 Informer,同时提供了一致的接口(PodInformer)供客户端代码使用。

这里的NewPodInformer其实是调用了另一个informer但是自动将 tweakListOptions 参数设为 nil

Go 复制代码
func NewPodInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
    return NewFilteredPodInformer(client, namespace, resyncPeriod, indexers, nil)
}

这里的 NewFilteredPodInformer方法定义了wach和list两个函数,核心是client.CoreV1().Pods(namespace).Watch(context.TODO(), options),client.CoreV1().Pods(namespace).List(context.TODO(), options)

&cache.ListWatch{...} 创建一个 ListWatch 结构体的指针。

ListFunc: 是 ListWatch 结构体的一个字段,它期望一个函数作为值。

func(options metav1.ListOptions) (runtime.Object, error) { ... } 是一个匿名函数,它:

接受一个 metav1.ListOptions 参数

返回一个 runtime.Object 和一个 error

这个函数的内容:

如果 tweakListOptions 不为 nil,就调用它来修改 options

然后调用 client.CoreV1().Pods(namespace).List() 来获取 Pod 列表

Go 复制代码
func NewFilteredPodInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
	return cache.NewSharedIndexInformer(
		&cache.ListWatch{
			ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
				if tweakListOptions != nil {
					tweakListOptions(&options)
				}
				return client.CoreV1().Pods(namespace).List(context.TODO(), options)
			},
			WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
				if tweakListOptions != nil {
					tweakListOptions(&options)
				}
				return client.CoreV1().Pods(namespace).Watch(context.TODO(), options)
			},
		},
		&corev1.Pod{},
		resyncPeriod,
		indexers,
	)
}
func (f *podInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
	return NewFilteredPodInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}

&corev1.Pod{},

resyncPeriod,

indexers,

这三行简单的代码本身并不直接完成所有复杂的操作,而是为 cache.NewSharedIndexInformer 函数提供了必要的参数。这个函数内部会使用这些参数来设置和配置 Informer。

&corev1.Pod{}:这只是一个类型指示器。NewSharedIndexInformer 函数内部会使用反射来检查这个类型,以确定如何处理从 API 服务器接收的对象。

resyncPeriod:这个参数被传递给 Informer 的内部定时器。Informer 会创建一个 goroutine,定期触发重新同步操作。

indexers:这个参数用于初始化 Informer 的索引器。例如像这样:

Go 复制代码
cache.Indexers{
    cache.NamespaceIndex: cache.MetaNamespaceIndexFunc,
}

这个 Indexers 映射会被传递给 Informer 的内部存储(通常是一个 cache.ThreadSafeStore),用于设置索引函数。

可以看到最后定义了defaultInformer,是上面构造podInformer的时候,Informer方法中调用InformerFor的时候传递的一个函数。

总结以上内容

这段代码定义了 PodInformer 接口及其实现,采用了工厂模式和延迟初始化策略来管理 Kubernetes Pod 资源的 Informer。核心是 Informer() 方法,它通过调用 factory.InformerFor() 来获取或创建 cache.SharedIndexInformer 实例。这个过程的本质是构造一个 cache.SharedIndexInformer 类型的 informer。

具体流程如下:Informer() 方法调用 InformerFor(),并传递一个 defaultInformer 函数作为参数。这个 defaultInformer 函数实际上返回一个 NewFilteredPodInformer,而 NewFilteredPodInformer 在构造时会创建一个 cache.SharedIndexInformer 类型的对象。InformerFor() 函数首先检查是否已经存在对应类型的 informer 实例。如果已存在,直接返回该实例;如果不存在,则使用提供的 defaultInformer 函数创建新的实例。这种设计确保了 Informer 的单一实例,避免重复创建,从而提高了资源利用效率。

sharedIndexInformer

它包含了以下重要组件:

indexer: 用于存储和索引对象的本地缓存。

controller: 负责管理 Reflector,处理对象的添加、更新和删除。

processor: 处理事件分发给注册的事件处理器。

listerWatcher: 用于列出和监视资源的接口。

objectType: 指定了 Informer 处理的对象类型。

resyncCheckPeriod 和 defaultEventHandlerResyncPeriod: 控制重新同步的周期。

started 和 stopped: 控制 Informer 的生命周期。

watchErrorHandler: 处理监视过程中的错误。

transform: 允许在将对象添加到存储之前对其进行转换。

这个结构体的定义揭示了 SharedIndexInformer 的内部工作机制:

它维护了一个本地缓存(indexer),用于快速检索对象。

通过 controller 和 listerWatcher,它能够持续监视 Kubernetes API 服务器的变化。

使用 processor 来分发事件,确保所有注册的处理器都能接收到对象的变更通知。

通过 resync 机制,它可以周期性地重新同步所有对象,确保本地缓存与服务器状态一致。

Go 复制代码
type sharedIndexInformer struct {
	indexer    Indexer
	controller Controller

	processor             *sharedProcessor
	cacheMutationDetector MutationDetector

	listerWatcher ListerWatcher

	// objectType is an example object of the type this informer is expected to handle. If set, an event
	// with an object with a mismatching type is dropped instead of being delivered to listeners.
	objectType runtime.Object

	// objectDescription is the description of this informer's objects. This typically defaults to
	objectDescription string

	// resyncCheckPeriod is how often we want the reflector's resync timer to fire so it can call
	// shouldResync to check if any of our listeners need a resync.
	resyncCheckPeriod time.Duration
	// defaultEventHandlerResyncPeriod is the default resync period for any handlers added via
	// AddEventHandler (i.e. they don't specify one and just want to use the shared informer's default
	// value).
	defaultEventHandlerResyncPeriod time.Duration
	// clock allows for testability
	clock clock.Clock

	started, stopped bool
	startedLock      sync.Mutex

	// blockDeltas gives a way to stop all event distribution so that a late event handler
	// can safely join the shared informer.
	blockDeltas sync.Mutex

	// Called whenever the ListAndWatch drops the connection with an error.
	watchErrorHandler WatchErrorHandler

	transform TransformFunc
}

除了这个结构,sharedIndexInformer 还实现了许多重要的方法来完成其功能。如:Run(stopCh <-chan struct{}),这是启动 informer 的主要方法。它会启动底层的 controller 和 processor。AddEventHandler(handler ResourceEventHandler)用于添加事件处理器,这些处理器会在资源发生变化时被调用。等等。

复制代码
package main

import (
    "time"

    "k8s.io/client-go/kubernetes"
    "k8s.io/client-go/tools/cache"
    "k8s.io/client-go/util/workqueue"
)

func main() {
    // 假设已经有一个 Kubernetes 客户端的实例 clientset
    var clientset *kubernetes.Clientset

    // 创建一个 sharedInformerFactory 实例
    informerFactory := NewSharedInformerFactory(clientset, time.Minute*10)

    // 创建一个 podInformer 实例
    podInformer := informerFactory.Core().V1().Pods()

    // 启动 informer 以监听 Pod 资源变化
    stopCh := make(chan struct{})
    defer close(stopCh)
    podInformer.Informer().Run(stopCh)
}
Channel

<-chan struct{} 类型的 channel 在协程间传递简单的停止信号非常实用。它能够高效地实现协程间的同步和通信。在下面的代码中,创建了一个通道,这个通道将用来从集群获得informer的信息,struct{} 是 Go 语言中最小的数据类型,只占用 1 个字节的内存。相比于使用其他更复杂的数据类型作为 channel 元素,struct{} 可以减少内存开销。<-chan struct{} 类型的 channel 发送和接收数据都非常简单,因为 struct{} 类型没有任何有意义的值。无需进行复杂的数据处理或转换。

Go 语言中,chan 是一个数据类型,用于实现协程之间的通信和同步。它是 Go 语言并发编程的核心概念之一。

channel 有以下几个重要的特点:

无缓冲 vs 有缓冲:

无缓冲 channel: 发送和接收操作必须是同步的,发送者会一直阻塞到有接收者接收数据。

有缓冲 channel: 发送操作会先将数据存入缓冲区,直到缓冲区满时才会阻塞。接收操作会从缓冲区取出数据。

方向性:

单向 channel: 只能用于发送或接收数据,如 chan<- int 和 <-chan int。

双向 channel: 既可以用于发送又可以用于接收数据,如 chan int。

阻塞和非阻塞:

在没有准备好接收或发送数据时,channel 操作会阻塞当前协程,直到有其他协程准备好为止。

可以使用 select 语句来非阻塞地处理多个 channel。

InformerDemo

Informer 在监听到相关事件时,会自动调用 EventNil 类型实现的 OnAdd 方法,并将事件对应的对象(在这个例子中是 v1.Pod 类型)传递给 obj interface{} 参数。也就是说,这个 obj interface{} 参数并不是从 EventNil 结构体中获取的,而是由 Informer 在调用 OnAdd 方法时动态传入的。Informer 知道 EventNil 实现了 cache.ResourceEventHandler 接口,所以在触发事件时会自动调用 EventNil 类型的 OnAdd、OnUpdate 和 OnDelete 方法,并传入适当的参数。

Go 复制代码
package main

import (
	"flag"
	"fmt"
	"time"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/labels"
	kubeInformers "k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
)

func main() {
	var kubeconfig *string // 声明为指针
	kubeconfig = flag.String("kubeconfig", "C:/Users/gx/.kube/config", "absolute path to the kubeconfig file")
	flag.Parse() // 需要解析命令行参数

	config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
	if err != nil { // 'error' 改为 'err','nul' 改为 'nil'
		panic(err.Error())
	}

	// create the clientset
	kubeClient, err := kubernetes.NewForConfig(config)
	if err != nil { // 需要检查错误
		panic(err.Error())
	}

	// 使用 kubeClient ...
	kubeInformerfactory := kubeInformers.NewSharedInformerFactory(kubeClient, 10*time.Second)
	podLister := kubeInformerfactory.Core().V1().Pods().Lister()
	//deploymentLister = kubeInformerfactory.Apps().V1().Deployments().Lister()

	pl := podLister.Pods("monitor-sa")
	i := kubeInformerfactory.Core().V1().Pods().Informer()
	e := &EventNil{}

	stopCh := make(chan struct{})
	kubeInformerfactory.Start(stopCh)
	kubeInformerfactory.WaitForCacheSync(stopCh)
	i.AddEventHandler(e)
	pods, err := pl.List(labels.Everything())
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(len(pods))
	for _, pod := range pods {
		fmt.Println("Pod Name:", pod.Name)
	}

	select {}
}

type EventNil struct {
}

func (receiver *EventNil) OnAdd(obj interface{}, isInInitialList bool) {
	pod, ok := obj.(*v1.Pod)
	if !ok {
		fmt.Println("Failed to cast obj to *v1.Pod")
		return
	}
	fmt.Println("add \n", pod.Name)
}
func (receiver *EventNil) OnUpdate(oldObj, newObj interface{}) {
}

func (receiver *EventNil) OnDelete(obj interface{}) {
	pod, ok := obj.(*v1.Pod)
	if !ok {
		fmt.Println("Failed to cast obj to *v1.Pod")
		return
	}
	fmt.Println("delete \n", pod.Name)
}

可以看到这里的type EventNil的struct是在函数外定义的。

通过实验发现,删除deployment的一个pod的时候,一般是先创建新的pod,再删除旧的pod。可以看到删除demo-deployment-7fd4444d-bdt8j的时候,先创建了新的pod

add

demo-deployment-7fd4444d-j5hzr

delete

demo-deployment-7fd4444d-bdt8j

Go 复制代码
[root@master ~]# kubectl get pods
NAME                             READY   STATUS    RESTARTS   AGE
demo-deployment-7fd4444d-bdt8j   1/1     Running   0          19s
demo-deployment-7fd4444d-slwqm   1/1     Running   0          5m47s
[root@master ~]# kubectl delete pod demo-deployment-7fd4444d-bdt8j
pod "demo-deployment-7fd4444d-bdt8j" deleted
[root@master ~]# kubectl get pods
NAME                             READY   STATUS    RESTARTS   AGE
demo-deployment-7fd4444d-j5hzr   1/1     Running   0          12s
demo-deployment-7fd4444d-slwqm   1/1     Running   0          7m35s

kubeInformerFactory.Start(stopCh)

Start(stopCh) 会启动所有通过 factory 创建的 informer。通道 stopCh 会被传递给这些 informer。

当 stopCh 关闭时,所有这些 informer 都会停止运行。

kubeInformerFactory.WaitForCacheSync(stopCh)

WaitForCacheSync(stopCh) 会阻塞等待,直到所有 informer 的缓存都被同步(即 informer 从 Kubernetes 集群中获取到初始数据)。

如果 stopCh 关闭,则会提前退出等待。

接口实现

在 Go 语言中,接口是一种抽象类型,它定义了一组方法签名,但不包含方法的具体实现。一个接口只描述了对象应该做什么,而不关心对象是如何做的通过接口,代码的不同部分可以被解耦合。实现者不需要了解调用者的具体实现,只需遵循接口定义的方法签名。 例如下面的例子里面,一个接口就有两个不同的实现方式。

Go 复制代码
package main

import "fmt"

// 定义接口
type ResourceEventHandler interface {
	OnAdd(obj interface{})
	OnUpdate(oldObj, newObj interface{})
	OnDelete(obj interface{})
}
// 实现接口的结构体
type EventLogger struct{}
func (e *EventLogger) OnAdd(obj interface{}) {
	fmt.Println("Object added:", obj)
}
func (e *EventLogger) OnUpdate(oldObj, newObj interface{}) {
	fmt.Println("Object updated from", oldObj, "to", newObj)
}
func (e *EventLogger) OnDelete(obj interface{}) {
	fmt.Println("Object deleted:", obj)
}
Go 复制代码
// 另一个实现接口的结构体
type EventCounter struct {
	AddCount    int
	UpdateCount int
	DeleteCount int
}
func (c *EventCounter) OnAdd(obj interface{}) {
	c.AddCount++
}
func (c *EventCounter) OnUpdate(oldObj, newObj interface{}) {
	c.UpdateCount++
}
func (c *EventCounter) OnDelete(obj interface{}) {
	c.DeleteCount++
}
Go 复制代码
func main() {
	// 使用 EventLogger
	var handler ResourceEventHandler = &EventLogger{}
	handler.OnAdd("Pod1")
	handler.OnUpdate("Pod1", "Pod1 v2")
	handler.OnDelete("Pod1")

	// 使用 EventCounter
	counter := &EventCounter{}
	handler = counter
	handler.OnAdd("Pod2")
	handler.OnUpdate("Pod2", "Pod2 v2")
	handler.OnDelete("Pod2")

	fmt.Printf("Add: %d, Update: %d, Delete: %d\n", counter.AddCount, counter.UpdateCount, counter.DeleteCount)
}
相关推荐
云和数据.ChenGuang30 分钟前
微服务技术栈
微服务·云原生·架构
syty202034 分钟前
K8s是什么
容器·kubernetes·dubbo
江团1io02 小时前
微服务雪崩问题与系统性防御方案
微服务·云原生·架构
Evan Wang3 小时前
使用Terraform管理阿里云基础设施
阿里云·云原生·terraform
向上的车轮3 小时前
基于go语言的云原生TodoList Demo 项目,验证云原生核心特性
开发语言·云原生·golang
灵犀物润4 小时前
Kubernetes 配置检查与发布安全清单
安全·容器·kubernetes
360智汇云4 小时前
k8s交互桥梁:走进Client-Go
golang·kubernetes·交互
xy_recording5 小时前
Day20 K8S学习
学习·容器·kubernetes
衍余未了5 小时前
k8s 内置的containerd配置阿里云个人镜像地址及认证
java·阿里云·kubernetes
九章云极AladdinEdu5 小时前
Kubernetes设备插件开发实战:实现GPU拓扑感知调度
人工智能·机器学习·云原生·容器·kubernetes·迁移学习·gpu算力