【K8s源码品读】009:Phase 1 - kube-scheduler - Informer监听资源变化

简介: 了解Informer是如何从kube-apiserver监听资源变化的情况

聚焦目标

了解Informer是如何从kube-apiserver监听资源变化的情况

目录

  1. 什么是Informer
  2. Shared Informer的实现
  3. PodInformer的背后的实现
  4. 聚焦Reflect结构
  5. 本节小节

Informer

什么是Informer?这一节,我将先抛开代码,重点讲一下这个Informer,因为它是理解k8s运行机制的核心概念。

我没有在官方文档中找到Informer的明确定义,中文直译为通知器。从这个链接中,我们可以看到一个自定义资源的的处理流程。

我简单概况下,Informer的核心功能是 获取并监听(ListAndWatch)对应资源的增删改,触发相应的事件操作(ResourceEventHandler)

Shared Informer

/*
client 是连接到 kube-apiserver 的客户端。
我们要理解k8s的设计:
1. etcd是核心的数据存储,对资源的修改会进行持久化
2. 只有kube-apiserver可以访问etcd
所以,kube-scheduler要了解资源的变化情况,只能通过kube-apiserver
*/

// 定义了 Shared Informer,其中这个client是用来连接kube-apiserver的
c.InformerFactory = informers.NewSharedInformerFactory(client, 0)

// 这里解答了为什么叫shared:一个资源会对应多个Informer,会导致效率低下,所以让一个资源对应一个sharedInformer,而一个sharedInformer内部自己维护多个Informer
type sharedInformerFactory struct {
   
    client           kubernetes.Interface
    namespace        string
    tweakListOptions internalinterfaces.TweakListOptionsFunc
    lock             sync.Mutex
    defaultResync    time.Duration
    customResync     map[reflect.Type]time.Duration
  // 这个map就是维护多个Informer的关键实现
    informers map[reflect.Type]cache.SharedIndexInformer
    startedInformers map[reflect.Type]bool
}

// 运行函数
func (f *sharedInformerFactory) Start(stopCh <-chan struct{
   }) {
   
    f.lock.Lock()
    defer f.lock.Unlock()
    for informerType, informer := range f.informers {
   
        if !f.startedInformers[informerType] {
   
      // goroutine异步处理
            go informer.Run(stopCh)
      // 标记为已经运行,这样即使下次Start也不会重复运行
            f.startedInformers[informerType] = true
        }
    }
}

// 查找对应的informer
func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer {
   
    f.lock.Lock()
    defer f.lock.Unlock()
    // 找到就直接返回
    informerType := reflect.TypeOf(obj)
    informer, exists := f.informers[informerType]
    if exists {
   
        return informer
    }

    resyncPeriod, exists := f.customResync[informerType]
    if !exists {
   
        resyncPeriod = f.defaultResync
    }
    // 没找到就会新建
    informer = newFunc(f.client, resyncPeriod)
    f.informers[informerType] = informer
    return informer
}

// SharedInformerFactory 是 sharedInformerFactory 的接口定义
type SharedInformerFactory interface {
   
  // 我们这一阶段关注的Pod的Informer,属于核心资源
    Core() core.Interface
}

// core.Interface的定义
type Interface interface {
   
    // V1 provides access to shared informers for resources in V1.
    V1() v1.Interface
}

// v1.Interface 的定义
type Interface interface {
   
  // Pod的定义
    Pods() PodInformer
}

// PodInformer 是对应的接口
type PodInformer interface {
   
    Informer() cache.SharedIndexInformer
    Lister() v1.PodLister
}
// podInformer 是具体的实现
type podInformer struct {
   
    factory          internalinterfaces.SharedInformerFactory
    tweakListOptions internalinterfaces.TweakListOptionsFunc
    namespace        string
}

// 最后,我们可以看到podInformer调用了InformerFor函数进行了添加
func (f *podInformer) Informer() cache.SharedIndexInformer {
   
    return f.factory.InformerFor(&corev1.Pod{
   }, f.defaultInformer)
}

PodInformer

// 实例化PodInformer,把对应的List/Watch操作方法传入到实例化函数,生成统一的SharedIndexInformer接口
func NewFilteredPodInformer() cache.SharedIndexInformer {
   
    return cache.NewSharedIndexInformer(
    // List和Watch实现从PodInterface里面查询
        &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,
    )
}

// 我们先看看Pod基本的List和Watch是怎么定义的
// Pod基本的增删改查等操作
type PodInterface interface {
   
    List(ctx context.Context, opts metav1.ListOptions) (*v1.PodList, error)
    Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error)
    ...
}
// pods 是PodInterface的实现
type pods struct {
   
    client rest.Interface
    ns     string
}

// List 和 Watch 是依赖客户端,也就是从kube-apiserver中查询的
func (c *pods) List(ctx context.Context, opts metav1.ListOptions) (result *v1.PodList, err error) {
   
    err = c.client.Get().
        Namespace(c.ns).
        Resource("pods").
        VersionedParams(&opts, scheme.ParameterCodec).
        Timeout(timeout).
        Do(ctx).
        Into(result)
    return
}
func (c *pods) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
   
    return c.client.Get().
        Namespace(c.ns).
        Resource("pods").
        VersionedParams(&opts, scheme.ParameterCodec).
        Timeout(timeout).
        Watch(ctx)
}

// 在上面,我们看到了异步运行Informer的代码 go informer.Run(stopCh),我们看看是怎么run的
func (s *sharedIndexInformer) Run(stopCh <-chan struct{
   }) {
   
  // 这里有个 DeltaFIFO 的对象,
  fifo := NewDeltaFIFOWithOptions(DeltaFIFOOptions{
   
        KnownObjects:          s.indexer,
        EmitDeltaTypeReplaced: true,
    })
    // 传入这个fifo到cfg
    cfg := &Config{
   
        Queue:            fifo,
        ...
    }
    // 新建controller
    func() {
   
        s.startedLock.Lock()
        defer s.startedLock.Unlock()

        s.controller = New(cfg)
        s.controller.(*controller).clock = s.clock
        s.started = true
    }()
    // 运行controller
    s.controller.Run(stopCh)
}

// Controller的运行
func (c *controller) Run(stopCh <-chan struct{
   }) {
   
    // 
    r := NewReflector(
        c.config.ListerWatcher,
        c.config.ObjectType,
        c.config.Queue,
        c.config.FullResyncPeriod,
    )
    r.ShouldResync = c.config.ShouldResync
    r.clock = c.clock
    if c.config.WatchErrorHandler != nil {
   
        r.watchErrorHandler = c.config.WatchErrorHandler
    }

    c.reflectorMutex.Lock()
    c.reflector = r
    c.reflectorMutex.Unlock()

    var wg wait.Group
  // 生产,往Queue里放数据
    wg.StartWithChannel(stopCh, r.Run)
  // 消费,从Queue消费数据
    wait.Until(c.processLoop, time.Second, stopCh)
    wg.Wait()
}

Reflect

// 我们再回头看看这个Reflect结构
r := NewReflector(
      // ListerWatcher 我们已经有了解,就是通过client监听kube-apiserver暴露出来的Resource
        c.config.ListerWatcher,
        c.config.ObjectType,
      // Queue 是我们前文看到的一个 DeltaFIFOQueue,认为这是一个先进先出的队列
        c.config.Queue,
        c.config.FullResyncPeriod,
)

func (r *Reflector) Run(stopCh <-chan struct{
   }) {
   
    klog.V(2).Infof("Starting reflector %s (%s) from %s", r.expectedTypeName, r.resyncPeriod, r.name)
    wait.BackoffUntil(func() {
   
    // 调用了ListAndWatch
        if err := r.ListAndWatch(stopCh); err != nil {
   
            r.watchErrorHandler(r, err)
        }
    }, r.backoffManager, true, stopCh)
    klog.V(2).Infof("Stopping reflector %s (%s) from %s", r.expectedTypeName, r.resyncPeriod, r.name)
}

func (r *Reflector) ListAndWatch(stopCh <-chan struct{
   }) error {
   
        // watchHandler顾名思义,就是Watch到对应的事件,调用对应的Handler
        if err := r.watchHandler(start, w, &resourceVersion, resyncerrc, stopCh); err != nil {
   
            if err != errorStopRequested {
   
                switch {
   
                case isExpiredError(err):
                    klog.V(4).Infof("%s: watch of %v closed with: %v", r.name, r.expectedTypeName, err)
                default:
                    klog.Warningf("%s: watch of %v ended with: %v", r.name, r.expectedTypeName, err)
                }
            }
            return nil
        }
    }
}

func (r *Reflector) watchHandler() error {
   
loop:
    for {
   
    // 一个经典的GO语言select监听多channel的模式
        select {
   
    // 整体的step channel
        case <-stopCh:
            return errorStopRequested
    // 错误相关的error channel
        case err := <-errc:
            return err
    // 接收事件event的channel
        case event, ok := <-w.ResultChan():
      // channel被关闭,退出loop
            if !ok {
   
                break loop
            }

            // 一系列的资源验证代码跳过

            switch event.Type {
   
      // 增删改三种Event,分别对应到去store,即DeltaFIFO中,操作object
            case watch.Added:
                err := r.store.Add(event.Object)
            case watch.Modified:
                err := r.store.Update(event.Object)
            case watch.Deleted:
                err := r.store.Delete(event.Object)
            case watch.Bookmark:
            default:
                utilruntime.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", r.name, event))
            }
        }
    }
    return nil
}

Summary

  1. Informer 依赖于 Reflector 模块,它有个组件为 xxxInformer,如 podInformer
  2. 具体资源的 Informer 包含了一个连接到kube-apiserverclient,通过ListWatch接口查询资源变更情况
  3. 检测到资源发生变化后,通过Controller 将数据放入队列DeltaFIFOQueue里,生产阶段完成
相关实践学习
深入解析Docker容器化技术
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。Docker是世界领先的软件容器平台。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用Docker可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为Linux和Windows Server应用发布新功能。 在本套课程中,我们将全面的讲解Docker技术栈,从环境安装到容器、镜像操作以及生产环境如何部署开发的微服务应用。本课程由黑马程序员提供。 &nbsp; &nbsp; 相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
目录
相关文章
|
存储 Kubernetes 对象存储
部署DeepSeek但GPU不足,ACK One注册集群助力解决IDC GPU资源不足
借助阿里云ACK One注册集群,充分利用阿里云强大ACS GPU算力,实现DeepSeek推理模型高效部署。
|
Kubernetes 监控 调度
在K8S中,DaemonSet类型资源特性?
在K8S中,DaemonSet类型资源特性?
|
存储 Kubernetes 对象存储
部署DeepSeek但GPU不足,ACK One注册集群助力解决IDC GPU资源不足
部署DeepSeek但GPU不足,ACK One注册集群助力解决IDC GPU资源不足
503 3
|
弹性计算 运维 Kubernetes
使用ACK Edge统一管理多地域的ECS资源
本文介绍如何使用ACK Edge来管理分布在多个地域的ECS资源。
|
弹性计算 运维 Kubernetes
使用ACK Edge统一管理多地域的ECS资源
使用ACK Edge统一管理多地域的ECS资源
346 0
|
存储 Kubernetes 对象存储
部署 DeepSeek 但 GPU 不足,ACK One 注册集群助力解决 IDC GPU 资源不足
部署 DeepSeek 但 GPU 不足,ACK One 注册集群助力解决 IDC GPU 资源不足
|
JSON 运维 Kubernetes
|
Kubernetes 算法 调度
Kubernetes的灵魂核心:kube-scheduler
本文介绍了Kubernetes中关键组件kube-scheduler的工作原理,详细解释了其通过预选和优选过程为Pod选择合适节点的机制,并提供了一个简化的Python示例来模拟这一过程,帮助读者更好地理解和管理Kubernetes集群。
|
Kubernetes Cloud Native 应用服务中间件
Kubernetes 自动伸缩策略:优化资源利用率
【8月更文第29天】在现代云原生环境中,应用的流量往往具有不可预测性。为了应对这种变化,Kubernetes 提供了多种自动伸缩机制来动态调整应用实例的数量和每个实例分配的资源。本文将深入探讨两种主要的自动伸缩工具:水平 Pod 自动伸缩器 (HPA) 和垂直 Pod 伸缩器 (VPA),并提供实际的应用示例。
571 2
|
存储 Kubernetes 数据中心
在K8S中,同⼀个Pod内不同容器哪些资源是共用的,哪些资源是隔离的?
在K8S中,同⼀个Pod内不同容器哪些资源是共用的,哪些资源是隔离的?

热门文章

最新文章

推荐镜像

更多