K8S informer机制
一、informer介绍
Kubernetes基于声明式API的设计理念,所谓声明式API,即告诉Kubernetes Controller资源对象的期望状态,这样为Kubernetes在事件通知后,动作执行前这段过程里提供了更多的容错空间与扩展空间。这就需要Kubernetes Controller能够知道资源对象的当前状态,通常需要访问API Server才能获得资源对象,当Controller越来越多时,会导致API Server负载过大。
Kubernetes使用Informer代替Controller去访问API Server,Controller的所有操作都和Informer进行交互,而Informer并不会每次都去访问API Server。Informer使用ListAndWatch的机制,在Informer首次启动时,会调用LIST API获取所有最新版本的资源对象,然后再通过WATCH API来监听这些对象的变化,并将事件信息维护在一个只读的缓存队列中提升查询的效率,同时降低API Server的负载
除了ListAndWatch,Informer还可以注册相应的事件,之后如果监听到的事件变化就会调用对应的EventHandler,实现回调。Informer主要包含以下组件。
1) Controller:Informer的实施载体,可以创建reflector及控制processLoop。processLoop将DeltaFIFO队列中的数据pop出,首先调用Indexer进行缓存并建立索引,然后分发给processor进行处理。
2) Reflector:Informer并没有直接访问k8s-api-server,而是通过一个叫Reflector的对象进行api-server的访问。Reflector通过ListAndWatch监控指定的 kubernetes 资源,当资源发生变化的时候,例如发生了 Added 资源添加等事件,会将其资源对象存放在本地缓存 DeltaFIFO 中。
3) DeltaFIFO:是一个先进先出的缓存队列,用来存储 Watch API 返回的各种事件,如Added、Updated、Deleted 。
4) Indexer:Indexer使用一个线程安全的数据存储来存储对象和它们的键值。需要注意的是,Indexer中的数据与etcd中的数据是完全一致的,这样client-go需要数据时,无须每次都从api-server获取,从而减少了请求过多造成对api-server的压力。一句话总结:Indexer是用于存储+快速查找资源。
5) Processor:记录了所有的回调函数(即 ResourceEventHandler)的实例,并负责触发回调函数
二、informer机制流程图
Informer负责与Kubernetes APlServer进行Watch操作, Watch的资源,可以是Kubernetes内置资源对象,也可以是CRD。
Informer是一个带有本地缓存以及素引机制的核心工具包,当请求为查询操作的时候.会优先从本地缓存内去查找数据,而创建、更新、删除,这类的操作,则会根据事 件通知写入到 例 列D el taFI FO中 , 应的事件处理过后,更新本地缓存,使本地缓存与ETCD的数据保持一致。
informer抽象出来的这个缓存层,将查询相关操作的压力接收了下来,这样就不必每次都去调用APIServer的接口,减轻了APIServer的数据交互压力。
从上图就能看出来, Informer是有多个组件构成的。下面咱们就先简单了解一下都是什么组件,后面在详细的介绍一下这些组件。
• Reflector:使用List-Watch来保证本地缓存数据的、准确性、顺序性和一致性的, List对应资源的全量列表数据, Watch负责变化部分的数据, Watch指 定的 Kubemes资源,当watch的资源 发生 变化时,触发变更的事件,比如Added, Updated和Deleted事件,并将资源对象的变化事件存放到本地队列DeltaFIFO中。
• DetalFFO:是一个增量队列。记录了资源变化的过程, Reflector就相当于队列的生产者。这个组件可以拆分为两部分来理解,FFO就是一个 队列,拥有 队列 基本 方法,例如ADD,UPDATE,DELETE,POP,CLOSE等, Delta是一个资源对象存储,保存存储对象的消费类型,比如Added, Updated, Deleted等。
• indexer:用来存储资源对象并自带索引功能的本地存储, Reflector从DetaFIFO中将消费出来的资源对象存储到Indexer, Indery与Etcd中的数据完全保持一 致。从而 client- go可以本 地读取,减少Kubernetes APIServer的数据交互压力。
三、Reflector组件
Reflector是Client-Go中,用来监听指定资源的组件,当资源发生变化的时候,例如增加、更新、删除等操作的时候,会以事件的形式存入本地队列,然后有对应的方法处理。
在Reflector中,核心的部分就是List-Watch,其他功能基本上也是围绕这它来搞的。
在实例化Relector的过程中,有一个ListerWatcher的接口对象,这个结构对象有两个方法,分别是List和Watch,这两个方法就实现了咱们前面介绍的List-Watch功能,如果还没有了解过,可以看下上次视频内容,这边就不过多的介绍了。
下面咱们来说说 Reflector 的核心逻辑,其实它的核心逻辑可以简单归为三部分。
• List:调用List方法获取资源全部列表数据,“转换为资源对象列表,然后保存到本地缓存中。
• 定时同步:定时器定时触发同步机制,定时更新缓存数据,在Reflector的结构体对象中,是可以配置定时同步的周期时间的。
• Watch:监听资源的变化,并且调用对应的事件处理函数来进行处理。
Reflector组件对于数据更新同步,都是基于ResourceVersion来进行的,每个资源对象都会有ResourceVersion这个属性,当数据发生变化的时候, ResourceVersion也将会以递增的形式更新,这样就确保了变更事件的顺序性。
Watch资源对象的时候,ResourceVersion可以根据我们的需求,进行配置。
• ResourceVersion未设置的时候,从最新版本开始监听。
。为了建立初始状态, Watch从起始资源版本中存在的所有资源实例的合成“添加”事件开始。以下所有监视事件都针对在Watch开始的资源版本之后发生的 所有更改。
• ResourceVersion设置为"0"的时候,则表示从任意版本开始监听。
。以这种方式初始化的监视可能会返回任意陈旧的数据。首选可用的最新资源版本,但不是必需的。允许任何起始资源版本。由于分区或过时的缓存, Watch可能从客户端之前观察到的更旧的资源版本开始,特别是在高可用性配置中。不能容忍这种明显倒带的客户不应该用这种语义启动Watch。
。为了建立初始状态, Watch从起始资源版本中存在的所有资源实例的合成“添加”事件开始。以下所有监视事件都针对在watch开始的资源版本之后发生的所有更改。
• ResourceVersion从指定版本开始监听。
。监视事件适用于提供的资源版本之后的所有更改。Watch不会以所提供资源版本的合成“添加”事件启动。由于客户端提供了资源版本,因此假定客户端已 经具有起始资源版本的初始状态。
ResburceVersion不仅是在Reflector中有重要的应用, Update机制或Patch机制也是会基于ResourceVersion来比较两个资源对象,确定是否有 变化的。
当Watch资源断开的时候, Reflector会重新进行List-Watch以确保数据的可靠性。
同时Watch使用HTTP的长链接的形式进行资源的监听,保证了数据实时性的同时,还减轻Kubernetes APIServer的访问压力。
四、DeltaFIFO
DeltaFIFO是一个增量的本地队列,记录了资源对象的变化过程。它的生产者就是Reflector组件。将监听的对象,同步到DeltaFIFO中。
DeltaFIFO是分为两部分的。分别FIFO和Delta。FIFO就是一个先入先出的本地队列, Delta则是资源对象的变化,例如增加、删除、修改等。
FIFO负责接收Reflector传递过来的事件,并将其按照顺序存储,然后等待事件的处理函数进行处理,同时若出现多个相同的事件,则只会被处理一次。
FIFO既然是一个队列那么就肯定有队列相关的操作方法,在这里就是通过Queue这个接口对象,来实现队列所需的方法的,同时还根据需求拓展了一些其他的方法。例如:Pop、AddIfNotPresent等。
Delta有两个属性,分别是Type和Object。
• Type就表示这个事件的类型,就比如说Added表示增加, Updated表示更新等等。
• Object是一个interface类型的,它就表示一个具体Kubernetes资源对象,例如:Pod、Service等。
五、Indexer
Indexer是啥呢?
通过字面意思我们可以看出它叫做“索引器”对吧。它其实就是informer中LocalStore的部分。Indexer本身是一个存储,同时在存储的基础上扩展了索引的功能。
Reflector通过DeltaFiFO一系列的操作后,然后更新存储到indexer中。
Inderer中的数据,与Etcd中的数据是完全一致的,当Cient-Qo需要获取数据的时候,则无法每次都从APIServer中仅取,从而减轻了APIServer的 请求压力。
在更深入了解indexer之前,我们还需要知道indexer中几个非常重要的概念。
• IndexFunc:索引器函数,用于计算一个资源对象的索引值列表,可以根据需求定义其他的,比如根据Label标签, Arnotation等属性来生成索引值列 表。
• Index:存储数据,要查找某个命名空间下面的Pod,那就要让Pod按照其命名空间进行索引,对应的Index类型就是 map(namespace) sets. pod,
• Indexers:存储索引器, key为索引器名称, value为索引器的实现函数,例如:map["namespace"]MetaNamespaceIndexFunc,
• Indices:存储缓存器, key为索引器名称, value为缓存的数据,例如:map["namespace"]map[namespace]sets. pod,
最容易混淆的是Indexers和Indices这两个概念,我们可以这样理解:Indevers是存储索引(生成素引错)的, Indices里面是存储的真正的数据(对象键)。