记得大学刚毕业那年看了侯俊杰的《深入浅出MFC》,就对深入浅出这四个字特别偏好,并且成为了自己对技术的要求标准——对于技术的理解要足够的深刻以至于可以用很浅显的道理给别人讲明白。以下内容为个人见解,如有雷同,纯属巧合,如有错误,烦请指正。
本文基于kubernetes1.11版本,后续会根据kubernetes版本更新及时更新文档,所有代码引用为了简洁都去掉了日志打印相关的代码,尽量只保留有价值的内容。
在开始本文内容前,请先阅读《深入浅出kubernetes之client-go的SharedInformer》。
目录
各种group之Core
Core分组之PodInformer
前几天我写了一篇关于SharedInformer的文章,那么肯定会有人问SharedInformer在哪里使用,是怎么使用的?今天我就针对SharedInformer的应用写点东西,此时就不得不提一个新的概念:SharedInformerFactory。有JAVA经验的同学对于工厂类应该很熟悉了,为了照顾其他语言的同学,我简单解释一下:SharedInformerFactory就是构造各种Informer的地方。为什么是各种Informer呢,读过我《深入浅出kubernetes之client-go的SharedInformer》文章的同学应该知道,每个SharedInformer其实只负责一种对象,在构造SharedInformer的时候指定了对象类型。Kubernetes中一共有多少种对象,读者自行了解把,反正是很多,这里实在写不完!SharedInformerFactory可以构造Kubernetes里所有对象的Informer,而且主要用在controller-manager这个服务中。因为controller-manager负责管理绝大部分controller,每类controller不仅需要自己关注的对象的informer,同时也可能需要其他对象的Informer(比如ReplicationController也需要PodInformer,否则他无法感知Pod的启动和关闭,也就达不到监控的目的了),所以一个SharedInformerFactory可以让所有的controller共享使用同一个类对象的Informer。
说了这么多,SharedInformerFactory长啥样啊?
// 代码源自client-go/informers/factory.go
// SharedInformerFactory是个interfaces,所以肯定有具体的实现类
type SharedInformerFactory interface {
// 在informers这个包中又定义了一个SharedInformerFactory,这个主要是包内抽象,所以此处继承了这个接口
internalinterfaces.SharedInformerFactory
// 这个暂时不知道干啥用,所以我也不介绍他了
ForResource(resource schema.GroupVersionResource) (GenericInformer, error)
// 等待所有的Informer都已经同步完成,这里同步其实就是遍历调用SharedInformer.HasSynced()
// 所以函数需要周期性的调用指导所有的Informer都已经同步完毕
WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool
Admissionregistration() admissionregistration.Interface // 返回admissionregistration相关的Informer组
Apps() apps.Interface // 返回app相关的Informer组
Autoscaling() autoscaling.Interface // 返回autoscaling相关的Informer组
Batch() batch.Interface // 返回job相关的Informer组
Certificates() certificates.Interface // 返回certificates相关的Informer组
Coordination() coordination.Interface // 返回coordination相关的Informer组
Core() core.Interface // 返回core相关的Informer组
Events() events.Interface // 返回event相关的Informer组
Extensions() extensions.Interface // 返回extension相关的Informer组
Networking() networking.Interface // 返回networking相关的Informer组
Policy() policy.Interface // 返回policy相关的Informer组
Rbac() rbac.Interface // 返回rbac相关的Informer组
Scheduling() scheduling.Interface // 返回scheduling相关的Informer组
Settings() settings.Interface // 返回settings相关的Informer组
Storage() storage.Interface // 返回storage相关的Informer组
}
为了方便管理(我自己猜的哈),Kubernetes对Informer进行了分组,如下图所示:
上图展示了Kubernetes所有Informer的分类,细心的同学可能发现有好多一样名字的Informer(比如DeamonsetInformer),他们是同一个东西么?答案是完全不同的东西,虽然同名,但是他们在不同的包中,虽然代码上有很多相似的地方,但是确实是完全独立的对象。
SharedInformerFactory继承了internalinterfaces.SharedInformerFactory,我们至少要了解一下这个内部的SharedInformerFactory多定义了些啥?
// 代码源自client-go/informers/internalinterfaces/factory_interfaces.go
type SharedInformerFactory interface {
// 核心逻辑函数,类似于很多类的Run()函数
Start(stopCh <-chan struct{})
// 这个很关键,通过对象类型,返回SharedIndexInformer,这个SharedIndexInformer管理的就是指定的对象
// NewInformerFunc用于当SharedInformerFactory没有这个类型的Informer的时候创建使用
InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer
}
// 创建Informer的函数定义,这个函数需要apiserver的客户端以及同步周期,这个同步周期在SharedInformers反复提到
type NewInformerFunc func(kubernetes.Interface, time.Duration) cache.SharedIndexInformer
上面的代码应该是比较简单,理解起来应该不难。internalinterfaces.SharedInformerFactory其实只提供了一个能力,就是通过对象类型构造Informer。因为SharedInformerFactory管理的就是SharedIndexInformer对象,SharedIndexInformer存储的对象类型决定了他是什么Informer,致使SharedInformerFactory无需知道具体的Informer如何构造,所以需要外部传入构造函数,这样可以减低耦合性。
前面我提到了SharedInformerFactory只是个interface,肯定有一个实现类,按照kubernetes的风格,应该是sharedInformerFactory,有没有很机智?
// 代码源自client-go/informers/factory.go
type sharedInformerFactory struct {
// apiserver的客户端,暂时不用关心怎么实现的,只要知道他能列举和监听资源就可以了
client kubernetes.Interface
// 哈哈,这样看来每个namesapce需要一个SharedInformerFactory,那cache用namespace建索引还有啥用呢?
// 并不是所有的使用者都需要指定namesapce,比如kubectl,他就可以列举所有namespace的资源,所以他没有指定namesapce
namespace string
// 这是个函数指针,用来调整列举选项的,这个选项用来client列举对象使用
tweakListOptions internalinterfaces.TweakListOptionsFunc
// 互斥锁
lock sync.Mutex
// 默认的同步周期,这个在SharedInformer需要用
defaultResync time.Duration
// 每个类型的Informer有自己自定义的同步周期
customResync map[reflect.Type]time.Duration
// 每类对象一个Informer,但凡使用SharedInformerFactory构建的Informer同一个类型其实都是同一个Informer
informers map[reflect.Type]cache.SharedIndexInformer
// 各种Informer启动的标记
startedInformers map[reflect.Type]bool
}
有了具体的实现类,我们就可以看看这个类是怎么实现 SharedInformerFactory所有的功能点的。在开始分析前,我们来看看client-go里面提供构造SharedInformerFactory的几个接口函数:
// 代码源自client-go/tools/cache/shared_informer.go
// 这是一个通用的构造SharedInformerFactory的接口函数,没有任何其他的选项,只包含了apiserver的client以及同步周期
func NewSharedInformerFactory(client kubernetes.Interface, defaultResync time.Duration) SharedInformerFactory {
// 最终是调用NewSharedInformerFactoryWithOptions()实现的,无非没有选项而已
return NewSharedInformerFactoryWithOptions(client, defaultResync)
}
// 相比于上一个通用的构造函数,这个构造函数增加了namesapce过滤和调整列举选项
func NewFilteredSharedInformerFactory(client kubernetes.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory {
// 最终是调用NewSharedInformerFactoryWithOptions()实现的,无非选项是2个
// WithNamespace()和WithTweakListOptions()会在后文讲解
return NewSharedInformerFactoryWithOptions(client, defaultResync, WithNamespace(namespace), WithTweakListOptions(tweakListOptions))
}
// 到了构造SharedInformerFactory核心函数了,其实SharedInformerOption是个有意思的东西
// 我们写程序喜欢Option是个结构体,但是这种方式的扩展很麻烦,这里面用的是回调函数,这个让我get到新技能了
func NewSharedInformerFactoryWithOptions(client kubernetes.Interface, defaultResync time.Duration, options ...SharedInformerOption) SharedInformerFactory {
// 默认只有apiserver的client以及同步周期是需要外部提供的其他的都是可以有默认值的
factory := &sharedInformerFactory{
client: client,
namespace: v1.NamespaceAll,
defaultResync: defaultResync,
informers: make(map[reflect.Type]cache.SharedIndexInformer),
startedInformers: make(map[reflect.Type]bool),
customResync: make(map[reflect.Type]time.Duration),
}
//逐一遍历各个选项函数,opt是选项函数,下面面有详细介绍
for _, opt := range options {
factory = opt(factory)
}
return factory
}
其实kubernetes里面有很多处用回调函数做选项的,这里是我博客第一次遭遇,所以就拿它开刀把。 对于SharedInformerFactory来说,可以调整的选项只有namespace、tweakListOptions、customResync这三个成员变量,所以选项无非就是调整这三个变量。全部内容就在下面的代码注释里面了:
// 代码源自client-go/informers/factory.go
// 这个是SharedInformerFactory构造函数的选项,是一个函数指针,传入的是工厂指针,返回也是工厂指针
// 很明显,选项函数直接修改工厂对象,然后把修改的对象返回就可以了
type SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory
// 把每个对象类型的同步周期这个参数转换为SharedInformerOption类型
func WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) SharedInformerOption {
// 这个实现很简单了,我就不多解释了
return func(factory *sharedInformerFactory) *sharedInformerFactory {
for k, v := range resyncConfig {
factory.customResync[reflect.TypeOf(k)] = v
}
return factory
}
}
// 这个选项函数用于使用者自定义client的列举选项的
func WithTweakListOptions(tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerOption {
return func(factory *sharedInformerFactory) *sharedInformerFactory {
factory.tweakListOptions = tweakListOptions
return factory
}
}
// 这个选项函数用来设置namesapce过滤的
func WithNamespace(namespace string) SharedInformerOption {
return func(factory *sharedInformerFactory) *sharedInformerFactory {
factory.namespace = namespace
return factory
}
}
选项类型用函数指针的方式好处在于扩展比较容易,但是要把选项函数直接操作类成员,总感觉有那么一种比较别扭的赶脚,但是不得不称赞这种实现方式还是很不错的。好啦,工厂对象已经创建出来了,我们就要看他如何实现internalinterfaces里面定义的接口了,第一个就是Start()接口:
// 代码源自client-go/informers/factory.go
// 其实sharedInformerFactory的Start()函数就是启动所有具体类型的Informer的过程
// 因为每个类型的Informer都是SharedIndexInformer,需要需要把每个SharedIndexInformer都要启动起来
func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) {
// 加锁操作
f.lock.Lock()
defer f.lock.Unlock()
// 遍历informers这个map
for informerType, informer := range f.informers {
// 看看这个Informer是否已经启动过
if !f.startedInformers[informerType] {
// 如果没启动过,那就启动一个协程执行SharedIndexInformer的Run()函数,我们在分析SharedIndexInformer的时候
// 我们知道知道Run()是整个Informer的启动入口点,看了《深入浅出kubernetes之client-go的SharedInformer》
// 的同学应该会想Run()是谁调用的呢?这里面应该给你们答案了吧?
go informer.Run(stopCh)
// 设置Informer已经启动的标记
f.startedInformers[informerType] = true
}
}
}
这个函数比较简单的原因在于SharedIndexInformer已经帮它做了很多事情, 那这些SharedIndexInformer是如何添加到sharedInformerFactory的呢?请看下面的代码:
// 代码源自client-go/informers/factory.go
// InformerFor()相当于每个类型Informer的构造函数了,即便具体实现构造的地方是使用者提供的
// 这个函数需要使用者传入对象类型,因为在sharedInformerFactory里面是按照对象类型组织的Informer
// 更有趣的是这些Informer不是sharedInformerFactory创建的,需要使用者传入构造函数
// 这样做既保证了每个类型的Informer只构造一次,同时又保证了具体Informer构造函数的私有化能力
func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer {
// 加锁操作
f.lock.Lock()
defer f.lock.Unlock()
// 通过反射获取obj的类型
informerType := reflect.TypeOf(obj)
// 看看这个类型的Informer是否已经创建了?
informer, exists := f.informers[informerType]
// 如果Informer已经创建,那么就复用这个Informer
if exists {
return informer
}
// 获取这个类型定制的同步周期,如果定制的同步周期那就用统一的默认周期
resyncPeriod, exists := f.customResync[informerType]
if !exists {
resyncPeriod = f.defaultResync
}
// 调用使用者提供构造函数,然后把创建的Informer保存起来
informer = newFunc(f.client, resyncPeriod)
f.informers[informerType] = informer
return informer
}
// 代码源自client-go/informers/internalinterfaces/factory_interfaces.go
// 这个函数定义就是具体类型Informer的构造函数,后面会有地方说明如何使用
type NewInformerFunc func(kubernetes.Interface, time.Duration) cache.SharedIndexInformer
虽然每个类型的Informer不是工厂实现的构造函数,但是工厂决定了什么时候创建对象。肯定有很多人会想,我是使用者,我调用工厂创建Informer我还要传入构造函数,我怎么知道如何创建,退一步说,我都有构造函数直接构造了不就完了,还用你工厂干啥呢?这一点我也一度不理解,这就要让我看看SharedInformerFactory的使用者是谁了,知道用户是谁疑惑就解开了。让我们想想,InformerFor()这个函数是谁定义的?是internalinterfaces.SharedInformerFactory,这个包名internalinterfaces绝对不是随便起的,说明InformerFor()是给内部使用的,其实就是给具体类型的Informer使用的,这也就好理解为什么要传入构造函数了。从我看来SharedInformerFactory主要就是解决了各类型Informer的共用问题,避免了重复构造。那肯定有人会问给外面的接口是什么,Core()这一类的函数才是给外部使用的,这些接口函数会调用内部接口函数实现对象的构造。所以简单总总结来说,SharedInformerFactory一共有两类用户:
如果对于上面的总结还不太理解,我们就以PodInformer为例子,因为后面章节会有它的详细说明,说这里面不会对PodInformer多说什么,只是通过他的部分代码说明上面的总结的内容。
// 代码源自client-go/informers/core/v1/pod.go
// 这个PodInformer是抽象类,Informer()就是获取SharedIndexInformer的接口函数
type PodInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1.PodLister
}
// 这个是PodInformer的实现类,看到了没,他需要工厂对象的指针,貌似明细了很多把?
type podInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// 这个就是要传入工厂的构造函数了
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)
}
// 这个是实现Informer()的地方,看到了把,这里面调用了工厂的InformerFor把自己注册进去
func (f *podInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&corev1.Pod{}, f.defaultInformer)
}
也就是说SharedInformerFactory的使用者使用Core().Pod()(这个看不懂没关系,后面会有说明)这个接口构造了PodInformer,但是需要调用PodInformer.Informer()才能获取到的SharedIndexInformer,而正是这个接口实现了向工厂注册自己。既然已经涉及到了具体的Informer,我们就开始看看每个都是干啥的吧?
client-go为了方便管理,把Informer分类管理,具体的分类在开篇那个图里面已经展示了,这里就不再描述了,本章节的标题group就是分类的意思。由于每个Informer的实现(基本都包含Informer()和Lister()两个接口)都差不多,所以我只详细解析Core这一种,其他的读者自行查看就可以了,我会在后续专门的博客讲解每类对象是干什么用的,也会对相应部分由相关说明。
Core是必须要说的,因为他使用频率最大,从名字上也够霸气,内核组!那我们先看看SharedInformerFactory().Core()这个函数的实现:
// 代码源自client-go/informers/factory.go
func (f *sharedInformerFactory) Core() core.Interface {
// 调用了内核包里面的New()函数,详情见下文分析
return core.New(f, f.namespace, f.tweakListOptions)
}
通过SharedInformerFactory().Core()获取内核Informer的分组就是构造了一个对象,如下代码所示:
// 代码源自client-go/informers/core/interface.go
// Interface又是一个被玩坏的名字,如果没有报名,根本不知道干啥的
type Interface interface {
V1() v1.Interface // 只有V1一个版本
}
// 这个是Interface的实现类,从名字上没任何关联吧?其实开发者命名也是挺有意思的,Interface定义的是接口
// 供外部使用,group也有意义,因为Core确实是内核Informer的分组
type group struct {
// 需要工厂对象的指针
factory internalinterfaces.SharedInformerFactory
// 这两个变量决定了Core这个分组对于SharedInformerFactory来说只有以下两个选项
namespace string
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// 构造Interface的接口
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
// 代码也挺简单的,不多说了
return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// 实现V1()这个接口的函数
func (g *group) V1() v1.Interface {
// 通过调用v1包的New()函数实现的,下面会有相应代码的分析
return v1.New(g.factory, g.namespace, g.tweakListOptions)
}
Core分组中的V1版本如下代码所示(感觉一层一层没完没了~):
// 代码源自client-go/informers/core/v1/interface.go
// 还是抽象类
type Interface interface {
// 获取ComponentStatusInformer
ComponentStatuses() ComponentStatusInformer
// 获取ConfigMapInformer
ConfigMaps() ConfigMapInformer
// 获取EndpointsInformer
Endpoints() EndpointsInformer
// 获取EventInformer
Events() EventInformer
// 获取LimitRangeInformer
LimitRanges() LimitRangeInformer
// 获取NamespaceInformer
Namespaces() NamespaceInformer
// 获取NodeInformer
Nodes() NodeInformer
// 获取PersistentVolumeInformer
PersistentVolumes() PersistentVolumeInformer
// 获取PersistentVolumeClaimInformer
PersistentVolumeClaims() PersistentVolumeClaimInformer
// 获取PodInformer
Pods() PodInformer
// 获取PodTemplateInformer
PodTemplates() PodTemplateInformer
// 获取ReplicationControllerInformer
ReplicationControllers() ReplicationControllerInformer
// 获取ResourceQuotaInformer
ResourceQuotas() ResourceQuotaInformer
// 获取SecretInformer
Secrets() SecretInformer
// 获取ServiceInformer
Services() ServiceInformer
// 获取ServiceAccountInformer
ServiceAccounts() ServiceAccountInformer
}
// 这个就是上面抽象类的实现了,这个和Core分组的命名都是挺有意思,分组用group作为实现类名
// 这个用version作为实现类名,确实这个是V1版本
type version struct {
// 工厂的对象指针
factory internalinterfaces.SharedInformerFactory
// 两个选项,不多说了,说了好多遍了
namespace string
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// 这个就是Core分组V1版本的构造函数啦
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
// 应该好理解吧?
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
Core分组有管理了很多Informer,每一个Informer负责获取相应类型的对象。每个对象干啥用的不是本文讲解重点,所以下面的章节我以PodInformer为例说一说具体类型的Informer的实现。
PodInformer是通过Core分组Pods()创建的,我们来看看代码实现:
// 代码源自client-go/informers/core/v1/interface.go
// 上面我们已经说过了version是v1.Interface的实现
func (v *version) Pods() PodInformer {
// 返回了podInformer的对象,说明podInformer是PodInformer 实现类
return &podInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
继续深入,万里长征最后一公里了~
// 代码源自client-go/informers/core/v1/pod.go
// PodInformer定义了两个接口,分别为Informer()和Lister(),Informer()用来获取SharedIndexInformer对象
// Lister()用来获取PodLister对象,这个后面会有说明,当前可以不用关心
type PodInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1.PodLister
}
// PodInformer的实现类,参数都是上面层层传递下来的,这里不说了
type podInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// 这个就是需要传递给SharedInformerFactory的构造函数啦,前面也提到了
func (f *podInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
// NewFilteredPodInformer下面有代码注释
return NewFilteredPodInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
// 实现了PodInformer.Informer()接口函数
func (f *podInformer) Informer() cache.SharedIndexInformer {
// 此处调用了工厂实现了Informer的创建
return f.factory.InformerFor(&corev1.Pod{}, f.defaultInformer)
}
// 实现了PodInformer.Lister()接口函数
func (f *podInformer) Lister() v1.PodLister {
return v1.NewPodLister(f.Informer().GetIndexer())
}
// 真正创建PodInformer的函数
func NewFilteredPodInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
// 还有谁记得构造SharedIndexInformer需要写啥?自己温习《深入浅出kubernetes之client-go的SharedInformer》
return cache.NewSharedIndexInformer(
// 需要ListWatch两个函数,就是用apiserver的client实现的,此处不重点解释每个代码什么意思
// 读者应该能够看懂是利用client实现了Pod的List和Watch
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CoreV1().Pods(namespace).List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CoreV1().Pods(namespace).Watch(options)
},
},
// 这个是要传入对象的类型,肯定是Pod了
&corev1.Pod{},
// 同步周期
resyncPeriod,
// 对象键的计算函数
indexers,
)
}
至此,就算是把Informer先关的流程全部走通了,用户通过SharedInformerFactory的接口简单调用获取了PodInformer,但是后面的逻辑实现确实相对比较复杂,但总给我一种感觉就是又绕且效率不高~为什么呢, 因为SharedIndexInformer其实可以简单理解为一个二层map,第一层按照索引键函数名称分组,但实际基本就获取对象namesapce一种函数。第二层是按照namesapce分组(刚刚说了就获取对象namesapce一种函数),但是我们看到PodInformer是需要指定namesapce过滤的(当然可以不指定namespace)。但是通盘理解了以后就不那么绕了,开源大神写代码考虑的问题比较多,自然感觉有些臃肿。
而PodLister的实现其实主要就是还是依赖Indexer(SharedIndexInformer的cache实现了他)这个类实现的检索,读者自己看吧,写的有点累了~