K8s是如何Watch的?

1. 概述

进入 K8s 的世界,会发现几乎所有对象都被抽象为了资源(Resource),包括 K8s Core Resources(Pod, Service, Namespace 等)、CRD、APIService 扩展的资源类型。同时 K8s 底层将这些资源统一抽象为了 RESTful 的存储(Storage),一方面服务端按目录形式(/registry/xxx) 存放在 ETCD 中,另一方面也为客户端提供了 RESTful API 接口,便于对资源的操作(get/post/put/patch/delete 等)。

K8s Watch API 就是为资源提供的一种持续监听其变化的机制,当资源有任何变化的时候,都可以实时、顺序、可靠的传递给客户端,使得用户可以针对目标资源进行灵活应用与操作。

那 K8s Watch 机制是怎么实现的呢?底层具体依赖了哪些技术?

本文将从 HTTP 协议、APIServer 启动、ETCD Watch 封装、服务端 Watch 实现、客户端 Watch 实现等方面,对 K8s Watch 实现机制进行了解析。

大致流程如下

本文及后续相关文章都基于 K8s v1.23

2. 从 HTTP 说起

2.1 Content-Length

如下图所示,HTTP 发送请求 Request 或服务端 Response,会在 HTTP header 中携带 Content-Length,以表明此次传输的总数据长度。如果 Content-Length 长度与实际传输长度不一致,则会发生异常(大于实际值会超时, 小于实际值会截断并可能导致后续的数据解析混乱)。

curl baidu.com -v> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: baidu.com
> Accept: */*< HTTP/1.1 200 OK
< Date: Thu, 17 Mar 2022 04:15:25 GMT
< Server: Apache
< Last-Modified: Tue, 12 Jan 2010 13:48:00 GMT
< ETag: "51-47cf7e6ee8400"
< Accept-Ranges: bytes
< Content-Length: 81
< Cache-Control: max-age=86400
< Expires: Fri, 18 Mar 2022 04:15:25 GMT
< Connection: Keep-Alive
< Content-Type: text/html<html>
<meta http-equiv="refresh" content="0;url=http://www.baidu.com/">
</html>

如果服务端提前不知道要传输数据的总长度,怎么办?

2.2 Chunked Transfer Encoding

HTTP 从 1.1 开始增加了分块传输编码(Chunked Transfer Encoding),将数据分解成一系列数据块,并以一个或多个块发送,这样服务器可以发送数据而不需要预先知道发送内容的总大小。数据块长度以十六进制的形式表示,后面紧跟着 \r\n,之后是分块数据本身,后面也是 \r\n,终止块则是一个长度为 0 的分块。

> GET /test HTTP/1.1
> Host: baidu.com
> Accept-Encoding: gzip< HTTP/1.1 200 OK
< Server: Apache
< Date: Sun, 03 May 2015 17:25:23 GMT
< Content-Type: text/html
< Transfer-Encoding: chunked
< Connection: keep-alive
< Content-Encoding: gzip4\r\n        (bytes to send)
Wiki\r\n     (data)
6\r\n        (bytes to send)
pedia \r\n   (data)
E\r\n        (bytes to send)
in \r\n
\r\n
chunks.\r\n  (data)
0\r\n        (final byte - 0)
\r\n         (end message)

为了实现以流(Streaming)的方式 Watch 服务端资源变更,HTTP1.1 Server 端会在 Header 里告诉 Client 要变更 Transfer-Encoding 为 chunked,之后进行分块传输,直到 Server 端发送了大小为 0 的数据。

2.3 HTTP/2

HTTP/2 并没有使用 Chunked Transfer Encoding 进行流式传输,而是引入了以 Frame(帧) 为单位来进行传输,其数据完全改变了原来的编解码方式,整个方式类似很多 RPC协议。Frame 由二进制编码,帧头固定位置的字节描述 Body 长度,就可以读取 Body 体,直到 Flags 遇到 END_STREAM。这种方式天然支持服务端在 Stream 上发送数据,不需要通知客户端做什么改变。

+-----------------------------------------------+
|                 Body Length (24)                   | ----Frame Header
+---------------+---------------+---------------+
|   Type (8)    |   Flags (8)   |
+-+-------------+---------------+-------------------+
|R|                 Stream Identifier (31)          |
+=+=================================================+
|                   Frame Payload (0...)        ...    ----Frame Data
+---------------------------------------------------+

K8s 为了充分利用 HTTP/2 在 Server-Push、Multiplexing 上的高性能 Stream 特性,在实现 RESTful Watch 时,提供了 HTTP1.1/HTTP2 的协议协商(ALPN, Application-Layer Protocol Negotiation) 机制,在服务端优先选中 HTTP2,协商过程如下:

curl  https://{kube-apiserver}/api/v1/watch/namespaces/default/pods/mysql-0 -v* ALPN, offering h2
* ALPN, offering http/1.1
* SSL verify...
* ALPN, server accepted to use h2
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7f2b921a6a90)
> GET /api/v1/watch/namespaces/default/pods/mysql-0 HTTP/2
> Host: 9.165.12.1
> user-agent: curl/7.79.1
> accept: */*
> authorization: Bearer xxx
> 
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!< HTTP/2 200 
< cache-control: no-cache, private
< content-type: application/json
< date: Thu, 17 Mar 2022 04:46:36 GMT{"type":"ADDED","object":{"kind":"Pod","apiVersion":"v1","metadata":xxx}}

3. APIServer 启动

APIServer 启动采用 Cobra 命令行,解析相关 flags 参数,经过 Complete(填充默认值)->Validate(校验) 逻辑后,通过 Run 启动服务。启动入口如下:

// kubernetes/cmd/kube-apiserver/app/server.go
// NewAPIServerCommand creates a *cobra.Command object with default parameters
func NewAPIServerCommand() *cobra.Command {s := options.NewServerRunOptions()cmd := &amp;cobra.Command{Use: "kube-apiserver",...RunE: func(cmd *cobra.Command, args []string) error {...// set default optionscompletedOptions, err := Complete(s)if err != nil {return err}// validate optionsif errs := completedOptions.Validate(); len(errs) != 0 {return utilerrors.NewAggregate(errs)}return Run(completedOptions, genericapiserver.SetupSignalHandler())},}...return cmd
}

在 Run 函数中,按序分别初始化 APIServer 链(APIExtensionsServer、KubeAPIServer、AggregatorServer),分别服务于 CRD(用户自定义资源)、K8s API(内置资源)、API Service(API 扩展资源) 对应的资源请求。相关代码如下:

// kubernetes/cmd/kube-apiserver/app/server.go
// 创建 APIServer 链(APIExtensionsServer、KubeAPIServer、AggregatorServer),分别服务 CRD、K8s API、API Service
func CreateServerChain(completedOptions completedServerRunOptions, stopCh <-chan struct{}) (*aggregatorapiserver.APIAggregator, error) {// 创建 APIServer 通用配置kubeAPIServerConfig, serviceResolver, pluginInitializer, err := CreateKubeAPIServerConfig(completedOptions)if err != nil {return nil, err}...// 第一:创建 APIExtensionsServerapiExtensionsServer, err := createAPIExtensionsServer(apiExtensionsConfig, genericapiserver.NewEmptyDelegateWithCustomHandler(notFoundHandler))if err != nil {return nil, err}// 第二:创建 KubeAPIServerkubeAPIServer, err := CreateKubeAPIServer(kubeAPIServerConfig, apiExtensionsServer.GenericAPIServer)if err != nil {return nil, err}...// 第三:创建 AggregatorServeraggregatorServer, err := createAggregatorServer(aggregatorConfig, kubeAPIServer.GenericAPIServer, apiExtensionsServer.Informers)if err != nil {// we don't need special handling for innerStopCh because the aggregator server doesn't create any go routinesreturn nil, err}return aggregatorServer, nil
}

之后,经过非阻塞(NonBlockingRun) 方式启动 SecureServingInfo.Serve,并配置 HTTP2(默认开启) 相关传输选项,最后启动 Serve 监听客户端请求。

4. ETCD 资源封装

ETCD 实现 Watch 机制,经历了从 ETCD2 到 ETCD3 实现方式的转变。ETCD2 通过长轮询 Long-Polling 的方式监听资源事件的变更;ETCD3 则通过基于 HTTP2 的 gRPC 实现 Watch stream,性能得到了很大的提升。

Polling(轮询):由于 http1.x 没有服务端 push 的机制,为了 Watch 服务端的数据变化,最简单的办法当然是客户端去 pull:客户端每隔定长时间去服务端拉数据同步,无论服务端有没有数据变化。但是必然存在通知不及时和大量无效的轮询的问题。

Long-Polling(长轮询):就是在这个 Polling 的基础上的优化,当客户端发起 Long-Polling 时,如果服务端没有相关数据,会 hold 住请求,直到服务端有数据要发或者超时才会返回。

在上一步配置 APIServerConfig 时,封装了底层存储用的 ETCD。以 kubeAPIServerConfig 为例,说明 K8s 内置资源是如何封装 ETCD 底层存储的。

首先,通过 buildGenericConfig 实例化 RESTOptionsGetter,用于封装 RESTStorage。之后通过 InstallLegacyAPI -> NewLegacyRESTStorage 实例化 K8s 内置资源的 RESTStorage,包括 podStorage、nsStorage、pvStorage、serviceStorage 等,用于 APIServer 在处理客户端资源请求时,调用的后端资源存储。

InstallLegacyAPI 源码如下:

// kubernetes/pkg/controlplane/instance.go
// 注册 K8s 的内置资源,并封装到对应的 RESTStorage(如 podStorage/pvStorage)
func (m *Instance) InstallLegacyAPI(c *completedConfig, restOptionsGetter generic.RESTOptionsGetter) error {...legacyRESTStorage, apiGroupInfo, err := legacyRESTStorageProvider.NewLegacyRESTStorage(c.ExtraConfig.APIResourceConfigSource, restOptionsGetter)if err != nil {return fmt.Errorf("error building core storage: %v", err)}if len(apiGroupInfo.VersionedResourcesStorageMap) == 0 { // if all core storage is disabled, return.return nil}controllerName := "bootstrap-controller"coreClient := corev1client.NewForConfigOrDie(c.GenericConfig.LoopbackClientConfig)bootstrapController, err := c.NewBootstrapController(legacyRESTStorage, coreClient, coreClient, coreClient, coreClient.RESTClient())if err != nil {return fmt.Errorf("error creating bootstrap controller: %v", err)}m.GenericAPIServer.AddPostStartHookOrDie(controllerName, bootstrapController.PostStartHook)m.GenericAPIServer.AddPreShutdownHookOrDie(controllerName, bootstrapController.PreShutdownHook)...return nil
}

在实例化 ETCD 底层存储中,通过开关 EnableWatchCache 来控制是否启用 Watch 缓存。如果启用了,则会先走 StorageWithCacher 逻辑,然后才走 UndecoratedStorage 真正调用底层 ETCD3 存储。

K8s 当前只支持 ETCD3,不再支持 ETCD2。K8s 充分信任 ETCD3 的 Watch 机制,保证资源状态与 ETCD 底层存储的一致性。

整个调用过程如下:

K8s 各类资源(CRD/Core/Aggregator) 都统一以 RESTful 风格暴露 HTTP 请求接口,并支持多种类型的编解码格式,如 json/yaml/protobuf。

5. 客户端 Watch 实现

经过上面的步骤,APIServer 服务端已准备好 K8s 各类资源的 RESTStorage(底层封装了 ETCD3),此时客户端可通过 RESTful HTTP 接口向 APIServer 发出资源请求,包括 GET/POST/PATCH/WATCH/DELETE 等操作。

客户端 Watch 包括:
(1). kubectl get xxx -w,获取某类资源、并持续监听资源变化;
(2). client-go 中 Reflector ListAndWatch APIServer 各类资源,点此查看;

我们以 kubectl get pod -w 为例,说明客户端是如何实现资源的 Watch 操作。

首先,kubectl 也是通过 Cobra 命令行解析参数(--watch,或 --watch-only),然后调用 Run 调用 cli-runtime 包下面的 Watch 接口,之后通过 RESTClient.Watch 向 APIServer 发起 Watch 请求,获得一个流式 watch.Interface,然后不断从其中 ResultChan 获取 watch.Event。之后,根据客户端发送的编解码类型(json/yaml/protobuf),从 stream 中按帧(Frame) 读取并解码(Decode) 数据,输出显示到命令行终端。

客户端通过 RESTClient 发起 Watch 请求,代码如下:

// kubernetes/staging/src/k8s.io/cli-runtime/pkg/resource/helper.go
func (m *Helper) Watch(namespace, apiVersion string, options *metav1.ListOptions) (watch.Interface, error) {options.Watch = truereturn m.RESTClient.Get().NamespaceIfScoped(namespace, m.NamespaceScoped).Resource(m.Resource).VersionedParams(options, metav1.ParameterCodec).Watch(context.TODO())
}

客户端 Watch 实现过程小结如下:

6. 服务端 Watch 实现

服务端 APIServer 启动后,一直在持续监听着各类资源的变更事件。在接收到某类资源的 Watch 请求后,调用 RESTStorage 的 Watch 接口,通过开关 EnableWatchCache 来控制是否启用 Watch 缓存,最终通过 etcd3.Watch 封装实现了 ETCD 底层的 Event 变更事件。

RESTStorage 就是在 APIServer 启动时候,提前注册、封装的 ETCD 资源存储。

etcd3.watcher 通过两个 channel(incomingEventChan、resultChan,默认容量都为 100) 实现 ETCD 底层事件到 watch.Event 的转换,然后通过 serveWatch 流式监听返回的 watch.Interface,不断从 resultChan 中取出变更事件。之后,根据客户端发送的编解码类型(json/yaml/protobuf),编码(Encode) 数据,按帧(Frame) 组装后发送到 stream 中给客户端。

服务端通过 serveWatch 流式监听返回的 watch.Interface,代码如下:

// kubernetes/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/get.go
func ListResource(r rest.Lister, rw rest.Watcher, scope *RequestScope, forceWatch bool, minRequestTimeout time.Duration) http.HandlerFunc {return func(w http.ResponseWriter, req *http.Request) {...if opts.Watch || forceWatch {...watcher, err := rw.Watch(ctx, &amp;opts)if err != nil {scope.err(err, w, req)return}requestInfo, _ := request.RequestInfoFrom(ctx)metrics.RecordLongRunning(req, requestInfo, metrics.APIServerComponent, func() {serveWatch(watcher, scope, outputMediaType, req, w, timeout)})return}...}
}

K8s 在 v1.11 之后将 WATCH/WATCHLIST 类型的 action.Verb 废弃了,统一都交由 LIST -> restfulListResource 处理。

服务端 Watch 实现过程小结如下:

APIServer 除了支持 HTTP2,也支持 WebSocket 通信。当客户端请求包含了 Upgrade: websocket,Connection: Upgrade 时,则服务端会通过 WebSocket 与客户端进行数据传输。

值得注意的是,底层 ETCD 事件通过 transform 函数转换为 watch.Event,包括以下几种类型(Type):

7. 小结

本文通过分析 K8s 中 APIServer 启动、ETCD watch 封装、服务端 Watch 实现、客户端 Watch 实现等核心流程,对 K8s Watch 实现机制进行了解析。通过源码、图文方式说明了相关流程逻辑,以期更好的理解 K8s Watch 实现细节。

K8s 底层完全信任 ETCD(ListAndWatch),将各类资源统一抽象为了 RESTful 的存储(Storage),通过 Watch 机制获取各类资源的变更事件,然后通过 Informer 机制分发给下游监听的 ResourceEventHandler,最终由 Controller 实现资源的业务逻辑处理。随着 ETCD3 在 HTTP/2 基础上不断优化完善,K8s 将提供更高效、更稳定的编排能力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/14869.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

jellyfish安装及使用(Bioinformatics工具-020)

01 背景 基因组survey以测序技术为基础&#xff0c;基于小片段文库的低深度测序&#xff0c;通过K-mer分析&#xff0c;快速获得基因组大小、杂合度、重复序列比例等基本信息&#xff0c;为制定该物种的全基因组de novo测序策略提供有效依据。 jellyfish (水母) 是一个用于快…

Docker-镜像迁移的三种方式=>备份恢复公有仓库私有仓库

制作好的镜像要被别人使用&#xff0c;有三种方式&#xff1a; 1.先备份镜像&#xff0c;别人通过u盘或者其它方式拷贝后&#xff0c;再恢复镜像&#xff0c;这种方式比较麻烦 2.将制作的镜像上传到公共镜像仓库&#xff0c;被别人拉取后使用&#xff0c;但可能存在网络不通畅或…

【零基础C语言】内存函数

前言&#xff1a; 我们之前学过strcpy&#xff0c;strcmp等等函数&#xff0c;他们可以拷贝字符串和比较字符串等等&#xff0c;那么有没有什么函数不光可以拷贝字符串还可以拷贝其他的数据呢&#xff0c;答案就是内存函数。 相较于字符串函数&#xff0c;内存函数可以拷贝的…

赎金信[简单]

优质博文&#xff1a;IT-BLOG-CN 一、题目 给你两个字符串&#xff1a;ransomNote和magazine&#xff0c;判断ransomNote能不能由magazine里面的字符构成。如果可以&#xff0c;返回true&#xff1b;否则返回false。magazine中的每个字符只能在ransomNote中使用一次。 示例 …

DPDK实践之(1)dpdk基础使用

DPDK实践之(1)dpdk基础使用 Author: Once Day Date: 2024年5月19日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文档可参考专栏&#xff1a;Linux基础知识_Once…

C语言 | Leetcode C语言题解之第109题有序链表转换二叉搜索树

题目&#xff1a; 题解&#xff1a; int getLength(struct ListNode* head) {int ret 0;while (head ! NULL) {ret, head head->next;}return ret; }struct TreeNode* buildTree(struct ListNode** head, int left, int right) {if (left > right) {return NULL;}int …

Mac维护神器CleanMyMac X成为你的苹果电脑得力助手

在数字化时代&#xff0c;Mac电脑已成为众多用户的首选。然而&#xff0c;随着频繁的使用和数据量的日益增长&#xff0c;许多Mac用户面临着系统杂乱、存储空间不足以及隐私保护等问题。幸运的是&#xff0c;"CleanMyMac X"这款优化和清理工具应运而生&#xff0c;它…

ROCm上情感分析:使用循环神经网络

15.2. 情感分析&#xff1a;使用循环神经网络 — 动手学深度学习 2.0.0 documentation (d2l.ai) 代码 import torch from torch import nn from d2l import torch as d2lbatch_size 64 train_iter, test_iter, vocab d2l.load_data_imdb(batch_size)class BiRNN(nn.Module):…

java抽象类,接口,枚举练习题

第一题&#xff1a; 答案&#xff1a; class Animal{//成员变量protected String name;protected int weight;//构造方法public Animal(){this.name"refer";this.weight50;}public Animal(String name,int weight){this.namename;this.weightweight;}//成员方法publ…

Bugku Crypto 部分题目简单题解(四)

目录 python_jail 简单的rsa 托马斯.杰斐逊 这不是md5 进制转换 affine Crack it rsa python_jail 启动场景 使用虚拟机nc进行连接 输入print(flag) 发现报错&#xff0c;经过测试只能传入10个字符多了就会报错 利用python中help()函数&#xff0c;借报错信息带出flag变…

【力扣刷题笔记第三期】Python 数据结构与算法

先从简单的题型开始刷起&#xff0c;一起加油啊&#xff01;&#xff01; 点个关注和收藏呗&#xff0c;一起刷题鸭&#xff01;&#xff01; 第一批题目 1.设备编号 给定一个设备编号区间[start, end]&#xff0c;包含4或18的编号都不能使用&#xff0c;如&#xff1a;418、…

java抽象类和接口知识总结

一.抽象类 1.啥是抽象类 用专业语言描述就是&#xff1a;如果一个类中没有包含足够的信息来描绘一个具体的对象&#xff0c;这样的类就是抽象类 当然这话说的也很抽象&#xff0c;所以我们来用人话来解释一下抽象类 抛开编程语言这些&#xff0c;就以现实举例&#xff0c;我…

每日练习之排序——链表的合并;完全背包—— 兑换零钱

链表的合并 题目描述 运行代码 #include<iostream> #include<algorithm> using namespace std; int main() { int a[31];for(int i 1;i < 30;i)cin>>a[i];sort(a 1,a 1 30);for(int i 1;i < 30;i)cout<<a[i]<<" ";cout&…

Mysql之Innodb存储引擎

1.Innodb数据存储 innodb如今能够做到mysql的默认数据存储引擎&#xff0c;肯定有着其好处的&#xff0c;那么innodb有什么好处呢? 1. 当意外断电或者重启&#xff0c; InnoDB 能够做到奔溃恢复&#xff0c;撤销没有提交的数据 2.InnoDB 存储引擎维护自己的缓冲池&#xff0c…

医院挂号就诊系统的设计与实现

前端使用Vue.js 后端使用SpiringBoot MyBatis 数据使用MySQL 需要项目和论文加企鹅&#xff1a;2583550535 医院挂号就诊系统的设计与实现_哔哩哔哩_bilibili 随着社会的发展&#xff0c;医疗资源分布不均&#xff0c;患者就诊难、排队时间长等问题日益突出&#xff0c;传统的…

Hadoop3:HDFS的Fsimage和Edits文件介绍

一、概念 Fsimage文件&#xff1a;HDFS文件系统元数据的一个永久性的检查点&#xff0c;其中包含HDFS文件系统的所有目 录和文件inode的序列化信息。 Edits文件&#xff1a;存放HDFS文件系统的所有更新操作的路径&#xff0c;文件系统客户端执行的所有写操作首先 会被记录到Ed…

二叉树的链式结构

1.二叉树的遍历 2.二叉树链式结构的实现 3.解决单值二叉树题 1.二叉树的遍历 1.1前序&#xff0c;中序以及后序遍历 二叉树的遍历是按照某种特定的规则&#xff0c;依次对二叉树的结点进行相应的操作&#xff0c;并且每个结点只操作一次。 二叉树的遍历有这些规则&#xff…

主流电商平台商品实时数据采集API接口||抖音电商数据分析实例|可视化

— 1 — 抖音电商数据【抖音电商API数据采集】分析场景 1. 这里&#xff0c;我们选择“伊利”这个品牌作为案例进行分析&#xff0c;在短短的4个月里&#xff0c;从最初每月营收17.07万&#xff0c;到6月份达到了2485.54 万&#xff0c;伊利的牛奶&#xff0c;有点牛&#xff…

Spring 对 Junit4,Junit5 的支持上的运用

1. Spring 对 Junit4,Junit5 的支持上的运用 文章目录 1. Spring 对 Junit4,Junit5 的支持上的运用每博一文案2. Spring对Junit4 的支持3. Spring对Junit5的支持4. 总结&#xff1a;5. 最后&#xff1a; 每博一文案 关于理想主义&#xff0c;在知乎上看到一句话&#xff1a;“…

Xline社区会议Call Up|在 CURP 算法中实现联合共识的安全性

为了更全面地向大家介绍Xline的进展&#xff0c;同时促进Xline社区的发展&#xff0c;我们将于2024年5月31日北京时间11:00 p.m.召开Xline社区会议。 欢迎您届时登陆zoom观看直播&#xff0c;或点击“阅读原文”链接加入会议&#xff1a; 会议号: 832 1086 6737 密码: 41125…