Indexer 概述
在 Kubernetes client-go 中,Indexer 是一个关键的本地存储组件。它的主要职责是建立索引并缓存 Resource 对象。通过维护与 ETCD 一致的数据副本,当需要获取资源时,可以直接从本地缓存读取,无需每次都请求 APIServer,从而有效降低了对 APIServer 和 ETCD 的压力。
数据流通常是从 DeltaFIFO 中 Pop 出来的资源对象交给 HandlerDeltas 处理,随后同步到 Indexer 中。以下是 HandleDeltas 方法的核心逻辑:
// k8s.io/client-go/tools/cache/shared_informer.go
func (s *sharedIndexInformer) HandleDeltas(obj interface{}) error {
s.blockDeltas.Lock()
defer s.blockDeltas.Unlock()
if deltas, ok := obj.(Deltas); ok {
return processDeltas(s, s.indexer, s.transform, deltas)
}
return errors.New("object given as Process argument is not Deltas")
}
// k8s.io/client-go/tools/cache/controller.go
func processDeltas(
handler ResourceEventHandler,
clientState Store,
transformer TransformFunc,
deltas Deltas) error {
// from oldest to newest
for _, d := range deltas {
obj := d.Object
if transformer != nil {
var err error
obj, err = transformer(obj)
if err != nil {
return err
}
}
switch d.Type {
case Sync, Replaced, Added, Updated:
if old, exists, err := clientState.Get(obj); err == nil && exists {
if err := clientState.Update(obj); err != nil {
return err
}
handler.OnUpdate(old, obj)
} else {
if err := clientState.Add(obj); err != nil {
return err
}
handler.OnAdd(obj)
}
case Deleted:
if err := clientState.Delete(obj); err != nil {
return err
}
handler.OnDelete(obj)
}
}
return nil
}
核心接口与结构
Indexer 接口继承自 Store 接口,Store 负责基础的本地缓存操作(增删改查),而 Indexer 在此基础上扩展了索引功能。
Store 接口定义
// staging/src/k8s.io/client-go/tools/cache/store.go
type Store interface {
Add(obj interface{}) error
Update(obj interface{}) error
Delete(obj interface{}) error
List() []interface{}
ListKeys() []string
Get(obj interface{}) (item interface{}, exists bool, err error)
GetByKey(key string) (item interface{}, exists bool, err error)
Replace([]interface{}, string) error
Resync() error
}
Indexer 接口定义
type Indexer interface {
Store
// 通过计算 obj 在 indexName 索引类中的索引键,获取所有对象
Index(indexName string, obj interface{}) ([]interface{}, error)
// 返回 indexKey 指定的所有对象键
IndexKeys(indexName, indexedValue string) ([]string, error)
// 获取 indexName 索引类中的所有索引键
ListIndexFuncValues(indexName string) []string
// 根据索引键获取对象列表
ByIndex(indexName, indexedValue string) ([]interface{}, error)
// 获取所有索引器
GetIndexers() Indexers
// 增加更多索引分类
AddIndexers(newIndexers Indexers) error
}
内部实现:threadSafeMap
实际的数据存储依赖于 cache 结构体,它包含一个 ThreadSafeStore 接口的实现以及用于生成唯一 Key 的 KeyFunc。
type cache struct {
cacheStorage ThreadSafeStore
keyFunc KeyFunc
}
ThreadSafeStore 接口封装了具体的线程安全操作,其底层由 threadSafeMap 实现。这个结构体不仅管理资源对象本身,还维护了索引映射关系。
type threadSafeMap struct {
lock sync.RWMutex
items map[string]interface{} // 存放资源对象
indexers Indexers // 索引函数映射
indices Indices // 索引数据映射
}
这里有两个关键类型:
IndexFunc: 用于从对象中提取索引键的函数。Index: 存储索引键到对象键集合的映射。
索引功能实现
索引功能的灵活性依赖于 IndexFunc。Kubernetes 提供了一个常用的默认索引函数 MetaNamespaceIndexFunc,它基于对象的命名空间进行索引。
func MetaNamespaceIndexFunc(obj interface{}) ([]string, error) {
meta, err := meta.Accessor(obj)
if err != nil {
return []string{""}, fmt.Errorf("object has no meta: %v", err)
}
return []string{meta.GetNamespace()}, nil
}
ByIndex 查询逻辑
当我们使用 ByIndex 方法时,实际上是在利用预先构建好的索引快速定位对象。大致流程如下:
- 查找索引函数:根据索引器名称找到对应的
IndexFunc。 - 查找索引数据:根据索引器名称获取对应的索引映射表。
- 获取对象列表:根据传入的索引值(如 namespace)从索引表中拿到对象键集合,再反查
items获取具体对象。
func (c *threadSafeMap) ByIndex(indexName, indexedValue string) ([]interface{}, error) {
c.lock.RLock()
defer c.lock.RUnlock()
// 1、查找索引器函数
indexFunc := c.indexers[indexName]
if indexFunc == nil {
return nil, fmt.Errorf("Index with name %s does not exist", indexName)
}
// 2、查找相应的缓存器函数
index := c.indices[indexName]
// 3、根据索引 Key 查询并返回结果
set := index[indexedValue]
list := make([]interface{}, 0, set.Len())
for key := range set {
list = append(list, c.items[key])
}
return list, nil
}
使用示例
在实际开发中,我们常利用索引来过滤特定命名空间或节点下的 Pod。
// 查询 default 命名空间下的所有 Pod
pods, err := index.ByIndex("namespace", "default")
if err != nil {
panic(err)
}
for _, pod := range pods {
fmt.Println(pod.(*v1.Pod).Name)
}
fmt.Println("------")
// 查询 node1 节点上的所有 Pod
pods, err = index.ByIndex("nodename", "node1")
if err != nil {
panic(err)
}
for _, pod := range pods {
fmt.Println(pod.(*v1.Pod).Name)
}
输出示例:
pod-1
pod-2
------
pod-1
总结
Indexer 不仅具备维护本地缓存的能力,更核心的价值在于其索引功能。通过 threadSafeMap 中的 indexers 和 indices 属性,我们可以实现高效的反向查找。例如,要查询某个 Node 下的所有 Pod,或者某个命名空间下的所有 Pod,直接利用索引都能秒级完成,避免了遍历整个缓存带来的性能损耗。

