第三课 k8s源码学习和二次开发-缓存机制Informers和Reflector组件学习
tags:
- k8s
- 源码学习
categories:
- 源码学习
- 二次开发
文章目录
第一节 former
1.1 Watch介绍
- 之前clientSet可以访问到我们的集群资源。但是如果我们想一直访问资源,不可能通过for循环去调用clientSet,因为会对ApiServer造成过大压力。
- 这里有个
Watch
可以用来监听对象的变化,增删改操作。
watch.Interface
追踪到 interface 我们可以发现
type Interface interface {
Stop()
ResultChan() <-chan Event
}
- 上面有个Event事件 继续追踪。上面一课我们知道集群中所有内置资源对象都实现了runtime.Object。
// Event represents a single event to a watched resource.
// +k8s:deepcopy-gen=true
type Event struct {
Type EventType
Object runtime.Object
}
- 上面这个Event, 实际上决定是什么类型的事件。追踪进入看看一些定义的事件类型常量。
type EventType string
const (
Added EventType = "ADDED"
Modified EventType = "MODIFIED"
Deleted EventType = "DELETED"
Bookmark EventType = "BOOKMARK"
Error EventType = "ERROR"
DefaultChanSize int32 = 100
)
- 但是我们仍然不建议直接通过调用Watch去监控资源变化,因为k8s中集群的资源对象非常大,如果单纯的只通过Watch去获取资源对象的话,ApiServer内部压力还是比大的。
- 为了解决这个问题,需要通过缓存去完成,集群中的资源较多,我们需要自己在客户端去维护一套缓存,而这个维护成本也是非常大的。client-go为我们提供了一个缓存机制Informer
1.2 Informers介绍
-
因为集群中的资源较多,我们需要自己在客户端去维护一套缓存,而这个维护成本也是非常大的。为此 client-go 也提供了自己的实现机制,那就是Informers。Informers 是这个事件接口和带索引查找功能的内存缓存的组合,这样也是目前最常用的用法。
-
Informers 第一次被调用的时候会首先在客户端调用 List 来获取全量的对象集合,然后通过 Watch 来获取增量的对象更新缓存。
-
运行原理
- 一个控制器每次需要获取对象的时候都要访问APIServer,这会给系统带来很高的负载,Informers 的内存缓存就是来解决这个问题的
- 此外Informers还可以几乎实时的监控对象的变化,而不需要轮询请求,这样就可以保证客户端的缓存数据和服务端的数据一致,就可以大大降低APIServer的压力了。
-
如上图展示了 Informer 的基本处理流程:
- 以 events 事件的方式从 APIServer 获取数据
- 提供一个类似客户端的 Lister 接口,从内存缓存中 get 和 list 对象
- 为添加、删除、更新注册事件处理程序
-
此外 Informers 也有错误处理方式,当长期运行的 watch 连接中断时,它们会尝试使用另一个 watch 请求来恢复连接,在不丢失任何事件的情况下恢复事件流。
-
如果中断的时间较长,而且 APIServer 丢失了事件(etcd 在新的 watch 请求成功之前从数据库中清除了这些事件),那么 Informers 就会重新 List 全量数据。
-
而且在重新 List 全量操作的时候还可以配置一个重新同步的周期参数,用于协调内存缓存数据和业务逻辑的数据一致性,每次过了该周期后,注册的事件处理程序就将被所有的对象调用,通常这个周期参数以分为单位,比如10分钟或者30分钟。
注意:重新同步是纯内存操作,不会触发对服务器的调用。
- Informers 的这些高级特性以及超强的鲁棒性,都足以让我们不去直接使用客户端的 Watch() 方法来处理自己的业务逻辑,而且在 Kubernetes源码中也有很多地方都有使用到Informers。但是在使用 Informers 的时候,通常每个 GroupVersionResource(GVR)只实例化一个 Informers,但是有时候我们在一个应用中往往有使用多种资源对象的需求,这个时候为了方便共享 Informers,我们可以通过使用共享 Informer 工厂来实例化一个 Informer。
- 共享 Informer 工厂允许我们在应用中为同一个资源共享 Informer,也就是说不同的控制器循环可以使用相同的 watch 连接到后台的 APIServer,例如,kube-controller-manager 中的控制器数据量就非常多,但是对于每个资源(比如 Pod),在这个进程中只有一个 Informer。
1.3 Informers使用
- 首先我们创建一个Clientset对象,然后使用Clientset来创建一个共享的 Informer 工厂, Informer是通过 informer-gen这个代码生成器工具自动生成的,位于
k8s.io/client-go/informers
中。 - 创建一个用于获取Deployment的共享Informer,代码如下所示:
package main
import (
"flag"
"fmt"
"path/filepath"
"time"
v1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
)
func main() {
var err error
var config *rest.Config
var kubeconfig *string
if home := homedir.HomeDir(); home != "" {
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "[可选] kubeconfig 绝对路径")
} else {
kubeconfig = flag.String("kubeconfig", "", "kubeconfig 绝对路径")
}
// 初始化 rest.Config 对象
if config, err = rest.InClusterConfig(); err != nil {
if config, err = clientcmd.BuildConfigFromFlags("", *kubeconfig); err != nil {
panic(err.Error())
}
}
// 创建 Clientset 对象
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err.Error())
}
// 初始化 informer factory(为了测试方便这里设置每30s重新 List 一次)
informerFactory := informers.NewSharedInformerFactory(clientset, time.Second*30)
// 对 Deployment 监听
deployInformer := informerFactory.Apps().V1().Deployments()
// 创建 Informer(相当于注册到工厂中去,这样下面启动的时候就会去 List & Watch 对应的资源)
informer := deployInformer.Informer()
// 创建 Lister
deployLister := deployInformer.Lister()
// 注册事件处理程序
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: onAdd,
UpdateFunc: onUpdate,
DeleteFunc: onDelete,
})
stopper := make(chan struct{
})
defer close(stopper)
// 启动 informer,List & Watch
informerFactory.Start(stopper)
// 等待所有启动的 Informer 的缓存被同步
informerFactory.WaitForCacheSync(stopper)
// 从本地缓存中获取 default 中的所有 deployment 列表
deployments, err := deployLister.Deployments("default").List(labels.Everything())
if err != nil {
panic(err)
}
for idx, deploy := range deployments {
fmt.Printf("%d -> %s\n", idx