Kubernetes List-Watch 机制原理与实现 - chunked
概述
Kubernetes 中主要通过 List-Watch 机制实现组件间的异步消息通信,List-Watch 机制的实现原理值得深入分析下 。
在 Kubernetes client-go 源码分析 - ListWatcher 中我们讲到 client-go 的 ListWatcher 是如何实现 List() 和 Watch() 两个方法的,在那篇文章里我们一路跟到 RESTClient 调用 apiserver 后没有就继续往下挖掘了,因为下面的技术相对独立,要聊清楚也需要不小的篇幅,于是我们单独在“原理篇”里分析 List-Watch 的实现。
今天我们先从 http 协议层面来分析 watch 的实现机制,抓包看下调用 watch 接口时数据包流向是怎样的。
http chunked
Kubernetes 里的 watch 长链接是通过 http 协议 chunked 机制实现的,在响应头里加一个 Transfer-Encoding: chunked 就可以实现分段响应。我们用 golang 来模拟一下这个过程,从而先理解 chunked 是什么。
写个 demo 程序,server 端代码如下:
1func Server() {
2 http.HandleFunc("/name", func(w http.ResponseWriter, r *http.Request) {
3 flusher := w.(http.Flusher)
4 for i := 0; i < 2; i++ {
5 fmt.Fprintf(w, "Daniel Hu\n")
6 flusher.Flush()
7 <-time.Tick(1 * time.Second)
8 }
9 })
10 log.Fatal(http.ListenAndServe(":8080", nil))
11}
这里的逻辑是当客户端请求 localhost:8080/name 的时候,服务器端响应两段:"Daniel Hu\n",然后直接运行,再随便用什么工具访问一下,比如 curl localhost:8080/name,抓个包可以看到如下响应体:

chunked 类型的 response 由一个个 chunk 组成,每个 chunk 都是格式都是 Chunk size + Chunk data + Chunk boundary,也就是块大小+数据+边界标识。chunk 的结尾是一个大小为0的 chunk,也就是"0\r\n"。串在一起整体格式类似这样:
- [Chunk size][Chunk data][Chunk boundary][Chunk size][Chunk data][Chunk boundary][Chunk size=0][Chunk boundary]
在上图的例子中,服务器端响应的内容是两个相同的字符串 "Daniel Hu\n",客户端拿到的也就是是 "10Daniel Hu\n\r\n10Daniel Hu\n\r\n0\r\n"
这种类型的数据怎么接收呢?可以这样玩:
1func Client() {
2 resp, err := http.Get("http://127.0.0.1:8080/name")
3 if err != nil {
4 log.Fatal(err)
5 }
6 defer resp.Body.Close()
7
8 fmt.Println(resp.TransferEncoding)
9
10 reader := bufio.NewReader(resp.Body)
11 for {
12 line, err := reader.ReadString('\n')
13 if len(line) > 0 {
14 fmt.Print(line)
15 }
16 if err == io.EOF {
17 break
18 }
19 if err != nil {
20 log.Fatal(err)
21 }
22 }
23}
输出内容如下(两个字符串中间会间隔1s):
1[chunked]
2Daniel Hu
3Daniel Hu
http 协议的 chunked 类型响应数据方式大概就是这个玩法,接下来我们看下调用 Kubernetes api 的时候,能不能找到里面的 chunked 痕迹。
watch api
一般现在搭建集群都是 https 暴露 api 了,而且是双向 TLS,所以我们需要先代理一道,方便调用和抓包。
本地通过 kubectl 来代理 apiserver 暴露外部访问:
kubectl proxy
1# kubectl proxy
2Starting to serve on 127.0.0.1:8001
然后开始 watch 一个资源,比如我这里选择 coredns 的 configmap:
curl localhost:8001/api/v1/watch/namespaces/kube-system/configmaps/coredns
1# curl localhost:8001/api/v1/watch/namespaces/kube-system/configmaps/coredns
2{"type":"ADDED","object":{"kind":"ConfigMap","apiVersion":"v1","metadata":{"name":"coredns","namespace":"kube-system","uid":"f2eb4080-ce86-436a-9cfb-5bfdd3f59433","resourceVersion":"747388","creationTimestamp":"2021-09-06T02:49:56Z","managedFields":[{"manager":"kubeadm","operation":"Update","apiVersion":"v1","time":"2021-09-06T02:49:56Z","fieldsType":"FieldsV1","fieldsV1":{"f:data":{}}},{"manager":"kubectl-edit","operation":"Update","apiVersion":"v1","time":"2021-10-09T10:16:13Z","fieldsType":"FieldsV1","fieldsV1":{"f:data":{"f:Corefile":{}}}}]},"data":{"Corefile":".:53 {\n errors\n health {\n lameduck 5s\n }\n ready\n kubernetes cluster.local in-addr.arpa ip6.arpa {\n pods insecure\n fallthrough in-addr.arpa ip6.arpa\n ttl 30\n }\n prometheus :9153\n forward . /etc/resolv.conf {\n max_concurrent 1000\n }\n cache 30\n loop\n reload\n loadbalance\n}\n"}}}
这时候可以马上拿到一个响应,然后我们通过 kubectl 命令去编辑一下这个 configmap,可以看到 watch 端继续收到一条消息:
1{"type":"MODIFIED","object":{"kind":"ConfigMap","apiVersion":"v1","metadata":{"name":"coredns","namespace":"kube-system","uid":"f2eb4080-ce86-436a-9cfb-5bfdd3f59433","resourceVersion":"747834","creationTimestamp":"2021-09-06T02:49:56Z","managedFields":[{"manager":"kubeadm","operation":"Update","apiVersion":"v1","time":"2021-09-06T02:49:56Z","fieldsType":"FieldsV1","fieldsV1":{"f:data":{}}},{"manager":"kubectl-edit","operation":"Update","apiVersion":"v1","time":"2021-10-09T10:16:13Z","fieldsType":"FieldsV1","fieldsV1":{"f:data":{"f:Corefile":{}}}}]},"data":{"Corefile":".:53 {\n errors\n health {\n lameduck 5s\n }\n ready\n kubernetes cluster.local in-addr.arpa ip6.arpa {\n pods insecure\n fallthrough in-addr.arpa ip6.arpa\n ttl 30\n }\n prometheus :9153\n forward . /etc/resolv.conf {\n max_concurrent 1000\n }\n cache 31\n loop\n reload\n loadbalance\n}\n"}}}
原来 apiserver 是这样将资源变更通知到 watcher 的。
这时候如果我们去抓包,依旧可以看到这两个响应信息的具体数据包格式,第一个响应体如下(截取了中间关键信息):
1// ……
20030 fe d7 b6 90 af fc 85 ac 48 54 54 50 2f 31 2e 31 ........HTTP/1.1
30040 20 32 30 30 20 4f 4b 0d 0a 41 75 64 69 74 2d 49 200 OK..Audit-I
4// ……
50160 2d 62 66 38 38 66 37 66 39 66 33 66 61 0d 0a 54 -bf88f7f9f3fa..T
60170 72 61 6e 73 66 65 72 2d 45 6e 63 6f 64 69 6e 67 ransfer-Encoding
70180 3a 20 63 68 75 6e 6b 65 64 0d 0a 0d 0a 33 61 38 : chunked....3a8
80190 0d 0a 7b 22 74 79 70 65 22 3a 22 41 44 44 45 44 ..{"type":"ADDED
901a0 22 2c 22 6f 62 6a 65 63 74 22 3a 7b 22 6b 69 6e ","object":{"kin
1001b0 64 22 3a 22 43 6f 6e 66 69 67 4d 61 70 22 2c 22 d":"ConfigMap","
11// ……
12
可以看到这里的 http 头有一个 Transfer-Encoding: chunked,下面的内容是 {"type":"ADDED…
继续看第二个包,第二个简单很多,少了 http 头信息,只是简单的第二个 chunk,长这样:
1// ……
20030 fe d7 fb 0b af fc c0 4a 33 61 62 0d 0a 7b 22 74 .......J3ab..{"t
30040 79 70 65 22 3a 22 4d 4f 44 49 46 49 45 44 22 2c ype":"MODIFIED",
40050 22 6f 62 6a 65 63 74 22 3a 7b 22 6b 69 6e 64 22 "object":{"kind"
50060 3a 22 43 6f 6e 66 69 67 4d 61 70 22 2c 22 61 70 :"ConfigMap","ap
6// ……
7
这里可以看到 0d 0a,出于程序员的职业敏感,得想到这个就是 \r\n,至于前面的 3ab,猜到了吗?转十进制就是 939,对应这个 chunk 的长度,这里和前面我们自己写的 http server 请求-响应格式就一致了。
(转载请保留本文原始链接 https://www.danielhu.cn)
Kubernetes List-Watch 机制原理与实现 - chunked的更多相关文章
- Atitit事件代理机制原理 基于css class的事件代理
Atitit事件代理机制原理 基于css class的事件代理 1.1. 在javasript中delegate这个词经常出现,看字面的意思,代理.委托1 1.2. 事件代理1 1.3. 代理标准化规 ...
- Atitit.软件与编程语言中的锁机制原理attilax总结
Atitit.软件与编程语言中的锁机制原理attilax总结 1. 用途 (Db,业务数据加锁,并发操作加锁.1 2. 锁得类型 排它锁 "互斥锁 共享锁 乐观锁与悲观锁1 2.1. 自旋锁 ...
- Atitit.病毒木马的快速扩散机制原理nio 内存映射MappedByteBuffer
Atitit.病毒木马的快速扩散机制原理nio 内存映射MappedByteBuffer 1. Java NIO(New Input/Output)1 1.1. 变更通知(因为每个事件都需要一个监听者 ...
- OAuth的机制原理讲解及开发流程
本想前段时间就把自己通过QQ OAuth1.0.OAuth2.0协议进行验证而实现QQ登录的心得及Demo实例分享给大家,可一直很忙,今天抽点时间说下OAuth1.0协议原理,及讲解下QQ对于Oaut ...
- ASP.NET运行机制原理
ASP.NET运行机制原理 一.浏览器和服务器的交互原理 (一).浏览器和服务器交互的简单描述: 1.通俗描述:我们平时通过浏览器来访问网站,其实就相当于你通过浏览器去另一台电脑上访问文件一样,只不过 ...
- ASP.NET运行机制原理 ---浏览器与IIS的交互过程 自己学习 网上查了下别人写的总结的很好 就转过来了 和自己写的还好里嘻嘻
一.浏览器和服务器的交互原理 (一).浏览器和服务器交互的简单描述: 1.通俗描述:我们平时通过浏览器来访问网站,其实就相当于你通过浏览器去访问一台电脑上访问文件一样,只不过浏览器的访问请求是由被访问 ...
- UITableViewCell的重用机制原理
UITableViewCell的重用机制原理 来自http://blog.csdn.net/omegayy/article/details/7356823 ====================== ...
- 【Xamarin挖墙脚系列:Xamarin.IOS机制原理剖析】
原文:[Xamarin挖墙脚系列:Xamarin.IOS机制原理剖析] [注意:]团队里总是有人反映卸载Xamarin,清理不完全.之前写过如何完全卸载清理剩余的文件.今天写了Windows下的批命令 ...
- 剖析Qt的事件机制原理
版权声明 请尊重原创作品.转载请保持文章完整性,并以超链接形式注明原始作者“tingsking18”和主站点地址,方便其他朋友提问和指正. QT源码解析(一) QT创建窗口程序.消息循环和WinMai ...
随机推荐
- .ssh/config 常用配置
不用每次都 -i 指定密钥,且避免连接自动断开 ControlMaster auto ControlPath ~/.ssh/connection-%r@%h:%p ControlPersist 4h ...
- HCNP Routing&Switching之OSPF LSA更新规则和路由汇总
前文我们了解了OSPF外部路由类型以及forwarding address字段的作用,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/15225673.html: ...
- JDK1.8源码阅读笔记(1)Object类
JDK1.8源码阅读笔记(1)Object类 Object 类属于 java.lang 包,此包下的所有类在使⽤时⽆需⼿动导⼊,系统会在程序编译期间⾃动 导⼊.Object 类是所有类的基类,当⼀ ...
- .NET 5 支持 Azure Functions OpenAPI 扩展啦
今年5月,在 Build大会上,Azure FunctionsOpenAPI的功能支持(预览版)正式宣布. 当时,它最高支持 v3 运行时--.NET Core 3.1 版本. 最近,它发布了 .NE ...
- NOIP模拟38:b
这是T2. 一个容斥(其实也可以欧拉反演做,但是我不会). 首先开一个桶,记录第i行的j有多少个. 然后枚举1-\(maxn\),枚举他的值域内的倍数,记录倍数在第i行有多少个,将个数 ...
- 优化技术专题-线程间的高性能消息框架-深入浅出Disruptor的使用和原理
前提概要 简单回顾 jdk 里的队列: 阻塞队列: ArrayBlockingQueue主要通过:数组(Object[])+ 计数器(count)+ ReetrantLock的Condition (n ...
- GDB调试:Linux开发人员必备技能
开篇词:Linux C/C++ 开发人员要熟练掌握 GDB 调试 大家好,我是范蠡,目前在某知名互联网旅游公司基础框架业务部技术专家组任开发经理一职. 本系列课程的主题是 Linux 后台开发的 C/ ...
- [推荐]MyBatis 核心技术与面试 34 讲
MyBatis 核心技术与面试 34 讲 职业生涯中常被问到: 如何成为某方面的高手? 如何快速搞定某项技术? 我现在的水平处于什么阶段? -- 我暗暗想,我们从小学到中学到大学,经历了大考三六九.小 ...
- C语言使用getch()读取方向键
初衷: 在解决N皇后问题时需要使用方向键实现布局切换,于是就在网上查找资料,感觉自己收获不小,就把自己总结的一些知识点给记录下来. 总结: 1.getch()读取字符需要一次. 2.getch()读取 ...
- PHP中环境变量的操作
在 PHP 中,我们可以通过 phpinfo() 查看到当前系统中的环境变量信息(Environment).在代码中,我们也可以通过两个函数,查看和修改相应的环境变量信息. getenv() 获取环境 ...