Kubernetes: client-go 源码剖析(一)
0. 前言
在看 kube-scheduler
组件的过程中遇到了 kube-scheduler
对于 client-go
的调用,泛泛的理解调用过程总有种隔靴搔痒的感觉,于是调转头先把 client-go
理清楚在回来看 kube-scheduler
。
为什么要看 client-go
,并且要深入到原理,源码层面去看。很简单,因为它很重要。重要在两方面:
kubernetes
组件通过client-go
和kube-apiserver
交互。client-go
简单,易用,大部分基于Kubernetes
做二次开发的应用,在和kube-apiserver
交互时会使用client-go
。
当然,不仅在于使用,理解层面,对于我们学习代码开发,架构等也有帮助。
1. client-go 客户端对象
client-go
支持四种客户端对象,分别是 RESTClient
,ClientSet
,DynamicClient
和 DiscoveryClient
:
组件或者二次开发的应用可以通过这四种客户端对象和 kube-apiserver
交互。其中,RESTClient
是最基础的客户端对象,它封装了 HTTP Request
,实现了 RESTful
风格的 API
。ClientSet
基于 RESTClient
,封装了对于 Resource
和 Version
的请求方法。DynamicClient
相比于 ClientSet
提供了全资源,包括自定义资源的请求方法。DiscoveryClient
用于发现 kube-apiserver
支持的资源组,资源版本和资源信息。
每种客户端适用的场景不同,主要是对 HTTP Request
做了层层封装,具体的代码实现可参考 client-go 客户端对象。
2. informer 机制
仅仅封装 HTTP Request
是不够的,组件通过 client-go
和 kube-apiserver
交互,必然对实时性,可靠性等有很高要求。试想,如果 ETCD
中存储的数据和组件通过 client-go
从 ETCD
获取的数据不匹配的话,那将会是一个非常严重的问题。
如何实现 client-go
的实时性,可靠性?client-go
给出的答案是:informer
机制。
client-go informer 流程图
informer
机制的核心组件包括:
Reflector
: 主要负责两类任务:- 通过
client-go
客户端对象 listkube-apiserver
资源,并且 watchkube-apiserver
资源变更。 - 作为生产者,将获取的资源放入
Delta FIFO
队列。
- 通过
Informer
: 主要负责三类任务:- 作为消费者,将
Reflector
放入队列的资源拿出来。 - 将资源交给
indexer
组件。 - 交给
indexer
组件之后触发回调函数,处理回调事件。
- 作为消费者,将
Indexer
:indexer
组件负责将资源信息存入到本地内存数据库(实际是map
对象),该数据库作为缓存存在,其资源信息和ETCD
中的资源信息完全一致(得益于watch
机制)。因此,client-go
可以从本地indexer
中读取相应的资源,而不用每次都从kube-apiserver
中获取资源信息。这也实现了client-go
对于实时性的要求。
接下来从源码角度看各个组件的处理流程,力图做到知其然,知其所以然。
2 informer 源码分析
直接阅读 informer
源码是非常晦涩难懂的,这里通过 informer
的代码示例开始学习:
package main
import (
"log"
"time"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/clientcmd"
)
func main() {
// 解析 kubeconfig
config, err := clientcmd.BuildConfigFromFlags("", "/root/.kube/config")
if err != nil {
panic(err)
}
// 创建 ClientSet 客户端对象
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err)
}
stopCh := make(chan struct{})
defer close(stopCh)
// 创建 sharedInformers
sharedInformers := informers.NewSharedInformerFactory(clientset, time.Minute)
// 创建 informer
informer := sharedInformers.Core().V1().Pods().Informer()
// 创建 Event 回调 handler
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
mObj := obj.(v1.Object)
log.Printf("New Pod Added to Store: %s", mObj.GetName())
},
UpdateFunc: func(oldObj, newObj interface{}) {
oObj := oldObj.(v1.Object)
nObj := newObj.(v1.Object)
log.Printf("%s Pod Updated to %s", oObj.GetName(), nObj.GetName())
},
DeleteFunc: func(obj interface{}) {
mObj := obj.(v1.Object)
log.Printf("Pod Deleted from Store: %s", mObj.GetName())
},
})
// 运行 informer
informer.Run(stopCh)
}
执行结果如下:
# go run informer.go
2023/12/14 12:00:26 New Pod Added to Store: prometheus-alertmanager-0
2023/12/14 12:01:26 prometheus-alertmanager-0 Pod Updated to prometheus-alertmanager-0
上述代码示例分为三部分:创建 informer
,创建 informer
的 EventHandler
,运行 informer
。下面,通过这三部分流程介绍 client-go
的核心组件。
2.1 创建 informer
创建 informer
分为两步。
1)创建工厂 sharedInformerFactory
// sharedInformers factory
sharedInformers := informers.NewSharedInformerFactory(clientset, time.Minute)
// client-go/informers/factory.go
func NewSharedInformerFactory(client kubernetes.Interface, defaultResync time.Duration) SharedInformerFactory {
return NewSharedInformerFactoryWithOptions(client, defaultResync)
}
func NewSharedInformerFactoryWithOptions(client kubernetes.Interface, defaultResync time.Duration, options ...SharedInformerOption) SharedInformerFactory {
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),
}
// Apply all options
for _, opt := range options {
factory = opt(factory)
}
return factory
}
sharedInformerFactory
实现了 SharedInformerFactory
接口,该工厂负责创建 informer
。
2)创建 informer
// 创建 informer
informer := sharedInformers.Core().V1().Pods().Informer()
// 调用 Core 方法
func (f *sharedInformerFactory) Core() core.Interface {
return core.New(f, f.namespace, f.tweakListOptions)
}
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 {
return v1.New(g.factory, g.namespace, g.tweakListOptions)
}
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// 调用 Pods 方法
func (v *version) Pods() PodInformer {
return &podInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
经过层层构建创建 podInformer
对象,该对象实现了 PodInformer
接口,调用接口的 Informer
方法创建 informer
对象:
func (f *podInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&corev1.Pod{}, f.defaultInformer)
}
podInformer.Informer
实际调用的是 sharedInformerFactory.InformerFor
:
func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer {
f.lock.Lock()
defer f.lock.Unlock()
// 反射出资源对象 obj 的 type
informerType := reflect.TypeOf(obj)
// 读取并判断资源对象的 informer
informer, exists := f.informers[informerType]
if exists {
return informer
}
...
// 调用 newFunc 创建 informer
informer = newFunc(f.client, resyncPeriod)
// 将 type:informer 加入到 factory 的 informers 中
f.informers[informerType] = informer
return informer
}
从 InformerFor
方法可以看出,sharedInformerFactory
的 share 体现在同一个资源类型共享 informer
。
这么设计在于,每个 informer
包括一个 Reflector
,Reflector
通过访问 kube-apiserver
实现 ListAndWatch
操作。共享 informer
实际是共享 Reflector
,这种共享机制将减少 Reflector
对于 kube-apiserver
的访问,降低 kube-apiserver
的负载,节约资源。
继续看,创建 informer
的 newFunc
函数做了什么:
informer = newFunc(f.client, resyncPeriod)
// client-go/informers/core/v1/pod.go
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)
}
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,
)
}
newFunc
实际调用的是 NewFilteredPodInformer
函数,在函数内创建 cache.ListAndWatch
对象,对象中包括 ListFunc
和 WatchFunc
回调函数,回调函数内调用 ClientSet
实现 list 和 watch 资源对象。
继续看 cache.NewSharedIndexInformer
:
// client-go/tools/cache/shared_informer.go
func NewSharedIndexInformer(lw ListerWatcher, exampleObject runtime.Object, defaultEventHandlerResyncPeriod time.Duration, indexers Indexers) SharedIndexInformer {
return NewSharedIndexInformerWithOptions(
lw,
exampleObject,
SharedIndexInformerOptions{
ResyncPeriod: defaultEventHandlerResyncPeriod,
Indexers: indexers,
},
)
}
func NewSharedIndexInformerWithOptions(lw ListerWatcher, exampleObject runtime.Object, options SharedIndexInformerOptions) SharedIndexInformer {
realClock := &clock.RealClock{}
return &sharedIndexInformer{
indexer: NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, options.Indexers),
processor: &sharedProcessor{clock: realClock},
listerWatcher: lw,
objectType: exampleObject,
objectDescription: options.ObjectDescription,
resyncCheckPeriod: options.ResyncPeriod,
defaultEventHandlerResyncPeriod: options.ResyncPeriod,
clock: realClock,
cacheMutationDetector: NewCacheMutationDetector(fmt.Sprintf("%T", exampleObject)),
}
}
在 NewSharedIndexInformerWithOptions
函数内创建 informer sharedIndexInformer
。可以看到,sharedIndexInformer
内包括了 indexer
核心组件。
informer
创建完成。接下来为 informer
添加回调函数 EventHandler
。
2.2 创建 EventHandler
代码实现如下:
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
mObj := obj.(v1.Object)
log.Printf("New Pod Added to Store: %s", mObj.GetName())
},
UpdateFunc: func(oldObj, newObj interface{}) {
oObj := oldObj.(v1.Object)
nObj := newObj.(v1.Object)
log.Printf("%s Pod Updated to %s", oObj.GetName(), nObj.GetName())
},
DeleteFunc: func(obj interface{}) {
mObj := obj.(v1.Object)
log.Printf("Pod Deleted from Store: %s", mObj.GetName())
},
})
创建 EventHandler
的 handler
中包括三种回调函数:AddFunc
,UpdateFunc
和 DeleteFunc
,三种回调函数分别在资源有增加,变更,删除时触发。
在 sharedIndexInformer.AddEventHandler
内,将 handler
传递给 sharedIndexInformer.AddEventHandlerWithResyncPeriod
方法,该方法主要创建 listener
对象:
// client-go/tools/cache/shared_informer.go
func (s *sharedIndexInformer) AddEventHandler(handler ResourceEventHandler) (ResourceEventHandlerRegistration, error) {
return s.AddEventHandlerWithResyncPeriod(handler, s.defaultEventHandlerResyncPeriod)
}
func (s *sharedIndexInformer) AddEventHandlerWithResyncPeriod(handler ResourceEventHandler, resyncPeriod time.Duration) (ResourceEventHandlerRegistration, error) {
...
listener := newProcessListener(handler, resyncPeriod, determineResyncPeriod(resyncPeriod, s.resyncCheckPeriod), s.clock.Now(), initialBufferSize, s.HasSynced)
if !s.started {
return s.processor.addListener(listener), nil
}
...
}
// client-go/tools/cache/shared_informer.go
func newProcessListener(handler ResourceEventHandler, requestedResyncPeriod, resyncPeriod time.Duration, now time.Time, bufferSize int, hasSynced func() bool) *processorListener {
ret := &processorListener{
nextCh: make(chan interface{}),
addCh: make(chan interface{}),
handler: handler,
syncTracker: &synctrack.SingleFileTracker{UpstreamHasSynced: hasSynced},
pendingNotifications: *buffer.NewRingGrowing(bufferSize),
requestedResyncPeriod: requestedResyncPeriod,
resyncPeriod: resyncPeriod,
}
ret.determineNextResync(now)
return ret
}
func (p *sharedProcessor) addListener(listener *processorListener) ResourceEventHandlerRegistration {
...
p.listeners[listener] = true
...
return listener
}
listener
对象包含通道 addCh
和 nextCh
,以及 handler
等对象。最后将 listener
存入 sharedIndexInformer.sharedProcessor
中。
创建完 informer
的 EventHandler
,接下来该运行 informer
了。
Kubernetes: client-go 源码剖析(一)的更多相关文章
- kubernetes dashboard backend源码剖析
dashboard架构主要由一个API handler 和 五个manager构成: API handler用来处理来自客户的http请求,不同的path路由到不同的的handler处理,使用的是go ...
- 自己实现多线程的socket,socketserver源码剖析
1,IO多路复用 三种多路复用的机制:select.poll.epoll 用的多的两个:select和epoll 简单的说就是:1,select和poll所有平台都支持,epoll只有linux支持2 ...
- 玩转Android之Picasso使用详详详详详详解,从入门到源码剖析!!!!
Picasso是Squareup公司出的一款图片加载框架,能够解决我们在Android开发中加载图片时遇到的诸多问题,比如OOM,图片错位等,问题主要集中在加载图片列表时,因为单张图片加载谁都会写.如 ...
- (升级版)Spark从入门到精通(Scala编程、案例实战、高级特性、Spark内核源码剖析、Hadoop高端)
本课程主要讲解目前大数据领域最热门.最火爆.最有前景的技术——Spark.在本课程中,会从浅入深,基于大量案例实战,深度剖析和讲解Spark,并且会包含完全从企业真实复杂业务需求中抽取出的案例实战.课 ...
- Appuim源码剖析(Bootstrap)
Appuim源码剖析(Bootstrap) SkySeraph Jan. 26th 2017 Email:skyseraph00@163.com 更多精彩请直接访问SkySeraph个人站点:www. ...
- 老李推荐: 第14章2节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-HierarchyViewer架构概述
老李推荐: 第14章2节<MonkeyRunner源码剖析> HierarchyViewer实现原理-HierarchyViewer架构概述 HierarchyViewer库的引入让M ...
- 豌豆夹Redis解决方案Codis源码剖析:Dashboard
豌豆夹Redis解决方案Codis源码剖析:Dashboard 1.不只是Dashboard 虽然名字叫Dashboard,但它在Codis中的作用却不可小觑.它不仅仅是Dashboard管理页面,更 ...
- 豌豆夹Redis解决方案Codis源码剖析:Proxy代理
豌豆夹Redis解决方案Codis源码剖析:Proxy代理 1.预备知识 1.1 Codis Codis就不详细说了,摘抄一下GitHub上的一些项目描述: Codis is a proxy base ...
- jdk源码剖析一:OpenJDK-Hotspot源码包目录结构
开启正文之前,先说一下源码剖析这一系列,就以“死磕到底”的精神贯彻始终,JDK-->JRE-->JVM(以openJDK代替) 最近想看看JDK8源码,但JDK中JVM(安装在本地C:\P ...
- Django----djagorest-framwork源码剖析
restful(表者征状态转移,面向资源编程)------------------------------------------->约定 从资源的角度审视整个网络,将分布在网络中某个节点的资源 ...
随机推荐
- 《小白WEB安全入门》03. 漏洞篇
@ 目录 SQL注入和简单绕过原理 什么是SQL 什么是SQL注入 XSS漏洞原理 什么是XSS XSS分类 NOSQL注入 什么是NOSQL CSRF原理 什么是CSRF 网络摄像头入侵原理 什么是 ...
- 对比 MyBatis 和 MyBatis-Plus 批量插入、批量更新的性能和区别
1 环境准备 demo 地址:learn-mybatis · Sean/spring-cloud-alibaba - 码云(gitee.com) 1.1 搭建 MyBatis-Plus 环境 创建 m ...
- openNebula集群搭建
openNebula集群搭建 目录 openNebula集群搭建 OpenNebula概述 环境介绍及部署前准备 1. 安装步骤 1.关闭防火墙 2.配置epel源地和opennebula源 3.安装 ...
- 使用MySQL存储过程提高数据库效率和可维护性
MySQL 存储过程是一种强大的数据库功能,它允许你在数据库中存储和执行一组SQL语句,类似于编程中的函数.存储过程可以大幅提高数据库的性能.安全性和可维护性.本文将详细介绍MySQL存储过程的使用. ...
- 一个关于 i++ 和 ++i 的面试题打趴了所有人
前言 都说大城市现在不好找工作,可小城市却也不好招人. 我们公司招了挺久都没招到,主管感到有些心累. 我提了点建议,是不是面试问的太深了,在这种小城市,能干活就行. 他说自己问的面试题都很浅显,如果答 ...
- Abp vNext 依赖注入
文章目录 介绍 ABP的依赖注入系统是基于Microsoft的依赖注入扩展库(Microsoft.Extensions.DependencyInjection nuget包)开发的.所以我们采用dot ...
- oracle-组合索引字段位置与查询效率之间的关系
Oracle索引组合字段的位置不同,当查询条件不能覆盖索引时,影响查询效率.查询条件是不是索引字段的第一列影响执行计划,实验验证 实验1:查询条件为组合索引的第一列--创建测试表 create tab ...
- ReactPortals传送门
ReactPortals传送门 React Portals提供了一种将子节点渲染到父组件以外的DOM节点的解决方案,即允许将JSX作为children渲染至DOM的不同部分,最常见用例是子组件需要从视 ...
- idea2020.3 安装插件JetBrains 插件市场安装 Cloud Toolkit
<Cloud Toolkit User Guide> 本文是 Alibaba Cloud Toolkit 的使用文档指引,所有相关的使用参考,都可以在本文中找到.如果在使用中有任何问题,请 ...
- fasthttp + `page partial gziped cache`: 页面输出服务性能提升20%
作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 接上一篇:http 中使用 gzip 输出内容时,如何预先 ...