目录
list-watch机制概述
K8S
组件之间仅采用HTTP 协议
通信,没有依赖中间件
。
List-watch
是K8S
统一的异步消息处理机制,保证了消息的实时性,可靠性,顺序性,性能等等,为声明式风格的API
奠定了良好的基础,它是优雅的通信方式,是K8S 架构
的精髓。
Kubernetes 核心机制list-watch
- Controller manager与api-server的通信主要通过两种方式:List 和 Watch。
- List是短连接实现,用于获取该资源的所有object;
- Watch是长连接实现,用于监听在List中获取的资源的变换。
- api-server检测到资源产生变更时,会主动通知到Controller manager(利用分块传输编码)。
list-watch操作主要完成以下几个事情:
- Watch核心数据存储是etcd,是典型的发布-订阅模式。但不直接访问etcd,通过apiserver发起请求,在组件启动时进行订阅。
- 可以带条件向apiserver发起的watch请求。例如,scheduler想要watch的是所有未被调度的Pod来进行调度操作;而kubelet只关心自己节点上的Pod列表。apiserver向etcd发起的watch是没有条件的,只能知道某个数据发生了变化或创建、删除,但不能过滤具体的值。也就是说对象数据的条件过滤必须在apiserver端而不是etcd端完成。
- list是watch失败,数据太过陈旧后的弥补手段,这方面详见 基于list-watch的Kubernetes异步事件处理框架详解-客户端部分。list本身是一个简单的列表操作。
Watch的分块传输编码
秘诀就是Chunked transfer encoding(分块传输编码),它首次出现在HTTP/1.1
。正如维基百科所说:
HTTP 分块传输编码允许服务器为动态生成的内容维持 HTTP 持久链接。通常,持久链接需要服务器在开始发送消息体前发送Content-Length消息头字段,但是对于动态生成的内容来说,在内容创建完之前是不可知的。使用分块传输编码,数据分解成一系列数据块,并以一个或多个块发送,这样服务器可以发送数据而不需要预先知道发送内容的总大小。
当客户端调用watch API
时,apiserve
r 在response
的HTTP Header
中设置Transfer-Encoding
的值为chunked
,表示采用分块传输
编码,客户端收到该信息后,便和服务端该链接,并等待下一个数据块,即资源的事件信息。
List-Watch 的设计理念
当设计优秀的一个异步消息的系统时,对消息机制
有至少如下四点要求:
- 消息可靠性
- 消息实时性
- 消息顺序性
- 高性能
首先消息
必须是可靠
的,list
和watch
一起保证了消息的可靠性,避免因消息丢失而造成状态不一致场景。具体而言,list API
可以查询当前的资源及其对应的状态(即期望的状态),客户端通过拿期望的状态
和实际的状态
进行对比,纠正状态不一致的资源。Watch API
和apiserver
保持一个长链接
,接收资源的状态变更事件
并做相应处理。如果仅调用watch API
,若某个时间点连接中断,就有可能导致消息丢失,所以需要通过list API
解决消息丢失
的问题。从另一个角度出发,我们可以认为list API
获取全量数据,watch API
获取增量数据。虽然仅仅通过轮询list API
,也能达到同步资源状态的效果,但是存在开销大,实时性不足的问题。
消息必须是实时的,list-watch
机制下,每当apiserver
的资源产生状态变更事件
,都会将事件及时的推送给客户端,从而保证了消息的实时性
。
消息的顺序性也是非常重要的,在并发的场景下,客户端在短时间内可能会收到同一个资源的多个事件,对于关注最终一致性
的K8S
来说,它需要知道哪个是最近发生的事件,并保证资源的最终状态如同最近事件所表述的状态一样。K8S
在每个资源的事件中都带一个resourceVersion
的标签,这个标签是递增的数字,所以当客户端并发处理同一个资源的事件时,它就可以对比resourceVersion
来保证最终的状态和最新的事件所期望的状态保持一致。
List-watch
还具有高性能的特点,虽然仅通过周期性调用list API
也能达到资源最终一致性的效果,但是周期性频繁的轮询大大的增大了开销,增加apiserver
的压力。而watch
作为异步消息通知机制,复用一条长链接,保证实时性的同时也保证了性能。
list-watch机制原理
Informer封装list-watch
K8s的informer模块封装list-watch API,用户只需要指定资源,编写事件处理函数,AddFunc,UpdateFunc和DeleteFunc等。
Informer是Client-go中的一个核心工具包。为了让Client-go更快地返回List/Get请求的结果、减少对Kubenetes API的直接调用,Informer被设计实现为一个依赖Kubernetes List/Watch API、可监听事件并触发回调函数的二级缓存工具包。
Informer设计实现
Informer组件:
- Controller 用于处理收到的事情,触发Processor中的回调函数
- Reflector:通过Kubernetes Watch API监听resource下的所有事件
- Lister:用来被调用List/Get方法
- Processor:记录并触发回调函数
- DeltaFIFO和LocalStore:DeltaFIFO和LocalStore是Informer的两级缓存。DeltaFIFO用来存储Watch API返回的各种事件,LocalStore是Lister的List/Get方法访问。
Watch 是如何实现的?
Watch的核心是长链接,通过HTTP 长链接接收apiserver发来的资源变更事件呢,秘诀是Chunked transfer encoding(分块传输编码),它首次出现在HTTP/1.1。
HTTP 分块传输编码允许服务器为动态生成的内容维持 HTTP 持久链接。通常,持久链接需要服务器在开始发送消息体前发送Content-Length消息头字段,但是对于动态生成的内容来说,在内容创建完之前是不可知的。使用分块传输编码,数据分解成一系列数据块,并以一个或多个块发送,这样服务器可以发送数据而不需要预先知道发送内容的总大小。
当客户端调用watch API时,apiserver 在response的HTTP Header中设置Transfer-Encoding的值为chunked,表示采用分块传输编码,客户端收到该信息后,便和服务端该链接,并等待下一个数据块,即资源的事件信息,直到客户主动断链。
list-watch 实现机制
1) List-watch的API处理, kube-apiserver API注册代码pkg/apiserver/api_installer.go
- rest.Storage对象会被转换为watcher和lister对象
- 提供list和watch服务的入口是同一个,在API接口中通过 GET /xxx/services?watch=ture来区分
- API处理函数是统一通过ListResource完成
2)ListResource()的具体实现
每次有一个watch的url请求过来,都会调用rw.Watch()创建一个watcher,然后使用serveWatch()来处理这个请求。watcher的生命周期是每个http请求的,这一点非常重要。
3) 响应http请求的过程serveWatch()的代码在/pkg/apiserver/watch.go里面
watcher的结果channel中读取一个event对象,然后持续不断的编码写入到http response的流当中。
list-watch 实现机制总结
list-watch客户端从调用到响应的整个流程:
评论区