Reflector

Informer启动后会连接API Server并进行全量资源查询,之后会对资源对象进行监听。以上操作主要是由Reflector实现的。源码路径为k8s.io/client-go/tools/cache/reflector.go。

Reflector使用的List/Watch方法主要分为2部分,第一部分用来获取全量的资源列表;第二部分是对资源对象进行监控。

首先看一下Reflector的结构体定义,示例如下所示。

type Reflector struct {
    name string
    expectedTypeName string
    expectedType reflect.Type
    expectedGVK *schema.GroupVersionKind
    store Store
    listerWatcher ListerWatcher
    backoffManager wait.BackoffManager
        initConnBackoffManager wait.BackoffManager
    resyncPeriod time.Duration
        ShouldResync func() bool
    clock clock.Clock
    paginatedResult bool
    lastSyncResourceVersion string
        isLastSyncResourceVersionUnavailable bool
        lastSyncResourceVersionMutex sync.RWMutex
    WatchListPageSize int64
        watchErrorHandler WatchErrorHandler
}

其中,listerWatcher这个字段是一个接口类型,Reflector在run方法中会调用ListAndWatch方法,而ListerAndWatch方法中实际执行的就是ListerWatcher的Lister方法和Watcher方法,分别用来获取全量的资源对象和对后面的事件变更进行监控。ListerWatcher定义如下所示。

type ListerWatcher interface {
    Lister
    Watcher
}

Lister和Watcher也是接口,Lister接口的代码如下所示。

type Lister interface {
    
    List(options metav1.ListOptions) (runtime.Object, error)
}

Watcher接口的代码如下所示。

type Watcher interface {
    Watch(options metav1.ListOptions) (watch.Interface, error)
}

而实际上r.ListerWacher.List真正调用的是Pod、Deployment等资源对象的Informer下的ListFunc函数和WatchFunc函数,源码路径为k8s.io/client-go/informers/core/v1/pods.go。

Pod Informer的ListFunc函数和WatchFunc函数代码如下所示。

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,
    )
}

ListFunc函数和WatchFunc函数是通过Clientset客户端与API Server交互后获得的,这也是为什么8.2.1节的代码示例中,在生成NewSharedInformerFactory之前需要先获得Clientset,示例如下所示。

//创建KController对象
func NewKubeController(kubeConfig *restclient.Config, clientset *kubernetes.
    Clientset, defaultResync time.Duration) *KubeController {
    kc := &KubeController{kubeConfig: kubeConfig, clientset: clientset}
    //需要传入Clientset
    kc.factory = informers.NewSharedInformerFactory(clientset, defaultResync)
    ...
    kc.deploymentInformer = kc.factory.Apps().V1().Deployments()
    kc.deploymentsLister = kc.deploymentInformer.Lister()
    kc.deploymentsSynced = kc.deploymentInformer.Informer().HasSynced
    kc.podInformer = kc.factory.Core().V1().Pods()
    kc.podsLister = kc.podInformer.Lister()
    kc.podsSynced = kc.podInformer.Informer().HasSynced
    ...
    return kc
}

List/Watch的逻辑比较长,下面分为2部分来分析核心的函数调用,第一部分示例如下所示。

func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
    klog.V(3).Infof("Listing and watching %v from %s", r.expectedTypeName, r.name)
    var resourceVersion string
    options := metav1.ListOptions{ResourceVersion: r.relistResourceVersion()}
    if err := func() error {
        initTrace := trace.New("Reflector ListAndWatch", trace.Field{"name", r.name})
        defer initTrace.LogIfLong(10 * time.Second)
        var list runtime.Object
        var paginatedResult bool
        var err error
        listCh := make(chan struct{}, 1)
        panicCh := make(chan interface{}, 1)
        //调用r.listerWatcher.List获取资源数据
        go func() {
            defer func() {
                if r := recover(); r != nil {
                    panicCh <- r
                }
            }()
            pager := pager.New(pager.SimplePageFunc(func(opts metav1.ListOptions) 
                (runtime.Object, error) {
                return r.listerWatcher.List(opts)
            }))
            switch {
            case r.WatchListPageSize != 0:
                pager.PageSize = r.WatchListPageSize
            case r.paginatedResult:
            case options.ResourceVersion != "" && options.ResourceVersion != "0":
                pager.PageSize = 0
            }
            list, paginatedResult, err = pager.List(context.Background(), options)
            if isExpiredError(err) || isTooLargeResourceVersionError(err) {
                r.setIsLastSyncResourceVersionUnavailable(true)
                list, paginatedResult, err = pager.List(context.Background(), metav1.
                    ListOptions{ResourceVersion: r.relistResourceVersion()})
            }
            close(listCh)
        }()
        select {
        case <-stopCh:
            return nil
        case r := <-panicCh:
            panic(r)
        case <-listCh:
        }
        if err != nil {
            return fmt.Errorf("failed to list %v: %v", r.expectedTypeName, err)
        }
        //调用listMetaInterface.GetResourceVersion获取资源的版本号
        if options.ResourceVersion == "0" && paginatedResult {
            r.paginatedResult = true
        }
        r.setIsLastSyncResourceVersionUnavailable(false) 
        initTrace.Step("Objects listed")
        listMetaInterface, err := meta.ListAccessor(list)
        if err != nil {
            return fmt.Errorf("unable to understand list result %#v: %v", list, err)
        }
        resourceVersion = listMetaInterface.GetResourceVersion()
        initTrace.Step("Resource version extracted")
        //调用meta.ExtractList 将资源转换成对象列表
        items, err := meta.ExtractList(list)
        if err != nil {
            return fmt.Errorf("unable to understand list result %#v (%v)", list, err)
        }
        initTrace.Step("Objects extracted")
        //调用r.syncWith 将资源对象列表中的资源对象和对应的版本号存入DeltaFIFO
        if err := r.syncWith(items, resourceVersion); err != nil {
            return fmt.Errorf("unable to sync list result: %v", err)
        }
        initTrace.Step("SyncWith done")

第一部分的主体逻辑和调用函数如下。

·调用r.listerWatcher.List获取资源数据。

·调用listMetaInterface.GetResourceVersion获取资源的版本号。

·调用meta.ExtractList将资源转换成对象列表。

·调用r.syncWith将资源对象列表中的资源对象和对应的版本号存入DeltaFIFO。

第二部分示例如下所示。

//调用r.setLastSyncResourceVersion设置最新的资源版本号
        r.setLastSyncResourceVersion(resourceVersion)
        initTrace.Step("Resource version updated")
        return nil
    }(); err != nil {
        return err
    }
    resyncerrc := make(chan error, 1)
    cancelCh := make(chan struct{})
    defer close(cancelCh)
    go func() {
        resyncCh, cleanup := r.resyncChan()
        defer func() {
            cleanup() 
        }()
        for {
            select {
            case <-resyncCh:
            case <-stopCh:
                return
            case <-cancelCh:
                return
            }
            if r.ShouldResync == nil || r.ShouldResync() {
                klog.V(4).Infof("%s: forcing resync", r.name)
                if err := r.store.Resync(); err != nil {
                    resyncerrc <- err
                    return
                }
            }
            cleanup()
            resyncCh, cleanup = r.resyncChan()
        }
    }()
    for {
        select {
        case <-stopCh:
            return nil
        default:
        }
        timeoutSeconds := int64(minWatchTimeout.Seconds() * (rand.Float64() + 1.0))
        options = metav1.ListOptions{
            ResourceVersion: resourceVersion,
            TimeoutSeconds: &timeoutSeconds,
            AllowWatchBookmarks: true,
        }
        start := r.clock.Now()
        //调用r.listerWatcher.Watch进行资源对象的事件监控
        w, err := r.listerWatcher.Watch(options)
        if err != nil {
            if utilnet.IsConnectionRefused(err) {
                <-r.initConnBackoffManager.Backoff().C()
                continue
            }
            return err
        }
     //触发事件后调用r.watchHandler将资源对象存入DeltaFIFO中并更新版本号
        if err := r.watchHandler(start, w, &resourceVersion, resyncerrc, stopCh); 
            err != nil {
            if err != errorStopRequested {
                switch {
                case isExpiredError(err):
t observed object.
                }
            }
            return nil
        }
    }
}

第二部分的主体逻辑和调用函数如下。

·调用r.setLastSyncResourceVersion设置最新的资源版本号。

·调用r.listerWatcher.Watch进行资源对象的事件监控。

·触发事件后调用r.watchHandler将资源对象存入DeltaFIFO中并更新版本号。

List/Watch方法逻辑中调用的函数较多,感兴趣的读者可以查阅源码。

关于Watch的底层实现,其实是使用HTTP长连接来监听事件的,并且使用了HTTP的分块传输编码(Chunked Transfer Encoding)。根据百度百科上的介绍,分块传输编码是HTTP中的一种数据传输机制,允许HTTP由网页服务器发送给客户端应用(通常是网页浏览器)的数据可以分成多个部分。分块传输编码只在HTTP 1.1版本中提供。

通常,HTTP应答消息中的数据是整个发送的,Content-Length消息头字段表示数据的长度。数据的长度很重要,因为客户端需要知道哪里是应答消息的结束,以及哪里是后续应答消息的开始。然而,使用分块传输编码将数据分解成一系列数据块,并以一个或多个块发送,这样服务器可以发送数据而不需要预先知道发送内容的大小。通常,数据块的大小是固定的,但也不总是这种情况。

posted @ 2023-02-26 14:26  muzinan110  阅读(93)  评论(0编辑  收藏  举报