侧边栏壁纸
博主头像
问道

问道的小花园,总能给你带来惊喜

  • 累计撰写 68 篇文章
  • 累计创建 35 个标签
  • 累计收到 6 条评论

kubernetes二次开发系列(3):list-watch机制

问道
2023-02-02 / 0 评论 / 4 点赞 / 1,080 阅读 / 3,397 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2023-02-02,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

目录


list-watch机制概述

K8S组件之间仅采用HTTP 协议通信,没有依赖中间件

List-watchK8S统一的异步消息处理机制,保证了消息的实时性,可靠性,顺序性,性能等等,为声明式风格的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时,apiserver 在responseHTTP Header中设置Transfer-Encoding的值为chunked,表示采用分块传输编码,客户端收到该信息后,便和服务端该链接,并等待下一个数据块,即资源的事件信息。

List-Watch 的设计理念

当设计优秀的一个异步消息的系统时,对消息机制有至少如下四点要求:

  • 消息可靠性
  • 消息实时性
  • 消息顺序性
  • 高性能

首先消息必须是可靠的,listwatch一起保证了消息的可靠性,避免因消息丢失而造成状态不一致场景。具体而言,list API可以查询当前的资源及其对应的状态(即期望的状态),客户端通过拿期望的状态实际的状态进行对比,纠正状态不一致的资源。Watch APIapiserver保持一个长链接,接收资源的状态变更事件并做相应处理。如果仅调用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客户端从调用到响应的整个流程:

4

评论区