想要读懂kubernetes 源码,首先要理解client-go, 因为client-go 是 Kubernetes 官方的 Go 语言客户端库,被 Kubernetes 中的众多组件和功能模块使用。其中包括 kube-apiserver、kube-controller-manager、kube-scheduler 等核心组件,以及一些扩展功能如 kube-proxy、kubelet 等。client-go 还提供了访问 Kubernetes API 的便捷方式,所以在后面开发CRD operator时,也被广泛使用。
1. Client-go的重要组件
DiscoveryClient:DiscoveryClient 是一个用于发现 Kubernetes API 的组件,它可以获取集群中所有的 API 版本和资源类型。
DynamicClient:DynamicClient 是一个用于处理 Kubernetes API 对象的组件,它允许进行动态操作,这意味着你可以编写处理任意 Kubernetes API 对象的代码,而不需要提前知道这些对象的模式。
RESTClient:RESTClient 是 client-go 的核心,它发送所有的 HTTP 请求。当你使用 ClientSet,DynamicClient 或 DiscoveryClient 时,这些请求实际上是由 RESTClient 发送的。
Informers和Listers:这些组件用于在本地缓存 Kubernetes API 对象,并提供了一种通知框架,以便可以在 Kubernetes 对象更改时接收到通知。这对于构建控制器和其他需要对 Kubernetes 状态进行长时间运行的操作非常有用。
Workqueue:这是一个实现了生产者/消费者队列的工具,主要用于构建控制器和其他需要处理 Kubernetes 对象的长时间运行的操作。
2. Client-go的cache工具包
在开发CRD operator时最离不开的就是cache,它提供了一些用于监听、缓存和查询 Kubernetes 资源对象的工具。下面这张图来自于client-go的文档。上半部分属于client-go cache提供的组件。下半部分是开发controller需要实现的操作。
-
Reflector:
Reflector 是一个用于从 Kubernetes API 获取资源并将其同步到本地缓存的工具。这个是事件驱动的。1.1监听事件:Reflector 首先会与 Kubernetes API 服务器建立连接,并通过 Watch 机制来监听资源对象的增删改事件。
1.2.处理事件:当 Reflector 接收到 API 服务器发送的事件通知时,它会将这些事件转换成 DeltaFIFO 中的操作,如 Add、Update、Delete 等。
1.3.操作 DeltaFIFO:Reflector 会将这些事件操作应用到 DeltaFIFO 中,以确保本地缓存中的资源对象能够与实际的集群状态保持同步。这可能涉及将事件操作应用到 DeltaFIFO 队列中,以确保它们按正确的顺序进行处理。
-
DeltaFIFO:
DeltaFIFO 是一个用于存储 Kubernetes 资源对象更改事件的队列,它是 Informer 和 Reflector 的底层存储结构。每种资源类型(如 Pod、Service、Deployment 等)都会有一个相关的 Informer,而每个 Informer 会维护一个自己的 DeltaFIFO 队列来追踪该类型资源的增删改变化事件。DeltaFIFO 队列默认情况下没有设置大小,如果需要对 DeltaFIFO 的大小进行限制,可以在创建 DeltaFIFO 实例时,通过设置 MaxSize 参数来指定队列的最大值。fifo := cache.NewDeltaFIFO(cache.MetaNamespaceKeyFunc, runtime.NewScheme(), nil, <maxSize>)
-
Informer:
Informer 是一个用于监听和缓存 Kubernetes API 资源对象的工具,它会在资源对象发生变化时发送通知。Informer 包括 SharedInformer 和具体的资源类型 Informer,如 PodInformer。虽然 podInformer 主要用于 Pod 资源对象的监听和消费,但它并不限定于只监听 Pod 资源的变化。实际上,podInformer 也可以监听与 Pod 相关的其他事件,比如 Node、Service、Endpoints 等与 Pod 相关的资源对象的变化。常见的Informer类型:1.PodInformer:用于监听和管理 Pod 资源对象的变化。 2.NodeInformer:用于监听和管理 Node 资源对象的变化。 3.ServiceInformer:用于监听和管理 Service 资源对象的变化。 4.DeploymentInformer:用于监听和管理 Deployment 资源对象的变化。 5.ReplicaSetInformer:用于监听和管理 ReplicaSet 资源对象的变化。 6.DaemonSetInformer:用于监听和管理 DaemonSet 资源对象的变化。 7.StatefulSetInformer:用于监听和管理 StatefulSet 资源对象的变化。 8.JobInformer:用于监听和管理 Job 资源对象的变化。 9.NamespaceInformer:用于监听和管理 Namespace 资源对象的变化。 10.ConfigMapInformer:用于监听和管理 ConfigMap 资源对象的变化。 11.SecretInformer:用于监听和管理 Secret 资源对象的变化。 12.PersistentVolumeInformer:用于监听和管理 PersistentVolume 资源对象的变化。 13.PersistentVolumeClaimInformer:用于监听和管理 PersistentVolumeClaim 资源对象的变化。
-
Indexer:
Indexer 提供了一种对缓存对象进行索引和查询的方式,可以根据不同的字段(如名称、标签等)对资源进行索引。Indexer 是基于 Store 接口扩展出来的。Store 提供了对 Kubernetes 资源对象进行基本操作的一组方法,如添加(Add)、更新(Update)、删除(Delete)、获取(Get)和列出(List)等。Indexer 在 Store 的基础上,增加了一组用于对 Kubernetes 资源对象进行索引和查询的方法,如 Index、IndexKeys、ListIndexFuncValues、ByIndex、GetIndexers 和 AddIndexers 等。
举个例子,假设我们想要根据一个 Pod 的标签来索引 Pod。我们可以定义一个索引函数,这个函数接收一个 Pod 对象,读取它的标签字段,然后返回这些标签作为索引键。这种索引函数可能如下所示(假设我们对 "app" 标签感兴趣):
func podAppIndexFunc(obj interface{}) ([]string, error) {
pod, ok := obj.(*v1.Pod)
if !ok {
return []string{}, nil
}
if app, exists := pod.Labels["app"]; exists {
return []string{app}, nil
}
return []string{}, nil
}
在这个例子中,索引函数接收一个 Pod 对象,检查它是否有 "app" 标签。如果有,函数就返回这个标签的值作为索引键。然后,你可以将这个索引函数添加到 Indexer 中:
indexers := cache.Indexers{
"byApp": podAppIndexFunc,
}
informer := cache.NewSharedIndexInformer(
&cache.ListWatch{...},
&v1.Pod{},
0, // 不同步
indexers,
)
然后,你就可以使用 "byApp" 索引来检索 Pod 了:
pods, err := informer.GetIndexer().ByIndex("byApp", "myapp")
这段代码会返回所有 "app" 标签值为 "myapp" 的 Pod。
5.ThreadSafeStore 的数据结构包括:
items:一个字典,用于存储 Kubernetes 资源对象,其中字典的键是资源对象的键(通常是资源的 Namespace 和 Name 的组合),字典的值是资源对象本身。
indexers:一个字典,用于存储多个索引函数,其中字典的键是索引的名称,字典的值是索引函数。索引函数用于根据资源对象的某些属性生成索引键。
indices:一个字典,用于存储由索引函数生成的索引键及对应的资源对象键。其中字典的键是索引键,字典的值是一个集合,集合中的元素是对应的资源对象键。
这些数据结构都由一个读写锁(sync.RWMutex)保护,以确保在多线程环境中对其进行操作时的线程安全。
3. Client-go是如何暴露给custom controller的?
从上图中可以看出,custom controller是通过调用三个reference(res event handlers reference, informer reference,indexer reference)实现的。具体的方法是:
ResourceEventHandler:ResourceEventHandler 是一个接口,可以定义对 Kubernetes 资源对象变更事件的处理方法。在 controller 中,你可以实现这个接口,然后将其添加到 Informer 中。当 Kubernetes 资源对象发生变更时,Informer 会调用相应的处理方法。
Informer:Informer 可以通过 client-go 的 informers 包来获取。每种类型的 Kubernetes 资源(如 Pod、Service、Deployment 等)都有自己的 Informer。controller 可以通过调用 Informer 的 Listers 方法来获取资源对象的列表,或者通过调用 Informer 的 AddEventHandler 方法来添加事件处理器。
Indexer:Indexer 是 Informer 的一部分,可以通过调用 Informer 的 GetIndexer 方法来获取。Indexer 提供了对 Kubernetes 资源对象进行索引和查询的功能,controller 可以使用这些功能来快速查找特定的资源对象。
举个例子:
import (
"k8s.io/client-go/informers"
"k8s.io/client-go/tools/cache"
)
func main() {
// 创建 Kubernetes 客户端
kubeClient, _ := kubernetes.NewForConfig(config)
// 创建 Informer 工厂
informerFactory := informers.NewSharedInformerFactory(kubeClient, 0)
// 获取 Pod Informer
podInformer := informerFactory.Core().V1().Pods().Informer()
// 在 Pod Informer 上添加事件处理器
podInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
// 处理 Pod 创建事件
},
UpdateFunc: func(oldObj, newObj interface{}) {
// 处理 Pod 更新事件
},
DeleteFunc: func(obj interface{}) {
// 处理 Pod 删除事件
},
})
// 获取 Indexer
indexer := podInformer.GetIndexer()
// 使用 Indexer 进行查询
pods, err := indexer.ByIndex("my-index", "my-value")
// 启动 Informer
informerFactory.Start(wait.NeverStop)
}
一个 ResourceEventHandler 被添加到 Pod Informer 中以处理 Pod 的变更事件,同时 Indexer 被用于查询特定的 Pod。